import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import * as monaco from 'monaco-editor';
import { themeColors } from './colorExtractor';
import { colors } from './colors';

export type SimpleEditorViewState = monaco.editor.ICodeEditorViewState | null;

interface ModelReference {
    model: monaco.editor.ITextModel;
    refs: number;
}

const modelReferences = new Map<string, ModelReference>();
let monacoConfigured = false;
let workspaceGlobalsSignature = '';
let workspaceGlobalsDisposables: monaco.IDisposable[] = [];

export interface SimpleEditorHandle {
    getViewState: () => SimpleEditorViewState;
    getFolds: () => SimpleEditorViewState;
    revealLine: (lineNumber: number) => void;
}

export interface SimpleEditorWorkspaceFile {
    path: string;
    content: string;
}

export interface SimpleEditorCollaborationPresence {
    user: {
        uuid: string;
        username: string;
        image?: string;
        connection_id?: string;
    };
    line: number;
    column: number;
    selectionStartLineNumber?: number;
    selectionStartColumn?: number;
    selectionEndLineNumber?: number;
    selectionEndColumn?: number;
}

export interface SimpleEditorCollaborationEdit {
    rangeOffset: number;
    rangeLength: number;
    text: string;
    baseHash?: string;
    baseLength?: number;
}

interface SimpleEditorCollaborationCursor extends SimpleEditorCollaborationPresence {
    key: string;
    top: number;
    left: number;
    height: number;
}

const areSameCollaborationCursors = (
    left: SimpleEditorCollaborationCursor[],
    right: SimpleEditorCollaborationCursor[]
) => {
    if (left.length !== right.length) return false;

    return left.every((entry, index) => {
        const other = right[index];
        return !!other &&
            entry.key === other.key &&
            entry.line === other.line &&
            entry.column === other.column &&
            entry.top === other.top &&
            entry.left === other.left &&
            entry.height === other.height &&
            entry.user.username === other.user.username &&
            entry.user.image === other.user.image;
    });
};

interface SimpleEditorProps {
    value: string;
    onChange: (value: string, position?: { lineNumber: number; column: number }) => void;
    filePath: string;
    workspaceFiles?: SimpleEditorWorkspaceFile[];
    readOnly?: boolean;
    onSave?: (currentContent: string) => void;
    onFileDrop?: (filePath: string) => void;
    onFocus?: () => void;
    onCursorChange?: (position: {
        lineNumber: number;
        column: number;
        immediate?: boolean;
        selectionStartLineNumber?: number;
        selectionStartColumn?: number;
        selectionEndLineNumber?: number;
        selectionEndColumn?: number;
    }) => void;
    onContentEdit?: (
        value: string,
        changes: SimpleEditorCollaborationEdit[],
        position?: { lineNumber: number; column: number }
    ) => void;
    collaborationPresence?: SimpleEditorCollaborationPresence[];
    editorViewState?: SimpleEditorViewState;
}

const getLiveAutocompleteEnabled = () => {
    if (typeof window === 'undefined') return true;
    return window.localStorage.getItem('bfm-live-autocomplete') !== 'false';
};

const toMonacoPath = (filePath: string) => {
    const normalized = filePath.startsWith('/') ? filePath : `/${filePath}`;
    return normalized.replace(/\/+/g, '/');
};

const getModelKey = (filePath: string) => monaco.Uri.file(toMonacoPath(filePath)).toString();

const hashCollaborationContent = (content: string) => {
    let hash = 0;
    for (let i = 0; i < content.length; i++) {
        hash = ((hash << 5) - hash + content.charCodeAt(i)) | 0;
    }

    return (hash >>> 0).toString(16).padStart(8, '0');
};

const getMinimalModelEdit = (model: monaco.editor.ITextModel, nextValue: string) => {
    const currentValue = model.getValue();
    if (currentValue === nextValue) return null;

    let prefixLength = 0;
    const minLength = Math.min(currentValue.length, nextValue.length);
    while (
        prefixLength < minLength &&
        currentValue.charCodeAt(prefixLength) === nextValue.charCodeAt(prefixLength)
    ) {
        prefixLength++;
    }

    let currentSuffixOffset = currentValue.length;
    let nextSuffixOffset = nextValue.length;
    while (
        currentSuffixOffset > prefixLength &&
        nextSuffixOffset > prefixLength &&
        currentValue.charCodeAt(currentSuffixOffset - 1) === nextValue.charCodeAt(nextSuffixOffset - 1)
    ) {
        currentSuffixOffset--;
        nextSuffixOffset--;
    }

    const start = model.getPositionAt(prefixLength);
    const end = model.getPositionAt(currentSuffixOffset);

    return {
        range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column),
        text: nextValue.slice(prefixLength, nextSuffixOffset),
        forceMoveMarkers: true,
    };
};

const isJavascriptLikeFile = (filePath: string) => {
    const language = getMonacoLanguageFromFilename(filePath);
    return language === 'javascript' || language === 'typescript';
};

