import { Button, Colors, IconButton } from 'browndash-components';
import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import React from 'react';
import { returnFalse, setupMoveUpEvents } from '../../ClientUtils';
import { Doc, DocListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { NumCast, StrCast } from '../../fields/Types';
import { emptyFunction, Utils } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from '../util/DragManager';
import { SnappingManager } from '../util/SnappingManager';
import { DocumentView } from './nodes/DocumentView';
import { ObservableReactComponent } from './ObservableReactComponent';

interface KeywordItemProps {
    doc: Doc;
    keyword: string;
    keywordDoc: Doc;
    setToEditing: () => void;
    isEditing: boolean;
}

/**
 * A component that handles individual keywords.
 */
@observer
export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
    constructor(props: any) {
        super(props);
        makeObservable(this);
        this.ref = React.createRef();
    }

    private ref: React.RefObject<HTMLDivElement>;

    /**
     * Gets the documents that a keyword is associated with.
     * @returns An array of documents that contain the keyword.
     */
    getKeywordCollectionDocs = () => {
        for (const doc of DocListCast(Doc.ActiveDashboard?.myKeywordCollections)) {
            if (doc.title === this._props.keyword) {
                return doc[DocData].docs;
            }
        }
        return null;
    };

    /**
     * Creates a smart collection.
     * @returns
     */
    createCollection = () => {
        // Get the documents that contain the keyword.
        const selected = DocListCast(this.getKeywordCollectionDocs()!);
        const newEmbeddings = selected.map(doc => Doc.MakeEmbedding(doc));

        // Create a new collection and set up configurations.
        const newCollection = ((doc: Doc) => {
            const docData = doc[DocData];
            docData.data = new List<Doc>(newEmbeddings);
            docData.title = this._props.keyword;
            doc._freeform_panX = doc._freeform_panY = 0;
            return doc;
        })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
        newEmbeddings.forEach(embed => (embed.embedContainer = newCollection));
        newCollection._width = 900;
        newCollection._height = 900;
        newCollection.layout_fitWidth = true;

        // Add the collection to the keyword document's list of associated smart collections.
        this._props.keywordDoc.collections = new List<Doc>([...DocListCast(this._props.keywordDoc.collections), newCollection]);
        newCollection[DocData].data_labels = new List<string>([this._props.keyword]);
        newCollection[DocData][`${this._props.keyword}`] = true;
        newCollection[DocData].showLabels = true;
        return newCollection;
    };

    @action
    handleDragStart = (e: React.PointerEvent) => {
        if (this._props.isEditing) {
            const clone = this.ref.current?.cloneNode(true) as HTMLElement;
            if (!clone) return;

            setupMoveUpEvents(
                this,
                e,
                () => {
                    const dragData = new DragManager.DocumentDragData([this.createCollection()]);
                    DragManager.StartDocumentDrag([this.ref.current!], dragData, e.clientX, e.clientY, {});
                    return true;
                },
                returnFalse,
                emptyFunction
            );
            e.preventDefault();
        }
    };

    @action
    removeLabel = () => {
        if (this._props.doc[DocData].data_labels) {
            if (this._props.doc.type === DocumentType.COL) {
                const filtered_collections = new List<Doc>(DocListCast(this._props.keywordDoc.collections).filter(doc => doc !== this._props.doc));
                this._props.keywordDoc.collections = filtered_collections;

                for (const cur_doc of DocListCast(this.getKeywordCollectionDocs()!, [])) {
                    this._props.doc[DocData].data = new List<Doc>(DocListCast(this._props.doc[DocData].data).filter(doc => !Doc.AreProtosEqual(cur_doc, doc)));
                }
            } else {
                const filtered_docs = new List<Doc>(DocListCast(this.getKeywordCollectionDocs()!).filter(doc => doc !== this._props.doc));
                this._props.keywordDoc[DocData].docs = filtered_docs;

                for (const collection of DocListCast(this._props.keywordDoc.collections)) {
                    collection[DocData].data = new List<Doc>(DocListCast(collection[DocData].data).filter(doc => !Doc.AreProtosEqual(this._props.doc, doc)));
                }
            }
        }
        this._props.doc[DocData].data_labels = (this._props.doc[DocData].data_labels as List<string>).filter(label => label !== this._props.keyword) as List<string>;
        this._props.doc![DocData][`${this._props.keyword}`] = false;
    };

    render() {
        return (
            <div className="keyword" onClick={this._props.setToEditing} onPointerDown={this.handleDragStart} ref={this.ref} key={Utils.GenerateGuid()}>
                {this._props.keyword}
                {this.props.isEditing && <IconButton tooltip={'Remove label'} onPointerDown={this.removeLabel} icon={'X'} style={{ width: '8px', height: '8px', marginLeft: '10px' }} />}
            </div>
        );
    }
}

