import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction, numberRange } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { CompileScript } from '../../util/Scripting';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, undoable } from '../../util/UndoManager';
import { EditableView } from '../EditableView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { CollectionStackingView } from './CollectionStackingView';
import './CollectionStackingView.scss';

interface CMVFieldRowProps {
    rows: () => number;
    headings: () => object[];
    Document: Doc;
    chromeHidden?: boolean;
    heading: string;
    headingObject: SchemaHeaderField | undefined;
    docList: Doc[];
    parent: CollectionStackingView;
    pivotField: string;
    type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
    createDropTarget: (ele: HTMLDivElement) => void;
    screenToLocalTransform: () => Transform;
    setDocHeight: (key: string, thisHeight: number) => void;
    refList: Element[];
    showHandle: boolean;
}

@observer
export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVFieldRowProps> {
    constructor(props: CMVFieldRowProps) {
        super(props);
        makeObservable(this);
    }

    @observable private _background = 'inherit';
    @observable private _createEmbeddingSelected: boolean = false;
    @observable private heading: string = '';
    @observable private color: string = '#f1efeb';
    @observable private collapsed: boolean = false;
    @observable private _paletteOn = false;
    private set _heading(value: string) {
        runInAction(() => {
            this._props.headingObject && (this._props.headingObject.heading = this.heading = value);
        });
    }
    private set _color(value: string) {
        runInAction(() => {
            this._props.headingObject && (this._props.headingObject.color = this.color = value);
        });
    }
    private set _collapsed(value: boolean) {
        runInAction(() => {
            this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value);
        });
    }

    private _dropDisposer?: DragManager.DragDropDisposer;
    private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
    private _contRef: React.RefObject<HTMLDivElement> = React.createRef();
    private _ele: HTMLDivElement | null = null;

    createRowDropRef = (ele: HTMLDivElement | null) => {
        this._dropDisposer?.();
        if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document);
        else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
        this._ele = ele;
    };
    @action
    componentDidMount() {
        this.heading = this._props.headingObject?.heading || '';
        this.color = this._props.headingObject?.color || '#f1efeb';
        this.collapsed = this._props.headingObject?.collapsed || false;
        this._ele && this.props.refList.push(this._ele);
    }
    componentWillUnmount() {
        this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
        this._ele = null;
    }

    getTrueHeight = () => {
        if (this.collapsed) {
            this._props.setDocHeight(this.heading, 20);
        } else {
            const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; // +15 accounts for the group header
            const transformScale = this._props.screenToLocalTransform().Scale;
            const trueHeight = rawHeight * transformScale;
            this._props.setDocHeight(this.heading, trueHeight);
        }
    };

    @undoBatch
    rowDrop = action((e: Event, de: DragManager.DropEvent) => {
        this._createEmbeddingSelected = false;
        if (de.complete.docDragData) {
            const key = this._props.pivotField;
            const castedValue = this.getValue(this.heading);
            if (this._props.parent.onInternalDrop(e, de)) {
                key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
            }
            return true;
        }
        return false;
    });

    getValue = (value: string) => {
        const parsed = parseInt(value);
        if (!isNaN(parsed)) return parsed;
        if (value.toLowerCase().indexOf('true') > -1) return true;
        if (value.toLowerCase().indexOf('false') > -1) return false;
        return value;
    };

    @action
    headingChanged = (value: string /* , shiftDown?: boolean */) => {
        this._createEmbeddingSelected = false;
        const key = this._props.pivotField;
        const castedValue = this.getValue(value);
        if (castedValue) {
            if (this._props.parent.colHeaderData) {
                if (this._props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
                    return false;
                }
            }
            key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
            this._heading = castedValue.toString();
            return true;
        }
        return false;
    };

    @action
    changeColumnColor = (color: string) => {
        this._createEmbeddingSelected = false;
        this._color = color;
    };

    pointerEnteredRow = action(() => {
        SnappingManager.IsDragging && (this._background = '#b4b4b4');
    });

    @action
    pointerLeaveRow = () => {
        this._createEmbeddingSelected = false;
        this._background = 'inherit';
    };

    @action
    addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
        if (!value && !forceEmptyNote) return false;
        this._createEmbeddingSelected = false;
        const { pivotField } = this._props;
        const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
        Doc.SetSelectOnLoad(newDoc);
        FormattedTextBox.SelectOnLoadChar = value;
        pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading));
        const docs = this._props.parent.childDocList;
        return docs ? !!docs.splice(0, 0, newDoc) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
    };

    deleteRow = undoable(
        action(() => {
            this._createEmbeddingSelected = false;
            const key = this._props.pivotField;
            key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
            if (this._props.parent.colHeaderData && this._props.headingObject) {
                const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject);
                this._props.parent.colHeaderData.splice(index, 1);
            }
        }),
        'delete row'
    );

    @action
    collapseSection = (e: PointerEvent) => {
        this._createEmbeddingSelected = false;
        this.toggleVisibility();
        e.stopPropagation();
    };

    headerMove = (e: PointerEvent) => {
        const embedding = Doc.MakeEmbedding(this._props.Document);
        const key = this._props.pivotField;
        let value = this.getValue(this.heading);
        value = typeof value === 'string' ? `"${value}"` : value;
        const script = `return doc.${key} === ${value}`;
        const compiled = CompileScript(script, { params: { doc: Doc.name } });
        if (compiled.compiled) {
            embedding.viewSpecScript = new ScriptField(compiled);
            DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY);
        }
        return true;
    };

    @action
    headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
        if (e.button === 0 && !e.ctrlKey) {
            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, clickEv => !this._props.chromeHidden && this.collapseSection(clickEv));
            this._createEmbeddingSelected = false;
        }
    };

    renderColorPicker = () => {
        const selected = this.color;

        const pink = PastelSchemaPalette.get('pink2');
        const purple = PastelSchemaPalette.get('purple4');
        const blue = PastelSchemaPalette.get('bluegreen1');
        const yellow = PastelSchemaPalette.get('yellow4');
        const red = PastelSchemaPalette.get('red2');
        const green = PastelSchemaPalette.get('bluegreen7');
        const cyan = PastelSchemaPalette.get('bluegreen5');
        const orange = PastelSchemaPalette.get('orange1');
        const gray = '#f1efeb';

        return (
            <div className="collectionStackingView-colorPicker">
                <div className="colorOptions">
                    <div className={'colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)} />
                    <div className={'colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)} />
                    <div className={'colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)} />
                    <div className={'colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)} />
                    <div className={'colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)} />
                    <div className={'colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)} />
                    <div className={'colorPicker' + (selected === green ? ' active' : '')} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)} />
                    <div className={'colorPicker' + (selected === cyan ? ' active' : '')} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)} />
                    <div className={'colorPicker' + (selected === orange ? ' active' : '')} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)} />
                </div>
            </div>
        );
    };

    toggleEmbedding = action(() => {
        this._createEmbeddingSelected = true;
    });
    toggleVisibility = () => {
        this._collapsed = !this.collapsed;
    };

    @action
    textCallback = (/* char: string */) => this.addDocument('', false);

    @computed get contentLayout() {
        const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap))));
        const showChrome = !this._props.chromeHidden;
        const stackPad = showChrome ? `0px ${this._props.parent.xMargin}px` : `${this._props.parent.yMargin}px ${this._props.parent.xMargin}px 0px ${this._props.parent.xMargin}px `;
        return this.collapsed ? null : (
            <div style={{ position: 'relative' }}>
                {showChrome ? (
                    <div
                        className="collectionStackingView-addDocumentButton"
                        style={
                            {
                                // width: style.columnWidth / style.numGroupColumns,
                                // padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`,
                            }
                        }>
                        <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" />
                    </div>
                ) : null}
                <div
                    className="collectionStackingView-masonryGrid"
                    ref={this._contRef}
                    style={{
                        padding: stackPad,
                        minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined,
                        width: this._props.parent.NodeWidth,
                        gridGap: this._props.parent.gridGap,
                        gridTemplateColumns: numberRange(rows).reduce(list => list + ` ${this._props.parent.columnWidth}px`, ''),
                    }}>
                    {this._props.parent.children(this._props.docList)}
                </div>
            </div>
        );
    }

    @computed get headingView() {
        const noChrome = this._props.chromeHidden;
        const key = this._props.pivotField;
        const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
        const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />;
        return this._props.Document.miniHeaders ? (
            <div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
        ) : !this._props.headingObject ? null : (
            <div className="collectionStackingView-sectionHeader" ref={this._headerRef}>
                <div
                    className="collectionStackingView-sectionHeader-subCont"
                    onPointerDown={this.headerDown}
                    title={evContents === `NO ${key.toUpperCase()} VALUE` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
                    style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this.color : 'lightgrey' }}>
                    {noChrome ? evContents : <div>{editableHeaderView}</div>}
                    {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? null : (
                        <div className="collectionStackingView-sectionColor">
                            <button
                                type="button"
                                className="collectionStackingView-sectionColorButton"
                                onPointerDown={e =>
                                    setupMoveUpEvents(
                                        this,
                                        e,
                                        returnFalse,
                                        emptyFunction,
                                        action(() => {
                                            this._paletteOn = !this._paletteOn;
                                        })
                                    )
                                }>
                                <FontAwesomeIcon icon="palette" size="lg" />
                            </button>
                            {this._paletteOn ? this.renderColorPicker() : null}
                        </div>
                    )}
                    {noChrome ? null : (
                        <button type="button" className="collectionStackingView-sectionDelete" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, noChrome ? emptyFunction : this.collapseSection)}>
                            <FontAwesomeIcon icon={this.collapsed ? 'chevron-down' : 'chevron-up'} size="lg" />
                        </button>
                    )}
                    {noChrome || evContents === `NO  ${key.toUpperCase()} VALUE` ? null : (
                        <div className="collectionStackingView-sectionOptions" onPointerDown={e => e.stopPropagation()}>
                            <button type="button" className="collectionStackingView-sectionOptionButton" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.deleteRow)}>
                                <FontAwesomeIcon icon="trash" size="lg" />
                            </button>
                        </div>
                    )}
                </div>
            </div>
        );
    }
    render() {
        const background = this._background;
        return (
            <div className="collectionStackingView-masonrySection" style={{ width: this._props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}>
                {this.headingView}
                {this.contentLayout}
            </div>
        );
    }
}
