/* eslint-disable prettier/prettier */
/* eslint-disable jsx-a11y/control-has-associated-label */
import mermaid from 'mermaid';
import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { Cast, DocCast, NumCast } from '../../../fields/Types';
import { Gestures } from '../../../pen-gestures/GestureTypes';
import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { LinkManager } from '../../util/LinkManager';
import { undoable } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { InkingStroke } from '../InkingStroke';
import './DiagramBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';

@observer
export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(DiagramBox, fieldKey);
    }
    static isPointInBox = (box: Doc, pt: number[]): boolean => {
        if (typeof pt[0] === 'number' && typeof box.x === 'number' && typeof box.y === 'number' && typeof pt[1] === 'number') {
            return pt[0] < box.x + NumCast(box.width) && pt[0] > box.x && pt[1] > box.y && pt[1] < box.y + NumCast(box.height);
        }
        return false;
    };

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
    }
    @observable renderDiv: React.ReactNode;
    @observable inputValue = '';
    @observable createInputValue = '';
    @observable loading = false;
    @observable errorMessage = '';
    @observable mermaidCode = '';
    @observable isExampleMenuOpen = false;
    @observable _showCode = false;
    @observable _inputValue = '';
    @observable _generating = false;
    @observable _errorMessage = '';

    @action handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        this.inputValue = e.target.value;
        console.log(e.target.value);
    };

    @computed get mermaidcode() {
        return Cast(this.Document[DocData].text, RichTextField, null)?.Text ?? '';
    }

    componentDidMount() {
        this._props.setContentViewBox?.(this);
        mermaid.initialize({
            securityLevel: 'loose',
            startOnLoad: true,
            darkMode: true,
            flowchart: { useMaxWidth: false, htmlLabels: true, curve: 'cardinal' },
            gantt: { useMaxWidth: true, useWidth: 2000 },
        });
        // when a new doc/text/ink/shape is created in the freeform view, this generates the corresponding mermaid diagram code
        reaction(
            () => DocListCast(this.Document.data),
            docArray => docArray.length && this.convertDrawingToMermaidCode(docArray),
            { fireImmediately: true }
        );
    }
    renderMermaid = (str: string) => {
        try {
            return mermaid.render('graph' + Date.now(), str);
        } catch (error) {
            return { svg: '', bindFunctions: undefined };
        }
    };

    renderMermaidAsync = async (mermaidCode: string, dashDiv: HTMLDivElement) => {
        try {
            const { svg, bindFunctions } = await this.renderMermaid(mermaidCode);
            dashDiv.innerHTML = svg;
            bindFunctions?.(dashDiv);
        } catch (error) {
            console.error('Error rendering Mermaid:', error);
        }
    };

    setMermaidCode = undoable((res: string) => {
        this.Document[DocData].text = new RichTextField(
            JSON.stringify({
                doc: {
                    type: 'doc',
                    content: [
                        {
                            type: 'code_block',
                            content: [
                                { type: 'text', text: `^@mermaids\n` },
                                { type: 'text', text: this.removeWords(res) },
                            ],
                        },
                    ],
                },
                selection: { type: 'text', anchor: 1, head: 1 },
            }),
            res
        );
    }, 'set mermaid code');

    generateMermaidCode = action(() => {
        this._generating = true;
        const prompt = 'Write this in mermaid code and only give me the mermaid code: ' + this._inputValue;
        gptAPICall(prompt, GPTCallType.MERMAID).then(
            action(res => {
                this._generating = false;
                if (res === 'Error connecting with API.') {
                    this._errorMessage = 'GPT call failed; please try again.';
                }
                // If GPT call succeeded, set mermaid code on Doc which will trigger a rendering if _showCode is false
                else if (res && this.isValidCode(res)) {
                    this.setMermaidCode(res);
                    this._errorMessage = '';
                } else {
                    this._errorMessage = 'GPT call succeeded but invalid html; please try again.';
                }
            })
        );
    });
    isValidCode = (html: string) => (html ? true : false);
    removeWords = (inputStrIn: string) => inputStrIn.replace('```mermaid', '').replace(`^@mermaids`, '').replace('```', '');

    // method to convert the drawings on collection node side the mermaid code
    convertDrawingToMermaidCode = async (docArray: Doc[]) => {
        const rectangleArray = docArray.filter(doc => doc.title === Gestures.Rectangle || doc.title === Gestures.Circle);
        const lineArray = docArray.filter(doc => doc.title === Gestures.Line || doc.title === Gestures.Stroke);
        const textArray = docArray.filter(doc => doc.type === DocumentType.RTF);
        await new Promise(resolve => setTimeout(resolve));
        const inkStrokeArray = lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).filter(inkView => inkView?.ComponentView instanceof InkingStroke);
        if (inkStrokeArray[0] && inkStrokeArray.length === lineArray.length) {
            let mermaidCode = `graph TD \n`;
            const inkingStrokeArray = inkStrokeArray.map(stroke => stroke?.ComponentView as InkingStroke).filter(stroke => stroke);
            for (const rectangle of rectangleArray) {
                for (const inkStroke of inkingStrokeArray) {
                    const inkData = inkStroke.inkScaledData();
                    const { inkScaleX, inkScaleY } = inkData;
                    const inkStrokeXArray = inkData.inkData.map(coord => coord.X * inkScaleX);
                    const inkStrokeYArray = inkData.inkData.map(coord => coord.Y * inkScaleY);
                    // need to minX and minY to since the inkStroke.x and.y is not relative to the doc. so I have to do some calcluations
                    const offX = Math.min(...inkStrokeXArray) - NumCast(inkStroke.Document.x);
                    const offY = Math.min(...inkStrokeYArray) - NumCast(inkStroke.Document.y);

                    const startX = inkStrokeXArray[0] - offX;
                    const startY = inkStrokeYArray[0] - offY;
                    const endX = inkStrokeXArray.lastElement() - offX;
                    const endY = inkStrokeYArray.lastElement() - offY;
                    if (DiagramBox.isPointInBox(rectangle, [startX, startY])) {
                        for (const rectangle2 of rectangleArray) {
                            if (DiagramBox.isPointInBox(rectangle2, [endX, endY])) {
                                const linkedDocs = LinkManager.Instance.getAllRelatedLinks(inkStroke.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, inkStroke.Document)));
                                const linkedDocText = Cast(linkedDocs[0]?.text, RichTextField, null)?.Text;
                                const linkText = linkedDocText ? `|${linkedDocText}|` : '';
                                mermaidCode += '   ' + Math.abs(NumCast(rectangle.x)) + this.getTextInBox(rectangle, textArray) + '-->' + linkText + Math.abs(NumCast(rectangle2.x)) + this.getTextInBox(rectangle2, textArray) + `\n`;
                            }
                        }
                    }
                }
                this.setMermaidCode(mermaidCode);
            }
        }
    };

    getTextInBox = (box: Doc, richTextArray: Doc[]) => {
        for (const textDoc of richTextArray) {
            if (DiagramBox.isPointInBox(box, [NumCast(textDoc.x), NumCast(textDoc.y)])) {
                switch (box.title) {
                        case Gestures.Rectangle: return '('  + ((textDoc.text as RichTextField)?.Text ?? '') +  ')';
                        case Gestures.Circle:    return '((' + ((textDoc.text as RichTextField)?.Text ?? '') + '))';
                        default:
                    } // prettier-ignore
            }
        }
        return '( )';
    };

    render() {
        return (
            <div className="DIYNodeBox">
                <div className="DIYNodeBox-searchbar">
                    <input type="text" value={this._inputValue} onKeyDown={action(e => e.key === 'Enter' && this.generateMermaidCode())} onChange={action(e => (this._inputValue = e.target.value))} />
                    <button type="button" onClick={this.generateMermaidCode}>
                        Gen
                    </button>
                    <input type="checkbox" onClick={action(() => (this._showCode = !this._showCode))} />
                </div>
                <div className="DIYNodeBox-content">
                    {this._showCode ? (
                        <FormattedTextBox {...this._props} fieldKey="text" />
                    ) : this._generating ? (
                        <div className="loading-circle" />
                    ) : (
                        <div className="diagramBox" ref={r => r && this.renderMermaidAsync.call(this, this.removeWords(this.mermaidcode), r)}>
                            {this._errorMessage || 'Type a prompt to generate a diagram'}
                        </div>
                    )}
                </div>
            </div>
        );
    }
    exampleButton = () => {
        if (this.isExampleMenuOpen) {
            this.isExampleMenuOpen = false;
        } else {
            this.isExampleMenuOpen = true;
        }
    };
    flowButton = () => {
        this.createInputValue = `flowchart TD
        A[Christmas] -->|Get money| B(Go shopping)
        B --> C{Let me think}
        C -->|One| D[Laptop]
        C -->|Two| E[iPhone]
        C -->|Three| F[fa:fa-car Car]`;
    };
    pieButton = () => {
        this.createInputValue = `pie title Pets adopted by volunteers
        "Dogs" : 386
        "Cats" : 85
        "Rats" : 15`;
    };
    timelineButton = () => {
        this.createInputValue = `gantt
        title A Gantt Diagram
        dateFormat  YYYY-MM-DD
        section Section
        A task           :a1, 2014-01-01, 30d
        Another task     :after a1  , 20d
        section Another
        Task in sec      :2014-01-12  , 12d
        another task      : 24d`;
    };
    classButton = () => {
        this.createInputValue = `classDiagram
        Animal <|-- Duck
        Animal <|-- Fish
        Animal <|-- Zebra
        Animal : +int age
        Animal : +String gender
        Animal: +isMammal()
        Animal: +mate()
        class Duck{
            +String beakColor
            +swim()
            +quack()
        }
        class Fish{
            -int sizeInFeet
            -canEat()
        }
        class Zebra{
            +bool is_wild
            +run()
        }`;
    };
    mindmapButton = () => {
        this.createInputValue = `mindmap
        root((mindmap))
            Origins
            Long history
            ::icon(fa fa-book)
            Popularisation
                British popular psychology author Tony Buzan
            Research
            On effectivness<br/>and features
            On Automatic creation
                Uses
                    Creative techniques
                    Strategic planning
                    Argument mapping
            Tools
            Pen and paper
            Mermaid`;
    };
    handleInputChangeEditor = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        if (typeof e.target.value === 'string') {
            this.createInputValue = e.target.value;
        }
    };
    removeWhitespace(str: string): string {
        return str.replace(/\s+/g, '');
    }
    autoResize(element: HTMLTextAreaElement): void {
        element.style.height = '5px';
        element.style.height = element.scrollHeight + 'px';
    }
    timeline = `gantt
    title College Timeline
    dateFormat  YYYY-MM-DD
    section Semester 1
    Orientation           :done,    des1, 2023-08-01, 2023-08-03
    Classes Start         :active,  des2, 2023-08-04, 2023-12-15
    Midterm Exams         :         des3, 2023-10-15, 2023-10-20
    End of Semester       :         des4, 2023-12-16, 2023-12-20
    section Semester 2
    Classes Start         :         des5, 2024-01-10, 2024-05-15
    Spring Break          :         des6, 2024-03-15, 2024-03-22
    Midterm Exams         :         des7, 2024-03-25, 2024-03-30
    Final Exams           :         des8, 2024-05-10, 2024-05-15
    section Summer Break
    Internship            :         des9, 2024-06-01, 2024-08-31
    section Semester 3
    Classes Start         :         des10, 2024-09-01, 2025-12-15
    Midterm Exams         :         des11, 2024-11-15, 2024-11-20
    End of Semester       :         des12, 2025-12-16, 2025-12-20
    section Semester 4
    Classes Start         :         des13, 2025-01-10, 2025-05-15
    Spring Break          :         des14, 2025-03-15, 2025-03-22
    Midterm Exams         :         des15, 2025-03-25, 2025-03-30
    Final Exams           :         des16, 2025-05-10, 2025-05-15
    Graduation            :         des17, 2025-05-20, 2025-05-21`;
}

Docs.Prototypes.TemplateMap.set(DocumentType.DIAGRAM, {
    layout: { view: DiagramBox, dataField: 'data' },
    options: {
        _height: 300, //
        _layout_fitWidth: true,
        _layout_nativeDimEditable: true,
        _layout_reflowVertical: true,
        _layout_reflowHorizontal: true,
        waitForDoubleClickToClick: 'always',
        systemIcon: 'BsGlobe',
    },
});
