import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
import { action, computed, observable, ObservableMap, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Field, Opt, StrListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
import { Docs } from '../../../documents/Documents';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
import { PinProps } from '../trails';
import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
import './DataVizBox.scss';
import { UndoManager, undoable } from '../../../util/UndoManager';
import { SidebarAnnos } from '../../SidebarAnnos';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { emptyFunction, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../../Utils';
import { DocumentView } from '../DocumentView';
import { DocumentManager } from '../../../util/DocumentManager';
import { TraceMobx } from '../../../../fields/util';
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { InkTool } from '../../../../fields/InkField';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm';

export enum DataVizView {
    TABLE = 'table',
    LINECHART = 'lineChart',
    HISTOGRAM = 'histogram',
    PIECHART = 'pieChart',
}

@observer
export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
    private _ffref = React.createRef<CollectionFreeFormView>();
    private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
    anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
    crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
    @observable _marqueeing: number[] | undefined;
    @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
    @computed get annotationLayer() {
        TraceMobx();
        return <div className="dataVizBox-annotationLayer" style={{ height: this.props.PanelHeight(), width: this.props.PanelWidth() }} ref={this._annotationLayer} />;
    }
    marqueeDown = (e: React.PointerEvent) => {
        if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._freeform_scale, 1) <= NumCast(this.rootDoc.freeform_scaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
            setupMoveUpEvents(
                this,
                e,
                action(e => {
                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                    //this._marqueeing = [e.pageX, e.pageY];
                    this._marqueeing = [e.clientX, e.clientY];
                    return true;
                }),
                returnFalse,
                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
                false
            );
        }
    };
    @action
    finishMarquee = () => {
        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
        this._marqueeing = undefined;
        this.props.select(false);
    };
    savedAnnotations = () => this._savedAnnotations;

    public static LayoutString(fieldStr: string) {
        return FieldView.LayoutString(DataVizBox, fieldStr);
    }

    // all datasets that have been retrieved from the server stored as a map from the dataset url to an array of records
    static dataset = new ObservableMap<string, { [key: string]: string }[]>();
    private _vizRenderer: LineChart | Histogram | PieChart | undefined;
    private _sidebarRef = React.createRef<SidebarAnnos>();

    // all CSV records in the dataset (that aren't an empty row)
    @computed.struct get records() {
        var records = DataVizBox.dataset.get(CsvCast(this.rootDoc[this.fieldKey]).url.href);
        return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
    }

    // currently chosen visualization type: line, pie, histogram, table
    @computed get dataVizView(): DataVizView {
        return StrCast(this.layoutDoc._dataViz, 'table') as DataVizView;
    }

    @computed get dataUrl() {
        return Cast(this.dataDoc[this.fieldKey], CsvField);
    }
    @computed.struct get axes() {
        return StrListCast(this.layoutDoc.dataViz_axes);
    }

    selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes));

    @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
    restoreView = (data: Doc) => {
        const changedView = this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
        const changedAxes = this.axes.join('') !== StrListCast(data.config_dataVizAxes).join('') && (this.layoutDoc._dataViz_axes = new List<string>(StrListCast(data.config_dataVizAxes)));
        this.layoutDoc.dataViz_selectedRows = Field.Copy(data.dataViz_selectedRows);
        this.layoutDoc.dataViz_histogram_barColors = Field.Copy(data.dataViz_histogram_barColors);
        this.layoutDoc.dataViz_histogram_defaultColor = data.dataViz_histogram_defaultColor;
        this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(data.dataViz_pie_sliceColors);
        Object.keys(this.layoutDoc).map(key => {
            if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
                this.layoutDoc['_' + key] = data[key];
            }
        });
        const func = () => this._vizRenderer?.restoreView(data);
        if (changedView || changedAxes) {
            setTimeout(func, 100);
            return true;
        }
        return func() ?? false;
    };
    getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => {
        const visibleAnchor = this._getAnchor?.(this._savedAnnotations, false); // use marquee anchor, otherwise, save zoom/pan as anchor
        const anchor = !pinProps
            ? this.rootDoc
            : this._vizRenderer?.getAnchor(pinProps) ??
            visibleAnchor ??
              Docs.Create.ConfigDocument({
                title: 'ImgAnchor:' + this.rootDoc.title,
                config_panX: NumCast(this.layoutDoc._freeform_panX),
                config_panY: NumCast(this.layoutDoc._freeform_panY),
                config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
                annotationOn: this.rootDoc,
                  // when we clear selection -> we should have it so chartBox getAnchor returns undefined
                  // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker)
                  /*put in some options*/
              });
        anchor.config_dataViz = this.dataVizView;
        anchor.config_dataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined;
        anchor.dataViz_selectedRows = Field.Copy(this.layoutDoc.dataViz_selectedRows);
        anchor.dataViz_histogram_barColors = Field.Copy(this.layoutDoc.dataViz_histogram_barColors);
        anchor.dataViz_histogram_defaultColor = this.layoutDoc.dataViz_histogram_defaultColor;
        anchor.dataViz_pie_sliceColors = Field.Copy(this.layoutDoc.dataViz_pie_sliceColors);
        Object.keys(this.layoutDoc).map(key => {
            if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
                anchor[key] = this.layoutDoc[key];
            }
        });
        
        this.addDocument(anchor);
        //addAsAnnotation && this.addDocument(anchor);
        return anchor;
    };

    createNoteAnnotation = () => {
        const createFunc = undoable(
            action(() => {
                const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']);
            }),
            'create note annotation'
        );
        if (!this.layoutDoc.layout_showSidebar) {
            this.toggleSidebar();
            setTimeout(createFunc);
        } else createFunc();
    };

    @observable _showSidebar = false;
    @observable _previewNativeWidth: Opt<number> = undefined;
    @observable _previewWidth: Opt<number> = undefined;
    @action
    toggleSidebar = () => {
        const prevWidth = this.sidebarWidth();
        this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
        this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
    };
    @computed get SidebarShown() {
        return this.layoutDoc._layout_showSidebar ? true : false;
    }
    @computed get sidebarHandle() {
        return (
            <div
                className="dataviz-overlayButton-sidebar"
                key="sidebar"
                title="Toggle Sidebar"
                style={{
                    display: !this.props.isContentActive() ? 'none' : undefined,
                    top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                }}
                onPointerDown={this.sidebarBtnDown}>
                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
            </div>
        );
    }
    /**
     * Toggle sidebar onclick the tiny comment button on the top right corner
     * @param e
     */
    sidebarBtnDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            (e, down, delta) =>
                runInAction(() => {
                    const localDelta = this.props
                        .ScreenToLocalTransform()
                        .scale(this.props.NativeDimScaling?.() || 1)
                        .transformDirection(delta[0], delta[1]);
                    const fullWidth = this.props.width;
                    const mapWidth = fullWidth! - this.sidebarWidth();
                    if (this.sidebarWidth() + localDelta[0] > 0) {
                        this.layoutDoc._layout_showSidebar = true;
                        this.layoutDoc._width = fullWidth! + localDelta[0];
                        this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth! + localDelta[0])).toString() + '%';
                    } else {
                        this.layoutDoc._layout_showSidebar = false;
                        this.layoutDoc._width = mapWidth;
                        this.layoutDoc._layout_sidebarWidthPercent = '0%';
                    }
                    return false;
                }),
            emptyFunction,
            () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar')
        );
    };
    getView = async (doc: Doc) => {
        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar();
        return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
    };
    @computed get sidebarWidthPercent() {
        return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
    }
    @computed get sidebarColor() {
        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
    }
    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
        if (!this.SidebarShown) this.toggleSidebar();
        return this.addDocument(doc, sidebarKey);
    };
    sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);

    componentDidMount() {
        this.props.setContentView?.(this);
        if (!DataVizBox.dataset.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) this.fetchData();
    }

    fetchData = () => {
        DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
        fetch('/csvData?uri=' + this.dataUrl?.url.href) //
            .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res))));
    };

    // toggles for user to decide which chart type to view the data in
    @computed get renderVizView() {
        const scale = this.props.NativeDimScaling?.() || 1;
        const sharedProps = {
            rootDoc: this.rootDoc,
            layoutDoc: this.layoutDoc,
            records: this.records,
            axes: this.axes,
            height: (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9,
            width: this.SidebarShown? this.props.PanelWidth()*.9/1.2: this.props.PanelWidth() * 0.9,
            margin: { top: 10, right: 25, bottom: 75, left: 45 },
        };
        if (!this.records.length) return 'no data/visualization';
        switch (this.dataVizView) {
            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
            case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
            case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
            case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} 
            margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />;
        } // prettier-ignore
    }

    @action
    onPointerDown = (e: React.PointerEvent): void => {
        if ((this.props.Document._freeform_scale || 1) !== 1) return;
        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
            this.props.select(false);
            MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
            this._marqueeing = [e.clientX, e.clientY];
            const target = e.target as any;
            if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
            } else {
                // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
                setTimeout(
                    action(() => (this._marqueeing = undefined)),
                    100
                ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.

                document.addEventListener('pointerup', this.onSelectEnd);
            }
        }
    };

    @action
    onSelectEnd = (e: PointerEvent): void => {
        this.props.select(false);
        document.removeEventListener('pointerup', this.onSelectEnd);

        const sel = window.getSelection();
        if (sel) {
            AnchorMenu.Instance.setSelectedText(sel.toString());
        }

        if (sel?.type === 'Range') {
            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
        }

        // Changing which document to add the annotation to (the currently selected PDF)
        GPTPopup.Instance.setSidebarId('data_sidebar');
        GPTPopup.Instance.addDoc = this.sidebarAddDocument;
    };

    render() {
        const scale = this.props.NativeDimScaling?.() || 1;

        return !this.records.length ? (
            // displays how to get data into the DataVizBox if its empty
            <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div>
        ) : (
            <div
                className="dataViz-box"
                onPointerDown={this.marqueeDown}
                style={{
                    pointerEvents: this.props.isContentActive() === true ? 'all' : 'none',
                    width: `${100 / scale}%`,
                    height: `${100 / scale}%`,
                    transform: `scale(${scale})`,
                    position: 'absolute',
                }}
                onWheel={e => e.stopPropagation()}
                ref={this._mainCont}
                >
                <div className={'datatype-button'}>
                    <Toggle text={'  TABLE  '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
                    <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
                    <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} />
                    <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} />
                </div>

                <CollectionFreeFormView
                    ref={this._ffref}
                    {...this.props}
                    setContentView={emptyFunction}
                    renderDepth={this.props.renderDepth + 1}
                    fieldKey={this.annotationKey}
                    styleProvider={this.props.styleProvider}
                    isAnnotationOverlay={true}
                    annotationLayerHostsContent={true}
                    PanelWidth={this.props.PanelWidth}
                    PanelHeight={this.props.PanelHeight}
                    select={emptyFunction}
                    isAnyChildContentActive={returnFalse}
                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                    removeDocument={this.removeDocument}
                    moveDocument={this.moveDocument}
                    addDocument={this.addDocument}>
                </CollectionFreeFormView>

                {this.renderVizView}
                <div className="dataviz-sidebar" 
                     style={{ width: `${this.sidebarWidthPercent}`, 
                     backgroundColor: `${this.sidebarColor}` }}
                     onPointerDown={this.onPointerDown}>
                    <SidebarAnnos
                        ref={this._sidebarRef}
                        {...this.props}
                        fieldKey={this.fieldKey}
                        rootDoc={this.rootDoc}
                        layoutDoc={this.layoutDoc}
                        dataDoc={this.dataDoc}
                        usePanelWidth={true}
                        showSidebar={this.SidebarShown}
                        nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                        PanelWidth={this.sidebarWidth}
                        sidebarAddDocument={this.sidebarAddDocument}
                        moveDocument={this.moveDocument}
                        removeDocument={this.sidebarRemoveDocument}
                    />
                </div>
                {this.sidebarHandle}
                {this.annotationLayer}
                {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
                    // <MarqueeAnnotator
                    //     rootDoc={this.rootDoc}
                    //     scrollTop={0}
                    //     down={this._marqueeing}
                    //     scaling={this.props.NativeDimScaling}
                    //     docView={this.props.DocumentView}
                    //     addDocument={this.sidebarAddDocument}
                    //     finishMarquee={this.finishMarquee}
                    //     savedAnnotations={this.savedAnnotations}
                    //     selectionText={returnEmptyString}
                    //     annotationLayer={this._annotationLayer.current}
                    //     mainCont={this._mainCont.current}
                    //     highlightDragSrcColor={''}
                    // />

                    <MarqueeAnnotator
                            rootDoc={this.rootDoc}
                            getPageFromScroll={undefined}
                            anchorMenuClick={this.anchorMenuClick}
                            scrollTop={0}
                            annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)}
                            addDocument={this.sidebarAddDocument}
                            docView={this.props.DocumentView!}
                            finishMarquee={this.finishMarquee}
                            savedAnnotations={this.savedAnnotations}
                            selectionText={returnEmptyString}
                            annotationLayer={this._annotationLayer.current}
                            mainCont={this._mainCont.current}
                            anchorMenuCrop={this.crop}
                        />
                    
                )}
            </div>
        );
    }
}
