import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { returnEmptyString } from '../../../Utils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { Cast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager } 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 { ObservableReactComponent } from '../ObservableReactComponent';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import './CollectionNoteTakingView.scss';

interface CSVFieldColumnProps {
    Document: Doc;
    TemplateDataDocument: Opt<Doc>;
    docList: Doc[];
    heading: string;
    pivotField: string;
    fieldKey: string | undefined;
    chromeHidden?: boolean;
    colHeaderData: SchemaHeaderField[] | undefined;
    headingObject: SchemaHeaderField | undefined;
    yMargin: number;
    numGroupColumns: number;
    gridGap: number;
    type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
    headings: () => object[];
    select: (ctrlPressed: boolean) => void;
    renderChildren: (docs: Doc[]) => JSX.Element[];
    addDocument: (doc: Doc | Doc[]) => boolean;
    createDropTarget: (ele: HTMLDivElement) => void;
    screenToLocalTransform: () => Transform;
    observeHeight: (myref: any) => void;
    unobserveHeight: (myref: any) => void;
    editableViewProps: () => any;
    resizeColumns: (headers: SchemaHeaderField[]) => boolean;
    maxColWidth: number;
    dividerWidth: number;
    availableWidth: number;
    PanelWidth: () => number;
}

/**
 * CollectionNoteTakingViewColumn represents an individual column rendered in CollectionNoteTakingView. The
 * majority of functions here are for rendering styles.
 */
@observer
export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSVFieldColumnProps> {
    @observable private _background = 'inherit';

    // columnWidth returns the width of a column in absolute pixels
    @computed get columnWidth() {
        if (!this._props.colHeaderData || !this._props.headingObject || this._props.colHeaderData.length === 1) return `${(this._props.availableWidth / this._props.PanelWidth()) * 100}%`;
        const i = this._props.colHeaderData.indexOf(this._props.headingObject);
        return ((this._props.colHeaderData[i].width * this._props.availableWidth) / this._props.PanelWidth()) * 100 + '%';
    }

    private dropDisposer?: DragManager.DragDropDisposer;
    private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();

    public static ColumnMargin = 10;
    @observable _heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading;
    @observable _color = this._props.headingObject ? this._props.headingObject.color : '#f1efeb';
    _ele: HTMLElement | null = null;

    createColumnDropRef = (ele: HTMLDivElement | null) => {
        this.dropDisposer?.();
        if (ele) {
            this._ele = ele;
            this._props.observeHeight(ele);
            this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
        }
    };

    componentWillUnmount() {
        this._props.unobserveHeight(this._ele);
    }

    @undoBatch
    columnDrop = action((e: Event, de: DragManager.DropEvent) => {
        const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
        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.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 pointerEntered = () => SnappingManager.IsDragging && (this._background = '#b4b4b4');
    @action pointerLeave = () => (this._background = 'inherit');
    @undoBatch
    addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true);

    // addNewTextDoc is called when a user starts typing in a column to create a new node
    @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 });
        const colValue = this.getValue(this._props.heading);
        newDoc[key] = colValue;
        FormattedTextBox.SetSelectOnLoad(newDoc);
        FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
        return this._props.addDocument?.(newDoc) || false;
    };

    // deleteColumn is called when a user deletes a column using the 'trash' icon in the button area.
    // If the user deletes the first column, the documents get moved to the second column. Otherwise,
    // all docs are added to the column directly to the left.
    @undoBatch
    deleteColumn = () => {
        const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null));
        if (this._props.headingObject) {
            this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
            colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1);
            this._props.resizeColumns(colHdrData);
        }
    };

    menuCallback = (x: number, y: number) => {
        ContextMenu.Instance.clearItems();
        const layoutItems: ContextMenuProps[] = [];
        const docItems: ContextMenuProps[] = [];
        const dataDoc = this._props.TemplateDataDocument || this._props.Document;
        const pivotValue = this.getValue(this._props.heading);

        DocUtils.addDocumentCreatorMenuItems(
            doc => {
                const key = this._props.pivotField;
                doc[key] = this.getValue(this._props.heading);
                FormattedTextBox.SetSelectOnLoad(doc);
                return this._props.addDocument?.(doc);
            },
            this._props.addDocument,
            x,
            y,
            true,
            this._props.pivotField,
            pivotValue
        );

        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.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
        !Doc.UserDoc().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);
            }
        });
        ContextMenu.Instance.displayMenu(x, y, undefined, true);
    };

    @computed get innards() {
        TraceMobx();
        const key = this._props.pivotField;
        const heading = this._heading;
        const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
        const evContents = heading ? heading : '25';
        const headingView = this._props.headingObject ? (
            <div
                key={heading}
                className="collectionNoteTakingView-sectionHeader"
                ref={this._headerRef}
                style={{
                    marginTop: 2 * this._props.yMargin,
                    width: 'calc(100% - 5px)',
                }}>
                <div
                    className="collectionNoteTakingView-sectionHeader-subCont"
                    title={evContents === `No Value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ''}
                    style={{ background: evContents !== `No Value` ? this._color : 'inherit' }}>
                    <EditableView GetValue={() => evContents} isEditingCallback={isEditing => isEditing && this._props.select(false)} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
                </div>
                {(this._props.colHeaderData?.length ?? 0) > 1 && (
                    <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
                        <FontAwesomeIcon icon="trash" size="lg" />
                    </button>
                )}
            </div>
        ) : null;
        const templatecols = this.columnWidth;
        const type = this._props.Document.type;
        return (
            <>
                {headingView}
                <div className="collectionNoteTakingView-columnStackContainer">
                    <div className="collectionNoteTakingView-columnStack">
                        <div
                            key={`${heading}-stack`}
                            className={`collectionNoteTakingView-Nodes`}
                            style={{
                                padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
                                gridGap: this._props.gridGap,
                                gridTemplateColumns: templatecols,
                            }}>
                            {this._props.renderChildren(this._props.docList)}
                        </div>

                        {!this._props.chromeHidden ? (
                            <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}>
                                <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
                                    <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.addTextNote} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
                                </div>
                                <div key={`${this._props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton">
                                    <EditableView {...this._props.editableViewProps()} />
                                </div>
                            </div>
                        ) : null}
                    </div>
                </div>
            </>
        );
    }

    render() {
        TraceMobx();
        const heading = this._heading;
        return (
            <div
                className="collectionNoteTakingViewFieldColumn"
                key={heading}
                style={{
                    width: this.columnWidth,
                    background: this._background,
                    marginLeft: this._props.headings().findIndex((h: any) => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Document.xMargin) : 0,
                }}
                ref={this.createColumnDropRef}
                onPointerEnter={this.pointerEntered}
                onPointerLeave={this.pointerLeave}>
                {this.innards}
            </div>
        );
    }
}
