// Blazor bridge for Monaco editor.
// Exposes window.MonacoBlazor with createEditor / setValue / getValue / dispose / setMarkers.
// Lazy-loads Monaco's AMD bundle the first time createEditor is called.

(function () {
    const VS_BASE = "/_content/ScadaLink.CentralUI/lib/monaco/vs";

    const editors = {};
    let readyPromise = null;

    function ensureLoaded() {
        if (readyPromise) return readyPromise;
        readyPromise = new Promise(function (resolve, reject) {
            const loader = document.createElement("script");
            loader.src = VS_BASE + "/loader.js";
            loader.onload = function () {
                // eslint-disable-next-line no-undef
                require.config({ paths: { vs: VS_BASE } });
                // eslint-disable-next-line no-undef
                require(["vs/editor/editor.main"], function () {
                    registerCSharpProviders();
                    resolve();
                });
            };
            loader.onerror = function (e) { reject(e); };
            document.head.appendChild(loader);
        });
        return readyPromise;
    }

    // ---- Roslyn-backed C# language providers --------------------------------

    const KIND_MAP = {
        Method: 0, Field: 4, Property: 9, Event: 10, Class: 6,
        Module: 8, Variable: 4, Text: 18
    };

    // Look up the SCADA context for a model by walking the editors map. Blazor
    // JS interop serializes records as PascalCase; we normalize to camelCase.
    async function lookupContext(model) {
        const empty = {
            declaredParameters: [], siblingScripts: [], declaredParameterShapes: [],
            selfAttributes: [], children: [], parent: null, scriptKind: 0
        };
        for (const key in editors) {
            if (editors[key].editor.getModel() === model) {
                try {
                    const got = await editors[key].dotNetRef.invokeMethodAsync("GetContext");
                    if (got) {
                        const kind = got.ScriptKind != null ? got.ScriptKind
                            : (got.scriptKind != null ? got.scriptKind : 0);
                        return {
                            declaredParameters: got.DeclaredParameters || got.declaredParameters || [],
                            siblingScripts: got.SiblingScripts || got.siblingScripts || [],
                            declaredParameterShapes: got.DeclaredParameterShapes || got.declaredParameterShapes || [],
                            selfAttributes: got.SelfAttributes || got.selfAttributes || [],
                            children: got.Children || got.children || [],
                            parent: got.Parent || got.parent || null,
                            scriptKind: kind
                        };
                    }
                } catch (e) { /* fall through */ }
                break;
            }
        }
        return empty;
    }

    function registerCSharpProviders() {
        // ----- Completions ---------------------------------------------------
        monaco.languages.registerCompletionItemProvider("csharp", {
            triggerCharacters: [".", "(", "\""],
            provideCompletionItems: async function (model, position) {
                try {
                    const ctx = await lookupContext(model);
                    const resp = await fetch("/api/script-analysis/completions", {
                        method: "POST",
                        credentials: "same-origin",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify({
                            codeText: model.getValue(),
                            line: position.lineNumber,
                            column: position.column,
                            declaredParameters: ctx.declaredParameters,
                            siblingScripts: ctx.siblingScripts,
                            selfAttributes: ctx.selfAttributes,
                            children: ctx.children,
                            parent: ctx.parent,
                            kind: ctx.scriptKind
                        })
                    });
                    if (!resp.ok) return { suggestions: [] };
                    const data = await resp.json();

                    // Snippet items (kind=Method with InsertTextRules=4) need
                    // the range to extend over Monaco's auto-closed `")` so the
                    // snippet replaces them cleanly. Detect it once.
                    const word = model.getWordUntilPosition(position);
                    const lineLen = model.getLineMaxColumn(position.lineNumber);
                    const lookahead = model.getValueInRange({
                        startLineNumber: position.lineNumber,
                        startColumn: position.column,
                        endLineNumber: position.lineNumber,
                        endColumn: Math.min(position.column + 2, lineLen)
                    });
                    let snippetTail = 0;
                    if (lookahead.startsWith("\")")) snippetTail = 2;
                    else if (lookahead.startsWith("\"")) snippetTail = 1;

                    const baseRange = {
                        startLineNumber: position.lineNumber,
                        endLineNumber: position.lineNumber,
                        startColumn: word.startColumn,
                        endColumn: word.endColumn
                    };
                    const snippetRange = {
                        ...baseRange,
                        endColumn: word.endColumn + snippetTail
                    };

                    return {
                        suggestions: (data.items || []).map(function (it) {
                            const isSnippet = it.insertTextRules === 4;
                            return {
                                label: it.label,
                                insertText: it.insertText,
                                detail: it.detail,
                                kind: KIND_MAP[it.kind] != null ? KIND_MAP[it.kind] : 18,
                                insertTextRules: isSnippet ? 4 : 0,
                                range: isSnippet ? snippetRange : baseRange,
                                command: isSnippet
                                    ? { id: "editor.action.triggerParameterHints", title: "Signature help" }
                                    : undefined
                            };
                        })
                    };
                } catch (e) {
                    return { suggestions: [] };
                }
            }
        });

        // ----- Hover ---------------------------------------------------------
        monaco.languages.registerHoverProvider("csharp", {
            provideHover: async function (model, position) {
                try {
                    const ctx = await lookupContext(model);
                    const resp = await fetch("/api/script-analysis/hover", {
                        method: "POST",
                        credentials: "same-origin",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify({
                            codeText: model.getValue(),
                            line: position.lineNumber,
                            column: position.column,
                            siblingScripts: ctx.siblingScripts,
                            declaredParameters: ctx.declaredParameterShapes,
                            selfAttributes: ctx.selfAttributes,
                            children: ctx.children,
                            parent: ctx.parent
                        })
                    });
                    if (!resp.ok) return null;
                    const data = await resp.json();
                    if (!data.markdown) return null;
                    return { contents: [{ value: data.markdown }] };
                } catch (e) { return null; }
            }
        });

        // ----- Document formatting ------------------------------------------
        monaco.languages.registerDocumentFormattingEditProvider("csharp", {
            provideDocumentFormattingEdits: async function (model) {
                try {
                    const resp = await fetch("/api/script-analysis/format", {
                        method: "POST",
                        credentials: "same-origin",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify({ code: model.getValue() })
                    });
                    if (!resp.ok) return [];
                    const data = await resp.json();
                    if (typeof data.code !== "string" || data.code === model.getValue()) return [];
                    return [{
                        range: model.getFullModelRange(),
                        text: data.code
                    }];
                } catch (e) { return []; }
            }
        });

        // ----- Inlay hints --------------------------------------------------
        monaco.languages.registerInlayHintsProvider("csharp", {
            provideInlayHints: async function (model) {
                try {
                    const ctx = await lookupContext(model);
                    const resp = await fetch("/api/script-analysis/inlay-hints", {
                        method: "POST",
                        credentials: "same-origin",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify({
                            code: model.getValue(),
                            siblingScripts: ctx.siblingScripts
                        })
                        // Note: inlay hints don't yet read children/parent shapes
                        // because they only label CallShared/CallScript args today.
                    });
                    if (!resp.ok) return { hints: [], dispose: function () {} };
                    const data = await resp.json();
                    return {
                        hints: (data.hints || []).map(function (h) {
                            return {
                                position: { lineNumber: h.line, column: h.column },
                                label: h.label,
                                kind: 2,
                                paddingRight: true
                            };
                        }),
                        dispose: function () {}
                    };
                } catch (e) { return { hints: [], dispose: function () {} }; }
            }
        });

        // ----- Signature help ------------------------------------------------
        monaco.languages.registerSignatureHelpProvider("csharp", {
            signatureHelpTriggerCharacters: ["(", ","],
            signatureHelpRetriggerCharacters: [","],
            provideSignatureHelp: async function (model, position) {
                try {
                    const ctx = await lookupContext(model);
                    const resp = await fetch("/api/script-analysis/signature-help", {
                        method: "POST",
                        credentials: "same-origin",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify({
                            codeText: model.getValue(),
                            line: position.lineNumber,
                            column: position.column,
                            siblingScripts: ctx.siblingScripts,
                            children: ctx.children,
                            parent: ctx.parent
                        })
                    });
                    if (!resp.ok) return null;
                    const data = await resp.json();
                    if (!data.label) return null;
                    return {
                        value: {
                            signatures: [{
                                label: data.label,
                                parameters: (data.parameters || []).map(function (p) {
                                    return { label: p.label, documentation: p.documentation };
                                })
                            }],
                            activeSignature: 0,
                            activeParameter: data.activeParameter || 0
                        },
                        dispose: function () {}
                    };
                } catch (e) { return null; }
            }
        });
    }

    async function fetchDiagnostics(model) {
        try {
            const ctx = await lookupContext(model);
            const resp = await fetch("/api/script-analysis/diagnostics", {
                method: "POST",
                credentials: "same-origin",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    code: model.getValue(),
                    declaredParameters: ctx.declaredParameters,
                    siblingScripts: ctx.siblingScripts,
                    kind: ctx.scriptKind
                })
            });
            if (!resp.ok) return [];
            const data = await resp.json();
            return data.markers || [];
        } catch (e) {
            return [];
        }
    }

    async function createEditor(id, host, options, dotNetRef) {
        await ensureLoaded();
        if (!host) return;
        const editor = monaco.editor.create(host, {
            value: options.value || "",
            language: options.language || "csharp",
            theme: "vs",
            minimap: { enabled: false },
            scrollBeyondLastLine: false,
            automaticLayout: true,
            fontSize: 13,
            lineNumbers: "on",
            renderLineHighlight: "line",
            readOnly: !!options.readOnly,
            tabSize: 4,
            insertSpaces: true,
            wordWrap: "off",
            fixedOverflowWidgets: true
        });
        let diagTimer = null;
        const scheduleDiagnostics = function () {
            if (diagTimer) clearTimeout(diagTimer);
            diagTimer = setTimeout(async function () {
                const model = editor.getModel();
                if (!model) return;
                const markers = await fetchDiagnostics(model);
                monaco.editor.setModelMarkers(model, "scadalink", markers);
                dotNetRef.invokeMethodAsync("OnMarkersChanged", markers).catch(function () {});
            }, 500);
        };

        editor.onDidChangeModelContent(function () {
            const value = editor.getValue();
            dotNetRef.invokeMethodAsync("OnValueChanged", value).catch(function () {});
            if (options.language === "csharp") scheduleDiagnostics();
        });
        editors[id] = { editor: editor, dotNetRef: dotNetRef };

        // Run an initial diagnostic pass so existing scripts show their markers.
        if (options.language === "csharp") scheduleDiagnostics();
    }

    function setEditorOption(id, optionName, value) {
        const entry = editors[id];
        if (!entry) return;
        if (optionName === "theme") {
            monaco.editor.setTheme(value);
            return;
        }
        const update = {};
        update[optionName] = value;
        entry.editor.updateOptions(update);
    }

    function format(id) {
        const entry = editors[id];
        if (!entry) return;
        entry.editor.getAction("editor.action.formatDocument")?.run();
    }

    function revealLine(id, line, column) {
        const entry = editors[id];
        if (!entry) return;
        entry.editor.revealLineInCenter(line);
        entry.editor.setPosition({ lineNumber: line, column: column || 1 });
        entry.editor.focus();
    }

    function setValue(id, value) {
        const entry = editors[id];
        if (!entry) return;
        if (entry.editor.getValue() !== value) {
            entry.editor.setValue(value || "");
        }
    }

    function getValue(id) {
        const entry = editors[id];
        return entry ? entry.editor.getValue() : null;
    }

    function setMarkers(id, markers) {
        const entry = editors[id];
        if (!entry) return;
        const model = entry.editor.getModel();
        if (!model) return;
        monaco.editor.setModelMarkers(model, "scadalink", markers || []);
    }

    function dispose(id) {
        const entry = editors[id];
        if (!entry) return;
        try { entry.editor.dispose(); } catch (e) {}
        delete editors[id];
    }

    window.MonacoBlazor = {
        createEditor: createEditor,
        setValue: setValue,
        getValue: getValue,
        setMarkers: setMarkers,
        setEditorOption: setEditorOption,
        format: format,
        revealLine: revealLine,
        dispose: dispose
    };
})();
