import { action, computed, makeObservable, observable, observe, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import OpenAI, { ClientOptions } from 'openai';
import * as React from 'react';
import { Doc } from '../../../../fields/Doc';
import { CsvCast, DocCast, PDFCast, StrCast } from '../../../../fields/Types';
import { Networking } from '../../../Network';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
import { LinkManager } from '../../../util/LinkManager';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
import './ChatBox.scss';
import MessageComponentBox from './MessageComponent';
import { ASSISTANT_ROLE, AssistantMessage, AI_Document, convertToAIDocument, Citation, CHUNK_TYPE, Chunk, getChunkType } from './types';
import { Vectorstore } from './vectorstore/VectorstoreUpload';
import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { Agent } from './Agent';
import dotenv from 'dotenv';
import { DocData, DocViews } from '../../../../fields/DocSymbols';
import { DocumentView } from '../DocumentView';
import { AnswerParser } from './AnswerParser';
import { DocumentManager } from '../../../util/DocumentManager';
import { UUID } from 'bson';
import { v4 as uuidv4 } from 'uuid';
import { aS } from '@fullcalendar/core/internal-common';

dotenv.config();

@observer
export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    @observable history: AssistantMessage[] = [];
    @observable.deep current_message: AssistantMessage | undefined = undefined;

    @observable isLoading: boolean = false;
    @observable isInitializing: boolean = true;
    @observable expandedScratchpadIndex: number | null = null;
    @observable inputValue: string = '';
    @observable private linked_docs_to_add: Doc[] = [];
    private openai: OpenAI;
    private vectorstore_id: string;
    private documents: AI_Document[] = [];
    private _oldWheel: any;
    private vectorstore: Vectorstore;
    private agent: Agent; // Add the ChatBot instance

    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(ChatBox, fieldKey);
    }

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
        this.history = [{ role: ASSISTANT_ROLE.ASSISTANT, text_content: 'Welcome to the Document Analyser Assistant! Link a document or ask questions to get started.' }];
        this.openai = this.initializeOpenAI();
        if (StrCast(this.dataDoc.vectorstore_id) == '') {
            this.vectorstore_id = uuidv4();
            this.dataDoc.vectorstore_id = this.vectorstore_id;
        } else {
            this.vectorstore_id = StrCast(this.dataDoc.vectorstore_id);
        }
        this.vectorstore = new Vectorstore(this.vectorstore_id);
        this.agent = new Agent(this.vectorstore); // Initialize the Agent
        reaction(
            () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text_content: msg.text_content, follow_up_questions: msg.follow_up_questions, citations: msg.citations })),
            serializableHistory => {
                this.dataDoc.data = JSON.stringify(serializableHistory);
            }
        );
    }

    addDocsToVectorstore = async (linkedDocs: Doc[]) => {
        await this.vectorstore.addAIDocs(linkedDocs);
    };

    // @action
    // uploadNewDocument = async (newDoc: Doc) => {
    //     const local_file_path: string = CsvCast(newDoc.data, PDFCast(newDoc.data)).url.pathname;
    //     const { document_json } = await Networking.PostToServer('/createDocument', { file_path: local_file_path });
    //     this.documents.push(...document_json.map(convertToAIDocument));
    //     //newDoc['ai_document'] = document_json;
    // };

    @action
    toggleToolLogs = (index: number) => {
        this.expandedScratchpadIndex = this.expandedScratchpadIndex === index ? null : index;
    };

    initializeOpenAI() {
        const configuration: ClientOptions = {
            apiKey: process.env.OPENAI_KEY,
            dangerouslyAllowBrowser: true,
        };
        return new OpenAI(configuration);
    }

    onPassiveWheel = (e: WheelEvent) => {
        if (this._props.isContentActive()) {
            e.stopPropagation();
        }
    };

    // getAssistantResponse() {
    //     return Docs.Create.MessageDocument(text, {});
    // }

    @action
    askGPT = async (event: React.FormEvent<HTMLFormElement>): Promise<void> => {
        event.preventDefault();
        this.inputValue = '';

        const textInput = event.currentTarget.elements.namedItem('messageInput') as HTMLInputElement;
        const trimmedText = textInput.value.trim();

        if (trimmedText) {
            try {
                textInput.value = '';
                runInAction(() => {
                    this.history.push({ role: ASSISTANT_ROLE.USER, text_content: trimmedText });
                    this.isLoading = true;
                });

                const response = await this.agent.askAgent(trimmedText); // Use the chatbot to get the response
                runInAction(() => {
                    this.history.push(AnswerParser.parse(response));
                });
                this.dataDoc.data = JSON.stringify(this.history);
            } catch (err) {
                console.error('Error:', err);
                runInAction(() => {
                    this.history.push({ role: ASSISTANT_ROLE.ASSISTANT, text_content: 'Sorry, I encountered an error while processing your request.' });
                });
            } finally {
                runInAction(() => {
                    this.isLoading = false;
                });
            }
        }
    };

    @action
    updateMessageCitations = (index: number, citations: Citation[]) => {
        if (this.history[index]) {
            this.history[index].citations = citations;
        }
    };

    @action
    handleCitationClick = (citation: Citation) => {
        console.log('Citation clicked:', citation);
        const currentLinkedDocs: Doc[] = this.linkedDocs;
        const chunk_id = citation.chunk_id;
        for (let doc of currentLinkedDocs) {
            const doc_chunks: Chunk[] = JSON.parse(StrCast(doc.ai_document)).chunks;
            const chunk_file_name = doc_chunks.find(chunk => chunk.id === chunk_id)?.metadata.file_path;
            const doc_url = CsvCast(doc.data, PDFCast(doc.data)).url.pathname;
            console.log('URL: ' + doc_url + ' Citation URL: ' + chunk_file_name);
            //const ai_field_id = doc[this.Document[Id] + '_ai_field_id'];
            if (chunk_file_name == doc_url) {
                DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {
                    console.log(doc.data);
                    //look at context path for each docview and choose the doc view that has as
                    //its parent the same collection view the chatbox is in
                    const first_view = Array.from(doc[DocViews])[0];
                    first_view.ComponentView?.search?.(citation.direct_text);
                });
            }
        }
        // You can implement additional functionality here, such as showing a modal with the full citation content
    };

    // @action
    // uploadLinks = async (linkedDocs: Doc[]) => {
    //     if (this.isInitializing) {
    //         console.log('Initialization in progress, upload aborted.');
    //         return;
    //     }
    //     const urls: string[] = linkedDocs.map(doc => CsvCast(doc.data, PDFCast(doc.data)).url.pathname);
    //     const csvUrls: string[] = urls.filter(url => url.endsWith('.csv'));
    //     console.log(this.assistantID, this.threadID, urls);

    //     await Networking.PostToServer('/uploadPDFs', { file_path: urls[0] });

    //     // linkedDocs.forEach((doc, i) => {
    //     //     doc[this.Document[Id] + '_ai_field_id'] = openaiFileIds[i];
    //     //     console.log('AI Field ID: ' + openaiFileIds[i]);
    //     // });

    //     // if (csvUrls.length > 0) {
    //     //     for (let i = 0; i < csvUrls.length; i++) {
    //     //         this.linkedCsvIDs.push(openaiFileIds[urls.indexOf(csvUrls[i])]);
    //     //     }
    //     //     console.log('linked csvs:' + this.linkedCsvIDs);
    //     //     await this.openai.beta.assistants.update(this.assistantID, {
    //     //         tools: [{ type: 'file_search' }, { type: 'code_interpreter' }],
    //     //         tool_resources: {
    //     //             file_search: {
    //     //                 vector_store_ids: [this.vectorStoreID],
    //     //             },
    //     //             code_interpreter: {
    //     //                 file_ids: this.linkedCsvIDs,
    //     //             },
    //     //         },
    //     //     });
    //     // }
    // };

    componentDidMount() {
        this._props.setContentViewBox?.(this);
        if (this.dataDoc.data) {
            try {
                const storedHistory = JSON.parse(StrCast(this.dataDoc.data));
                runInAction(() => {
                    this.history.push(
                        ...storedHistory.map((msg: AssistantMessage) => ({
                            role: msg.role,
                            text_content: msg.text_content,
                            follow_up_questions: msg.follow_up_questions,
                            citations: msg.citations,
                        }))
                    );
                });
            } catch (e) {
                console.error('Failed to parse history from dataDoc:', e);
            }
        }
        reaction(
            () => {
                const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document)
                    .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)))
                    .map(d => DocCast(d?.annotationOn, d))
                    .filter(d => d);
                return linkedDocs;
            },

            linked => this.linked_docs_to_add.push(...linked.filter(linkedDoc => !this.linked_docs_to_add.includes(linkedDoc)))
        );

        observe(
            // right now this skips during initialization which is necessary because it would be blank
            // However, it will upload the same link twice when it is
            this.linked_docs_to_add,
            change => {
                // observe pushes/splices on a user link DB 'data' field (should only happen for local changes)
                switch (change.type as any) {
                    case 'splice':
                        if ((change as any).addedCount > 0) {
                            // maybe check here if its already in the urls datadoc array so doesn't add twice
                            console.log((change as any).added as Doc[]);
                            this.addDocsToVectorstore((change as any).added as Doc[]);
                        }
                        // (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
                        break;
                    case 'update': // let oldValue = change.oldValue;
                    default:
                }
            },
            true
        );
    }

    @computed
    get linkedDocs() {
        //return (CollectionFreeFormDocumentView.from(this._props.DocumentView?.())?._props.parent as CollectionFreeFormView)?.childDocs.filter(doc => doc != this.Document) ?? [];
        return LinkManager.Instance.getAllRelatedLinks(this.Document)
            .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)))
            .map(d => DocCast(d?.annotationOn, d))
            .filter(d => d);
    }

    @action
    handleFollowUpClick = (question: string) => {
        console.log('Follow-up question clicked:', question);
        this.inputValue = question;
    };
    render() {
        return (
            <div className="chatBox">
                {this.isInitializing && <div className="initializing-overlay">Initializing...</div>}
                <div
                    className="scroll-box chat-content"
                    ref={r => {
                        this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
                        this._oldWheel = r;
                        r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                    }}>
                    <div className="messages">
                        {this.history.map((message, index) => (
                            //<DocumentView key={index} Document={message} index={index} onFollowUpClick={this.handleFollowUpClick} onCitationClick={this.handleCitationClick} updateMessageCitations={this.updateMessageCitations} />
                            <MessageComponentBox key={index} message={message} index={index} onFollowUpClick={this.handleFollowUpClick} onCitationClick={this.handleCitationClick} updateMessageCitations={this.updateMessageCitations} />
                        ))}
                        {this.current_message && (
                            <MessageComponentBox
                                key={this.history.length}
                                message={this.current_message}
                                index={this.history.length}
                                onFollowUpClick={this.handleFollowUpClick}
                                onCitationClick={this.handleCitationClick}
                                updateMessageCitations={this.updateMessageCitations}
                            />
                        )}
                    </div>
                </div>
                <form onSubmit={this.askGPT} className="chat-form">
                    <input type="text" name="messageInput" autoComplete="off" placeholder="Type a message..." value={this.inputValue} onChange={e => (this.inputValue = e.target.value)} />
                    <button type="submit" disabled={this.isLoading}>
                        {this.isLoading ? 'Thinking...' : 'Send'}
                    </button>
                </form>
            </div>
        );
    }
}

Docs.Prototypes.TemplateMap.set(DocumentType.CHAT, {
    layout: { view: ChatBox, dataField: 'data' },
    options: { acl: '', chat: '', chat_history: '', chat_thread_id: '', chat_assistant_id: '', chat_vector_store_id: '' },
});
