import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { RichTextField } from '../../../fields/RichTextField';
import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { EditableView } from '../EditableView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { ObservableReactComponent } from '../ObservableReactComponent';
import './CollectionStackingView.scss';

// So this is how we are storing a column
interface CSVFieldColumnProps {
    Document: Doc;
    TemplateDataDocument: Opt<Doc>;
    docList: Doc[];
    heading: string;
    pivotField: string;
    chromeHidden?: boolean;
    colHeaderData: SchemaHeaderField[] | undefined;
    headingObject: SchemaHeaderField | undefined;
    yMargin: number;
    columnWidth: number;
    numGroupColumns: number;
    gridGap: number;
    type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
    headings: () => object[];
    // I think that stacking view actually has a single column and then supposedly you can add more columns? Unsure
    renderChildren: (docs: Doc[]) => JSX.Element[];
    addDocument: (doc: Doc | Doc[]) => boolean;
    createDropTarget: (ele: HTMLDivElement) => void;
    screenToLocalTransform: () => Transform;
    refList: any[];
}

@observer
export class CollectionStackingViewFieldColumn extends ObservableReactComponent<CSVFieldColumnProps> {
    private dropDisposer?: DragManager.DragDropDisposer;
    private _disposers: { [name: string]: IReactionDisposer } = {};
    private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
    @observable _background = 'inherit';
    @observable _paletteOn = false;
    @observable _heading = '';
    @observable _color = '';

    constructor(props: any) {
        super(props);
        makeObservable(this);
        this._heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading;
        this._color = this._props.headingObject ? this._props.headingObject.color : '#f1efeb';
    }

    _ele: HTMLElement | null = null;

    protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => {
        const dragData = de.complete.docDragData;
        if (dragData) {
            const sourceDragAction = dragData.dropAction;
            const sameCollection = !dragData.draggedDocuments.some(d => !this._props.docList.includes(d));
            dragData.dropAction = !sameCollection // if doc from another tree
                ? sourceDragAction || targetDropAction // then use the source's dragAction otherwise the target's
                : sourceDragAction === dropActionType.inPlace // if source drag is inPlace
                  ? sourceDragAction // keep the doc in place
                  : dropActionType.same; // otherwise use same tree semantics to move within tree

            e.stopPropagation();
        }
    };

