import { toUpper } from 'lodash';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
import { PresBox } from '../nodes/trails';
import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTimeView.scss';
import React = require('react');

@observer
export class CollectionTimeView extends CollectionSubView() {
    _changing = false;
    @observable _layoutEngine = computePivotLayout.name;
    @observable _collapsed: boolean = false;
    @observable _childClickedScript: Opt<ScriptField>;
    @observable _viewDefDivClick: Opt<ScriptField>;
    @observable _focusPivotField: Opt<string>;

    async componentDidMount() {
        this.props.setContentView?.(this);
        //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
        ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; useRightSplit(alias, shiftKey); ";
        runInAction(() => {
            this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name });
            this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
        });
    }

    get pivotField() {
        return this._focusPivotField || StrCast(this.layoutDoc._pivotField);
    }

    getAnchor = (addAsAnnotation: boolean) => {
        const anchor = Docs.Create.HTMLAnchorDocument([], {
            title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
            annotationOn: this.rootDoc,
        });
        PresBox.pinDocView(anchor, { pinData: { viewType: true, pivot: true, filters: true } }, this.rootDoc);

        if (addAsAnnotation) {
            // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
            if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
                Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor);
            } else {
                this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]);
            }
        }
        return anchor;
    };

    @action
    scrollPreview = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
        // if in preview, then override document's fields with view spec
        this._focusFilters = StrListCast(anchor.presDocFilters);
        this._focusRangeFilters = StrListCast(anchor.presPinDocRangeFilters);
        this._focusPivotField = StrCast(anchor.presPivotField);
        return undefined;
    };

    layoutEngine = () => this._layoutEngine;
    toggleVisibility = action(() => (this._collapsed = !this._collapsed));

    onMinDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            action((e: PointerEvent, down: number[], delta: number[]) => {
                const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
                const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
                this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
                this.props.Document[this.props.fieldKey + '-timelineSpan'] = undefined;
                return false;
            }),
            returnFalse,
            emptyFunction
        );
    };

    onMaxDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            action((e: PointerEvent, down: number[], delta: number[]) => {
                const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
                const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
                this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
                return false;
            }),
            returnFalse,
            emptyFunction
        );
    };

    onMidDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            action((e: PointerEvent, down: number[], delta: number[]) => {
                const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
                const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
                this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
                this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
                return false;
            }),
            returnFalse,
            emptyFunction
        );
    };

    goTo = (prevFilterIndex: number) => {
        this.layoutDoc._pivotField = this.layoutDoc['_prevPivotFields' + prevFilterIndex];
        this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocFilter' + prevFilterIndex] as ObjectField);
        this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocRangeFilters' + prevFilterIndex] as ObjectField);
        this.layoutDoc._prevFilterIndex = prevFilterIndex;
    };

    @action
    contentsDown = (e: React.MouseEvent) => {
        const prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex);
        if (prevFilterIndex > 0) {
            this.goTo(prevFilterIndex - 1);
        } else {
            this.layoutDoc._docFilters = new List([]);
        }
    };

    @computed get contents() {
        return (
            <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
                <CollectionFreeFormView
                    {...this.props}
                    engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
                    fitContentsToBox={returnTrue}
                    childClickScript={this._childClickedScript}
                    viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick}
                    layoutEngine={this.layoutEngine}
                />
            </div>
        );
    }

    public static SyncTimelineToPresentation(doc: Doc) {
        const fieldKey = Doc.LayoutFieldKey(doc);
        doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)");
    }
    specificMenu = (e: React.MouseEvent) => {
        const layoutItems: ContextMenuProps[] = [];
        const doc = this.layoutDoc;

        layoutItems.push({
            description: 'Force Timeline',
            event: () => {
                doc._forceRenderEngine = computeTimelineLayout.name;
            },
            icon: 'compress-arrows-alt',
        });
        layoutItems.push({
            description: 'Force Pivot',
            event: () => {
                doc._forceRenderEngine = computePivotLayout.name;
            },
            icon: 'compress-arrows-alt',
        });
        layoutItems.push({
            description: 'Auto Time/Pivot layout',
            event: () => {
                doc._forceRenderEngine = undefined;
            },
            icon: 'compress-arrows-alt',
        });
        layoutItems.push({ description: 'Sync with presentation', event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: 'compress-arrows-alt' });

        ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
    };
    @computed get _allFacets() {
        const facets = new Set<string>();
        this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
        Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
        return Array.from(facets);
    }
    menuCallback = (x: number, y: number) => {
        ContextMenu.Instance.clearItems();
        const docItems: ContextMenuProps[] = [];
        const keySet: Set<string> = new Set();

        this.childLayoutPairs.map(pair =>
            this._allFacets
                .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string')
                .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey[0] !== '#' || fieldKey === '#') && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
                .map(fieldKey => keySet.add(fieldKey))
        );
        Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
        docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' });
        ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' });
        const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
        ContextMenu.Instance.displayMenu(x, y, ':');
    };

    @computed get pivotKeyUI() {
        return (
            <div className={'pivotKeyEntry'}>
                <EditableView
                    GetValue={returnEmptyString}
                    SetValue={(value: any) => {
                        if (value?.length) {
                            this.layoutDoc._pivotField = value;
                            return true;
                        }
                        return false;
                    }}
                    background={'#f1efeb'} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
                    contents={':' + StrCast(this.layoutDoc._pivotField)}
                    showMenuOnLoad={true}
                    display={'inline'}
                    menuCallback={this.menuCallback}
                />
            </div>
        );
    }

    render() {
        let nonNumbers = 0;
        this.childDocs.map(doc => {
            const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField])));
            if (Number.isNaN(num)) {
                nonNumbers++;
            }
        });
        const forceLayout = StrCast(this.layoutDoc._forceRenderEngine);
        const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6;
        if (doTimeline !== (this._layoutEngine === computeTimelineLayout.name)) {
            if (!this._changing) {
                this._changing = true;
                setTimeout(
                    action(() => {
                        this._layoutEngine = doTimeline ? computeTimelineLayout.name : computePivotLayout.name;
                        this._changing = false;
                    }),
                    0
                );
            }
        }

        return (
            <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this.props.PanelWidth(), height: '100%' }}>
                {this.pivotKeyUI}
                {this.contents}
                {!this.props.isSelected() || !doTimeline ? null : (
                    <>
                        <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
                        <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
                        <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
                    </>
                )}
            </div>
        );
    }
}

ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
    const pivotField = StrCast(pivotDoc._pivotField, 'author');
    let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
    const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
    pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
    pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
    pivotDoc['_prevPivotFields' + prevFilterIndex] = pivotField;
    pivotDoc._prevFilterIndex = ++prevFilterIndex;
    pivotDoc._docFilters = new List();
    setTimeout(
        action(() => {
            const filterVals = bounds.payload as string[];
            filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check'));
            const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
            if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
                if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
                    pivotDoc._pivotField = filterVals[0];
                }
            }
            const newFilters = StrListCast(pivotDoc._docFilters);
            if (newFilters.length && originalFilter.length && newFilters.lastElement() === originalFilter.lastElement()) {
                pivotDoc._prevFilterIndex = --prevFilterIndex;
                pivotDoc['_prevDocFilter' + prevFilterIndex] = undefined;
                pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = undefined;
                pivotDoc['_prevPivotFields' + prevFilterIndex] = undefined;
            }
        })
    );
});
