// @ts-check

/**
 * The editor system for the BridgeJS Playground.
 */
export class EditorSystem {
    /**
     * Creates a new instance of the EditorSystem.
     */
    constructor() {
        this.editors = new Map();
        this.config = {
            input: [
                {
                    key: 'swift',
                    id: 'swiftEditor',
                    language: 'swift',
                    placeholder: '',
                    readOnly: false,
                    modelUri: 'Playground.swift'
                },
                {
                    key: 'dts',
                    id: 'dtsEditor',
                    language: 'typescript',
                    placeholder: '',
                    readOnly: false,
                    modelUri: 'bridge-js.d.ts'
                }
            ],
            output: [
                {
                    key: 'dts-generated',
                    id: 'dtsOutput',
                    language: 'typescript',
                    placeholder: '// Generated TypeScript will appear here...',
                    readOnly: true,
                    modelUri: 'Playground.d.ts'
                },
                {
                    key: 'swift-import-macros',
                    id: 'swiftImportMacrosOutput',
                    language: 'swift',
                    placeholder: '// Import Swift Macros will appear here...',
                    readOnly: true,
                    modelUri: 'Playground.Macros.swift'
                },
                {
                    key: 'swift-glue',
                    id: 'swiftGlueOutput',
                    language: 'swift',
                    placeholder: '// Swift Glue will appear here...',
                    readOnly: true,
                    modelUri: 'BridgeJS.swift'
                },
                {
                    key: 'js-generated',
                    id: 'jsOutput',
                    language: 'javascript',
                    placeholder: '// Generated JavaScript will appear here...',
                    readOnly: true,
                    modelUri: 'bridge-js.js'
                }
            ]
        };
        this.activeTabs = {
            input: this.config.input[0]?.key,
            output: this.config.output[0]?.key
        };
    }

    async init() {
        await this.loadMonaco();
        this.createEditors();
        this.setupTabSystem();
        this.setupResizeHandling();
    }

    async loadMonaco() {
        return new Promise((resolve) => {
            // @ts-ignore
            require.config({ paths: { vs: 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } });
            // @ts-ignore
            require(['vs/editor/editor.main'], resolve);
        });
    }

    createEditors() {
        const commonOptions = {
            automaticLayout: true,
            minimap: { enabled: false },
            scrollBeyondLastLine: false,
            fontSize: 14,
            fontFamily: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace',
            lineNumbers: 'on',
            roundedSelection: false,
            scrollbar: { vertical: 'visible', horizontal: 'visible' },
            fixedOverflowWidgets: true,
            renderWhitespace: 'none',
            wordWrap: 'off'
        };

        // Create all editors from config
        [...this.config.input, ...this.config.output].forEach(config => {
            const element = document.getElementById(config.id);
            if (!element) {
                console.warn(`Editor element not found: ${config.id}`);
                return;
            }

            // @ts-ignore
            const model = monaco.editor.createModel(
                config.placeholder,
                config.language,
                // @ts-ignore
                monaco.Uri.parse(config.modelUri)
            );

            // @ts-ignore
            const editor = monaco.editor.create(element, {
                ...commonOptions,
                value: config.placeholder,
                language: config.language,
                readOnly: config.readOnly,
                model: model
            });

            this.editors.set(config.key, editor);
        });
    }

    setupTabSystem() {
        // Setup tab listeners
        [...this.config.input, ...this.config.output].forEach(config => {
            const button = document.querySelector(`[data-tab="${config.key}"]`);
            if (button) {
                button.addEventListener('click', () => this.switchTab(config.key));
            }
        });

        // Initial tab state
        this.updateTabStates();
    }

    switchTab(tabKey) {
        const config = this.getConfigByKey(tabKey);
        if (!config) return;

        if (this.config.input.some(c => c.key === tabKey)) {
            this.activeTabs.input = tabKey;
        } else {
            this.activeTabs.output = tabKey;
        }

        this.updateTabStates();
    }

