import React, { memo, useCallback, useContext, useMemo, useRef } from 'react'
import { autocompletion } from '@codemirror/autocomplete'
import CodeMirror from '@uiw/react-codemirror'
import FlowContext from '../../lib/flowContext'
import { useResizeHeight } from '../../hooks/useResizeHeight'
import { nunjucksCompletionSource } from '../../lib/nunjucksAutocomplete'
import { plantumlLanguage } from '../../lib/plantumlLanguage'
import { useTheme } from '../../lib/themeContext'
import {
    BaseNode,
    BaseNodeContent,
    BaseNodeFooter,
    BaseNodeHeaderRow,
} from './BaseNode'
import { Code2, ScrollText, Variable } from 'lucide-react'
import {
    MenubarItem,
    MenubarSub,
    MenubarSubContent,
    MenubarSubTrigger,
} from '../ui/menubar'
import { InputHandle, OutputHandle } from './NodeHandles'
import { NodeFooterEdgeIndicators } from './NodeFooterEdgeIndicators'
import { NodeHeaderTitle } from './NodeHeaderTitle'
import { NodeMenubar } from './NodeMenubar'

type Props = {
    id: string
    data: any
    width?: number
    height?: number
}

const DEFAULT_PLANTUML = '@startuml\n\n@enduml\n'

