import { computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as XRegExp from 'xregexp';
import { OmitKeys, Without, emptyPath } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { ScriptField } from '../../../fields/ScriptField';
import { Cast, StrCast } from '../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { InkingStroke } from '../InkingStroke';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { CollectionDockingView } from '../collections/CollectionDockingView';
import { CollectionView } from '../collections/CollectionView';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
import { PresElementBox } from '../nodes/trails/PresElementBox';
import { SearchBox } from '../search/SearchBox';
import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
import { AudioBox } from './AudioBox';
import { ComparisonBox } from './ComparisonBox';
import { DataVizBox } from './DataVizBox/DataVizBox';
import { DocumentViewProps } from './DocumentView';
import './DocumentView.scss';
import { EquationBox } from './EquationBox';
import { FieldView, FieldViewProps } from './FieldView';
import { FontIconBox } from './FontIconBox/FontIconBox';
import { FunctionPlotBox } from './FunctionPlotBox';
import { ImageBox } from './ImageBox';
import { KeyValueBox } from './KeyValueBox';
import { LabelBox } from './LabelBox';
import { LinkAnchorBox } from './LinkAnchorBox';
import { LinkBox } from './LinkBox';
import { LoadingBox } from './LoadingBox';
import { MapBox } from './MapBox/MapBox';
import { MapPushpinBox } from './MapBox/MapPushpinBox';
import { PDFBox } from './PDFBox';
import { PhysicsSimulationBox } from './PhysicsBox/PhysicsSimulationBox';
import { RecordingBox } from './RecordingBox';
import { ScreenshotBox } from './ScreenshotBox';
import { ScriptingBox } from './ScriptingBox';
import { VideoBox } from './VideoBox';
import { WebBox } from './WebBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { ImportElementBox } from './importBox/ImportElementBox';
import { PresBox } from './trails/PresBox';

const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?

type BindingProps = Without<FieldViewProps, 'fieldKey'>;
export interface JsxBindings {
    props: BindingProps;
}

class ObserverJsxParser1 extends JsxParser {
    constructor(props: any) {
        super(props);
        observer(this as any);
    }
}

export const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;

interface HTMLtagProps {
    Document: Doc;
    htmltag: string;
    onClick?: ScriptField;
    onInput?: ScriptField;
    scaling: number;
    children?: JSX.Element[];
}

//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} overflow='hidden' position='absolute' width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'> <ImageBox {...props} fieldKey={'data'}/> <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px' transform='translate(-40px, 45px) rotate(-45deg)' position='absolute' color='{this.bannerColor===`green`?`light`:`dark`}blue' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'> {this.title}</HTMLspan></HTMLdiv>"
//"<HTMLdiv borderRadius='100px' overflow='hidden' position='absolute' width='100%' height='100%'
//          transform='rotate({2*this.x+this.y}deg)'
//          onClick = { this.bannerColor = this.bannerColor === 'red' ? 'green' : 'red' } >
//    <ImageBox {...props} fieldKey={'data'}/>
//    <HTMLspan width='200px' top='0' height='35px' textAlign='center' paddingTop='10px'
//              transform='translate(-40px, 45px) rotate(-45deg)' position='absolute'
//              color='{this.bannerColor===`green`?`light`:`dark`}blue'
//              backgroundColor='{this.bannerColor===`green`?`dark`:`light`}blue'>
//       {this.title}
//    </HTMLspan>
//  </HTMLdiv>"
@observer
export class HTMLtag extends React.Component<HTMLtagProps> {
    click = (e: React.MouseEvent) => {
        const clickScript = (this.props as any).onClick as Opt<ScriptField>;
        clickScript?.script.run({ this: this.props.Document, self: this.props.Document, scale: this.props.scaling });
    };
    onInput = (e: React.FormEvent<HTMLDivElement>) => {
        const onInputScript = (this.props as any).onInput as Opt<ScriptField>;
        onInputScript?.script.run({ this: this.props.Document, self: this.props.Document, value: (e.target as any).textContent });
    };
    render() {
        const style: { [key: string]: any } = {};
        const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
        const replacer = (match: any, expr: string, offset: any, string: any) => {
            // bcz: this executes a script to convert a property expression string:  { script }  into a value
            return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
        };
        Object.keys(divKeys).map((prop: string) => {
            const p = (this.props as any)[prop] as string;
            style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer);
        });
        const Tag = this.props.htmltag as keyof JSX.IntrinsicElements;
        return (
            <Tag style={style} onClick={this.click} onInput={this.onInput as any}>
                {this.props.children}
            </Tag>
        );
    }
}

@observer
export class DocumentContentsView extends ObservableReactComponent<
    DocumentViewProps &
        FieldViewProps & {
            setHeight?: (height: number) => void;
            layout_fieldKey: string;
        }
