import { h, Component, ComponentChild, createRef } from 'preact';
import { asArray } from 'web-annotation-utils';
import type { BodyChoice, TextualBody, WebAnnotation } from 'web-annotation-utils';
import classes from './AnnotationBody.module.scss';

interface AnnotationBodyProps {
  body: WebAnnotation['body'];
  bodyValue: WebAnnotation['bodyValue'];
  editable?: boolean;
  onChange?: (newBody: WebAnnotation['body']) => void;
  onBlur?: () => void;
  inFocus?: boolean;
}

interface AnnotationBodyState {}

export class AnnotationBody extends Component<
  AnnotationBodyProps,
  AnnotationBodyState
> {
  editorElement = createRef<HTMLDivElement>();

  componentDidMount() {
    if (this.props.inFocus) {
      this.editorElement.current?.focus({ preventScroll: true });
    }
  }

  componentDidUpdate(previousProps: Readonly<AnnotationBodyProps>) {
    if (this.props.inFocus && !previousProps.inFocus) {
      this.editorElement.current?.focus();
    }
  }

  render({ body, bodyValue, editable }: AnnotationBodyProps) {
    // An annotation either contains a `bodyValue` (simply a string), or an actual `body`.
    if (bodyValue !== undefined) return this.renderBodyValue(bodyValue);
    const result = this.renderBody(body);
    if (result === null && editable) {
      // For an empty but editable body, render an empty text field.
      return this.renderBodyValue('');
    } else {
      return result;
    }
  }

  renderBodyValue(bodyValue: string) {
    // A bodyValue is defined as equivalent to a TextualBody containing this value.
    return this.renderTextualBody({
      type: 'TextualBody',
      value: bodyValue,
      format: 'text/plain',
    });
  }

  renderTextualBody(body: TextualBody) {
    // TODO use other available information: textDirection, format, …?
    return (
      <div
        ref={this.editorElement}
        class={classes.annotationBodyText}
        contentEditable={this.props.editable}
        spellcheck={false}
        onInput={(e) =>
          this.props.onChange?.({
            ...body,
            value: (e.target as HTMLParagraphElement).textContent!,
          })
        }
        onBlur={() => this.props.onBlur?.()}
      >
        {body.value}
      </div>
    );
  }

  renderBody(body: WebAnnotation['body']): ComponentChild {
    if (!body) {
      return null;
    }

    // A body can take many forms. Handle each as well as we can.

    // Firstly, it could be a string, identifying the body resource.
    if (typeof body === 'string') {
      // We assume the body is the URL of the body content.
      // TODO Handle the case where this string instead refers to a JSON-LD node (e.g. a SpecificResource or Choice).
      return this.renderIframe(body);
    }

    // There can be multiple bodies (according to the spec, each body is equally applicable to each target).
    if (Array.isArray(body)) {
      // We simply concatenate the bodies. Perhaps not the clearest/prettiest, but simple.
      return body.map((actualBody) => this.renderBody(actualBody));
    }

    // TextualBody, a body consisting of a simple text value.
    if ('type' in body && body.type === 'TextualBody') {
      return this.renderTextualBody(body as TextualBody);
    }

    if ('type' in body && body.type === 'Choice') {
      const bodyOptions = (body as BodyChoice).items;
      if (bodyOptions.length === 0) return null;
      // The default option is listed first; take that.
      return this.renderBody(bodyOptions[0]);
    }

    if ('source' in body) {
      // The body is a Specific Resource.
      // TODO Try render exactly that part of the resource indicated by body.selector.
      return this.renderIframe(body.source);
    }

    // The body is an External Web Resource. Depending on its type, render an appropriate element.

    if (
      asArray(body.format).every((item) => item.startsWith('image/')) ||
      asArray(body.type).every((item) => item === 'Image')
    ) {
      return this.renderImage(body.id);
    }

    if (
      asArray(body.format).every((item) => item.startsWith('audio/')) ||
      asArray(body.type).every((item) => item === 'Sound')
    ) {
      return this.renderAudio(body.id);
    }

    if (
      asArray(body.format).every((item) => item.startsWith('video/')) ||
      asArray(body.type).every((item) => item === 'Video')
    ) {
      return this.renderAudio(body.id);
    }

    return this.renderIframe(body.id);
  }

  renderImage(bodyUrl: string) {
    return <img src={bodyUrl}></img>;
  }

  renderAudio(bodyUrl: string) {
    return (
      <audio class={classes.annotationBodyAudio} controls src={bodyUrl}></audio>
    );
  }

  renderVideo(bodyUrl: string) {
    return <video class={classes.annotationBodyVideo} src={bodyUrl}></video>;
  }

  renderIframe(bodyUrl: string) {
    return (
      <iframe
        class={classes.annotationBodyIframe}
        sandbox=""
        src={bodyUrl}
      ></iframe>
    );
  }
}