interface KeywordBoxProps {
    doc: Doc;
    isEditing: boolean;
}

/**
 * A component that handles the keyword display for documents.
 */
@observer
export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
    @observable _currentInput: string = '';
    private height: number = 0;
    private ref: React.RefObject<HTMLDivElement>;

    @computed
    get currentScale() {
        return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
    }

    @computed
    get cur_height() {
        return this.ref.current?.offsetHeight ? this.ref.current?.offsetHeight : 0;
    }

    constructor(props: any) {
        super(props);
        makeObservable(this);
        this.ref = React.createRef();

        reaction(
            () => this.cur_height,
            () => {
                this._props.doc[DocData].keywordHeight = this.height;
            }
        );
    }

    componentDidMount(): void {
        this.height = this.ref.current?.offsetHeight ? this.ref.current?.offsetHeight : 0;
        this._props.doc[DocData].keywordHeight = this.height;
    }

    componentDidUpdate(prevProps: Readonly<KeywordBoxProps>): void {
        this.height = this.ref.current?.offsetHeight ? this.ref.current?.offsetHeight : 0;
        this._props.doc[DocData].keywordHeight = this.height;
    }

    @action
    setToEditing = () => {
        this._props.isEditing = true;
    };

    @action
    setToView = () => {
        this._props.isEditing = false;
    };

    /**
     * Gets the document associated with a keyword.
     * @param keyword The keyword being searched for
     * @returns A Doc containing keyword information
     */
    getKeywordCollection = (keyword: string) => {
        // Look for the keyword document.
        for (const doc of DocListCast(Doc.ActiveDashboard!.myKeywordCollections)) {
            if (doc.title === keyword) {
                return doc;
            }
        }

        // If not contained, create a new document and add it to the active Dashboard's keyword list.
        const keywordCollection = new Doc();
        keywordCollection.title = keyword;
        keywordCollection[DocData].docs = new List<Doc>();
        keywordCollection.collections = new List<Doc>();
        if (Doc.ActiveDashboard) {
            Doc.ActiveDashboard.myKeywordCollections = new List<Doc>([...DocListCast(Doc.ActiveDashboard.myKeywordCollections), keywordCollection]);
        }

        return keywordCollection;
    };

    /**
     * Adds the keyword to the document.
     * @param keyword
     */
    submitLabel = (keyword: string) => {
        // If the active Dashboard does not have a keyword collection, create it.
        if (Doc.ActiveDashboard && !Doc.ActiveDashboard.myKeywordCollections) {
            Doc.ActiveDashboard.myKeywordCollections = new List<Doc>();
        }

        const submittedLabel = keyword.trim();
        if (submittedLabel && !this._props.doc![DocData][`${submittedLabel}`]) {
            // If the keyword collection is not in active Dashboard, add it as a new doc, with the keyword as its title.
            const keywordCollection = this.getKeywordCollection(submittedLabel);

            // If the document has no keywords field, create the field.
            if (!this._props.doc[DocData].data_labels) {
                this._props.doc[DocData].data_labels = new List<string>();
            }

            // If the document is of type COLLECTION, make it a smart collection, otherwise, add the keyword to the document.
            if (this._props.doc.type === DocumentType.COL) {
                keywordCollection.collections = new List<Doc>([...DocListCast(keywordCollection.collections), this._props.doc]);

                // Iterate through the keyword document's collections and add a copy of the document to each collection
                for (const doc of DocListCast(keywordCollection[DocData].docs)) {
                    const newEmbedding = Doc.MakeEmbedding(doc);
                    this._props.doc[DocData].data = new List<Doc>([...DocListCast(this._props.doc[DocData].data), newEmbedding]);
                    newEmbedding.embedContainer = this._props.doc;
                }
            } else {
                // Add this document to the keyword's collection of associated documents.
                keywordCollection[DocData].docs = new List<Doc>([...DocListCast(keywordCollection[DocData].docs), this._props.doc]);

                // Iterate through the keyword document's collections and add a copy of the document to each collection
                for (const collection of DocListCast(keywordCollection.collections)) {
                    const newEmbedding = Doc.MakeEmbedding(this._props.doc);
                    collection[DocData].data = new List<Doc>([...DocListCast(collection.data), newEmbedding]);
                    newEmbedding.embedContainer = collection;
                }
            }

            // Push the keyword to the document's keyword list field.
            (this._props.doc![DocData].data_labels! as List<string>).push(submittedLabel);
            this._props.doc![DocData][`${submittedLabel}`] = true;
            this._currentInput = ''; // Clear the input box
        }
    };

    @action
    onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        this._currentInput = e.target.value;
    };

    render() {
        const keywordsList = this._props.doc[DocData].data_labels ? this._props.doc[DocData].data_labels : new List<string>();
        const seldoc = DocumentView.SelectedDocs().lastElement();
        if (SnappingManager.IsDragging || !(seldoc === this._props.doc) || !this._props.isEditing) {
            setTimeout(
                action(() => {
                    if ((keywordsList as List<string>).length === 0) {
                        this._props.doc[DocData].showLabels = false;
                    }
                    this.setToView();
                })
            );
        }

        return (
            <div
                className="keywords-container"
                ref={this.ref}
                style={{
                    transformOrigin: 'top left',
                    overflow: 'hidden',
                    transform: `scale(${1 / this.currentScale})`,
                    backgroundColor: this._props.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
                    borderColor: this._props.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
                    maxWidth: `400px`,
                }}>
                <div className="keywords-content">
                    <div className="keywords-list">
                        {(keywordsList as List<string>).map(keyword => {
                            return <KeywordItem key={Utils.GenerateGuid()} doc={this._props.doc} keyword={keyword} keywordDoc={this.getKeywordCollection(keyword)} setToEditing={this.setToEditing} isEditing={this._props.isEditing}></KeywordItem>;
                        })}
                    </div>
                    {this._props.isEditing ? (
                        <div className="keyword-editing-box">
                            <div className="keyword-input-box">
                                <input
                                    value={this._currentInput}
                                    autoComplete="off"
                                    onChange={this.onInputChange}
                                    onKeyDown={e => {
                                        e.key === 'Enter' ? this.submitLabel(this._currentInput) : null;
                                        e.stopPropagation();
                                    }}
                                    type="text"
                                    placeholder="Input keywords for document..."
                                    aria-label="keyword-input"
                                    className="keyword-input"
                                    style={{ width: '100%', borderRadius: '5px' }}
                                />
                            </div>
                            {Doc.ActiveDashboard?.myKeywordCollections ? (
                                <div className="keyword-suggestions-box">
                                    {DocListCast(Doc.ActiveDashboard?.myKeywordCollections).map(doc => {
                                        const keyword = StrCast(doc.title);
                                        return (
                                            <Button
                                                style={{ margin: '2px 2px', border: '1px solid black', backgroundColor: 'lightblue', color: 'black' }}
                                                text={keyword}
                                                color={SnappingManager.userVariantColor}
                                                tooltip={'Add existing keyword'}
                                                onClick={() => {
                                                    this.submitLabel(keyword);
                                                }}
                                                key={Utils.GenerateGuid()}
                                            />
                                        );
                                    })}
                                </div>
                            ) : (
                                <div></div>
                            )}
                            <div className="keyword-buttons">
                                <IconButton
                                    tooltip={'Close Menu'}
                                    onPointerDown={() => {
                                        if ((keywordsList as List<string>).length === 0) {
                                            this._props.doc[DocData].showLabels = false;
                                        } else {
                                            this.setToView();
                                        }
                                    }}
                                    icon={'x'}
                                    style={{ width: '4px' }}
                                />
                            </div>
                        </div>
                    ) : (
                        <div></div>
                    )}
                </div>
            </div>
        );
    }
}
