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

interface CSVFieldColumnProps {
    Document: Doc;
    DataDoc: Opt<Doc>;
    docList: Doc[];
    heading: string;
    pivotField: string;
    chromeHidden?: boolean;
    columnHeaders: SchemaHeaderField[] | undefined;
    headingObject: SchemaHeaderField | undefined;
    yMargin: number;
    numGroupColumns: number;
    gridGap: number;
    type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
    headings: () => object[];
    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: (isAdd: boolean, colWidth: number, colIndex: number) => boolean;
    PanelWidth: number;
    maxColWidth: number;
    dividerWidth: number;
    availableWidth: number;
}

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

    // columnWidth returns the width of a column in absolute pixels
    @computed get columnWidth() {
        if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length === 1) return '100%';
        const i = this.props.columnHeaders.indexOf(this.props.headingObject);
        return this.props.columnHeaders[i].width * 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));
    });

    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.columnHeaders?.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.GetIsDragging() && (this._background = '#b4b4b4');
    @action pointerLeave = () => (this._background = 'inherit');
    textCallback = (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, _fitWidth: true, title: value, _autoHeight: true });
        const colValue = this.getValue(this.props.heading);
        newDoc[key] = colValue;
        FormattedTextBox.SelectOnLoad = newDoc[Id];
        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
    @action
    deleteColumn = () => {
        const acolumnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null);
        if (acolumnHeaders && this.props.headingObject) {
            const index = acolumnHeaders.indexOf(this.props.headingObject);
            const columnHeaders = acolumnHeaders; // new List<SchemaHeaderField>(acolumnHeaders.map(header => header[Copy]())); // needed for undo to work properly.  otherwise we end up changing field values in the undo stack since they are shared by reference
            const newColIndex = index > 0 ? index - 1 : 1;
            const newColHeader = this.props.columnHeaders ? this.props.columnHeaders[newColIndex] : undefined;
            const newHeading = newColHeader ? newColHeader.heading : 'unset';
            this.props.docList.forEach(d => (d[this.props.pivotField] = newHeading));
            const colWidth = this.props.columnHeaders ? this.props.columnHeaders[index].width : 0;
            columnHeaders.splice(index, 1);
            //Doc.GetProto(this.props.Document).columnHeaders = columnHeaders;
            this.props.resizeColumns(false, colWidth, index);
        }
    };

    menuCallback = (x: number, y: number) => {
        ContextMenu.Instance.clearItems();
        const layoutItems: ContextMenuProps[] = [];
        const docItems: ContextMenuProps[] = [];
        const dataDoc = this.props.DataDoc || 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.SelectOnLoad = doc[Id];
                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, _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} SetValue={this.headingChanged} contents={evContents} oneLine={true} />
                </div>
                {(this.props.columnHeaders?.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 style={{ height: '100%' }}>
                    <div
                        key={`${heading}-stack`}
                        className={`collectionNoteTakingView-Nodes`}
                        style={{
                            padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
                            margin: 'auto',
                            width: 'max-content',
                            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 ? (
                        <div className="collectionNoteTakingView-DocumentButtons" style={{ marginBottom: 10 }}>
                            <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton">
                                <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} 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>
            </>
        );
    }

    render() {
        TraceMobx();
        const heading = this._heading;
        return (
            <div
                className={'collectionNoteTakingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
                key={heading}
                style={{
                    width: this.columnWidth,
                    background: this._background,
                }}
                ref={this.createColumnDropRef}
                onPointerEnter={this.pointerEntered}
                onPointerLeave={this.pointerLeave}>
                {this.innards}
            </div>
        );
    }
}
