import React, { memo, useCallback, useContext, useEffect, useRef, useState } from 'react'
import Editor, { loader } from '@monaco-editor/react'
import FlowContext from '../../lib/flowContext'
import {
    BaseNode,
    BaseNodeContent,
    BaseNodeFooter,
    BaseNodeHeader,
    BaseNodeHeaderTitle,
} from './BaseNode'
import { GitBranchPlus, Pencil } from 'lucide-react'
import {
    Menubar,
    MenubarContent,
    MenubarItem,
    MenubarLabel,
    MenubarMenu,
    MenubarSeparator,
    MenubarSub,
    MenubarSubContent,
    MenubarSubTrigger,
    MenubarTrigger,
} from '../ui/menubar'
import { InputHandle, OutputHandle } from './NodeHandles'

// Ensure Monaco can load its web workers under Vite by pointing to a CDN
loader.config({ paths: { vs: 'https://unpkg.com/monaco-editor@latest/min/vs' } })

type Props = {
    id: string
    data: any
}

export const ConfigNode = memo(function ConfigNode({ id, data }: Props) {
    const [value, setValue] = useState<string>(data?.yaml ?? '# Enter YAML here\n')

    const ctx = useContext(FlowContext)
    const setNodes = ctx?.setNodes
    const storedYaml = ctx?.nodes?.find((n: any) => n.id === id)?.data?.yaml ?? ''

    const editorRef = useRef<any>(null)
    const monacoRef = useRef<any>(null)

    const incomingEdges = ctx?.edges?.filter((e: any) => e.target === id) ?? []
    const incomingIds = incomingEdges.map((e: any) => e.source).sort()
    const connectedConfigNodes = (ctx?.nodes ?? []).filter((n: any) => incomingIds.includes(n.id) && n.type === 'config')
    const connectedVariableNodes = (ctx?.nodes ?? []).filter((n: any) => incomingIds.includes(n.id) && n.type === 'variable')
    const hasDependencies = connectedConfigNodes.length > 0 || connectedVariableNodes.length > 0

    useEffect(() => {
        if (setNodes) {
            setNodes((nds: any[]) => nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, yaml: value } } : n)))
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    const onChange = useCallback(
        (val?: string) => {
            const v = val ?? ''
            setValue(v)
            // eslint-disable-next-line no-console
            console.debug('ConfigNode:onChange', id, v.substring(0, 60))
            if (setNodes) {
                setNodes((nds: any[]) => nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, yaml: v } } : n)))
            }
        },
        [id, setNodes]
    )

    const handleEditorMount = useCallback((editor: any, monaco: any) => {
        editorRef.current = editor
        monacoRef.current = monaco

        // Prevent React Flow and other global handlers from interfering with typing
        try {
            editor.onKeyDown((e: any) => {
                if (e?.browserEvent) {
                    e.browserEvent.stopPropagation()
                }
            })
            editor.onMouseDown((e: any) => {
                if (e?.event) {
                    e.event.stopPropagation()
                }
            })
        } catch {
            // ignore if Monaco internals change
        }
    }, [])

    const insertIncludeFromNode = useCallback(
        (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
            const ref = `${sourceNode.id}.yaml`
            const includeText = `!include ${ref}\n`
            const editor = editorRef.current
            const monaco = monacoRef.current

            try {
                if (editor && monaco) {
                    const model = editor.getModel()
                    const lineCount = model.getLineCount()
                    const selection = editor.getSelection()

                    let range
                    if (mode === 'prepend') {
                        range = new monaco.Range(1, 1, 1, 1)
                    } else if (mode === 'append') {
                        range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
                    } else if (selection) {
                        range = new monaco.Range(selection.startLineNumber, selection.startColumn, selection.startLineNumber, selection.startColumn)
                    } else {
                        range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
                    }

                    editor.executeEdits('insert-include', [{ range, text: includeText, forceMoveMarkers: true }])
                    const newVal = editor.getModel().getValue()
                    onChange(newVal)
                    return
                }

                if (mode === 'prepend') {
                    onChange(includeText + value)
                } else {
                    onChange(value + '\n' + includeText)
                }
            } catch (err) {
                // eslint-disable-next-line no-console
                console.error('Insert include failed', err)
            }
        },
        [onChange, value]
    )

    const insertVariableReference = useCallback(
        (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
            const insertText = `\${${sourceNode.id}}`
            const editor = editorRef.current
            const monaco = monacoRef.current

            try {
                if (editor && monaco) {
                    const model = editor.getModel()
                    const lineCount = model.getLineCount()
                    const selection = editor.getSelection()

                    let range
                    if (mode === 'prepend') {
                        range = new monaco.Range(1, 1, 1, 1)
                    } else if (mode === 'append') {
                        range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
                    } else if (selection) {
                        range = new monaco.Range(selection.startLineNumber, selection.startColumn, selection.startLineNumber, selection.startColumn)
                    } else {
                        range = new monaco.Range(lineCount + 1, 1, lineCount + 1, 1)
                    }

                    editor.executeEdits('insert-var', [{ range, text: insertText, forceMoveMarkers: true }])
                    const newVal = editor.getModel().getValue()
                    onChange(newVal)
                    return
                }

                if (mode === 'prepend') {
                    onChange(insertText + value)
                } else {
                    onChange(value + insertText)
                }
            } catch (err) {
                // eslint-disable-next-line no-console
                console.error('Insert variable reference failed', err)
            }
        },
        [onChange, value]
    )

    return (
        <BaseNode className="w-80">
            <BaseNodeHeader className="border-b">
                <Pencil className="size-4" />
                <BaseNodeHeaderTitle>{id}.yml</BaseNodeHeaderTitle>
            </BaseNodeHeader>

            <BaseNodeContent>
                {hasDependencies && (
                    <div className="w-full">
                        <Menubar className="h-auto bg-none p-1 border-none shadow-none">
                            <MenubarMenu>
                                <MenubarTrigger className="px-1.5 py-0 text-xs">
                                    <GitBranchPlus className="size-3.5" />
                                </MenubarTrigger>
                                <MenubarContent className="min-w-[12rem]">
                                    {connectedConfigNodes.map((n: any) => (
                                        <MenubarSub key={`config-${n.id}`}>
                                            <MenubarSubTrigger className="text-xs">
                                                {n.data?.title ?? n.id}
                                            </MenubarSubTrigger>
                                            <MenubarSubContent>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertIncludeFromNode(n, 'prepend')}
                                                >
                                                    Prepend include
                                                </MenubarItem>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertIncludeFromNode(n, 'append')}
                                                >
                                                    Append include
                                                </MenubarItem>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertIncludeFromNode(n, 'cursor')}
                                                >
                                                    Insert at cursor
                                                </MenubarItem>
                                            </MenubarSubContent>
                                        </MenubarSub>
                                    ))}
                                    {connectedVariableNodes.map((n: any) => (
                                        <MenubarSub key={`var-${n.id}`}>
                                            <MenubarSubTrigger className="text-xs">
                                                {n.id}
                                            </MenubarSubTrigger>
                                            <MenubarSubContent>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertVariableReference(n, 'cursor')}
                                                >
                                                    Insert at cursor
                                                </MenubarItem>
                                            </MenubarSubContent>
                                        </MenubarSub>
                                    ))}
                                </MenubarContent>
                            </MenubarMenu>
                        </Menubar>
                    </div>
                )}

                <div style={{ height: 180 }} className="w-full nodrag nopan">
                    <Editor
                        height="100%"
                        defaultLanguage="yaml"
                        value={value}
                        theme="vs-light"
                        onMount={handleEditorMount}
                        onChange={onChange}
                        options={{
                            minimap: { enabled: false },
                            fontSize: 12,
                            lineHeight: 16,
                            glyphMargin: false,
                            renderLineHighlight: 'none',
                            lineDecorationsWidth: 2,
                            padding: { top: 0, bottom: 4 },
                            lineNumbers: 'on',
                        }}
                    />
                </div>
            </BaseNodeContent>

            <BaseNodeFooter>
                <div className="w-full text-xs text-gray-500">{storedYaml ? `${storedYaml.length} chars` : 'none'}</div>
            </BaseNodeFooter>

            <InputHandle id="ain" />
            <OutputHandle id="out" />
        </BaseNode>
    )
})

ConfigNode.displayName = 'ConfigNode'

export default ConfigNode