const stripBlockComments = (content: string) => content.replace(/\/\*[\s\S]*?\*\//g, '');

const stripLineComments = (content: string) => stripBlockComments(content).replace(/(^|[^:])\/\/.*$/gm, '$1');

const unique = (items: string[]) => Array.from(new Set(items));

const getEditorDomStyles = () => `
.betterfiles-monaco-root {
    display: block !important;
    height: 100% !important;
    max-width: 100% !important;
    min-width: 0 !important;
    overflow: hidden !important;
    width: 100% !important;
}

.betterfiles-monaco-root .monaco-editor,
.betterfiles-monaco-root .monaco-editor .margin,
.betterfiles-monaco-root .monaco-editor-background {
    background: ${themeColors.page.background} !important;
}

.betterfiles-monaco-root .monaco-editor {
    max-width: 100% !important;
    width: 100% !important;
}

.betterfiles-monaco-root .monaco-scrollable-element {
    max-width: 100% !important;
    overflow: hidden !important;
}

.betterfiles-monaco-root .monaco-editor,
.betterfiles-monaco-root .monaco-editor .monaco-mouse-cursor-text {
    font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace !important;
}

.betterfiles-monaco-root .monaco-editor .view-overlays .selected-text,
.betterfiles-monaco-root .monaco-editor .view-overlays.focused .selected-text,
.betterfiles-monaco-root .monaco-editor.focused .selected-text {
    background-color: ${colors.primary.opacity(0.42)} !important;
    border-radius: 2px;
}

.betterfiles-monaco-root .monaco-editor .view-overlays .selectionHighlight {
    background-color: ${colors.primary.opacity(0.22)} !important;
    border: 1px solid ${colors.primary.opacity(0.32)} !important;
}

.betterfiles-monaco-root .monaco-editor .view-overlays .current-line {
    border-color: ${colors.primary.opacity(0.18)} !important;
}

.betterfiles-monaco-root .betterfiles-collab-line {
    background: ${colors.primary.opacity(0.12)} !important;
    border-left: 2px solid ${colors.primary.default};
}

.betterfiles-monaco-root .betterfiles-collab-selection {
    background: ${colors.primary.opacity(0.20)} !important;
    border: 1px solid ${colors.primary.opacity(0.30)} !important;
    border-radius: 2px;
}

.betterfiles-monaco-root .betterfiles-collab-cursor {
    border-left: 2px solid ${colors.primary.default};
    display: inline-block;
    height: 1.25em;
    margin-left: -1px;
    vertical-align: text-bottom;
}
`;

const findMatchingBrace = (content: string, openBraceIndex: number) => {
    let depth = 0;
    let quote: '"' | "'" | '`' | null = null;
    let escaped = false;

    for (let index = openBraceIndex; index < content.length; index += 1) {
        const char = content[index];

        if (quote) {
            if (escaped) {
                escaped = false;
            } else if (char === '\\') {
                escaped = true;
            } else if (char === quote) {
                quote = null;
            }
            continue;
        }

        if (char === '"' || char === "'" || char === '`') {
            quote = char;
            continue;
        }

        if (char === '{') depth += 1;
        if (char === '}') {
            depth -= 1;
            if (depth === 0) return index;
        }
    }

    return -1;
};

const extractClassMethods = (classBody: string) => {
    const methods: Array<{ name: string; isStatic: boolean }> = [];
    const methodPattern = /(?:^|[\n;])\s*(?:(?:public|private|protected|readonly|abstract|override)\s+)*(?:(static)\s+)?(?:async\s+)?([A-Za-z_$][\w$]*)\s*\(/g;
    const ignored = new Set(['constructor', 'if', 'for', 'while', 'switch', 'catch', 'function']);
    let match: RegExpExecArray | null;

    while ((match = methodPattern.exec(classBody)) !== null) {
        const methodName = match[2];
        if (!ignored.has(methodName)) {
            methods.push({ name: methodName, isStatic: !!match[1] });
        }
    }

    return unique(methods.map((method) => `${method.isStatic ? 'static ' : ''}${method.name}`))
        .map((entry) => {
            const isStatic = entry.startsWith('static ');
            return {
                name: isStatic ? entry.slice(7) : entry,
                isStatic,
            };
        });
};

const getWorkspaceModuleNames = (filePath: string) => {
    const normalized = toMonacoPath(filePath).replace(/^\/+/, '');
    const fileName = normalized.split('/').pop() || normalized;
    const withoutExtension = normalized.replace(/\.[^.]+$/, '');
    const fileNameWithoutExtension = fileName.replace(/\.[^.]+$/, '');

    return unique([
        normalized,
        `./${normalized}`,
        `/${normalized}`,
        withoutExtension,
        `./${withoutExtension}`,
        `/${withoutExtension}`,
        fileName,
        `./${fileName}`,
        fileNameWithoutExtension,
        `./${fileNameWithoutExtension}`,
    ].filter(Boolean));
};

const extractExportedWorkspaceDeclarations = (file: SimpleEditorWorkspaceFile) => {
    if (!isJavascriptLikeFile(file.path)) return { globals: [], modules: [] };

    const content = stripLineComments(file.content);
    const globals: string[] = [];
    const moduleExports: string[] = [];
    let defaultExportName: string | null = null;
    const classPattern = /export\s+(default\s+)?class\s+([A-Za-z_$][\w$]*)/g;
    const functionPattern = /export\s+(default\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/g;
    const variablePattern = /export\s+(?:declare\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)/g;
    const namedExportPattern = /export\s*\{([^}]+)\}/g;
    let match: RegExpExecArray | null;

    while ((match = classPattern.exec(content)) !== null) {
        const exportName = match[2];
        if (!defaultExportName || match[1]) defaultExportName = exportName;
        const bodyStart = content.indexOf('{', match.index);
        const bodyEnd = bodyStart >= 0 ? findMatchingBrace(content, bodyStart) : -1;
        const methods = bodyStart >= 0 && bodyEnd > bodyStart ? extractClassMethods(content.slice(bodyStart + 1, bodyEnd)) : [];
        const methodDeclarations = methods
            .map((method) => `    ${method.isStatic ? 'static ' : ''}${method.name}(...args: any[]): any;`)
            .join('\n');

        globals.push(`declare class ${exportName} {\n${methodDeclarations}\n}`);
        moduleExports.push(`export class ${exportName} {\n${methodDeclarations}\n}`);
    }

    while ((match = functionPattern.exec(content)) !== null) {
        const exportName = match[2];
        if (!defaultExportName || match[1]) defaultExportName = exportName;
        globals.push(`declare function ${exportName}(...args: any[]): any;`);
        moduleExports.push(`export function ${exportName}(...args: any[]): any;`);
    }

    while ((match = variablePattern.exec(content)) !== null) {
        if (!defaultExportName) defaultExportName = match[1];
        globals.push(`declare const ${match[1]}: any;`);
        moduleExports.push(`export const ${match[1]}: any;`);
    }

    while ((match = namedExportPattern.exec(content)) !== null) {
        match[1]
            .split(',')
            .map((part) => part.trim().split(/\s+as\s+/i).pop()?.trim())
            .filter((exportName): exportName is string => !!exportName && /^[A-Za-z_$][\w$]*$/.test(exportName))
            .forEach((exportName) => {
                if (!defaultExportName) defaultExportName = exportName;
                globals.push(`declare const ${exportName}: any;`);
                moduleExports.push(`export const ${exportName}: any;`);
            });
    }

    if (defaultExportName) {
        moduleExports.push(`export default ${defaultExportName};`);
    }

    const modules = getWorkspaceModuleNames(file.path).map((moduleName) => (
        `declare module "${moduleName}" {\n${unique(moduleExports).join('\n')}\n}`
    ));

    return { globals, modules };
};

const updateWorkspaceGlobals = (workspaceFiles: SimpleEditorWorkspaceFile[]) => {
    const exportedDeclarations = workspaceFiles.map(extractExportedWorkspaceDeclarations);
    const declarations = unique([
        ...exportedDeclarations.flatMap((entry) => entry.globals),
        ...exportedDeclarations.flatMap((entry) => entry.modules),
    ]);
    const signature = declarations.join('\n');
    if (signature === workspaceGlobalsSignature) return;

    workspaceGlobalsDisposables.forEach((disposable) => disposable.dispose());
    workspaceGlobalsDisposables = [];
    workspaceGlobalsSignature = signature;

    if (!signature) return;

    const source = [
        '// Generated by Better Files from currently open editor tabs.',
        '// Keeps Monaco aware of exported JavaScript and TypeScript symbols without requiring a server-side index.',
        signature,
    ].join('\n');

    workspaceGlobalsDisposables = [
        monaco.languages.typescript.javascriptDefaults.addExtraLib(source, 'file:///betterfiles-open-tabs.generated.d.ts'),
        monaco.languages.typescript.typescriptDefaults.addExtraLib(source, 'file:///betterfiles-open-tabs.generated.d.ts'),
    ];
};

const componentToHex = (value: number) => Math.max(0, Math.min(255, Math.round(value)))
    .toString(16)
    .padStart(2, '0');

const toMonacoColor = (color: string, alpha?: number) => {
    const normalized = color.trim();
    const hex = normalized.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
    if (hex) {
        const value = hex[1].length === 3
            ? hex[1].split('').map((part) => `${part}${part}`).join('')
            : hex[1];
        return alpha === undefined ? `#${value}` : `#${value}${componentToHex(alpha * 255)}`;
    }

    const rgb = normalized.match(/^rgba?\(([^)]+)\)$/i);
    if (rgb) {
        const parts = rgb[1].split(',').map((part) => Number.parseFloat(part.trim()));
        if (parts.length >= 3 && parts.slice(0, 3).every((part) => Number.isFinite(part))) {
            const resolvedAlpha = alpha ?? parts[3];
            return `#${componentToHex(parts[0])}${componentToHex(parts[1])}${componentToHex(parts[2])}${
                resolvedAlpha === undefined || Number.isNaN(resolvedAlpha) ? '' : componentToHex(resolvedAlpha * 255)
            }`;
        }
    }

    return normalized;
};

const getMonacoLanguageFromFilename = (filename: string): string => {
    const lowerFilename = filename.toLowerCase().replace(/\\/g, '/');
    const baseName = lowerFilename.split('/').pop() || lowerFilename;
    const ext = baseName.split('.').pop() || '';

    if (baseName === 'dockerfile' || baseName.startsWith('dockerfile.')) return 'dockerfile';
    if (baseName === '.env' || baseName.startsWith('.env.')) return 'ini';
    if (baseName.endsWith('.blade.php')) return 'php';
    if (baseName === 'makefile' || baseName === 'gnumakefile') return 'shell';
    if (baseName.includes('nginx')) return 'ini';

    const languageMap: Record<string, string> = {
        abap: 'abap',
        apex: 'apex',
        bat: 'bat',
        cmd: 'bat',
        bicep: 'bicep',
        c: 'cpp',
        h: 'cpp',
        cc: 'cpp',
        cpp: 'cpp',
        cxx: 'cpp',
        hpp: 'cpp',
        cs: 'csharp',
        clj: 'clojure',
        cljs: 'clojure',
        coffee: 'coffee',
        css: 'css',
        dart: 'dart',
        diff: 'diff',
        patch: 'diff',
        dockerfile: 'dockerfile',
        ex: 'elixir',
        exs: 'elixir',
        fs: 'fsharp',
        fsi: 'fsharp',
        fsx: 'fsharp',
        go: 'go',
        gql: 'graphql',
        graphql: 'graphql',
        hcl: 'hcl',
        tf: 'hcl',
        hbs: 'handlebars',
        handlebars: 'handlebars',
        html: 'html',
        htm: 'html',
        ini: 'ini',
        conf: 'ini',
        cfg: 'ini',
        properties: 'ini',
        java: 'java',
        js: 'javascript',
        cjs: 'javascript',
        mjs: 'javascript',
        jsx: 'javascript',
        json: 'json',
        jsonc: 'json',
        ipynb: 'json',
        jl: 'julia',
        kt: 'kotlin',
        kts: 'kotlin',
        less: 'less',
        liquid: 'liquid',
        lua: 'lua',
        m: 'objective-c',
        mm: 'objective-c',
        md: 'markdown',
        markdown: 'markdown',
        mdx: 'mdx',
        mysql: 'mysql',
        pas: 'pascal',
        p: 'pascal',
        pl: 'perl',
        pm: 'perl',
        php: 'php',
        phtml: 'php',
        ps1: 'powershell',
        psm1: 'powershell',
        psd1: 'powershell',
        proto: 'protobuf',
        pug: 'pug',
        py: 'python',
        pyw: 'python',
        r: 'r',
        razor: 'razor',
        redis: 'redis',
        rst: 'restructuredtext',
        rb: 'ruby',
        rs: 'rust',
        scala: 'scala',
        sc: 'scala',
        scm: 'scheme',
        scss: 'scss',
        sass: 'scss',
        sh: 'shell',
        bash: 'shell',
        zsh: 'shell',
        fish: 'shell',
        sol: 'solidity',
        sql: 'sql',
        pgsql: 'pgsql',
        swift: 'swift',
        sv: 'systemverilog',
        svh: 'systemverilog',
        tcl: 'tcl',
        toml: 'ini',
        ts: 'typescript',
        tsx: 'typescript',
        twig: 'twig',
        vb: 'vb',
        wgsl: 'wgsl',
        xml: 'xml',
        svg: 'xml',
        xsd: 'xml',
        xsl: 'xml',
        yaml: 'yaml',
        yml: 'yaml',
        log: 'plaintext',
        txt: 'plaintext',
        text: 'plaintext',
    };

    return languageMap[ext] || 'plaintext';
};

type CompletionCategory =
    | 'class'
    | 'constant'
    | 'function'
    | 'interface'
    | 'keyword'
    | 'method'
    | 'module'
    | 'property'
    | 'snippet'
    | 'struct'
    | 'type'
    | 'variable';

interface BetterCompletionEntry {
    label: string;
    category: CompletionCategory;
    detail: string;
    documentation?: string;
    insertText?: string;
    snippet?: boolean;
    sortGroup?: string;
}

interface SymbolPattern {
    pattern: RegExp;
    category: CompletionCategory;
    detail: string;
}

const wordEntries = (
    words: string[],
    category: CompletionCategory,
    detail: string,
    sortGroup = '2',
): BetterCompletionEntry[] => words.map((label) => ({
    label,
    category,
    detail,
    sortGroup,
}));

const snippetEntry = (
    label: string,
    insertText: string,
    detail: string,
    documentation?: string,
): BetterCompletionEntry => ({
    label,
    insertText,
    detail,
    documentation,
    category: 'snippet',
    snippet: true,
    sortGroup: '0',
});

const functionEntry = (
    label: string,
    insertText: string,
    detail: string,
    documentation?: string,
): BetterCompletionEntry => ({
    label,
    insertText,
    detail,
    documentation,
    category: 'function',
    snippet: true,
    sortGroup: '1',
});

const LANGUAGE_COMPLETIONS: Record<string, BetterCompletionEntry[]> = {
    rust: [
        ...wordEntries([
            'as', 'async', 'await', 'break', 'const', 'continue', 'crate', 'dyn', 'else', 'enum', 'extern',
            'false', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub',
            '_', 'ref', 'return', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true', 'type', 'unsafe',
            'use', 'where', 'while', 'abstract', 'become', 'box', 'do', 'final', 'macro', 'override', 'priv',
            'try', 'typeof', 'unsized', 'virtual', 'yield', 'gen', 'macro_rules', 'raw', 'safe', 'union', '\'static',
        ], 'keyword', 'Rust keyword'),
        ...wordEntries([
            'bool', 'char', 'str', 'String', 'usize', 'isize', 'u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16',
            'i32', 'i64', 'i128', 'f32', 'f64', 'Vec', 'Option', 'Result', 'Box', 'Rc', 'Arc', 'Cell', 'RefCell',
            'HashMap', 'HashSet', 'BTreeMap', 'BTreeSet', 'Cow', 'Path', 'PathBuf', 'OsStr', 'OsString',
        ], 'type', 'Rust standard type'),
        ...wordEntries([
            'Some', 'None', 'Ok', 'Err', 'Default', 'Clone', 'Copy', 'Debug', 'Display', 'From', 'Into', 'TryFrom',
            'TryInto', 'Iterator', 'IntoIterator', 'Future', 'Send', 'Sync', 'Sized', 'Drop', 'ToString', 'AsRef',
            'AsMut', 'FromStr',
        ], 'constant', 'Rust prelude item'),
        ...wordEntries([
            'std::fs', 'std::io', 'std::env', 'std::fmt', 'std::path', 'std::time', 'std::thread', 'std::sync',
            'std::collections', 'std::process', 'std::net',
        ], 'module', 'Rust standard module'),
        functionEntry('println!', 'println!("${1:value}");', 'Rust macro', 'Print to stdout with a trailing newline.'),
        functionEntry('print!', 'print!("${1:value}");', 'Rust macro', 'Print to stdout without adding a newline.'),
        functionEntry('eprintln!', 'eprintln!("${1:value}");', 'Rust macro', 'Print to stderr with a trailing newline.'),
        functionEntry('format!', 'format!("${1:value}")', 'Rust macro', 'Create a formatted String.'),
        functionEntry('dbg!', 'dbg!(${1:value})', 'Rust macro', 'Print and return a debug value.'),
        functionEntry('vec!', 'vec![${1:items}]', 'Rust macro', 'Create a Vec.'),
        functionEntry('panic!', 'panic!("${1:message}")', 'Rust macro', 'Stop execution with an error.'),
        functionEntry('assert!', 'assert!(${1:condition});', 'Rust macro', 'Assert that a condition is true.'),
        functionEntry('assert_eq!', 'assert_eq!(${1:left}, ${2:right});', 'Rust macro', 'Assert that two values are equal.'),
        functionEntry('String::from', 'String::from("${1:value}")', 'Rust String constructor'),
        functionEntry('Vec::new', 'Vec::new()', 'Rust Vec constructor'),
        functionEntry('HashMap::new', 'HashMap::new()', 'Rust HashMap constructor'),
        functionEntry('Ok', 'Ok(${1:value})', 'Rust Result value'),
        functionEntry('Err', 'Err(${1:error})', 'Rust Result error'),
        functionEntry('Some', 'Some(${1:value})', 'Rust Option value'),
        snippetEntry('fn main', 'fn main() {\n\t${1}\n}', 'Rust main function'),
        snippetEntry('pub fn', 'pub fn ${1:name}(${2})${3: -> ${4:ReturnType}} {\n\t${5}\n}', 'Rust public function'),
        snippetEntry('impl block', 'impl ${1:Type} {\n\tpub fn ${2:new}(${3}) -> Self {\n\t\t${4:Self}\n\t}\n}', 'Rust impl block'),
        snippetEntry('match', 'match ${1:value} {\n\t${2:pattern} => ${3:result},\n\t_ => ${4:default},\n}', 'Rust match expression'),
        snippetEntry('if let', 'if let ${1:Some(value)} = ${2:option} {\n\t${3}\n}', 'Rust if let'),
        snippetEntry('for loop', 'for ${1:item} in ${2:items} {\n\t${3}\n}', 'Rust for loop'),
        snippetEntry('struct', 'struct ${1:Name} {\n\t${2:field}: ${3:Type},\n}', 'Rust struct'),
        snippetEntry('enum', 'enum ${1:Name} {\n\t${2:Variant},\n}', 'Rust enum'),
        snippetEntry('test', '#[test]\nfn ${1:test_name}() {\n\t${2}\n}', 'Rust test function'),
        snippetEntry('derive', '#[derive(Debug, Clone)]\n${1}', 'Rust derive attribute'),
    ],
    cpp: [
        ...wordEntries([
            'alignas', 'alignof', 'auto', 'bool', 'break', 'case', 'char', 'const', 'constexpr', 'continue',
            'default', 'do', 'double', 'else', 'enum', 'extern', 'false', 'float', 'for', 'goto', 'if', 'inline',
            'int', 'long', 'nullptr', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static',
            'static_assert', 'struct', 'switch', 'thread_local', 'true', 'typedef', 'typeof', 'typeof_unqual',
            'union', 'unsigned', 'void', 'volatile', 'while', '_Alignas', '_Alignof', '_Atomic', '_BitInt',
            '_Bool', '_Complex', '_Decimal128', '_Decimal32', '_Decimal64', '_Generic', '_Imaginary', '_Noreturn',
            '_Static_assert', '_Thread_local',
        ], 'keyword', 'C keyword'),
        ...wordEntries([
            'size_t', 'ssize_t', 'ptrdiff_t', 'intptr_t', 'uintptr_t', 'int8_t', 'int16_t', 'int32_t', 'int64_t',
            'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t', 'FILE', 'time_t', 'clock_t', 'va_list',
        ], 'type', 'C standard type'),
        ...wordEntries([
            'NULL', 'EOF', 'EXIT_SUCCESS', 'EXIT_FAILURE', 'errno', 'stdin', 'stdout', 'stderr', 'INT_MAX', 'INT_MIN',
            'UINT_MAX', 'CHAR_BIT', 'BUFSIZ', 'FILENAME_MAX',
        ], 'constant', 'C standard constant'),
        ...wordEntries([
            '#include', '#define', '#ifdef', '#ifndef', '#if', '#elif', '#else', '#endif', '#pragma', '#error',
            '#warning', '__FILE__', '__LINE__', '__DATE__', '__TIME__', '__func__', '__STDC__',
        ], 'keyword', 'C preprocessor'),
        functionEntry('printf', 'printf("${1:%s}\\n", ${2:value});', 'C stdio function'),
        functionEntry('fprintf', 'fprintf(${1:stderr}, "${2:%s}\\n", ${3:value});', 'C stdio function'),
        functionEntry('scanf', 'scanf("${1:%d}", &${2:value});', 'C stdio function'),
        functionEntry('fopen', 'fopen("${1:path}", "${2:r}")', 'C stdio function'),
        functionEntry('fclose', 'fclose(${1:file});', 'C stdio function'),
        functionEntry('malloc', 'malloc(sizeof(${1:type}) * ${2:count})', 'C allocation function'),
        functionEntry('calloc', 'calloc(${1:count}, sizeof(${2:type}))', 'C allocation function'),
        functionEntry('realloc', 'realloc(${1:pointer}, sizeof(${2:type}) * ${3:count})', 'C allocation function'),
        functionEntry('free', 'free(${1:pointer});', 'C allocation function'),
        functionEntry('memset', 'memset(${1:pointer}, ${2:0}, ${3:size});', 'C memory function'),
        functionEntry('memcpy', 'memcpy(${1:dest}, ${2:src}, ${3:size});', 'C memory function'),
        functionEntry('strlen', 'strlen(${1:string})', 'C string function'),
        functionEntry('strcmp', 'strcmp(${1:left}, ${2:right})', 'C string function'),
        functionEntry('strcpy', 'strcpy(${1:dest}, ${2:src});', 'C string function'),
        functionEntry('snprintf', 'snprintf(${1:buffer}, ${2:size}, "${3:%s}", ${4:value});', 'C stdio function'),
        functionEntry('atoi', 'atoi(${1:string})', 'C conversion function'),
        functionEntry('strtol', 'strtol(${1:string}, ${2:NULL}, ${3:10})', 'C conversion function'),
        functionEntry('exit', 'exit(${1:EXIT_SUCCESS});', 'C process function'),
        functionEntry('perror', 'perror("${1:message}");', 'C error helper'),
        snippetEntry('main', 'int main(int argc, char **argv) {\n\t${1:return 0;}\n}', 'C main function'),
        snippetEntry('include stdio', '#include <stdio.h>\n${1}', 'C include'),
        snippetEntry('include stdlib', '#include <stdlib.h>\n${1}', 'C include'),
        snippetEntry('include guard', '#ifndef ${1:HEADER_H}\n#define ${1:HEADER_H}\n\n${2}\n\n#endif', 'C include guard'),
        snippetEntry('function', '${1:int} ${2:name}(${3:void}) {\n\t${4}\n}', 'C function'),
        snippetEntry('struct typedef', 'typedef struct ${1:Name} {\n\t${2:int field};\n} ${1:Name};', 'C typedef struct'),
        snippetEntry('for loop', 'for (${1:int i = 0}; ${2:i < count}; ${3:i++}) {\n\t${4}\n}', 'C for loop'),
        snippetEntry('switch', 'switch (${1:value}) {\ncase ${2:0}:\n\t${3}\n\tbreak;\ndefault:\n\t${4}\n\tbreak;\n}', 'C switch statement'),
    ],
    java: [
        ...wordEntries([
            'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue',
            'default', 'do', 'double', 'else', 'enum', 'extends', 'final', 'finally', 'float', 'for', 'goto', 'if',
            'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'package', 'private',
            'protected', 'public', 'return', 'short', 'static', 'strictfp', 'super', 'switch', 'synchronized',
            'this', 'throw', 'throws', 'transient', 'try', 'void', 'volatile', 'while', 'exports', 'module',
            'non-sealed', 'open', 'opens', 'permits', 'provides', 'record', 'requires', 'sealed', 'to', 'transitive',
            'uses', 'var', 'when', 'with', 'yield',
        ], 'keyword', 'Java keyword'),
        ...wordEntries([
            'String', 'Object', 'Integer', 'Long', 'Double', 'Float', 'Boolean', 'Character', 'Byte', 'Short',
            'Void', 'Class', 'Exception', 'RuntimeException', 'Throwable', 'List', 'ArrayList', 'LinkedList',
            'Map', 'HashMap', 'Set', 'HashSet', 'Optional', 'Stream', 'Collectors', 'Arrays', 'Collections',
            'StringBuilder', 'StringBuffer', 'LocalDate', 'LocalDateTime', 'Instant', 'Duration', 'Path', 'Files',
            'File', 'IOException', 'Runnable', 'Thread', 'CompletableFuture',
        ], 'class', 'Java standard class'),
        ...wordEntries(['true', 'false', 'null'], 'constant', 'Java literal'),
        ...wordEntries([
            'java.util', 'java.io', 'java.nio.file', 'java.time', 'java.math', 'java.net', 'java.util.stream',
            'java.util.concurrent',
        ], 'module', 'Java package'),
        functionEntry('System.out.println', 'System.out.println(${1:value});', 'Java print helper'),
        functionEntry('System.err.println', 'System.err.println(${1:value});', 'Java print helper'),
        functionEntry('Objects.requireNonNull', 'Objects.requireNonNull(${1:value});', 'Java utility method'),
        functionEntry('List.of', 'List.of(${1:items})', 'Java collection helper'),
        functionEntry('Map.of', 'Map.of(${1:key}, ${2:value})', 'Java collection helper'),
        functionEntry('Optional.ofNullable', 'Optional.ofNullable(${1:value})', 'Java optional helper'),
        snippetEntry('class', 'public class ${1:Name} {\n\t${2}\n}', 'Java class'),
        snippetEntry('main', 'public static void main(String[] args) {\n\t${1}\n}', 'Java main method'),
        snippetEntry('method', 'public ${1:void} ${2:name}(${3}) {\n\t${4}\n}', 'Java method'),
        snippetEntry('constructor', 'public ${1:Name}(${2}) {\n\t${3}\n}', 'Java constructor'),
        snippetEntry('interface', 'public interface ${1:Name} {\n\t${2}\n}', 'Java interface'),
        snippetEntry('record', 'public record ${1:Name}(${2}) {\n\t${3}\n}', 'Java record'),
        snippetEntry('try catch', 'try {\n\t${1}\n} catch (${2:Exception} ${3:e}) {\n\t${4:e.printStackTrace();}\n}', 'Java try/catch'),
        snippetEntry('for loop', 'for (int ${1:i} = 0; ${1:i} < ${2:size}; ${1:i}++) {\n\t${3}\n}', 'Java for loop'),
        snippetEntry('foreach', 'for (${1:Type} ${2:item} : ${3:items}) {\n\t${4}\n}', 'Java enhanced for loop'),
        snippetEntry('stream map', '${1:items}.stream()\n\t.map(${2:item} -> ${3:item})\n\t.collect(Collectors.toList())', 'Java stream pipeline'),
    ],
    csharp: [
        ...wordEntries([
            'abstract', 'as', 'base', 'bool', 'break', 'byte', 'case', 'catch', 'char', 'checked', 'class', 'const',
            'continue', 'decimal', 'default', 'delegate', 'do', 'double', 'else', 'enum', 'event', 'explicit',
            'extern', 'false', 'finally', 'fixed', 'float', 'for', 'foreach', 'goto', 'if', 'implicit', 'in',
            'int', 'interface', 'internal', 'is', 'lock', 'long', 'namespace', 'new', 'null', 'object', 'operator',
            'out', 'override', 'params', 'private', 'protected', 'public', 'readonly', 'ref', 'return', 'sbyte',
            'sealed', 'short', 'sizeof', 'stackalloc', 'static', 'string', 'struct', 'switch', 'this', 'throw',
            'true', 'try', 'typeof', 'uint', 'ulong', 'unchecked', 'unsafe', 'ushort', 'using', 'virtual', 'void',
            'volatile', 'while', 'add', 'alias', 'and', 'ascending', 'args', 'async', 'await', 'by', 'descending',
            'dynamic', 'equals', 'file', 'from', 'get', 'global', 'group', 'init', 'into', 'join', 'let', 'nameof',
            'nint', 'not', 'notnull', 'nuint', 'on', 'or', 'orderby', 'partial', 'record', 'remove', 'required',
            'scoped', 'select', 'set', 'unmanaged', 'value', 'var', 'when', 'where', 'with', 'yield',
        ], 'keyword', 'C# keyword'),
        ...wordEntries([
            'String', 'Object', 'Task', 'ValueTask', 'List', 'Dictionary', 'HashSet', 'IEnumerable', 'IQueryable',
            'ICollection', 'IList', 'IDictionary', 'Exception', 'DateTime', 'DateTimeOffset', 'TimeSpan', 'Guid',
            'CancellationToken', 'HttpClient', 'Stream', 'File', 'Path', 'Console', 'Math', 'Random', 'Regex',
        ], 'class', 'C#/.NET type'),
        ...wordEntries(['Console', 'Enumerable', 'StringComparer', 'File', 'Path', 'Task'], 'module', 'C#/.NET helper'),
        functionEntry('Console.WriteLine', 'Console.WriteLine(${1:value});', 'C# console method'),
        functionEntry('Console.Write', 'Console.Write(${1:value});', 'C# console method'),
        functionEntry('nameof', 'nameof(${1:symbol})', 'C# keyword expression'),
        functionEntry('Task.Run', 'Task.Run(() => ${1:work})', 'C# task helper'),
        functionEntry('string.IsNullOrWhiteSpace', 'string.IsNullOrWhiteSpace(${1:value})', 'C# string helper'),
        functionEntry('Enumerable.Empty', 'Enumerable.Empty<${1:Type}>()', 'C# LINQ helper'),
        snippetEntry('class', 'public class ${1:Name}\n{\n\t${2}\n}', 'C# class'),
        snippetEntry('record', 'public record ${1:Name}(${2});', 'C# record'),
        snippetEntry('interface', 'public interface ${1:IName}\n{\n\t${2}\n}', 'C# interface'),
        snippetEntry('main', 'public static void Main(string[] args)\n{\n\t${1}\n}', 'C# main method'),
        snippetEntry('async main', 'public static async Task Main(string[] args)\n{\n\t${1}\n}', 'C# async main method'),
        snippetEntry('property', 'public ${1:string} ${2:Name} { get; set; }', 'C# auto property'),
        snippetEntry('constructor', 'public ${1:Name}(${2})\n{\n\t${3}\n}', 'C# constructor'),
        snippetEntry('foreach', 'foreach (var ${1:item} in ${2:items})\n{\n\t${3}\n}', 'C# foreach loop'),
        snippetEntry('try catch', 'try\n{\n\t${1}\n}\ncatch (${2:Exception} ${3:ex})\n{\n\t${4}\n}', 'C# try/catch'),
        snippetEntry('using block', 'using (${1:var resource = expression})\n{\n\t${2}\n}', 'C# using block'),
        snippetEntry('xml summary', '/// <summary>\n/// ${1:Description}\n/// </summary>', 'C# XML documentation'),
    ],
    go: [
        ...wordEntries([
            'break', 'default', 'func', 'interface', 'select', 'case', 'defer', 'go', 'map', 'struct', 'chan',
            'else', 'goto', 'package', 'switch', 'const', 'fallthrough', 'if', 'range', 'type', 'continue', 'for',
            'import', 'return', 'var',
        ], 'keyword', 'Go keyword'),
        ...wordEntries([
            'bool', 'byte', 'complex64', 'complex128', 'error', 'float32', 'float64', 'int', 'int8', 'int16',
            'int32', 'int64', 'rune', 'string', 'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'uintptr', 'any',
            'comparable',
        ], 'type', 'Go predeclared type'),
        ...wordEntries(['true', 'false', 'iota', 'nil'], 'constant', 'Go predeclared identifier'),
        ...wordEntries([
            'fmt', 'os', 'io', 'bufio', 'strings', 'strconv', 'time', 'context', 'errors', 'log', 'net/http',
            'encoding/json', 'sync', 'math', 'sort', 'path/filepath', 'testing',
        ], 'module', 'Go standard package'),
        functionEntry('append', 'append(${1:slice}, ${2:values})', 'Go built-in function'),
        functionEntry('clear', 'clear(${1:mapOrSlice})', 'Go built-in function'),
        functionEntry('close', 'close(${1:channel})', 'Go built-in function'),
        functionEntry('complex', 'complex(${1:real}, ${2:imag})', 'Go built-in function'),
        functionEntry('copy', 'copy(${1:dst}, ${2:src})', 'Go built-in function'),
        functionEntry('delete', 'delete(${1:map}, ${2:key})', 'Go built-in function'),
        functionEntry('imag', 'imag(${1:value})', 'Go built-in function'),
        functionEntry('len', 'len(${1:value})', 'Go built-in function'),
        functionEntry('cap', 'cap(${1:value})', 'Go built-in function'),
        functionEntry('make', 'make(${1:type}, ${2:size})', 'Go built-in function'),
        functionEntry('new', 'new(${1:type})', 'Go built-in function'),
        functionEntry('panic', 'panic(${1:value})', 'Go built-in function'),
        functionEntry('print', 'print(${1:value})', 'Go built-in function'),
        functionEntry('println', 'println(${1:value})', 'Go built-in function'),
        functionEntry('real', 'real(${1:value})', 'Go built-in function'),
        functionEntry('recover', 'recover()', 'Go built-in function'),
        functionEntry('fmt.Println', 'fmt.Println(${1:value})', 'Go fmt function'),
        functionEntry('fmt.Printf', 'fmt.Printf("${1:%v}\\n", ${2:value})', 'Go fmt function'),
        snippetEntry('package main', 'package main\n\nfunc main() {\n\t${1}\n}', 'Go main package'),
        snippetEntry('import block', 'import (\n\t"${1:fmt}"\n)', 'Go import block'),
        snippetEntry('func', 'func ${1:name}(${2}) ${3:error} {\n\t${4:return nil}\n}', 'Go function'),
        snippetEntry('method', 'func (${1:r} ${2:Receiver}) ${3:Name}(${4}) ${5:error} {\n\t${6:return nil}\n}', 'Go method'),
        snippetEntry('struct', 'type ${1:Name} struct {\n\t${2:Field} ${3:string}\n}', 'Go struct'),
        snippetEntry('interface', 'type ${1:Name} interface {\n\t${2:Method}(${3}) ${4:error}\n}', 'Go interface'),
        snippetEntry('if err', 'if err != nil {\n\treturn ${1:err}\n}', 'Go error guard'),
        snippetEntry('for range', 'for ${1:_, item} := range ${2:items} {\n\t${3}\n}', 'Go range loop'),
        snippetEntry('goroutine', 'go func() {\n\t${1}\n}()', 'Go goroutine'),
        snippetEntry('test', 'func Test${1:Name}(t *testing.T) {\n\t${2}\n}', 'Go test function'),
    ],
    python: [
        ...wordEntries([
            'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue',
            'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in',
            'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield',
            'match', 'case', 'type',
        ], 'keyword', 'Python keyword'),
        ...wordEntries([
            'abs', 'aiter', 'all', 'anext', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes',
            'callable', 'chr', 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod', 'enumerate',
            'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash',
            'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'locals',
            'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
            'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str',
            'sum', 'super', 'tuple', 'type', 'vars', 'zip', '__import__',
        ], 'function', 'Python built-in'),
        ...wordEntries([
            'os', 'sys', 'pathlib', 'json', 're', 'math', 'datetime', 'time', 'typing', 'dataclasses', 'asyncio',
            'logging', 'argparse', 'collections', 'itertools', 'functools', 'subprocess', 'threading', 'unittest',
            'pytest', 'requests', 'flask', 'django',
        ], 'module', 'Python module'),
        ...wordEntries([
            'Exception', 'ValueError', 'TypeError', 'RuntimeError', 'KeyError', 'IndexError', 'AttributeError',
            'ImportError', 'ModuleNotFoundError', 'FileNotFoundError', 'NotImplementedError', 'StopIteration',
        ], 'class', 'Python exception'),
        functionEntry('print', 'print(${1:value})', 'Python built-in'),
        functionEntry('len', 'len(${1:value})', 'Python built-in'),
        functionEntry('range', 'range(${1:stop})', 'Python built-in'),
        functionEntry('open', 'open(${1:path}, "${2:r}", encoding="${3:utf-8}")', 'Python built-in'),
        snippetEntry('def', 'def ${1:name}(${2}) -> ${3:None}:\n\t${4:pass}', 'Python function'),
        snippetEntry('async def', 'async def ${1:name}(${2}) -> ${3:None}:\n\t${4:pass}', 'Python async function'),
        snippetEntry('class', 'class ${1:Name}:\n\tdef __init__(self, ${2}) -> None:\n\t\t${3:pass}', 'Python class'),
        snippetEntry('if main', 'if __name__ == "__main__":\n\t${1:main()}', 'Python main guard'),
        snippetEntry('try except', 'try:\n\t${1}\nexcept ${2:Exception} as ${3:exc}:\n\t${4:raise}', 'Python try/except'),
        snippetEntry('with open', 'with open(${1:path}, "${2:r}", encoding="${3:utf-8}") as ${4:file}:\n\t${5}', 'Python file context'),
        snippetEntry('for loop', 'for ${1:item} in ${2:items}:\n\t${3}', 'Python for loop'),
        snippetEntry('list comprehension', '[${1:item} for ${1:item} in ${2:items}]', 'Python list comprehension'),
        snippetEntry('dataclass', '@dataclass\nclass ${1:Name}:\n\t${2:field}: ${3:str}', 'Python dataclass'),
        snippetEntry('pytest test', 'def test_${1:name}() -> None:\n\t${2:assert True}', 'Python pytest test'),
    ],
    php: [
        ...wordEntries([
            '__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class',
            'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty',
            'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'enum', 'eval', 'exit',
            'extends', 'final', 'finally', 'fn', 'for', 'foreach', 'function', 'global', 'goto', 'if',
            'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list',
            'match', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'readonly', 'require',
            'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while',
            'xor', 'yield', 'from',
        ], 'keyword', 'PHP keyword'),
        ...wordEntries([
            'true', 'false', 'null', 'PHP_VERSION', 'PHP_OS', 'PHP_EOL', 'DIRECTORY_SEPARATOR', 'PATH_SEPARATOR',
            'STDIN', 'STDOUT', 'STDERR', '__CLASS__', '__DIR__', '__FILE__', '__FUNCTION__', '__LINE__',
            '__METHOD__', '__NAMESPACE__', '__TRAIT__',
        ], 'constant', 'PHP constant'),
        ...wordEntries([
            '$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_REQUEST', '$_SESSION', '$_ENV', '$_COOKIE',
            '$argc', '$argv', '$this',
        ], 'variable', 'PHP predefined variable'),
        ...wordEntries([
            'Exception', 'Throwable', 'RuntimeException', 'InvalidArgumentException', 'DateTime', 'DateTimeImmutable',
            'DateInterval', 'ArrayObject', 'stdClass', 'PDO', 'Closure', 'Generator', 'Iterator', 'Countable',
            'Stringable',
        ], 'class', 'PHP built-in class'),
        ...wordEntries([
            'strlen', 'str_contains', 'str_starts_with', 'str_ends_with', 'strpos', 'substr', 'trim', 'explode',
            'implode', 'sprintf', 'printf', 'json_encode', 'json_decode', 'array_map', 'array_filter',
            'array_reduce', 'array_merge', 'array_key_exists', 'count', 'in_array', 'is_array', 'is_string',
            'is_int', 'is_bool', 'is_null', 'isset', 'empty', 'file_get_contents', 'file_put_contents', 'fopen',
            'fclose', 'var_dump', 'print_r', 'preg_match', 'preg_replace', 'htmlspecialchars', 'getenv', 'date',
            'time', 'password_hash', 'password_verify',
        ], 'function', 'PHP built-in function'),
        functionEntry('echo', 'echo ${1:value};', 'PHP language construct'),
        functionEntry('var_dump', 'var_dump(${1:value});', 'PHP debug function'),
        functionEntry('json_encode', 'json_encode(${1:value})', 'PHP JSON function'),
        functionEntry('json_decode', 'json_decode(${1:json}, true)', 'PHP JSON function'),
        snippetEntry('<?php', '<?php\n\n${1}', 'PHP open tag'),
        snippetEntry('function', 'function ${1:name}(${2}): ${3:void}\n{\n\t${4}\n}', 'PHP function'),
        snippetEntry('class', 'class ${1:Name}\n{\n\t${2}\n}', 'PHP class'),
        snippetEntry('constructor', 'public function __construct(${1})\n{\n\t${2}\n}', 'PHP constructor'),
        snippetEntry('namespace', 'namespace ${1:App};\n\n${2}', 'PHP namespace'),
        snippetEntry('foreach', 'foreach (${1:items} as ${2:item}) {\n\t${3}\n}', 'PHP foreach'),
        snippetEntry('try catch', 'try {\n\t${1}\n} catch (${2:Throwable} ${3:$e}) {\n\t${4}\n}', 'PHP try/catch'),
        snippetEntry('match', 'match (${1:value}) {\n\t${2:condition} => ${3:result},\n\tdefault => ${4:null},\n}', 'PHP match expression'),
    ],
};

const LANGUAGE_SYMBOL_PATTERNS: Record<string, SymbolPattern[]> = {
    rust: [
        { pattern: /\bfn\s+([A-Za-z_]\w*)\s*\(/g, category: 'function', detail: 'Rust function in open editor' },
        { pattern: /\b(?:struct|enum|trait|type)\s+([A-Za-z_]\w*)/g, category: 'type', detail: 'Rust type in open editor' },
        { pattern: /\b(?:let|const|static)\s+(?:mut\s+)?([A-Za-z_]\w*)/g, category: 'variable', detail: 'Rust variable in open editor' },
    ],
    cpp: [
        { pattern: /^\s*(?:static\s+|extern\s+|inline\s+)?[A-Za-z_][\w\s*]*\s+([A-Za-z_]\w*)\s*\([^;{}]*\)\s*\{/gm, category: 'function', detail: 'C function in open editor' },
        { pattern: /\b(?:struct|enum|union|typedef\s+struct)\s+([A-Za-z_]\w*)/g, category: 'struct', detail: 'C type in open editor' },
        { pattern: /^\s*#\s*define\s+([A-Za-z_]\w*)/gm, category: 'constant', detail: 'C macro in open editor' },
    ],
    java: [
        { pattern: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, category: 'class', detail: 'Java type in open editor' },
        { pattern: /\b(?:public|private|protected|static|final|synchronized|\s)+\s*[A-Za-z_<>\[\], ?]+\s+([A-Za-z_]\w*)\s*\(/g, category: 'method', detail: 'Java method in open editor' },
        { pattern: /\b(?:final\s+)?[A-Za-z_<>\[\], ?]+\s+([A-Za-z_]\w*)\s*[=;]/g, category: 'variable', detail: 'Java variable in open editor' },
    ],
    csharp: [
        { pattern: /\b(?:class|interface|enum|record|struct)\s+([A-Za-z_]\w*)/g, category: 'class', detail: 'C# type in open editor' },
        { pattern: /\b(?:public|private|protected|internal|static|async|virtual|override|sealed|\s)+\s*[A-Za-z_<>\[\], ?]+\s+([A-Za-z_]\w*)\s*\(/g, category: 'method', detail: 'C# method in open editor' },
        { pattern: /\b(?:var|[A-Za-z_<>\[\], ?]+)\s+([A-Za-z_]\w*)\s*(?:=|;)/g, category: 'variable', detail: 'C# variable in open editor' },
    ],
    go: [
        { pattern: /\bfunc\s+(?:\([^)]+\)\s*)?([A-Za-z_]\w*)\s*\(/g, category: 'function', detail: 'Go function in open editor' },
        { pattern: /\btype\s+([A-Za-z_]\w*)\s+(?:struct|interface|func|\w+)/g, category: 'type', detail: 'Go type in open editor' },
        { pattern: /\b(?:var|const)\s+([A-Za-z_]\w*)/g, category: 'variable', detail: 'Go variable in open editor' },
    ],
    python: [
        { pattern: /^\s*def\s+([A-Za-z_]\w*)\s*\(/gm, category: 'function', detail: 'Python function in open editor' },
        { pattern: /^\s*async\s+def\s+([A-Za-z_]\w*)\s*\(/gm, category: 'function', detail: 'Python async function in open editor' },
        { pattern: /^\s*class\s+([A-Za-z_]\w*)/gm, category: 'class', detail: 'Python class in open editor' },
        { pattern: /^\s*([A-Za-z_]\w*)\s*=/gm, category: 'variable', detail: 'Python variable in open editor' },
    ],
    php: [
        { pattern: /\bfunction\s+([A-Za-z_]\w*)\s*\(/g, category: 'function', detail: 'PHP function in open editor' },
        { pattern: /\b(?:class|interface|trait|enum)\s+([A-Za-z_]\w*)/g, category: 'class', detail: 'PHP type in open editor' },
        { pattern: /(\$[A-Za-z_]\w*)/g, category: 'variable', detail: 'PHP variable in open editor' },
    ],
};

const getCompletionKind = (category: CompletionCategory) => {
    switch (category) {
        case 'class':
            return monaco.languages.CompletionItemKind.Class;
        case 'constant':
            return monaco.languages.CompletionItemKind.Constant;
        case 'function':
            return monaco.languages.CompletionItemKind.Function;
        case 'interface':
            return monaco.languages.CompletionItemKind.Interface;
        case 'method':
            return monaco.languages.CompletionItemKind.Method;
        case 'module':
            return monaco.languages.CompletionItemKind.Module;
        case 'property':
            return monaco.languages.CompletionItemKind.Property;
        case 'snippet':
            return monaco.languages.CompletionItemKind.Snippet;
        case 'struct':
            return monaco.languages.CompletionItemKind.Struct;
        case 'type':
            return monaco.languages.CompletionItemKind.Struct;
        case 'variable':
            return monaco.languages.CompletionItemKind.Variable;
        case 'keyword':
        default:
            return monaco.languages.CompletionItemKind.Keyword;
    }
};

const extractModelSymbols = (model: monaco.editor.ITextModel, language: string): BetterCompletionEntry[] => {
    const patterns = LANGUAGE_SYMBOL_PATTERNS[language] || [];
    if (!patterns.length) return [];

    const models = [
        model,
        ...monaco.editor.getModels().filter((candidate) => (
            candidate !== model &&
            !candidate.isDisposed() &&
            candidate.getLanguageId() === language
        )),
    ];
    const entries: BetterCompletionEntry[] = [];
    const seen = new Set<string>();

    models.forEach((sourceModel) => {
        const content = sourceModel.getValue();
        const sourcePath = sourceModel === model ? undefined : sourceModel.uri.path;

        patterns.forEach(({ pattern, category, detail }) => {
            pattern.lastIndex = 0;
            let match: RegExpExecArray | null;

            while ((match = pattern.exec(content)) !== null && entries.length < 180) {
                const label = match[1];
                if (!label || seen.has(`${category}:${label}`)) continue;

                seen.add(`${category}:${label}`);
                entries.push({
                    label,
                    category,
                    detail,
                    documentation: sourcePath ? `From ${sourcePath}` : undefined,
                    sortGroup: '3',
                });
            }
        });
    });

    return entries;
};

const buildLanguageSuggestions = (
    model: monaco.editor.ITextModel,
    position: monaco.IPosition,
    configuredEntries: BetterCompletionEntry[],
) => {
    const language = model.getLanguageId();
    const word = model.getWordUntilPosition(position);
    const line = model.getLineContent(position.lineNumber);
    const prefixBeforeWord = word.startColumn > 1 ? line.charAt(word.startColumn - 2) : '';
    const shouldReplacePrefix =
        (language === 'php' && prefixBeforeWord === '$') ||
        (language === 'cpp' && prefixBeforeWord === '#');
    const range = {
        startLineNumber: position.lineNumber,
        endLineNumber: position.lineNumber,
        startColumn: shouldReplacePrefix ? word.startColumn - 1 : word.startColumn,
        endColumn: word.endColumn,
    };
    const seen = new Set<string>();

    return [...configuredEntries, ...extractModelSymbols(model, language)]
        .filter((entry) => {
            const key = `${entry.category}:${entry.label}:${entry.insertText || ''}`;
            if (seen.has(key)) return false;
            seen.add(key);
            return true;
        })
        .map((entry, index) => ({
            label: entry.label,
            kind: getCompletionKind(entry.category),
            insertText: entry.insertText || entry.label,
            insertTextRules: entry.snippet ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet : undefined,
            range,
            detail: entry.detail,
            documentation: entry.documentation,
            sortText: `${entry.sortGroup || '2'}-${String(index).padStart(4, '0')}-${entry.label}`,
        }));
};

const registerLanguageCompletionProvider = (
    languageSelector: string | string[],
    configuredEntries: BetterCompletionEntry[],
    triggerCharacters: string[] = ['.', ':', '#', '$', '>', '<', '_'],
) => {
    monaco.languages.registerCompletionItemProvider(languageSelector, {
        triggerCharacters,
        provideCompletionItems: (model, position) => ({
            suggestions: buildLanguageSuggestions(model, position, configuredEntries),
        }),
    });
};

const configureMonaco = () => {
    if (monacoConfigured) return;

    monaco.editor.defineTheme('betterfiles-dark', {
        base: 'vs-dark',
        inherit: true,
        rules: [],
        colors: {
            'editor.background': toMonacoColor(themeColors.page.background),
            'editor.foreground': toMonacoColor(themeColors.page.primary),
            'editorLineNumber.foreground': toMonacoColor(themeColors.page.primaryHover),
            'editorLineNumber.activeForeground': toMonacoColor(colors.primary.light),
            'editorCursor.foreground': toMonacoColor(colors.primary.default),
            'editor.lineHighlightBackground': toMonacoColor(colors.primary.default, 0.1),
            'editor.selectionBackground': toMonacoColor(colors.primary.default, 0.45),
            'editor.inactiveSelectionBackground': toMonacoColor(colors.primary.default, 0.26),
            'editor.selectionHighlightBackground': toMonacoColor(colors.primary.default, 0.22),
            'editor.wordHighlightBackground': toMonacoColor(colors.primary.default, 0.18),
            'editor.wordHighlightStrongBackground': toMonacoColor(colors.primary.default, 0.24),
            'editorIndentGuide.background1': toMonacoColor(themeColors.page.secondary),
            'editorIndentGuide.activeBackground1': toMonacoColor(themeColors.page.secondaryHover),
            'editorGutter.background': toMonacoColor(themeColors.page.background),
            'editorWidget.background': toMonacoColor(themeColors.page.secondary),
            'editorWidget.border': toMonacoColor(themeColors.page.secondaryHover),
            'input.background': toMonacoColor(themeColors.page.background),
            'input.foreground': toMonacoColor(themeColors.page.primary),
            'input.border': toMonacoColor(themeColors.page.secondaryHover),
            'focusBorder': toMonacoColor(colors.primary.default),
        },
    });

    const ts = monaco.languages.typescript;
    const compilerOptions: monaco.languages.typescript.CompilerOptions = {
        allowJs: true,
        allowImportingTsExtensions: true,
        allowNonTsExtensions: true,
        checkJs: false,
        jsx: ts.JsxEmit.React,
        module: ts.ModuleKind.ESNext,
        moduleResolution: ts.ModuleResolutionKind.NodeJs,
        noEmit: true,
        target: ts.ScriptTarget.ESNext,
    };
    const diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions = {
        noSemanticValidation: false,
        noSyntaxValidation: false,
        diagnosticCodesToIgnore: [
            2307, // Cannot find module or its corresponding type declarations.
            2792, // Cannot find module with the current moduleResolution setting.
            7016, // Could not find a declaration file for module.
        ],
    };

    ts.javascriptDefaults.setEagerModelSync(true);
    ts.typescriptDefaults.setEagerModelSync(true);
    ts.javascriptDefaults.setCompilerOptions(compilerOptions);
    ts.typescriptDefaults.setCompilerOptions(compilerOptions);
    ts.javascriptDefaults.setDiagnosticsOptions(diagnosticsOptions);
    ts.typescriptDefaults.setDiagnosticsOptions(diagnosticsOptions);

    monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
        validate: true,
        allowComments: true,
        enableSchemaRequest: true,
        schemas: [],
    } as any);

    monaco.languages.registerCompletionItemProvider(['javascript', 'typescript'], {
        provideCompletionItems: (model, position) => {
            const word = model.getWordUntilPosition(position);
            const range = {
                startLineNumber: position.lineNumber,
                endLineNumber: position.lineNumber,
                startColumn: word.startColumn,
                endColumn: word.endColumn,
            };
            const snippets = ['console', 'return', 'const', 'let', 'async', 'await', 'function', 'class', 'import', 'export'];

            return {
                suggestions: snippets.map((label) => ({
                    label,
                    kind: monaco.languages.CompletionItemKind.Keyword,
                    insertText: label,
                    range,
                })),
            };
        },
    });

    registerLanguageCompletionProvider('rust', LANGUAGE_COMPLETIONS.rust);
    registerLanguageCompletionProvider('cpp', LANGUAGE_COMPLETIONS.cpp);
    registerLanguageCompletionProvider('java', LANGUAGE_COMPLETIONS.java);
    registerLanguageCompletionProvider('csharp', LANGUAGE_COMPLETIONS.csharp);
    registerLanguageCompletionProvider('go', LANGUAGE_COMPLETIONS.go);
    registerLanguageCompletionProvider('python', LANGUAGE_COMPLETIONS.python);
    registerLanguageCompletionProvider('php', LANGUAGE_COMPLETIONS.php);

    monacoConfigured = true;
};

const acquireModel = (filePath: string, value: string) => {
    const uri = monaco.Uri.file(toMonacoPath(filePath));
    const key = getModelKey(filePath);
    const language = getMonacoLanguageFromFilename(filePath);
    let reference = modelReferences.get(key);

    if (!reference || reference.model.isDisposed()) {
        reference = {
            model: monaco.editor.createModel(value, language, uri),
            refs: 0,
        };
        modelReferences.set(key, reference);
    } else {
        monaco.editor.setModelLanguage(reference.model, language);
    }

    reference.refs += 1;
    return { key, model: reference.model };
};

const releaseModel = (key: string | null) => {
    if (!key) return;
    const reference = modelReferences.get(key);
    if (!reference) return;

    reference.refs -= 1;
    if (reference.refs <= 0) {
        reference.model.dispose();
        modelReferences.delete(key);
    }
};

const SimpleEditor = React.forwardRef<SimpleEditorHandle, SimpleEditorProps>(({
    value,
    onChange,
    filePath,
    readOnly = false,
    onSave,
    onFileDrop,
    onFocus,
    onCursorChange,
    onContentEdit,
    collaborationPresence = [],
    editorViewState,
    workspaceFiles = [],
}, ref) => {
    const containerRef = useRef<HTMLDivElement>(null);
    const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
    const modelKeyRef = useRef<string | null>(null);
    const workspaceModelKeysRef = useRef<Map<string, string>>(new Map());
    const workspaceModelSyncSignatureRef = useRef('');
    const workspaceContentSignatureCacheRef = useRef<Map<string, { content: string; signature: string }>>(new Map());
    const lastFilePathRef = useRef(filePath);
    const isUpdatingRef = useRef(false);
    const lastEmittedValueRef = useRef(value);
    const lastModelValueRef = useRef(value);
    const collaborationDecorationIdsRef = useRef<string[]>([]);
    const collaborationDecorationsSignatureRef = useRef<string | null>(null);
    const collaborationRafRef = useRef<number | null>(null);
    const collaborationPositionFallbackUntilRef = useRef(0);
    const suppressCursorPresenceUntilRef = useRef(0);
    const pendingLocalChangeFrameRef = useRef<number | null>(null);
    const [isReady, setIsReady] = useState(false);
    const [isDragOver, setIsDragOver] = useState(false);
    const [collaborationCursors, setCollaborationCursors] = useState<SimpleEditorCollaborationCursor[]>([]);

    const onChangeRef = useRef(onChange);
    const onSaveRef = useRef(onSave);
    const onFocusRef = useRef(onFocus);
    const onCursorChangeRef = useRef(onCursorChange);
    const onContentEditRef = useRef(onContentEdit);

    useImperativeHandle(ref, () => ({
        getViewState: () => editorRef.current?.saveViewState() || null,
        getFolds: () => editorRef.current?.saveViewState() || null,
        revealLine: (lineNumber: number) => {
            const editor = editorRef.current;
            if (!editor) return;

            const model = editor.getModel();
            const safeLine = Math.min(Math.max(1, lineNumber), model?.getLineCount() || 1);
            editor.revealLineInCenter(safeLine, monaco.editor.ScrollType.Smooth);
            editor.setPosition({ lineNumber: safeLine, column: 1 });
            editor.focus();
        },
    }), []);

    useEffect(() => {
        onChangeRef.current = onChange;
    }, [onChange]);

    useEffect(() => {
        onSaveRef.current = onSave;
    }, [onSave]);

    useEffect(() => {
        onFocusRef.current = onFocus;
    }, [onFocus]);

    useEffect(() => {
        onCursorChangeRef.current = onCursorChange;
    }, [onCursorChange]);

    useEffect(() => {
        onContentEditRef.current = onContentEdit;
    }, [onContentEdit]);

    const emitCursorPresence = useCallback((immediate = true) => {
        if (isUpdatingRef.current) return;
        if (Date.now() < suppressCursorPresenceUntilRef.current) return;

        const editor = editorRef.current;
        const position = editor?.getPosition();
        if (!editor || !position) return;

        const selection = editor.getSelection();
        onCursorChangeRef.current?.({
            lineNumber: position.lineNumber,
            column: position.column,
            immediate,
            selectionStartLineNumber: selection?.selectionStartLineNumber,
            selectionStartColumn: selection?.selectionStartColumn,
            selectionEndLineNumber: selection?.positionLineNumber,
            selectionEndColumn: selection?.positionColumn,
        });
    }, []);

    const updateCollaborationCursors = useCallback(() => {
        const editor = editorRef.current;
        const model = editor?.getModel();
        if (!editor || !model) {
            setCollaborationCursors([]);
            return;
        }

        const lineCount = model.getLineCount();
        setCollaborationCursors((current) => {
            const currentByKey = new Map(current.map((entry) => [entry.key, entry]));
            const next = collaborationPresence
                .map((entry) => {
                    const key = entry.user.connection_id || entry.user.uuid;
                    const lineNumber = Math.min(Math.max(1, entry.line), lineCount);
                    const maxColumn = model.getLineMaxColumn(lineNumber);
                    const column = Math.min(Math.max(1, entry.column), maxColumn);
                    const position = editor.getScrolledVisiblePosition({ lineNumber, column });

                    if (!position) {
                        const previous = currentByKey.get(key);

                        if (!previous || Date.now() > collaborationPositionFallbackUntilRef.current) return null;

                        return {
                            ...previous,
                            ...entry,
                            line: lineNumber,
                            column,
                        };
                    }

                    return {
                        ...entry,
                        key,
                        line: lineNumber,
                        column,
                        top: position.top,
                        left: position.left,
                        height: position.height,
                    };
                })
                .filter((entry): entry is SimpleEditorCollaborationCursor => !!entry);

            return areSameCollaborationCursors(current, next) ? current : next;
        });
    }, [collaborationPresence]);

    const scheduleCollaborationCursorUpdate = useCallback(() => {
        if (collaborationRafRef.current !== null || typeof window === 'undefined') return;

        collaborationRafRef.current = window.requestAnimationFrame(() => {
            collaborationRafRef.current = null;
            updateCollaborationCursors();
        });
    }, [updateCollaborationCursors]);

    useEffect(() => {
        if (!containerRef.current || editorRef.current) return;

        configureMonaco();

        const acquired = acquireModel(filePath, value);
        modelKeyRef.current = acquired.key;
        if (acquired.model.getValue() !== value) {
            acquired.model.setValue(value);
        }

        const autocompleteEnabled = getLiveAutocompleteEnabled();
        const editor = monaco.editor.create(containerRef.current, {
            model: acquired.model,
            theme: 'betterfiles-dark',
            fontFamily: '"JetBrains Mono", "Fira Code", "Cascadia Code", Consolas, monospace',
            fontSize: 14,
            lineHeight: 21,
            tabSize: 4,
            insertSpaces: true,
            detectIndentation: true,
            readOnly,
            domReadOnly: readOnly,
            automaticLayout: true,
            minimap: {
                enabled: true,
                autohide: true,
                renderCharacters: false,
                showSlider: 'mouseover',
            },
            scrollbar: {
                alwaysConsumeMouseWheel: false,
                horizontal: 'auto',
                vertical: 'auto',
                useShadows: false,
            },
            smoothScrolling: false,
            cursorSmoothCaretAnimation: 'off',
            cursorBlinking: 'blink',
            renderLineHighlight: 'line',
            renderWhitespace: 'selection',
            renderControlCharacters: true,
            roundedSelection: false,
            scrollBeyondLastLine: false,
            folding: true,
            foldingStrategy: 'auto',
            showFoldingControls: 'mouseover',
            links: true,
            colorDecorators: true,
            bracketPairColorization: { enabled: true },
            guides: { indentation: true, bracketPairs: true },
            formatOnPaste: true,
            formatOnType: false,
            wordWrap: 'off',
            quickSuggestions: autocompleteEnabled,
            suggestOnTriggerCharacters: autocompleteEnabled,
            acceptSuggestionOnCommitCharacter: true,
            tabCompletion: 'on',
            multiCursorModifier: 'ctrlCmd',
            dragAndDrop: false,
            copyWithSyntaxHighlighting: false,
        });
        const ownerWindow = containerRef.current.ownerDocument.defaultView || window;
        const layoutEditor = () => {
            const target = containerRef.current;
            if (!target) return;

            const rect = target.getBoundingClientRect();
            if (rect.width > 0 && rect.height > 0) {
                editor.layout({
                    width: Math.max(1, Math.floor(rect.width)),
                    height: Math.max(1, Math.floor(rect.height)),
                });
                return;
            }

            editor.layout();
        };

        ownerWindow.requestAnimationFrame(layoutEditor);
        ownerWindow.setTimeout(layoutEditor, 50);
        ownerWindow.setTimeout(layoutEditor, 120);
        ownerWindow.setTimeout(layoutEditor, 500);
        ownerWindow.setTimeout(layoutEditor, 1000);

        if (editorViewState) {
            editor.restoreViewState(editorViewState);
        }

        const contentDisposable = editor.onDidChangeModelContent((event) => {
            if (onContentEditRef.current || onCursorChangeRef.current) {
                collaborationPositionFallbackUntilRef.current = Date.now() + 600;
            }
            if (isUpdatingRef.current) return;

            const nextValue = editor.getValue();
            const contentEditHandler = onContentEditRef.current;

            if (contentEditHandler && event.changes.length > 0) {
                const previousValue = lastModelValueRef.current;
                const shouldHashPatchBase = previousValue.length <= 256 * 1024;
                const baseHash = shouldHashPatchBase ? hashCollaborationContent(previousValue) : undefined;
                const baseLength = shouldHashPatchBase ? previousValue.length : undefined;
                const changes = event.changes.map((change) => ({
                    rangeOffset: change.rangeOffset,
                    rangeLength: change.rangeLength,
                    text: typeof change.text === 'string' ? change.text : '',
                    baseHash,
                    baseLength,
                }));
                const position = editor.getPosition();

                contentEditHandler(nextValue, changes, position ? {
                    lineNumber: position.lineNumber,
                    column: position.column,
                } : undefined);
            }

            lastModelValueRef.current = nextValue;

            if (pendingLocalChangeFrameRef.current !== null) {
                ownerWindow.cancelAnimationFrame(pendingLocalChangeFrameRef.current);
            }

            pendingLocalChangeFrameRef.current = ownerWindow.requestAnimationFrame(() => {
                pendingLocalChangeFrameRef.current = null;
                if (isUpdatingRef.current || editor.getModel()?.isDisposed()) return;

                const nextValue = editor.getValue();
                const position = editor.getPosition();
                lastEmittedValueRef.current = nextValue;
                onChangeRef.current(nextValue, position ? {
                    lineNumber: position.lineNumber,
                    column: position.column,
                } : undefined);
            });
        });

        const focusDisposable = editor.onDidFocusEditorWidget(() => {
            onFocusRef.current?.();
        });

        const cursorDisposable = editor.onDidChangeCursorPosition(() => emitCursorPresence(true));
        const selectionDisposable = editor.onDidChangeCursorSelection(() => emitCursorPresence(true));

        editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
            onSaveRef.current?.(editor.getValue());
        });

        const container = containerRef.current;
        const handleDrop = (e: DragEvent) => {
            const isFileDrag = e.dataTransfer?.types.includes('application/x-filetree-drag');
            const data = e.dataTransfer?.getData('text/plain');
            if (isFileDrag || (data && data.startsWith('/'))) {
                e.preventDefault();
                e.stopPropagation();
            }
        };
        const handleDragOver = (e: DragEvent) => {
            const hasText = e.dataTransfer?.types.includes('text/plain');
            const hasFileTree = e.dataTransfer?.types.includes('application/x-filetree-drag');
            if (hasText || hasFileTree) {
                e.preventDefault();
                if (hasFileTree) e.stopPropagation();
            }
        };

        container.addEventListener('drop', handleDrop, true);
        container.addEventListener('dragover', handleDragOver, true);

        editorRef.current = editor;
        setIsReady(true);

        return () => {
            if (collaborationRafRef.current !== null) {
                ownerWindow.cancelAnimationFrame(collaborationRafRef.current);
                collaborationRafRef.current = null;
            }
            if (pendingLocalChangeFrameRef.current !== null) {
                ownerWindow.cancelAnimationFrame(pendingLocalChangeFrameRef.current);
                pendingLocalChangeFrameRef.current = null;
            }
            contentDisposable.dispose();
            focusDisposable.dispose();
            cursorDisposable.dispose();
            selectionDisposable.dispose();
            container.removeEventListener('drop', handleDrop, true);
            container.removeEventListener('dragover', handleDragOver, true);
            editor.dispose();
            editorRef.current = null;
            releaseModel(modelKeyRef.current);
            modelKeyRef.current = null;
        };
    }, []);

    useEffect(() => {
        configureMonaco();

        const nextWorkspaceModels = new Map<string, string>();
        const previousWorkspaceModels = workspaceModelKeysRef.current;
        const activeModelKey = modelKeyRef.current;
        const seenSignatureKeys = new Set<string>();
        const syncSignature = workspaceFiles
            .map((file) => {
                if (!file.path) return '';
                const key = getModelKey(file.path);
                seenSignatureKeys.add(key);
                if (key === activeModelKey) return `${key}:active`;

                const cached = workspaceContentSignatureCacheRef.current.get(key);
                if (cached?.content === file.content) return cached.signature;

                const signature = `${key}:${file.content.length}:${hashCollaborationContent(file.content)}`;
                workspaceContentSignatureCacheRef.current.set(key, { content: file.content, signature });
                return signature;
            })
            .join('\n');

        workspaceContentSignatureCacheRef.current.forEach((_, key) => {
            if (!seenSignatureKeys.has(key)) {
                workspaceContentSignatureCacheRef.current.delete(key);
            }
        });

        if (syncSignature === workspaceModelSyncSignatureRef.current) return;
        workspaceModelSyncSignatureRef.current = syncSignature;

        updateWorkspaceGlobals(workspaceFiles);

        workspaceFiles.forEach((file) => {
            if (!file.path) return;

            const key = getModelKey(file.path);
            const existingKey = previousWorkspaceModels.get(file.path);
            let model = monaco.editor.getModel(monaco.Uri.file(toMonacoPath(file.path)));

            if (existingKey !== key || !model || model.isDisposed()) {
                const acquired = acquireModel(file.path, file.content);
                model = acquired.model;
            } else {
                monaco.editor.setModelLanguage(model, getMonacoLanguageFromFilename(file.path));
            }

            if (key !== activeModelKey && model.getValue() !== file.content) {
                model.setValue(file.content);
            }

            nextWorkspaceModels.set(file.path, key);
        });

        previousWorkspaceModels.forEach((key, path) => {
            if (!nextWorkspaceModels.has(path)) {
                releaseModel(key);
            }
        });

        workspaceModelKeysRef.current = nextWorkspaceModels;
    }, [workspaceFiles]);

    useEffect(() => () => {
        workspaceModelKeysRef.current.forEach((key) => releaseModel(key));
        workspaceModelKeysRef.current.clear();
        workspaceModelSyncSignatureRef.current = '';
        workspaceContentSignatureCacheRef.current.clear();
    }, []);

    useEffect(() => {
        const editor = editorRef.current;
        const model = editor?.getModel();
        if (!editor || !model) return;

        const lineCount = model.getLineCount();
        const entries = collaborationPresence.map((entry) => {
            const lineNumber = Math.min(Math.max(1, entry.line), lineCount);
            const maxColumn = model.getLineMaxColumn(lineNumber);
            const column = Math.min(Math.max(1, entry.column), maxColumn);
            const selectionStartLineNumber = Math.min(Math.max(1, entry.selectionStartLineNumber || lineNumber), lineCount);
            const selectionStartColumn = Math.min(
                Math.max(1, entry.selectionStartColumn || column),
                model.getLineMaxColumn(selectionStartLineNumber)
            );
            const selectionEndLineNumber = Math.min(Math.max(1, entry.selectionEndLineNumber || lineNumber), lineCount);
            const selectionEndColumn = Math.min(
                Math.max(1, entry.selectionEndColumn || column),
                model.getLineMaxColumn(selectionEndLineNumber)
            );

            return {
                entry,
                lineNumber,
                column,
                selectionStartLineNumber,
                selectionStartColumn,
                selectionEndLineNumber,
                selectionEndColumn,
                key: entry.user.connection_id || entry.user.uuid,
            };
        });
        const signature = entries
            .map(({
                entry,
                key,
                lineNumber,
                column,
                selectionStartLineNumber,
                selectionStartColumn,
                selectionEndLineNumber,
                selectionEndColumn,
            }) => `${key}:${lineNumber}:${column}:${selectionStartLineNumber}:${selectionStartColumn}:${selectionEndLineNumber}:${selectionEndColumn}:${entry.user.username}:${entry.user.image || ''}`)
            .join('|');

        if (signature === collaborationDecorationsSignatureRef.current) {
            scheduleCollaborationCursorUpdate();
            return;
        }

        collaborationDecorationsSignatureRef.current = signature;

        const decorations = entries.flatMap(({
            entry,
            lineNumber,
            column,
            selectionStartLineNumber,
            selectionStartColumn,
            selectionEndLineNumber,
            selectionEndColumn,
        }) => {
            const hasSelection = selectionStartLineNumber !== selectionEndLineNumber || selectionStartColumn !== selectionEndColumn;
            const baseDecorations = [
                {
                    range: new monaco.Range(lineNumber, 1, lineNumber, 1),
                    options: {
                        isWholeLine: true,
                        className: 'betterfiles-collab-line',
                        hoverMessage: {
                            value: `${entry.user.username} is editing line ${lineNumber}, column ${column}`,
                        },
                    },
                },
                {
                    range: new monaco.Range(lineNumber, column, lineNumber, column),
                    options: {
                        beforeContentClassName: 'betterfiles-collab-cursor',
                        hoverMessage: {
                            value: `${entry.user.username} is editing line ${lineNumber}, column ${column}`,
                        },
                    },
                },
            ];

            if (hasSelection) {
                baseDecorations.unshift({
                    range: new monaco.Range(
                        selectionStartLineNumber,
                        selectionStartColumn,
                        selectionEndLineNumber,
                        selectionEndColumn
                    ),
                    options: {
                        inlineClassName: 'betterfiles-collab-selection',
                        hoverMessage: {
                            value: `${entry.user.username} selected text near line ${lineNumber}`,
                        },
                    },
                });
            }

            return baseDecorations;
        });

        collaborationDecorationIdsRef.current = editor.deltaDecorations(
            collaborationDecorationIdsRef.current,
            decorations
        );
        scheduleCollaborationCursorUpdate();
    }, [collaborationPresence, scheduleCollaborationCursorUpdate]);

    useEffect(() => {
        const handler = (e: Event) => {
            const enabled = (e as CustomEvent).detail?.enabled ?? true;
            editorRef.current?.updateOptions({
                quickSuggestions: enabled,
                suggestOnTriggerCharacters: enabled,
            });
        };
        window.addEventListener('bfm:autocomplete-toggle', handler);
        return () => window.removeEventListener('bfm:autocomplete-toggle', handler);
    }, []);

    useEffect(() => {
        const editor = editorRef.current;
        if (!editor || !isReady) return;

        const disposables = [
            editor.onDidScrollChange(scheduleCollaborationCursorUpdate),
            editor.onDidLayoutChange(scheduleCollaborationCursorUpdate),
            editor.onDidChangeModelContent(scheduleCollaborationCursorUpdate),
        ];

        scheduleCollaborationCursorUpdate();

        return () => {
            disposables.forEach((disposable) => disposable.dispose());
        };
    }, [isReady, scheduleCollaborationCursorUpdate]);

    useEffect(() => {
        if (!editorRef.current || !isReady || filePath === lastFilePathRef.current) return;

        const ownerWindow = containerRef.current?.ownerDocument.defaultView || window;
        if (pendingLocalChangeFrameRef.current !== null) {
            ownerWindow.cancelAnimationFrame(pendingLocalChangeFrameRef.current);
            pendingLocalChangeFrameRef.current = null;
        }

        const previousKey = modelKeyRef.current;
        const acquired = acquireModel(filePath, value);
        modelKeyRef.current = acquired.key;
        collaborationDecorationsSignatureRef.current = null;
        collaborationDecorationIdsRef.current = editorRef.current.deltaDecorations(collaborationDecorationIdsRef.current, []);
        setCollaborationCursors([]);
        isUpdatingRef.current = true;
        editorRef.current.setModel(acquired.model);
        if (acquired.model.getValue() !== value) {
            acquired.model.setValue(value);
        }
        if (editorViewState) {
            editorRef.current.restoreViewState(editorViewState);
        } else {
            editorRef.current.setPosition({ lineNumber: 1, column: 1 });
            editorRef.current.revealPositionInCenterIfOutsideViewport({ lineNumber: 1, column: 1 });
        }
        isUpdatingRef.current = false;
        lastFilePathRef.current = filePath;
        lastEmittedValueRef.current = value;
        lastModelValueRef.current = value;
        releaseModel(previousKey);
    }, [filePath, isReady]);

    useEffect(() => {
        if (!editorRef.current || !isReady) return;
        if (value === lastEmittedValueRef.current) return;

        const editor = editorRef.current;
        const model = editorRef.current.getModel();
        if (model && model.getValue() !== value) {
            const edit = getMinimalModelEdit(model, value);
            if (!edit) {
                lastEmittedValueRef.current = value;
                return;
            }

            const ownerWindow = containerRef.current?.ownerDocument.defaultView || window;
            if (pendingLocalChangeFrameRef.current !== null) {
                ownerWindow.cancelAnimationFrame(pendingLocalChangeFrameRef.current);
                pendingLocalChangeFrameRef.current = null;
            }

            const editStartOffset = model.getOffsetAt({
                lineNumber: edit.range.startLineNumber,
                column: edit.range.startColumn,
            });
            const editEndOffset = model.getOffsetAt({
                lineNumber: edit.range.endLineNumber,
                column: edit.range.endColumn,
            });
            const editDelta = edit.text.length - (editEndOffset - editStartOffset);
            const transformOffset = (offset: number) => {
                if (offset <= editStartOffset) return offset;
                if (offset >= editEndOffset) return offset + editDelta;
                return editStartOffset + edit.text.length;
            };
            const selections = editor.getSelections()?.map((selection) => ({
                startOffset: model.getOffsetAt({
                    lineNumber: selection.selectionStartLineNumber,
                    column: selection.selectionStartColumn,
                }),
                endOffset: model.getOffsetAt({
                    lineNumber: selection.positionLineNumber,
                    column: selection.positionColumn,
                }),
            }));
            const scrollTop = editor.getScrollTop();
            const scrollLeft = editor.getScrollLeft();
            isUpdatingRef.current = true;
            suppressCursorPresenceUntilRef.current = Date.now() + 120;
            editor.executeEdits('better-files-remote-sync', [edit]);
            if (selections?.length) {
                const maxOffset = model.getValueLength();
                const toPosition = (offset: number) => model.getPositionAt(Math.min(Math.max(0, offset), maxOffset));

                editor.setSelections(selections.map((selection) => {
                    const start = toPosition(transformOffset(selection.startOffset));
                    const end = toPosition(transformOffset(selection.endOffset));

                    return new monaco.Selection(
                        start.lineNumber,
                        start.column,
                        end.lineNumber,
                        end.column
                    );
                }));
            }
            editor.setScrollTop(scrollTop);
            editor.setScrollLeft(scrollLeft);
            isUpdatingRef.current = false;
            suppressCursorPresenceUntilRef.current = Date.now() + 120;
        }
        lastEmittedValueRef.current = value;
        lastModelValueRef.current = value;
    }, [value, isReady]);

    useEffect(() => {
        editorRef.current?.updateOptions({
            readOnly,
            domReadOnly: readOnly,
        });
    }, [readOnly]);

    useEffect(() => {
        if (!editorRef.current || !containerRef.current) return;

        const ownerWindow = containerRef.current.ownerDocument.defaultView || window;
        const layoutEditor = () => {
            const editor = editorRef.current;
            const target = containerRef.current;
            if (!editor || !target) return;

            const rect = target.getBoundingClientRect();
            if (rect.width > 0 && rect.height > 0) {
                editor.layout({
                    width: Math.max(1, Math.floor(rect.width)),
                    height: Math.max(1, Math.floor(rect.height)),
                });
                return;
            }

            editor.layout();
        };
        const handleWindowResize = () => {
            layoutEditor();
        };
        const resizeObserver = new ResizeObserver(() => {
            layoutEditor();
        });

        resizeObserver.observe(containerRef.current);
        ownerWindow.addEventListener('resize', handleWindowResize);
        ownerWindow.requestAnimationFrame(layoutEditor);

        return () => {
            resizeObserver.disconnect();
            ownerWindow.removeEventListener('resize', handleWindowResize);
        };
    }, [isReady]);

    const handleDragOver = (e: React.DragEvent) => {
        e.preventDefault();
        e.stopPropagation();
        if (onFileDrop) {
            setIsDragOver(true);
        }
    };

    const handleDragLeave = (e: React.DragEvent) => {
        e.preventDefault();
        e.stopPropagation();
        if (!e.currentTarget.contains(e.relatedTarget as Node)) {
            setIsDragOver(false);
        }
    };

    const handleDrop = (e: React.DragEvent) => {
        e.preventDefault();
        e.stopPropagation();
        setIsDragOver(false);

        let path = e.dataTransfer.getData('text/plain');
        if (!path) {
            const filetreeData = e.dataTransfer.getData('application/x-filetree-drag');
            if (filetreeData) {
                try {
                    const data = JSON.parse(filetreeData);
                    if (data.multiple && data.files && data.files.length > 0) {
                        path = data.files[0].path;
                    } else if (data.path && data.isFile) {
                        path = data.path;
                    }
                } catch (error) {
                    console.error('Failed to parse filetree drag data', error);
                }
            }
        }

        if (path && onFileDrop) {
            onFileDrop(path);
        }
    };

    return (
        <div
            className='relative flex h-full min-h-0 w-full min-w-0 max-w-full flex-1 overflow-hidden p-0'
            style={{
                alignSelf: 'stretch',
                borderRadius: 'inherit',
                display: 'flex',
                flex: '1 1 0%',
                flexBasis: 0,
                height: '100%',
                maxWidth: '100%',
                minHeight: 0,
                minWidth: 0,
                overflow: 'hidden',
                padding: 0,
                position: 'relative',
                width: '100%',
            }}
            onClick={() => onFocus?.()}
            onDragEnter={handleDragOver}
            onDragOver={handleDragOver}
            onDragLeave={handleDragLeave}
            onDrop={handleDrop}
        >
            <style>{getEditorDomStyles()}</style>
            <div
                ref={containerRef}
                className='betterfiles-monaco-root h-full w-full min-h-0 overflow-hidden'
                style={{
                    background: themeColors.page.background,
                    borderRadius: 'inherit',
                    display: 'block',
                    flex: '1 1 0%',
                    height: '100%',
                    maxWidth: '100%',
                    minHeight: 0,
                    minWidth: 0,
                    overflow: 'hidden',
                    width: '100%',
                }}
            />
            {collaborationCursors.length > 0 && (
                <div className='pointer-events-none absolute inset-0 z-10 overflow-hidden'>
                    {collaborationCursors.map((entry) => (
                        <div
                            key={entry.key}
                            className='absolute flex items-center gap-1'
                            style={{
                                left: entry.left + 6,
                                top: entry.top + entry.height / 2,
                                transform: 'translateY(-50%)',
                            }}
                        >
                            <img
                                src={entry.user.image}
                                alt=''
                                className='h-5 w-5 rounded-full border border-neutral-900 bg-neutral-700'
                            />
                            <span className='rounded bg-neutral-900 px-1.5 py-0.5 text-[10px] leading-none text-neutral-100 shadow'>
                                {entry.user.username} L{entry.line}
                            </span>
                        </div>
                    ))}
                </div>
            )}
            {isDragOver && (
                <div
                    className='absolute inset-0 z-20 flex items-center justify-center pointer-events-none'
                    style={{
                        background: colors.primary.opacity(0.15),
                        border: `2px dashed ${colors.primary.default}`,
                        borderRadius: 'inherit',
                    }}
                >
                    <span className='text-white text-lg font-medium' style={{ color: colors.primary.default }}>
                        Drop to open in split view
                    </span>
                </div>
            )}
        </div>
    );
});

SimpleEditor.displayName = 'SimpleEditor';

export { getMonacoLanguageFromFilename };
export default SimpleEditor;
