import { makeObservable, observable, action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import './DiagramBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
import mermaid from 'mermaid';
import { Doc, DocListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
import { ContextMenu } from '../ContextMenu';
import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
import { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
import OpenAI, { ClientOptions } from 'openai';
import { line } from 'd3';
import { InkingStroke } from '../InkingStroke';
import { DocumentManager } from '../../util/DocumentManager';
import { C } from '@fullcalendar/core/internal-common';
import { Docs } from '../../documents/Documents';

@observer
export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(DiagramBox, fieldKey);
    }
    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
    private _dragRef = React.createRef<HTMLDivElement>();
    private chartContent: string="graph L;";
    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
    }

    @observable inputValue = '';
    @observable loading = false;
    @observable errorMessage = '';
    @observable mermaidCode = '';

    @action handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        this.inputValue = e.target.value;
    };
    async componentDidMount() {
        console.log(this.chartContent)
        this._props.setContentViewBox?.(this);
        mermaid.initialize({
            securityLevel: 'loose',
            startOnLoad: true,
            flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' },
        });
        this.mermaidCode = "asdasdasd"; 
        if(this.chartContent!="graph LR;"){
            this.chartContent=await this.createMermaidCode()
            //console.log(this.chartContent)
        }
        this.renderMermaidAsync(this.chartContent)
        this.testInkingStroke()
    }
    renderMermaid = async (str: string) => {
        try {
            const { svg, bindFunctions } = await this.mermaidDiagram(str);
            return { svg, bindFunctions };
        } catch (error) {
            console.error('Error rendering mermaid diagram:', error);
            return { svg: '', bindFunctions: undefined };
        }
    };
    mermaidDiagram = async (str: string) => {
        return await mermaid.render('graph' + Date.now(), str);
    };

    async renderMermaidAsync(mermaidCode: string) {
        try {
            const { svg, bindFunctions } = await this.renderMermaid(mermaidCode);
            const dashDiv = document.getElementById('dashDiv');
            if (dashDiv) {
                dashDiv.innerHTML = svg;
                if (bindFunctions) {
                    bindFunctions(dashDiv);
                }
            }
        } catch (error) {
            console.error('Error rendering Mermaid:', error);
        }
    }
    @action handleRenderClick = () => {
        // Call the GPT model and get the HTML output
        // const modelOutput = getHtmlOutput(this.inputValue);
        // this.htmlCode = modelOutput;
        this.generateMermaidCode();
    };
    @action generateMermaidCode = async () => {
        console.log('Generating Mermaid Code');
        this.loading = true;

        let res = await gptAPICall(this.inputValue, GPTCallType.MERMAID);
        this.loading = false;
        if (res == 'Error connecting with API.') {
            // If GPT call failed
            console.error('GPT call failed');
            this.errorMessage = 'GPT call failed; please try again.';
        } else if (res != null) {
            // If GPT call succeeded, set htmlCode;;; TODO: check if valid html
            if (this.isValidCode(res)) {
                this.mermaidCode = res;
                console.log('GPT call succeeded:' + res);
                this.errorMessage = '';
            } else {
                console.error('GPT call succeeded but invalid html; please try again.');
                this.errorMessage = 'GPT call succeeded but invalid html; please try again.';
            }
        }
        this.renderMermaidAsync.call(this, this.removeWords(this.mermaidCode));
        this.loading = false;
    };
    isValidCode = (html: string) => {
        return true;
    };
    testInkingStroke=()=>{
        if (this.Document.data instanceof List) {
            let docArray: Doc[] = DocListCast(this.Document.data);
            let lineArray = docArray.filter(doc => doc.title == 'line' || doc.title == 'stroke');
            setTimeout(() => {
                let inkStrokeArray=lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
                .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
                console.log(inkStrokeArray)
            });
        }
    }

    removeWords(inputStr: string) {
        inputStr = inputStr.replace('```mermaid', '');
        return inputStr.replace('```', '');
    }

    async createMermaidCode() {
        let mermaidCode = 'graph LR;';
        if (this.Document.data instanceof List) {
            let docArray: Doc[] = DocListCast(this.Document.data);
            let rectangleArray = docArray.filter(doc => doc.title == 'rectangle' || doc.title == 'circle');
            let lineArray = docArray.filter(doc => doc.title == 'line' || doc.title == 'stroke');
            let textArray = docArray.filter(doc => doc.type == 'rich text');
            const timeoutPromise = () => new Promise(resolve => {
                setTimeout(resolve, 0);
            });
            await timeoutPromise();
            let inkStrokeArray=lineArray.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.()))
            .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
            if(inkStrokeArray[0]){
                let inkingStrokeArray=inkStrokeArray.map(stroke=>stroke?.ComponentView)
                for (let i = 0; i < rectangleArray.length; i++) {       
                    const rectangle = rectangleArray[i];
                    for (let j = 0; j < lineArray.length; j++) {
                        let inkStrokeXArray=(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData.map(coord=>coord.X)
                        let inkStrokeYArray=(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData.map(coord=>coord.Y)
                        let minX:number=Math.min(...inkStrokeXArray)
                        let minY:number=Math.min(...inkStrokeYArray)
                        let inkScaleX=(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkScaleX
                        let inkScaleY=(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkScaleY
                        let StartX:number=0
                        let StartY:number=0
                        let EndX:number=0
                        let EndY:number=0
                        StartX=(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData[j].X*inkScaleX-minX*inkScaleX+(lineArray[j]?.x as number)
                        StartY=(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData[j].Y*inkScaleY-minY*inkScaleY+(lineArray[j]?.y as number)
                        EndX=(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData[(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData.length-1].X*inkScaleX-minX*inkScaleX+(lineArray[j].x as number)
                        EndY=(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData[(inkingStrokeArray[j] as InkingStroke)?.inkScaledData().inkData.length-1].Y*inkScaleY-minY*inkScaleY+(lineArray[j].y as number)
                        if (this.isPointInBox(rectangle, [StartX,StartY])) {
                            for (let k = 0; k < rectangleArray.length; k++) {
                                const rectangle2 = rectangleArray[k];
                                if (this.isPointInBox(rectangle2, [EndX,EndY]) && typeof rectangle.x === 'number' && typeof rectangle2.x === 'number') {
                                
                                    mermaidCode += Math.abs(rectangle.x) + this.getTextInBox(rectangle, textArray) + '---' + Math.abs(rectangle2.x) + this.getTextInBox(rectangle2, textArray) + ';';
                                    //console.log(mermaidCode)
                                }
                            }
                        }
                    }
                }
            }

        }
        console.log(this)
        this.addDocument(Docs.Create.TextDocument("asd", { title:"hellotitle", x: 0, y: 0 }));
        let docArray: Doc[] = DocListCast(this.Document.data);
        console.log(docArray.length)
        //console.log(mermaidCode)
        return mermaidCode
    };

    getTextInBox = (box: Doc, richTextArray: Doc[]): string => {
        for (let i = 0; i < richTextArray.length; i++) {
            let textDoc = richTextArray[i];
            if (typeof textDoc.x === 'number' && typeof textDoc.y === 'number' && typeof box.x === 'number' && typeof box.height === 'number' && typeof box.width === 'number' && typeof box.y === 'number') {
                if (textDoc.x > box.x && textDoc.x < box.x + box.width && textDoc.y > box.y && textDoc.y < box.y + box.height) {
                    if (box.title == 'rectangle') {
                        return '(' + (textDoc.text as RichTextField)?.Text + ')';
                    }
                    if (box.title == 'circle') {
                        return '((' + (textDoc.text as RichTextField)?.Text + '))';
                    }
                }
            }
        }
        return '( )';
    };
    isPointInBox = (box: Doc, line: number[]): boolean => {
        if (typeof line[0] === 'number' && typeof box.x === 'number' && typeof box.width === 'number' && typeof box.height === 'number' && typeof box.y === 'number' && typeof line[1] === 'number') {
            return line[0] < box.x + box.width&& line[0] > box.x && line[1] > box.y && line[1] < box.y + box.height;
        } else {
            return false;
        }
    };

    render() {
        return (
            <div ref={this._ref} className="DIYNodeBox">
                <div ref={this._dragRef} className="DIYNodeBox-wrapper">
                    <div className="search-bar">
                        <input type="text" value={this.inputValue} onChange={this.handleInputChange} />
                        <button onClick={this.handleRenderClick}>Generate</button>
                    </div>
                    <div className="content">
                        {this.mermaidCode ? (
                            <div id="dashDiv" className="diagramBox"></div>
                        ) : (
                            <div>{this.loading ? <div className="loading-circle"></div> : <div>{this.errorMessage ? this.errorMessage : 'Insert prompt to generate diagram'}</div>}</div>
                        )}
                    </div>
                </div>
            </div>
        );
    }
}
