import functionPlot, { Chart } from 'function-plot';
import { action, computed, makeObservable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, DocListCast, NumListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { DocUtils } from '../../documents/DocUtils';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { PinDocView, PinProps } from '../PinFuncs';
import { FieldView, FieldViewProps } from './FieldView';

@observer
export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(FunctionPlotBox, fieldKey);
    }
    public static GraphCount = 0;
    _plot: Chart | undefined;
    _plotId = '';
    _plotEle: HTMLDivElement | null = null;

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
        this._plotId = 'graph' + FunctionPlotBox.GraphCount++;
    }

    componentDidMount() {
        this._props.setContentViewBox?.(this);
        reaction(
            () => [this.graphFuncs, this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
            () => this.createGraph()
        );
    }
    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
        const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document });
        PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), datarange: true } }, this.Document);
        if (this._plot) {
            anchor.config_xRange = new List<number>(Array.from(this._plot.options.xAxis?.domain ?? []));
            anchor.config_yRange = new List<number>(Array.from(this._plot.options.yAxis?.domain ?? []));
        }
        if (addAsAnnotation) this.addDocument(anchor);
        return anchor;
    };
    @computed get graphFuncs() {
        const links = Doc.Links(this.Document)
            .map(d => Doc.getOppositeAnchor(d, this.Document))
            .filter(d => d)
            .map(d => d!);
        const funcs = links.concat(DocListCast(this.dataDoc[this.fieldKey])).map(doc =>
            StrCast(doc.text, 'x^2')
                .replace(/\\sqrt/g, 'sqrt')
                .replace(/\\frac\{(.*)\}\{(.*)\}/g, '($1/$2)')
                .replace(/\\left/g, '')
                .replace(/\\right/g, '')
                .replace(/\{/g, '')
                .replace(/\}/g, '')
        );
        return funcs;
    }
    computeYScale = (width: number, height: number, xScale: number[]) => {
        const xDiff = xScale[1] - xScale[0];
        const yDiff = (height * xDiff) / width;
        return [-yDiff / 2, yDiff / 2];
    };
    createGraph = (ele?: HTMLDivElement) => {
        this._plotEle = ele || this._plotEle;
        const width = this._props.PanelWidth();
        const height = this._props.PanelHeight();
        const xrange = NumListCast(this.layoutDoc.xRange, [-10, 10])!;
        try {
            this._plotEle?.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
            this._plot = functionPlot({
                target: '#' + this._plotEle?.id,
                width,
                height,
                xAxis: { domain: xrange },
                yAxis: { domain: this.computeYScale(width, height, xrange) }, //  Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) },
                grid: true,
                data: this.graphFuncs.map(fn => ({
                    fn,
                    // derivative: { fn: "2 * x", updateOnMouseMove: true }
                })),
            });
        } catch (e) {
            console.log(e);
        }
    };

    @undoBatch
    drop = (e: Event, de: DragManager.DropEvent) => {
        if (de.complete.docDragData?.droppedDocuments.length) {
            const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => {
                // const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc);
                if (res) {
                    const link = DocUtils.MakeLink(doc, this.Document, { layout_isSvg: true, link_relationship: 'function', link_description: 'input' });
                    link && this._props.addDocument?.(link);
                }
                return res;
            }, true);
            !added && e.preventDefault();
            e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
            return added;
        }
        return false;
    };

    _dropDisposer: DragManager.DragDropDisposer | undefined;
    protected createDropTarget = (ele: HTMLDivElement) => {
        this._dropDisposer?.();
        if (ele) {
            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc);
        }
        // if (this.layout_autoHeight) this.tryUpdateScrollHeight();
    };
    @computed get theGraph() {
        return (
            <div
                id={`${this._plotId}`}
                ref={r => r && this.createGraph(r)}
                style={{ position: 'absolute', width: '100%', height: '100%' }}
                onPointerDown={e => {
                    e.stopPropagation();
                    setupMoveUpEvents(
                        this,
                        e,
                        returnFalse,
                        action(() => {
                            if (this._plot?.options.xAxis?.domain) {
                                this.Document.xRange = new List<number>(this._plot.options.xAxis.domain);
                            }
                            if (this._plot?.options.yAxis?.domain) {
                                this.Document.yRange = new List<number>(this._plot.options.yAxis.domain);
                            }
                        }),
                        emptyFunction,
                        false,
                        false
                    );
                }}
            />
        );
    }
    render() {
        TraceMobx();
        return (
            <div
                ref={this.createDropTarget}
                style={{
                    pointerEvents: !this._props.isContentActive() ? 'all' : undefined,
                    width: this._props.PanelWidth(),
                    height: this._props.PanelHeight(),
                }}>
                {this.theGraph}
                <div
                    style={{
                        display: this._props.isContentActive() ? 'none' : undefined,
                        position: 'absolute',
                        width: '100%',
                        height: '100%',
                        pointerEvents: 'all',
                    }}
                />
            </div>
        );
    }
}

Docs.Prototypes.TemplateMap.set(DocumentType.FUNCPLOT, {
    layout: { view: FunctionPlotBox, dataField: 'data' },
    options: { acl: '', _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true },
});
