import React from 'react';
import ReactDOM from 'react-dom';
import Emitter from 'tiny-emitter';
import {
    WebAnnotation,
    createEnvironment,
    setLocale,
} from 'eu-eleysion-recogito-client-core';
import TextAnnotator from './TextAnnotator';
import { deflateHTML } from './utils';

import 'eu-eleysion-recogito-client-core/themes/default';

/**
 * The entrypoint into the application. Provides the
 * externally visible JavaScript API.
 */
export class Recogito {
    constructor(config) {
        // API calls to this instance are forwarded through a ref
        this._app = React.createRef();

        // Event handling via tiny-emitter
        this._emitter = new Emitter();

        // Environment settings container
        this._environment = createEnvironment();

        // The content element (which contains the text we want to annotate)
        // is wrapped in a DIV ('wrapperEl'). The application container DIV,
        // which holds the editor popup, will be attached as a child to the
        // wrapper element (=a sibling to the content element). This way,
        // content and editor share the same CSS position reference frame.
        //
        // <wrapperEl>
        //   <contentEl />
        //   <appContainerEl />
        // </wrapperEl>
        //
        let contentEl = config.content.nodeType
            ? config.content
            : document.getElementById(config.content);

        // Deep-clone the original node, so we can easily destroy the Recogito instance
        this._originalContent = contentEl.cloneNode(true);

        // Unless this is preformatted text, remove multi spaces and
        // empty text nodes, so that HTML char offsets == browser offsets.
        if (config.mode !== 'pre') contentEl = deflateHTML(contentEl);

        this._wrapperEl = document.createElement('DIV');
        this._wrapperEl.className = 'r6o-content-wrapper';
        this._wrapperEl.style.position = 'relative';

        if (contentEl instanceof HTMLBodyElement) {
            this._wrapperEl.append(...contentEl.childNodes);
            contentEl.appendChild(this._wrapperEl);
        } else {
            contentEl.parentNode.insertBefore(this._wrapperEl, contentEl);
            this._wrapperEl.appendChild(contentEl);
        }

        this._appContainerEl = document.createElement('DIV');
        this._wrapperEl.appendChild(this._appContainerEl);

        setLocale(config.locale);

        ReactDOM.render(
            <TextAnnotator
                ref={this._app}
                env={this._environment}
                contentEl={contentEl}
                wrapperEl={this._wrapperEl}
                config={config}
                onAnnotationSelected={this.handleAnnotationSelected}
                onAnnotationCreated={this.handleAnnotationCreated}
                onAnnotationUpdated={this.handleAnnotationUpdated}
                onAnnotationDeleted={this.handleAnnotationDeleted}
                onCancelSelected={this.handleCancelSelected}
                relationVocabulary={config.relationVocabulary}
            />,
            this._appContainerEl
        );
    }

    handleAnnotationSelected = (annotation, element) =>
        this._emitter.emit('selectAnnotation', annotation.underlying, element);

    handleAnnotationCreated = (annotation, overrideId) =>
        this._emitter.emit(
            'createAnnotation',
            annotation.underlying,
            overrideId
        );

    handleAnnotationUpdated = (annotation, previous) =>
        this._emitter.emit(
            'updateAnnotation',
            annotation.underlying,
            previous.underlying
        );

    handleAnnotationDeleted = (annotation) =>
        this._emitter.emit('deleteAnnotation', annotation.underlying);

    handleCancelSelected = (annotation) =>
        this._emitter.emit('cancelSelected', annotation.underlying);

    /******************/
    /*  External API  */
    /******************/

    // Common shorthand for handling annotationOrId args
    _wrap = (annotationOrId) =>
        annotationOrId?.type === 'Annotation'
            ? new WebAnnotation(annotationOrId)
            : annotationOrId;

    addAnnotation = (annotation) =>
        this._app.current.addAnnotation(new WebAnnotation(annotation));

    clearAnnotations = () => this.setAnnotations(null);

    clearAuthInfo = () => (this._environment.user = null);

    destroy = () => {
        ReactDOM.unmountComponentAtNode(this._appContainerEl);
        this._wrapperEl.parentNode.insertBefore(
            this._originalContent,
            this._wrapperEl
        );
        this._wrapperEl.parentNode.removeChild(this._wrapperEl);
    };

    get disableEditor() {
        return this._app.current.disableEditor;
    }

    set disableEditor(disabled) {
        this._app.current.disableEditor = disabled;
    }

    get disableSelect() {
        return this._app.current.disableSelect;
    }

    set disableSelect(select) {
        this._app.current.disableSelect = select;
    }

    getAnnotations = () => {
        const annotations = this._app.current.getAnnotations();
        return annotations.map((a) => a.underlying);
    };

    loadAnnotations = (url, requestArgs) =>
        fetch(url, requestArgs)
            .then((response) => response.json())
            .then((annotations) => {
                return this.setAnnotations(annotations).then(() => annotations);
            });

    off = (event, callback) => this._emitter.off(event, callback);

    on = (event, handler) => this._emitter.on(event, handler);

    get readOnly() {
        return this._app.current.readOnly;
    }

    set readOnly(readOnly) {
        this._app.current.readOnly = readOnly;
    }

    removeAnnotation = (annotation) =>
        this._app.current.removeAnnotation(new WebAnnotation(annotation));

    selectAnnotation = (annotationOrId) => {
        const selected = this._app.current.selectAnnotation(
            this._wrap(annotationOrId)
        );
        return selected?.underlying;
    };

    setAnnotations = (arg) => {
        const annotations = arg || [];
        const webannotations = annotations.map((a) => new WebAnnotation(a));
        return this._app.current.setAnnotations(webannotations);
    };

    setAuthInfo = (authinfo) => (this._environment.user = authinfo);

    /**
     * Activates annotation or relationship drawing mode.
     * @param mode a string, either ANNOTATION (default) or RELATIONS
     */
    setMode = (mode) => this._app.current.setMode(mode);

    setServerTime = (timestamp) => this._environment.setServerTime(timestamp);

    get widgets() {
        return this._app.current.widgets;
    }

    set widgets(widgets) {
        this._app.current.widgets = widgets;
    }
}

export const init = (config) => new Recogito(config);
