import { h, Fragment, Component } from 'preact';
import type { AnnotationSourceDescriptor } from '../storage/AnnotationSource';
import { RpcClient } from 'webextension-rpc';
import type { backgroundRpcServer } from '../background';
import { MarginalAnnotations } from './MarginalAnnotations';
import { ToolbarButtons } from './ToolbarButtons';
import {
  discoverAnnotationsEmbeddedAsJSONLD,
  discoverAnnotationSources,
} from './discovery';
import { targetsUrl } from 'web-annotation-utils';
import { unique } from '../util/unique';
import type { IAnnotationWithSource } from '../storage/Annotation';
import { AnnotationCreationHelper } from './AnnotationCreationHelper';

const backgroundRpc = new RpcClient<typeof backgroundRpcServer>();
const getAnnotationsForTargetUrls = backgroundRpc.func(
  'getAnnotationsForTargetUrls',
);
const isSourceSubscribed = backgroundRpc.func('isSourceSubscribed');
const updateAnnotation = backgroundRpc.func('updateAnnotation');
const deleteAnnotation = backgroundRpc.func('deleteAnnotation');

interface AppProps {
  appContainer: Node;
}

interface AppState {
  storedAnnotations: IAnnotationWithSource[];
  embeddedAnnotations: IAnnotationWithSource[];
  annotationInFocus?: IAnnotationWithSource;
  discoveredLinkedSources: (AnnotationSourceDescriptor & {
    subscribed: boolean;
  })[];
  discoveredEmbeddedSources: (AnnotationSourceDescriptor & {
    subscribed: boolean;
  })[];
}

export class App extends Component<AppProps, AppState> {
  state: AppState = {
    storedAnnotations: [],
    embeddedAnnotations: [],
    discoveredLinkedSources: [],
    discoveredEmbeddedSources: [],
  };

  async componentDidMount() {
    try {
      await Promise.all([
        this.loadStoredAnnotations(),
        this.discoverEmbeddedAnnotations(),
        this.discoverLinkedAnnotationSources(),
      ]);
    } catch (error) {
      console.log(error);
    }
  }

  async loadStoredAnnotations() {
    // Find annotations in our storage that target the current page.
    const urls = [document.URL];
    const canonicalLink = document.querySelector(
      'link[rel~="canonical"]',
    ) as HTMLLinkElement | null;
    if (canonicalLink) urls.push(canonicalLink.href);
    const storedAnnotations = await getAnnotationsForTargetUrls(urls);
    console.log(
      `We got these annotations for <${document.URL}>:`,
      storedAnnotations,
    );
    this.setState({
      storedAnnotations,
    });
  }

  async discoverEmbeddedAnnotations() {
    // Find annotations embedded inside the page.
    const embeddedAnnotations = discoverAnnotationsEmbeddedAsJSONLD();
    const embeddedAnnotationsTargetingThisPage = embeddedAnnotations.filter(
      (annotation) => targetsUrl(annotation.target, document.URL),
    );
    console.log(
      `Found ${embeddedAnnotations.length} embedded annotations in the page, of which ${embeddedAnnotationsTargetingThisPage.length} target this page itself.`,
    );
    this.setState({
      embeddedAnnotations: embeddedAnnotationsTargetingThisPage.map(
        (annotation) => ({
          _id: 0,
          source: {
            _id: -1,
            active: false,
            type: 'embeddedJsonld',
            url: document.URL,
          },
          annotation,
        }),
      ),
    });

    // A page with embedded annotations targeting *other* pages is considered an annotation source.
    if (
      embeddedAnnotations.length > embeddedAnnotationsTargetingThisPage.length
    ) {
      const pageAsAnnotationSource: AnnotationSourceDescriptor = {
        title: document.title,
        url: document.URL.split('#')[0],
        type: 'embeddedJsonld',
      };
      this.setState({
        discoveredEmbeddedSources: await this.checkDiscoveredAnnotationSources([
          pageAsAnnotationSource,
        ]),
      });
    }
  }

  async discoverLinkedAnnotationSources() {
    // Find annotations sources advertised by the current page.
    const discoveredSources = discoverAnnotationSources();
    this.setState({
      discoveredLinkedSources: await this.checkDiscoveredAnnotationSources(
        discoveredSources,
      ),
    });
  }

  async checkDiscoveredAnnotationSources(
    discoveredSources: AnnotationSourceDescriptor[],
  ) {
    // For each discovered source, note if we already have it in our database.
    return await Promise.all(
      discoveredSources.map(async (source) => ({
        ...source,
        subscribed: await isSourceSubscribed(source),
      })),
    );
  }

  async onAnnotationCreated(annotation: IAnnotationWithSource) {
    await this.loadStoredAnnotations();
    this.setState({ annotationInFocus: annotation });
  }

  onSubscriptionChange() {
    this.discoverLinkedAnnotationSources();
    this.discoverEmbeddedAnnotations();
  }

  render(
    { appContainer }: AppProps,
    {
      storedAnnotations,
      embeddedAnnotations,
      annotationInFocus,
      discoveredLinkedSources,
      discoveredEmbeddedSources,
    }: AppState,
  ) {
    const annotationsToShow = unique(
      [...storedAnnotations, ...embeddedAnnotations],
      (obj) => obj.annotation.canonical || obj.annotation.id,
    );
    const discoveredSources = [
      ...discoveredLinkedSources,
      ...discoveredEmbeddedSources,
    ];
    const toolbarButtons =
      discoveredSources.length > 0 ? (
        <ToolbarButtons
          {...{
            onChange: () => this.onSubscriptionChange(),
            discoveredSources,
          }}
        />
      ) : undefined;
    return (
      <>
        <MarginalAnnotations
          {...{
            annotations: annotationsToShow,
            annotationInFocus,
            toolbarButtons,
            appContainer,
            onUpdateAnnotation: async (...args) => {
              await updateAnnotation(...args);
              // messes up text while editing.
              // await this.loadStoredAnnotations();
            },
            onDeleteAnnotation: async (...args) => {
              await deleteAnnotation(...args);
              await this.loadStoredAnnotations();
            },
          }}
        />
        <AnnotationCreationHelper
          onAnnotationCreated={(annotation) =>
            this.onAnnotationCreated(annotation)
          }
        />
      </>
    );
  }
}