export const ConfigNode = memo(function ConfigNode({ id, data, width, height }: Props) {
    const plantumlValue = data?.plantuml ?? DEFAULT_PLANTUML
    const { theme } = useTheme()
    const ctx = useContext(FlowContext)
    const setNodes = ctx?.setNodes

    const editorRef = useRef<unknown>(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 connectedFunctionNodes = (ctx?.nodes ?? []).filter((n: any) => incomingIds.includes(n.id) && n.type === 'function')
    const hasDependencies = connectedConfigNodes.length > 0 || connectedVariableNodes.length > 0 || connectedFunctionNodes.length > 0

    const onChange = useCallback(
        (val: string) => {
            if (setNodes) {
                setNodes((nds: any[]) => nds.map((n) => (n.id === id ? { ...n, data: { ...n.data, plantuml: val } } : n)))
            }
        },
        [id, setNodes]
    )

    const insertAt = useCallback(
        (insertText: string, mode: 'prepend' | 'append' | 'cursor') => {
            const ref = editorRef.current as { view: { state: { doc: { length: number; toString(): string }; selection: { main: { from: number } } }; dispatch: (arg: { changes: { from: number; to: number; insert: string } }) => void } } | null
            if (ref?.view) {
                const view = ref.view
                const doc = view.state.doc
                const len = doc.length
                let from: number
                if (mode === 'prepend') {
                    from = 0
                } else if (mode === 'append') {
                    from = len
                } else {
                    const main = view.state.selection.main
                    from = main.from
                }
                view.dispatch({ changes: { from, to: from, insert: insertText } })
                const newVal = view.state.doc.toString()
                onChange(newVal)
                return
            }
            if (mode === 'prepend') {
                onChange(insertText + plantumlValue)
            } else {
                onChange(plantumlValue + insertText)
            }
        },
        [onChange, plantumlValue]
    )

    const insertExtendsFromNode = useCallback(
        (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
            insertAt(`{% extends "${sourceNode.id}" %}\n`, mode)
        },
        [insertAt]
    )

    const insertIncludeFromNode = useCallback(
        (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
            insertAt(`{% include "${sourceNode.id}" %}\n`, mode)
        },
        [insertAt]
    )

    const insertImportFromNode = useCallback(
        (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
            insertAt(`{% import "${sourceNode.id}" as ${sourceNode.id} %}\n`, mode)
        },
        [insertAt]
    )

    const insertVariableReference = useCallback(
        (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
            insertAt(`{{ ${sourceNode.id} }}`, mode)
        },
        [insertAt]
    )

    const insertFunctionCall = useCallback(
        (sourceNode: any, mode: 'prepend' | 'append' | 'cursor') => {
            insertAt(`{{ ${sourceNode.id}() }}`, mode)
        },
        [insertAt]
    )

    const variableIds = useMemo(() => connectedVariableNodes.map((n: any) => n.id), [connectedVariableNodes])
    const functionIds = useMemo(() => connectedFunctionNodes.map((n: any) => n.id), [connectedFunctionNodes])
    const configTitles = useMemo(
        () => connectedConfigNodes.map((n: any) => n.data?.title ?? n.id),
        [connectedConfigNodes],
    )
    const extensions = useMemo(
        () => [
            plantumlLanguage.extension,
            autocompletion({
                override: [nunjucksCompletionSource(variableIds, configTitles, functionIds)],
                activateOnTyping: true,
            }),
        ],
        [variableIds, functionIds, configTitles],
    )
    const [editorHeight, editorContainerRef] = useResizeHeight(180)

    const nunjucksTagSnippets = useMemo(
        () => [
            { label: 'Variable {{ }}', snippet: '{{  }}' },
            { label: 'if / endif', snippet: '{% if  %}\n  \n{% endif %}' },
            { label: 'for / endfor', snippet: '{% for  in  %}\n  \n{% endfor %}' },
            { label: 'set', snippet: '{% set  =  %}' },
            { label: 'block / endblock', snippet: '{% block  %}\n  \n{% endblock %}' },
            { label: 'extends', snippet: '{% extends "" %}' },
            { label: 'include', snippet: '{% include "" %}' },
            { label: 'import', snippet: '{% import "" as  %}' },
            { label: 'raw / endraw', snippet: '{% raw %}\n  \n{% endraw %}' },
        ],
        []
    )

    const dimensions =
        width != null && height != null && width > 0 && height > 0
            ? { width, height }
            : undefined

    return (
        <BaseNode className="min-w-80 min-h-[280px]" dimensions={dimensions} resizable nodeId={id} handles={<><InputHandle id="ain" nodeId={id} /><OutputHandle id="out" /></>}>
            <BaseNodeHeaderRow icon={<ScrollText className="size-4" />} title={<NodeHeaderTitle nodeId={id} displayTitle={`${id}`} />} />

            <BaseNodeContent>
                <div className="shrink-0 w-full">
                    <NodeMenubar
                        nodeId={id}
                        nodeType="config"
                        editInputsContent={
                            hasDependencies ? (
                                <>
                                    {connectedConfigNodes.map((n: any) => (
                                        <MenubarSub key={`${n.id}`}>
                                            <MenubarSubTrigger className="text-xs flex items-center gap-2">
                                                <ScrollText className="size-3.5 shrink-0" />
                                                {n.data?.title ?? n.id}
                                            </MenubarSubTrigger>
                                            <MenubarSubContent>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertExtendsFromNode(n, 'cursor')}
                                                >
                                                    Extend
                                                </MenubarItem>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertIncludeFromNode(n, 'cursor')}
                                                >
                                                    Include
                                                </MenubarItem>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertImportFromNode(n, 'cursor')}
                                                >
                                                    Import
                                                </MenubarItem>
                                            </MenubarSubContent>
                                        </MenubarSub>
                                    ))}
                                    {connectedVariableNodes.map((n: any) => (
                                        <MenubarSub key={`${n.id}`}>
                                            <MenubarSubTrigger className="text-xs flex items-center gap-2">
                                                <Variable className="size-3.5 shrink-0" />
                                                {n.id}
                                            </MenubarSubTrigger>
                                            <MenubarSubContent>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertVariableReference(n, 'cursor')}
                                                >
                                                    Insert
                                                </MenubarItem>
                                            </MenubarSubContent>
                                        </MenubarSub>
                                    ))}
                                    {connectedFunctionNodes.map((n: any) => (
                                        <MenubarSub key={`${n.id}`}>
                                            <MenubarSubTrigger className="text-xs flex items-center gap-2">
                                                <Code2 className="size-3.5 shrink-0" />
                                                {n.id}
                                            </MenubarSubTrigger>
                                            <MenubarSubContent>
                                                <MenubarItem
                                                    className="text-xs"
                                                    onClick={() => insertFunctionCall(n, 'cursor')}
                                                >
                                                    Insert
                                                </MenubarItem>
                                            </MenubarSubContent>
                                        </MenubarSub>
                                    ))}
                                </>
                            ) : undefined
                        }
                        insertTagsContent={
                            <>
                                {nunjucksTagSnippets.map(({ label, snippet }) => (
                                    <MenubarItem
                                        key={label}
                                        className="text-xs"
                                        onClick={() => insertAt(snippet, 'cursor')}
                                    >
                                        {label}
                                    </MenubarItem>
                                ))}
                            </>
                        }
                    />
                </div>

                <div ref={editorContainerRef} className="min-h-0 flex-1 w-full nodrag nopan overflow-hidden border-t border-input">
                    <CodeMirror
                        // @ts-expect-error ref is { view, state, editor }; package ref type not in our node_modules
                        ref={editorRef}
                        value={plantumlValue}
                        height={`${editorHeight}px`}
                        theme={theme}
                        extensions={extensions}
                        onChange={onChange}
                        basicSetup={{ lineNumbers: true, foldGutter: false }}
                        className="text-sm [&_.cm-editor]:outline-none [&_.cm-editor]:cursor-text [&_.cm-gutters]:border-0"
                    />
                </div>
            </BaseNodeContent>

            <BaseNodeFooter>
                <NodeFooterEdgeIndicators nodeId={id} nodeType="config">
                    {plantumlValue ? `${plantumlValue.length} chars` : 'none'}
                </NodeFooterEdgeIndicators>
            </BaseNodeFooter>
        </BaseNode>
    )
})

ConfigNode.displayName = 'ConfigNode'

export default ConfigNode
