// ==========================================
// Chat Switchboard — CM6 Chat Input Factory
// ==========================================
// Minimal markdown editor for the chat message
// input. No line numbers, no gutter. Enter sends,
// Shift+Enter inserts newline. Auto-growing height.
//
// Usage:
//   const editor = CM.chatInput(container, {
//       placeholder: 'Send a message...',
//       onSubmit: (text) => sendMessage(text),
//       onChange: (text) => updateInputTokens(text),
//       maxHeight: 200,
//       darkMode: true,
//   });
//
//   editor.getValue()
//   editor.setValue(str)
//   editor.focus()
//   editor.destroy()
// ==========================================

import { EditorView, keymap, drawSelection, dropCursor,
         highlightSpecialChars, placeholder as placeholderExt,
         ViewPlugin, Decoration } from '@codemirror/view';
import { EditorState, Compartment, RangeSetBuilder } from '@codemirror/state';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { syntaxHighlighting, defaultHighlightStyle, syntaxTree } from '@codemirror/language';

import { chatInputTheme } from './theme.mjs';

// ── Auto-Height Extension ───────────────────

/**
 * Extension that auto-grows the editor height to fit content,
 * up to maxHeight pixels. Mirrors the existing textarea behavior:
 *   this.style.height = 'auto';
 *   this.style.height = Math.min(this.scrollHeight, 200) + 'px';
 */
function autoHeight(maxHeight = 200) {
    return EditorView.updateListener.of((update) => {
        if (update.docChanged || update.geometryChanged) {
            const dom = update.view.dom;
            dom.style.height = 'auto';
            const scrollH = update.view.contentDOM.scrollHeight;
            // Account for .cm-editor padding (12px top + 12px bottom from wrapper CSS)
            const padV = 24;
            const targetH = Math.min(scrollH + padV, maxHeight);
            dom.style.height = targetH + 'px';
            dom.style.overflowY = scrollH + padV > maxHeight ? 'auto' : 'hidden';
        }
    });
}

// ── Fenced Code Block Decorations ───────────
//
// Scans the markdown syntax tree for FencedCode nodes
// and applies line decorations so they render as visual
// code blocks (dark background, monospace, rounded corners)
// inside the chat input — matching the claude.ai UX.

const codeBlockDeco = ViewPlugin.fromClass(class {
    decorations;

    constructor(view) {
        this.decorations = this.buildDecorations(view);
    }

    update(update) {
        // Rebuild when content changes, viewport shifts, or the parser
        // finishes an incremental parse (tree reference changes)
        if (update.docChanged || update.viewportChanged ||
            syntaxTree(update.startState) !== syntaxTree(update.state)) {
            this.decorations = this.buildDecorations(update.view);
        }
    }

    buildDecorations(view) {
        const builder = new RangeSetBuilder();
        const tree = syntaxTree(view.state);
        const doc = view.state.doc;

        tree.iterate({
            enter(node) {
                if (node.name !== 'FencedCode') return;

                const startLine = doc.lineAt(node.from);
                const endLine = doc.lineAt(node.to);

                for (let ln = startLine.number; ln <= endLine.number; ln++) {
                    const line = doc.line(ln);
                    let cls;

                    if (ln === startLine.number) {
                        cls = 'cm-codeblock cm-codeblock-open';
                    } else if (ln === endLine.number) {
                        cls = 'cm-codeblock cm-codeblock-close';
                    } else {
                        cls = 'cm-codeblock cm-codeblock-mid';
                    }

                    // Single-line code block (just ```)
                    if (startLine.number === endLine.number) {
                        cls = 'cm-codeblock cm-codeblock-open cm-codeblock-close';
                    }

                    builder.add(line.from, line.from, Decoration.line({ class: cls }));
                }
            },
        });

        return builder.finish();
    }
}, {
    decorations: (v) => v.decorations,
});


// ── Code Block Shortcuts ────────────────────

/**
 * Input handler: triple backtick → fenced code block.
 * Triggers when user types the third ` completing ```
 * at the start of a line (possibly preceded only by whitespace/text).
 */
function tripleBacktickHandler() {
    return EditorView.inputHandler.of((view, from, to, text) => {
        if (text !== '`') return false;

        const line = view.state.doc.lineAt(from);
        const textBeforeCursor = view.state.doc.sliceString(line.from, from);

        // Match `` at end of line (completing ```) — only at line start
        if (/^``$/.test(textBeforeCursor.trimStart()) && textBeforeCursor.trimStart() === '``') {
            const leadingWhitespace = textBeforeCursor.match(/^(\s*)/)[1];
            const block = leadingWhitespace + '```\n' + leadingWhitespace + '\n' + leadingWhitespace + '```';
            view.dispatch({
                changes: { from: line.from, to, insert: block },
                selection: { anchor: line.from + leadingWhitespace.length + 4 },
            });
            return true;
        }
        return false;
    });
}