> {
    constructor(props: any) {
        super(props);
        makeObservable(this);
    }

    @computed get layout(): string {
        TraceMobx();
        if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
        if (!this.layoutDoc) return '<p>awaiting layout</p>';
        if (this._props.layout_fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layout_fieldKey ? this._props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
        if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
        if (typeof layout === 'string') return layout;
        return '<p>Loading layout</p>';
    }

    get layoutDoc() {
        // bcz: replaced this with below : is it correct?  change was made to accommodate passing fieldKey's from a layout script
        // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
        const template: Doc =
            this._props.LayoutTemplate?.() ||
            (this._props.LayoutTemplateString && this._props.Document) ||
            (this._props.layout_fieldKey && StrCast(this._props.Document[this._props.layout_fieldKey]) && this._props.Document) ||
            Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
        return Doc.expandTemplateLayout(template, this._props.Document);
    }

    CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
        const docOnlyProps = [
            // these are the properties in  DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
            'hideResizeHandles',
            'hideTitle',
            'contentPointerEvents',
            'radialMenu',
            'LayoutTemplateString',
            'LayoutTemplate',
            'dontCenter',
            'contextMenuItems',
            //'onClick', // don't need to omit this since it will be set
            'onDoubleClick',
            'onPointerDown',
            'onPointerUp',
        ];
        const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined);
        const list: BindingProps & React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLDivElement>, HTMLDivElement> = {
            ...this._props,
            Document: this.layoutDoc ?? this._props.Document,
            TemplateDataDocument: templateDataDoc instanceof Promise ? undefined : templateDataDoc,
            onClick: onClick as any as React.MouseEventHandler, // pass onClick script as if it were a real function -- it will be interpreted properly in the HTMLtag
            onInput: onInput as any as React.FormEventHandler,
        };
        return {
            props: {
                ...OmitKeys(list, [...docOnlyProps], '').omit,
            },
        };
    }

    // componentWillUpdate(oldProps: any, newState: any) {
    //     // console.log("willupdate", oldProps, this._props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
    // }

    @computed get renderData() {
        TraceMobx();
        let layoutFrame = this.layout;

        // replace code content with a script  >{content}<   as in  <HTMLdiv>{this.title}</HTMLdiv>
        const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
            return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
        };
        layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);

        // replace HTML<tag> with corresponding HTML tag as in:  <HTMLdiv> becomes  <HTMLtag Document={props.Document} htmltag='div'>
        const replacer2 = (match: any, p1: string, offset: any, string: any) => {
            return `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
        };
        layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);

        // replace /HTML<tag> with </HTMLdiv>  as in:  </HTMLdiv> becomes  </HTMLtag>
        const replacer3 = (match: any, p1: string, offset: any, string: any) => {
            return `</HTMLtag`;
        };
        layoutFrame = layoutFrame.replace(/<\/HTML([a-zA-Z0-9_-]+)/g, replacer3);

        // add onClick function to props
        const makeFuncProp = (func: string) => {
            const splits = layoutFrame.split(`${func}=`);
            if (splits.length > 1) {
                const code = XRegExp.matchRecursive(splits[1], '{', '}', '', { valueNames: ['between', 'left', 'match', 'right', 'between'] });
                layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1);
                const script = code[1].value.replace(/^‘/, '').replace(/’$/, ''); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts
                return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: 'number', value: 'string' });
            }
            return undefined;
            // add input function to props
        };
        const onClick = makeFuncProp('onClick');
        const onInput = makeFuncProp('onInput');
        const bindings = this.CreateBindings(onClick, onInput);
        return { bindings, layoutFrame };
    }

    render() {
        TraceMobx();
        const { bindings, layoutFrame } = this.renderData;

        return this._props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
            <ObserverJsxParser
                key={42}
                blacklistedAttrs={emptyPath}
                renderInWrapper={false}
                components={{
                    FormattedTextBox,
                    ImageBox,
                    FontIconBox,
                    LabelBox,
                    EquationBox,
                    FieldView,
                    CollectionFreeFormView,
                    CollectionDockingView,
                    CollectionSchemaView,
                    CollectionView,
                    WebBox,
                    KeyValueBox,
                    PDFBox,
                    VideoBox,
                    AudioBox,
                    RecordingBox,
                    PresBox,
                    YoutubeBox,
                    PresElementBox,
                    SearchBox,
                    FunctionPlotBox,
                    DashWebRTCVideo,
                    LinkAnchorBox,
                    InkingStroke,
                    LinkBox,
                    ScriptingBox,
                    MapBox,
                    ScreenshotBox,
                    DataVizBox,
                    HTMLtag,
                    ComparisonBox,
                    LoadingBox,
                    PhysicsSimulationBox,
                    SchemaRowBox,
                    ImportElementBox,
                    MapPushpinBox,
                }}
                bindings={bindings}
                jsx={layoutFrame}
                showWarnings={true}
                onError={(test: any) => {
                    console.log('DocumentContentsView:' + test, bindings, layoutFrame);
                }}
            />
        );
    }
}
