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 { Id } from "../../../fields/FieldSymbols";
import { RichTextField } from "../../../fields/RichTextField";
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { ScriptField } from "../../../fields/ScriptField";
import { NumCast } from "../../../fields/Types";
import { ImageField } from "../../../fields/URLField";
import { TraceMobx } from "../../../fields/util";
import { emptyFunction, returnEmptyString, setupMoveUpEvents } 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";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;

// So this is how we are storing a column
interface CSVFieldColumnProps {
    Document: Doc;
    DataDoc: Opt<Doc>;
    docList: Doc[];
    heading: string;
    pivotField: string;
    chromeHidden?: boolean;
    columnHeaders: 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[];
    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: (n: number) => void
    columnStartXCoords: number[]
    PanelWidth: number
    maxColWidth: number
}

@observer
export class CollectionNoteTakingViewFieldColumn extends React.Component<CSVFieldColumnProps> {
    @observable private _background = "inherit";

    // It seems like this is being a little funky atm
    @computed get columnWidth() {
      // base cases
      if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length == 1) {
        return this.props.maxColWidth
      }
      const i = this.props.columnHeaders.indexOf(this.props.headingObject)
      if (i < 0) {
        return this.props.maxColWidth
      }
      const endColValue = i == this.props.numGroupColumns - 1 ? this.props.PanelWidth : this.props.columnStartXCoords[i+1]
      return endColValue - this.props.columnStartXCoords[i] - 10
    }

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

    @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;

    // 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._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);

    @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 });
        newDoc[key] = this.getValue(this.props.heading);
        // TODO not sure why we have all of this "maxHeading stuff"
        // 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.SelectOnLoad = newDoc[Id];
        FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " ";
        return this.props.addDocument?.(newDoc) || false;
    }

    @action
    deleteColumn = () => {
        if (!this.props.columnHeaders) {
          return
        }
        if (this.props.headingObject) {
          const index = this.props.columnHeaders.indexOf(this.props.headingObject);
          const newIndex = index == 0 ? 1 : index - 1
          const newHeader = this.props.columnHeaders[newIndex];
          this.props.docList.forEach(d => d[this.props.pivotField] = newHeader.heading.toString())
          this.props.columnHeaders.splice(index, 1);
          this.props.resizeColumns(this.props.columnHeaders.length)
        }
    }

    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[]) => {
        console.log('in startDrag')
        // is MakeAlias a way to make a copy of a doc without rendering it?
        const alias = Doc.MakeAlias(this.props.Document);
        // alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1);
        alias._width = this.columnWidth;
        alias._pivotField = undefined;
        let value = this.getValue(this._heading);
        value = typeof value === "string" ? `"${value}"` : value;
        alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
        if (alias.viewSpecScript) {
            const options = {hideSource: false}
            DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY, options);
            console.log('in startDrag')
            return true;
        }
        return false;
    }

    menuCallback = (x: number, y: number) => {
        ContextMenu.Instance.clearItems();
        const layoutItems: ContextMenuProps[] = [];
        const docItems: ContextMenuProps[] = [];
        const dataDoc = this.props.DataDoc || this.props.Document;

        DocUtils.addDocumentCreatorMenuItems((doc) => {
            FormattedTextBox.SelectOnLoad = doc[Id];
            return this.props.addDocument?.(doc);
        }, this.props.addDocument, x, y, 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.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: (this.props.columnWidth) /
                    //     ((uniqueHeadings.length) || 1)
                    width: this.columnWidth
                }}>
                <div className="collectionNoteTakingView-sectionHeader-subCont" onPointerDown={this.headerDown}
                    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>
            </div> : (null);
        // const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
        const templatecols = `${this.columnWidth}px `;
        const type = this.props.Document.type;
        return <>
            {headingView}
            {<div>
              <div key={`${heading}-stack`} className={`collectionNoteTakingView-Nodes`}
                  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 ?
                  <div className="collectionNoteTakingView-DocumentButtons"
                      // style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
                      style={{ width: this.columnWidth, 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>
                      {(this.props.columnHeaders?.length && this.props.columnHeaders.length > 1) && 
                          <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}>
                          <FontAwesomeIcon icon="trash" size="lg" />
                      </button>
                      }
                  </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={"collectionNoteTakingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
                style={{
                  //TODO: change this so that it's based on the column width 
                    width: `${100 / (uniqueHeadings.length || 1)}%`,
                    height: "100%",
                    background: this._background
                }}
                ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
                {this.innards}
            </div >
        );
    }
}