/**
 * Wrap the selection with `before`/`after` markers (or insert an empty
 * pair and place the cursor between them).  Used for bold, italic, code.
 */
function _wrapWith(view, before, after) {
    const { from, to } = view.state.selection.main;
    if (from !== to) {
        const text = view.state.doc.sliceString(from, to);
        view.dispatch({
            changes: { from, to, insert: before + text + after },
            selection: { anchor: from + before.length, head: to + before.length },
        });
    } else {
        view.dispatch({
            changes: { from, to: from, insert: before + after },
            selection: { anchor: from + before.length },
        });
    }
    return true;
}

/**
 * Keybindings: Ctrl/Cmd+B (bold), Ctrl/Cmd+I (italic), Ctrl/Cmd+E (inline code).
 * Wraps selection or inserts an empty pair with cursor between markers.
 */
function formattingKeymap() {
    return keymap.of([
        { key: 'Mod-b', run: (view) => _wrapWith(view, '**', '**') },
        { key: 'Mod-i', run: (view) => _wrapWith(view, '_', '_') },
        { key: 'Mod-e', run: (view) => _wrapWith(view, '`', '`') },
    ]);
}

// ── Chat Input Factory ──────────────────────

/**
 * Create a chat message input with markdown highlighting.
 *
 * @param {HTMLElement} target - Container element
 * @param {Object} opts
 * @param {string}   [opts.placeholder='Send a message...']
 * @param {Function} [opts.onSubmit]   - Called with text on Enter (without Shift)
 * @param {Function} [opts.onChange]   - Called with text on every edit
 * @param {number}   [opts.maxHeight=200] - Max height in pixels before scrolling
 * @param {boolean}  [opts.darkMode]   - Auto-detected from body if omitted
 * @returns {{ getValue, setValue, focus, getView, destroy }}
 */
export function chatInput(target, opts = {}) {
    const {
        placeholder: placeholderText = 'Send a message...',
        onSubmit = null,
        onChange = null,
        maxHeight = 200,
        darkMode = document.documentElement.getAttribute('data-theme') !== 'light',
        extensions: extraExtensions = [],
    } = opts;

    // Build extensions
    const extensions = [
        // Markdown highlighting
        markdown({ base: markdownLanguage }),
        syntaxHighlighting(defaultHighlightStyle, { fallback: true }),

        // Markdown shortcuts
        tripleBacktickHandler(),  // ``` → fenced code block
        formattingKeymap(),       // Ctrl/Cmd+B, I, E → bold, italic, code

        // Code block visual decorations (WYSIWYG style)
        codeBlockDeco,

        // Base editing
        highlightSpecialChars(),
        history(),
        drawSelection(),
        dropCursor(),
        closeBrackets(),

        // Theme — minimal chrome, no gutters
        chatInputTheme,

        // Placeholder
        placeholderExt(placeholderText),

        // Auto-growing height
        autoHeight(maxHeight),

        // Keymaps — chat-specific: Enter sends, Shift+Enter newline
        keymap.of([
            // Enter to submit (must be before defaultKeymap)
            {
                key: 'Enter',
                run: (view) => {
                    if (onSubmit) {
                        const text = view.state.doc.toString();
                        onSubmit(text);
                        return true;
                    }
                    return false;
                },
            },
            // Shift+Enter to insert newline
            {
                key: 'Shift-Enter',
                run: (view) => {
                    view.dispatch(
                        view.state.replaceSelection('\n')
                    );
                    return true;
                },
            },
            ...closeBracketsKeymap,
            ...defaultKeymap,
            ...historyKeymap,
        ]),
    ];

    // Change listener
    if (onChange) {
        extensions.push(EditorView.updateListener.of((update) => {
            if (update.docChanged) {
                onChange(update.state.doc.toString());
            }
        }));
    }

    // Enable spell check and standard text input attributes
    extensions.push(
        EditorView.contentAttributes.of({
            spellcheck: 'true',
            autocapitalize: 'sentences',
            autocorrect: 'on',
        })
    );

    // Single-line display mode: wrap long lines
    extensions.push(EditorView.lineWrapping);

    // Extra extensions (e.g. @mention autocomplete)
    if (extraExtensions.length) {
        extensions.push(...extraExtensions);
    }

    const view = new EditorView({
        state: EditorState.create({ doc: '', extensions }),
        parent: target,
    });

    // ── Public API ──────────────────────────

    return {
        getValue() {
            return view.state.doc.toString();
        },

        setValue(text) {
            view.dispatch({
                changes: { from: 0, to: view.state.doc.length, insert: text || '' },
            });
        },

        focus() {
            view.focus();
        },

        /** Access the underlying EditorView */
        getView() {
            return view;
        },

        /** Clean up */
        destroy() {
            view.destroy();
        },
    };
}