    // This is likely similar to what we will be doing. Why do we need to make these refs?
    // is that the only way to have drop targets?
    createColumnDropRef = (ele: HTMLDivElement | null) => {
        this.dropDisposer?.();
        if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document, this.onInternalPreDrop.bind(this));
        else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
        this._ele = ele;
    };

    @action
    componentDidMount() {
        this._ele && this.props.refList.push(this._ele);
        this._disposers.collapser = reaction(
            () => this._props.headingObject?.collapsed,
            collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
            { fireImmediately: true }
        );
    }
    componentWillUnmount() {
        this._disposers.collapser?.();
        this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
        this._ele = null;
    }

    //TODO: what is scripting? I found it in SetInPlace def but don't know what that is
    @undoBatch
    columnDrop = action((e: Event, de: DragManager.DropEvent) => {
        const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
        this._props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this._props.pivotField, drop.val, false));
        return true;
    });
    getValue = (value: string): any => {
        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) => {
        const castedValue = this.getValue(value);
        if (castedValue) {
            if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
                return false;
            }
            this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
            if (this._props.headingObject) {
                this._props.headingObject.setHeading(castedValue.toString());
                this._heading = this._props.headingObject.heading;
            }
            return true;
        }
        return false;
    };

    @action
    changeColumnColor = (color: string) => {
        this._props.headingObject?.setColor(color);
        this._color = color;
    };

    @action pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4');
    @action pointerLeave = () => (this._background = 'inherit');
    @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);

    @action
    addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
        if (!value && !forceEmptyNote) return false;
        const key = this._props.pivotField;
        const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
        key && (newDoc[key] = this.getValue(this._props.heading));
        const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
        const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
        newDoc.heading = heading;
        FormattedTextBox.SetSelectOnLoad(newDoc);
        FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
        return this._props.addDocument?.(newDoc) || false;
    };

    @action
    deleteColumn = () => {
        this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
        if (this._props.colHeaderData && this._props.headingObject) {
            const index = this._props.colHeaderData.indexOf(this._props.headingObject);
            this._props.colHeaderData.splice(index, 1);
        }
    };

    @action
    collapseSection = () => {
        this._props.headingObject?.setCollapsed(!this._props.headingObject.collapsed);
        this.collapsed = BoolCast(this._props.headingObject?.collapsed);
    };

    headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);

    //TODO: I think this is where I'm supposed to edit stuff
    startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
        // is MakeEmbedding a way to make a copy of a doc without rendering it?
        const embedding = Doc.MakeEmbedding(this._props.Document);
        embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
        embedding._pivotField = undefined;
        let value = this.getValue(this._heading);
        value = typeof value === 'string' ? `"${value}"` : value;
        embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this._props.pivotField} === ${value}`, { doc: Doc.name });
        if (embedding.viewSpecScript) {
            DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY);
            return true;
        }
        return false;
    };

    renderColorPicker = () => {
        const gray = '#f1efeb';
        const selected = this._props.headingObject ? this._props.headingObject.color : gray;
        const colors = ['pink2', 'purple4', 'bluegreen1', 'yellow4', 'gray', 'red2', 'bluegreen7', 'bluegreen5', 'orange1'];
        return (
            <div className="collectionStackingView-colorPicker">
                <div className="colorOptions">
                    {colors.map(col => {
                        const palette = PastelSchemaPalette.get(col);
                        return <div className={'colorPicker' + (selected === palette ? ' active' : '')} style={{ backgroundColor: palette }} onClick={() => this.changeColumnColor(palette!)} />;
                    })}
                </div>
            </div>
        );
    };

    renderMenu = () => {
        return (
            <div className="collectionStackingView-optionPicker">
                <div className="optionOptions">
                    <div className={'optionPicker' + (true ? ' active' : '')} onClick={action(() => {})}>
                        Add options here
                    </div>
                </div>
            </div>
        );
    };

    @observable private collapsed: boolean = false;

    private toggleVisibility = action(() => (this.collapsed = !this.collapsed));

    menuCallback = (x: number, y: number) => {
        ContextMenu.Instance.clearItems();
        const layoutItems: ContextMenuProps[] = [];
        const docItems: ContextMenuProps[] = [];
        const dataDoc = this._props.TemplateDataDocument || this._props.Document;
        const width = this._ele ? DivWidth(this._ele) : 0;
        const height = this._ele ? DivHeight(this._ele) : 0;
        DocUtils.addDocumentCreatorMenuItems(
            doc => {
                FormattedTextBox.SetSelectOnLoad(doc);
                return this._props.addDocument?.(doc);
            },
            this._props.addDocument,
            0,
            0,
            true
        );

        Array.from(Object.keys(Doc.GetProto(dataDoc)))
            .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string')
            .map(fieldKey =>
                docItems.push({
                    description: ':' + fieldKey,
                    event: () => {
                        const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
                        if (created) {
                            if (this._props.Document.isTemplateDoc) {
                                Doc.MakeMetadataFieldTemplate(created, this._props.Document);
                            }
                            return this._props.addDocument?.(created);
                        }
                    },
                    icon: 'compress-arrows-alt',
                })
            );
        Array.from(Object.keys(Doc.GetProto(dataDoc)))
            .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length)
            .map(fieldKey =>
                docItems.push({
                    description: ':' + fieldKey,
                    event: () => {
                        const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
                        if (created) {
                            const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document;
                            if (container.isTemplateDoc) {
                                Doc.MakeMetadataFieldTemplate(created, container);
                                return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
                            }
                            return this._props.addDocument?.(created) || false;
                        }
                    },
                    icon: 'compress-arrows-alt',
                })
            );
        !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
        !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
        ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
            Doc.GetProto(this._props.Document)[name] = '';
            const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
            if (created) {
                if (this._props.Document.isTemplateDoc) {
                    Doc.MakeMetadataFieldTemplate(created, this._props.Document);
                }
                this._props.addDocument?.(created);
            }
        });
        const pt = this._props
            .screenToLocalTransform()
            .inverse()
            .transformPoint(width - 30, height);
        ContextMenu.Instance.displayMenu(pt[0], pt[1], undefined, true);
    };

    @computed get innards() {
        TraceMobx();
        const key = this._props.pivotField;
        const headings = this._props.headings();
        const heading = this._heading;
        const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
        const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
        const noValueHeader = `NO ${key.toUpperCase()} VALUE`;
        const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader;
        const headingView = this._props.headingObject ? (
            <div
                key={heading}
                className="collectionStackingView-sectionHeader"
                ref={this._headerRef}
                style={{
                    marginTop: this._props.yMargin,
                    width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1),
                }}>
                {/* the default bucket (no key value) has a tooltip that describes what it is.
                    Further, it does not have a color and cannot be deleted. */}
                <div
                    className="collectionStackingView-sectionHeader-subCont"
                    onPointerDown={this.headerDown}
                    title={evContents === noValueHeader ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
                    style={{ background: evContents !== noValueHeader ? this._color : 'inherit' }}>
                    <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
                    <div className="collectionStackingView-sectionColor" style={{ display: evContents === noValueHeader ? 'none' : undefined }}>
                        <button className="collectionStackingView-sectionColorButton" onClick={action(e => (this._paletteOn = !this._paletteOn))}>
                            <FontAwesomeIcon icon="palette" size="lg" />
                        </button>
                        {this._paletteOn ? this.renderColorPicker() : null}
                    </div>
                    <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
                        <FontAwesomeIcon icon="trash" size="lg" />
                    </button>
                    {/* {evContents === noValueHeader ? null : (
                        <div className="collectionStackingView-sectionOptions">
                            <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}>
                                <button className="collectionStackingView-sectionOptionButton">
                                    <FontAwesomeIcon icon="ellipsis-v" size="lg"></FontAwesomeIcon>
                                </button>
                            </Flyout>
                        </div>
                    )} */}
                </div>
                <div
                    className={'collectionStackingView-collapseBar' + (this._props.headingObject.collapsed === true ? ' active' : '')}
                    style={{ display: this._props.headingObject.collapsed === true ? 'block' : undefined }}
                    onClick={this.collapseSection}
                />
            </div>
        ) : null;
        const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
        const type = this._props.Document.type;
        return (
            <>
                {this._props.Document._columnsHideIfEmpty ? null : headingView}
                {this.collapsed ? null : (
                    <div>
                        <div
                            key={`${heading}-stack`}
                            className="collectionStackingView-masonrySingle"
                            style={{
                                padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
                                margin: 'auto',
                                width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
                                height: 'max-content',
                                position: 'relative',
                                gridGap: this._props.gridGap,
                                gridTemplateColumns: templatecols,
                                gridAutoRows: '0px',
                            }}>
                            {this._props.renderChildren(this._props.docList)}
                        </div>
                        {!this._props.chromeHidden && type !== DocumentType.PRES ? (
                            // TODO: this is the "new" button: see what you can work with here
                            // change cursor to pointer for this, and update dragging cursor
                            //TODO: there is a bug that occurs when adding a freeform document and trying to move it around
                            //TODO: would be great if there was additional space beyond the frame, so that you can actually see your
                            // bottom note
                            //TODO: ok, so we are using a single column, and this is it!
                            <div
                                key={`${heading}-add-document`}
                                onKeyDown={e => e.stopPropagation()}
                                className="collectionStackingView-addDocumentButton"
                                style={{ width: 'calc(100% - 25px)', maxWidth: this._props.columnWidth / this._props.numGroupColumns - 25, marginBottom: 10 }}>
                                <EditableView
                                    GetValue={returnEmptyString}
                                    SetValue={this.addNewTextDoc}
                                    textCallback={this.typedNote}
                                    placeholder={"Type ':' for commands"}
                                    contents={<FontAwesomeIcon icon={'plus'} />}
                                    menuCallback={this.menuCallback}
                                />
                            </div>
                        ) : null}
                    </div>
                )}
            </>
        );
    }

    render() {
        TraceMobx();
        const headings = this._props.headings();
        const heading = this._heading;
        const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
        return (
            <div
                className={'collectionStackingViewFieldColumn' + (SnappingManager.IsDragging ? 'Dragging' : '')}
                key={heading}
                style={{
                    width: `${100 / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1)}%`,
                    height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
                    background: this._background,
                }}
                ref={this.createColumnDropRef}
                onPointerEnter={this.pointerEntered}
                onPointerLeave={this.pointerLeave}>
                {this.innards}
            </div>
        );
    }
}
