/* eslint-disable react/require-default-props */
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as XRegExp from 'xregexp';
import { OmitKeys } from '../../../ClientUtils';
import { Without } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { ScriptField } from '../../../fields/ScriptField';
import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { ObservableReactComponent, ObserverJsxParser } from '../ObservableReactComponent';
import './DocumentView.scss';
import { FieldViewProps } from './FieldView';

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

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 = () => {
        const clickScript = this.props.onClick as Opt<ScriptField>;
        clickScript?.script.run({ this: this.props.Document, scale: this.props.scaling });
    };
    onInput = (e: React.FormEvent<unknown>) => {
        const onInputScript = this.props.onInput as Opt<ScriptField>;
        onInputScript?.script.run({ this: this.props.Document, value: (e.target as HTMLElement).textContent });
    };
    render() {
        const style: { [key: string]: unknown } = {};
        const divKeys = OmitKeys(this.props, [
            'children', //
            'dragStarting',
            'dragEnding',
            'htmltag',
            'scaling',
            'Document',
            'key',
            'onInput',
            'onClick',
            '__proto__',
        ]).omit;
        const replacer = (match: string, expr: string) =>
            // bcz: this executes a script to convert a property expression string:  { script }  into a value
            (ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: this.props.Document, scale: this.props.scaling }).result as string) || '';
        Object.keys(divKeys).forEach((prop: string) => {
            const p = (this.props as unknown as { [key: string]: string })[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}>
                {this.props.children}
            </Tag>
        );
    }
}

export interface DocumentContentsViewProps extends FieldViewProps {
    layoutFieldKey: string;
}
@observer
export class DocumentContentsView extends ObservableReactComponent<DocumentContentsViewProps> {
    private static DefaultLayoutString: string;
    /**
     * Set of all available rendering componets for Docs (e.g., ImageBox, CollectionFreeFormView, etc)
     */
    private static Components: { [key: string]: unknown };
    public static Init(defaultLayoutString: string, components: { [key: string]: unknown }) {
        DocumentContentsView.DefaultLayoutString = defaultLayoutString;
        DocumentContentsView.Components = components;
    }
    constructor(props: DocumentContentsViewProps) {
        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.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, DocumentContentsView.DefaultLayoutString);
        const tempLayout = DocCast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')]);
        const layoutDoc = tempLayout ?? this.layoutDoc;
        const layout = Cast(layoutDoc[layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(layoutDoc.layout_fieldKey, 'layout')], 'string');
        if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : DocumentContentsView.DefaultLayoutString;
        if (typeof layout === 'string') return layout;
        return '<p>Loading layout</p>';
    }

    get layoutDoc() {
        const template: Doc =
            this._props.LayoutTemplate?.() ||
            (this._props.LayoutTemplateString && this._props.Document) ||
            (this._props.layoutFieldKey && StrCast(this._props.Document[this._props.layoutFieldKey]) && this._props.Document) ||
            Doc.Layout(this._props.Document, this._props.layoutFieldKey ? Cast(this._props.Document[this._props.layoutFieldKey], 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',
            'bringToFront',
            'childContentPointerEvents',
            'LayoutTemplateString',
            'LayoutTemplate',
            'showTags',
            'layoutFieldKey',
            'dontCenter',
            'DataTransition',
            'contextMenuItems',
            // 'onClick', // don't need to omit this since it will be set
            'onDoubleClickScript',
            'onPointerDownScript',
            'onPointerUpScript',
        ];
        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 unknown as React.MouseEventHandler, // pass onClick script as if it were a real function -- it will be interpreted properly in the HTMLtag
            onInput: onInput as unknown as React.FormEventHandler,
        };
        return {
            props: {
                ...OmitKeys(list, [...docOnlyProps], '').omit,
            } as BindingProps,
        };
    }

    // 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: string, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { 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: string, p1: string) => `<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 */) => `</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, 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 };
    }

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

        return this._props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
            <ObserverJsxParser
                key={42}
                blacklistedAttrs={this.blacklistedAttrs}
                renderInWrapper={false}
                components={DocumentContentsView.Components}
                bindings={bindings}
                jsx={layoutFrame}
                showWarnings
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                onError={(test: any) => {
                    console.log('DocumentContentsView:' + test, bindings, layoutFrame);
                }}
            />
        );
    }
}