    updateTabStates() {
        // Update all tab buttons
        [...this.config.input, ...this.config.output].forEach(config => {
            const button = document.querySelector(`[data-tab="${config.key}"]`);
            const content = document.getElementById(`${config.id}Tab`);

            if (button) {
                const isActive = config.key === this.activeTabs.input || config.key === this.activeTabs.output;
                button.classList.toggle('active', isActive);
            }

            if (content) {
                const isActive = config.key === this.activeTabs.input || config.key === this.activeTabs.output;
                content.classList.toggle('active', isActive);
            }
        });
    }

    setupResizeHandling() {
        const layoutEditor = (editor) => {
            editor.layout({ width: 0, height: 0 });
            window.requestAnimationFrame(() => {
                const { width, height } = editor.getContainerDomNode().getBoundingClientRect();
                editor.layout({ width, height });
            });
        };

        window.addEventListener("resize", () => {
            this.editors.forEach(editor => layoutEditor(editor));
        });
    }

    // Data access
    getInputs() {
        return {
            swift: this.editors.get('swift')?.getValue() || '',
            dts: this.editors.get('dts')?.getValue() || ''
        };
    }

    /**
     * Sets the inputs for the editor system.
     * @param {{swift: string, dts: string}} sampleCode - The sample code to set the inputs to.
     */
    setInputs({ swift, dts }) {
        this.editors.get('swift')?.setValue(swift);
        this.editors.get('dts')?.setValue(dts);
    }

    updateOutputs(result) {
        const outputMap = {
            'swift-glue': () => result.swiftGlue,
            'swift-import-macros': () => result.importSwiftMacroDecls,
            'js-generated': () => result.outputJs,
            'dts-generated': () => result.outputDts
        };

        Object.entries(outputMap).forEach(([key, getContent]) => {
            const editor = this.editors.get(key);
            if (editor) {
                const content = getContent();
                editor.setValue(content || `// No ${key} output generated`);
            }
        });
    }

    addChangeListeners(callback) {
        this.config.input.forEach(config => {
            const editor = this.editors.get(config.key);
            if (editor) {
                editor.onDidChangeModelContent(callback);
            }
        });
    }

    clearDiagnostics() {
        // Remove all diagnostics owned by the playground.
        this.editors.forEach(editor => {
            const model = editor.getModel();
            if (!model || typeof monaco === 'undefined') return;
            monaco.editor.setModelMarkers(model, 'bridgejs', []);
        });
    }

    /**
     * @param {{file: string, startLineNumber: number, startColumn: number, endLineNumber?: number, endColumn?: number, message: string}[]} diagnostics
     */
    showDiagnostics(diagnostics) {
        if (typeof monaco === 'undefined') return;

        // Group diagnostics per model so we can set markers in batches.
        const markersByModel = new Map();

        diagnostics.forEach(diag => {
            const model = this.findModelForFile(diag.file);
            if (!model) return;

            const markers = markersByModel.get(model) ?? [];
            const lineLength = model.getLineMaxColumn(diag.startLineNumber);
            const endLine = diag.endLineNumber ?? diag.startLineNumber;
            const endColumn = Math.min(lineLength, diag.endColumn ?? diag.startColumn + 1);

            markers.push({
                severity: monaco.MarkerSeverity.Error,
                message: diag.message,
                startLineNumber: diag.startLineNumber,
                startColumn: diag.startColumn,
                endLineNumber: endLine,
                endColumn
            });

            markersByModel.set(model, markers);
        });

        markersByModel.forEach((markers, model) => {
            monaco.editor.setModelMarkers(model, 'bridgejs', markers);
        });
    }

    findModelForFile(fileName) {
        const normalized = fileName.startsWith('/') ? fileName.slice(1) : fileName;
        for (const editor of this.editors.values()) {
            const model = editor.getModel();
            if (!model) continue;
            const uriPath = model.uri.path.startsWith('/') ? model.uri.path.slice(1) : model.uri.path;
            if (uriPath === normalized || uriPath.endsWith('/' + normalized)) {
                return model;
            }
        }
        return null;
    }

    // Utility methods
    getConfigByKey(key) {
        return [...this.config.input, ...this.config.output].find(c => c.key === key);
    }

    getActiveTabs() {
        return this.activeTabs;
    }
}
