import { memo, useContext, useEffect, useMemo, useRef, useState } from 'react'
import FlowContext from '../../lib/flowContext'
import { useTheme } from '../../lib/themeContext'
import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia } from '../ui/empty'
import {
    BaseNode,
    BaseNodeContent,
    BaseNodeFooter,
    BaseNodeHeader,
    BaseNodeHeaderTitle,
} from './BaseNode'
import { Sparkles } from 'lucide-react'
import { InputHandle, OutputHandle } from './NodeHandles'

// Use relative URL so Vite dev proxy (and optional prod proxy) avoids CORS
const KROKI_PLANTUML_SVG = '/api/kroki/plantuml/svg'

type Props = {
    id: string
    data?: any
    style?: React.CSSProperties
}

const DEFAULT_CONFIG_NODE_STYLE = { width: 320, height: 320 }

const DARK_SKINPARAMS = `
skinparam backgroundColor #1e1e1e
skinparam defaultFontColor #e0e0e0
skinparam shadowing false
skinparam ArrowColor #b0b0b0
skinparam ArrowFontColor #e0e0e0
skinparam ActivityBackgroundColor #2d2d2d
skinparam ActivityBorderColor #6b6b6b
skinparam ActivityDiamondBackgroundColor #2d2d2d
skinparam ActivityDiamondBorderColor #6b6b6b
skinparam SequenceParticipantBackgroundColor #2d2d2d
skinparam SequenceParticipantBorderColor #6b6b6b
skinparam SequenceLifeLineBorderColor #6b6b6b
skinparam SequenceBoxBackgroundColor #252525
skinparam SequenceBoxBorderColor #6b6b6b
skinparam SequenceActorBackgroundColor #2d2d2d
skinparam SequenceActorBorderColor #6b6b6b
skinparam ClassBackgroundColor #2d2d2d
skinparam ClassBorderColor #6b6b6b
skinparam ComponentBackgroundColor #2d2d2d
skinparam ComponentBorderColor #6b6b6b
skinparam StateBackgroundColor #2d2d2d
skinparam StateBorderColor #6b6b6b
skinparam partitionBorderColor #6b6b6b
skinparam sequence {
  ArrowColor #b0b0b0
  LifeLineBorderColor #6b6b6b
  LifeLineBackgroundColor #2d2d2d
  ParticipantBorderColor #6b6b6b
  ActorBorderColor #6b6b6b
  BoxBorderColor #6b6b6b
}
skinparam activity {
  ArrowColor #b0b0b0
  BorderColor #6b6b6b
  DiamondBorderColor #6b6b6b
}
skinparam class {
  ArrowColor #b0b0b0
  BorderColor #6b6b6b
}
`

export const RenderingNode = memo(function RenderingNode({ id, width, height }: Props) {
    const [svgContent, setSvgContent] = useState<string | null>(null)
    const [error, setError] = useState<null | { kind: string; message: string }>(null)
    const [loading, setLoading] = useState(false)
    const runIdRef = useRef(0)
    const { theme } = useTheme()

    const ctx = useContext(FlowContext)
    const nodes = ctx?.nodes ?? []
    const edges = ctx?.edges ?? []
    const setNodes = ctx?.setNodes
    const setEdges = ctx?.setEdges

    const incomingEdges = useMemo(() => edges.filter((e: any) => e.target === id), [edges, id])
    const incomingIds = useMemo(() => incomingEdges.map((e: any) => e.source).sort(), [incomingEdges])
    const srcId = incomingIds.length > 0 ? incomingIds[0] : null
    const srcNode = nodes.find((n: any) => n.id === srcId)
    const plantumlText = srcNode && srcNode.type === 'config' ? srcNode?.data?.plantuml ?? '' : ''
    const srcData = srcNode?.data ?? {}

    const configSignature = useMemo(
        () =>
            nodes
                .filter((n: any) => n.type === 'config')
                .map((n: any) => `${n.id}:${n.data?.title ?? ''}:${n.data?.plantuml ?? ''}`)
                .join('|'),
        [nodes]
    )

    const edgesSignature = useMemo(
        () =>
            edges
                .map((e: any) => `${e.source}->${e.target}`)
                .sort()
                .join('|'),
        [edges]
    )

    const variablesSignature = useMemo(
        () =>
            nodes
                .filter((n: any) => n.type === 'variable')
                .map((n: any) => `${n.id}:${n.data?.value}`)
                .join('|'),
        [nodes]
    )

    const functionsSignature = useMemo(
        () =>
            nodes
                .filter((n: any) => n.type === 'function')
                .map((n: any) => `${n.id}:${n.data?.body ?? ''}`)
                .join('|'),
        [nodes]
    )

    useEffect(() => {
        if (!plantumlText) {
            if (incomingIds.length === 0) {
                setSvgContent(null)
                setError(null)
                setLoading(false)
                return
            }
            setSvgContent(null)
            setError({ kind: 'no-plantuml', message: 'No PlantUML found on connected configuration node' })
            setLoading(false)
            return
        }

        runIdRef.current += 1
        const thisRunId = runIdRef.current
        let cancelled = false

        const run = async () => {
            setLoading(true)
            setError(null)
            try {
                const configIdsUsed = new Set<string>()

                const resolveIncludes = (text: string, visited = new Set<string>()): string => {
                    const includeRegex = /^(\s*)!include\s+([^\s]+)\s*$/gm
                    return text.replace(includeRegex, (match: string, indent: string, ref: string) => {
                        const refName = ref.endsWith('.puml') ? ref.slice(0, -5) : ref
                        const refNode = nodes.find((n: any) => n.id === refName || n.data?.title === refName)
                        if (!refNode) throw new Error(`Included node not found: ${ref}`)
                        if (visited.has(refNode.id)) throw new Error(`Circular include detected: ${ref}`)

                        const isReachable = (startId: string, targetId: string) => {
                            const q: string[] = [startId]
                            const seen = new Set<string>([startId])
                            while (q.length) {
                                const cur = q.shift()!
                                if (cur === targetId) return true
                                for (const e of edges) {
                                    if (e.source === cur && !seen.has(e.target)) {
                                        seen.add(e.target)
                                        q.push(e.target)
                                    }
                                }
                            }
                            return false
                        }

                        if (!isReachable(refNode.id, id)) throw new Error(`Included node not connected to renderer: ${ref}`)

                        visited.add(refNode.id)
                        if (refNode.type === 'config') configIdsUsed.add(refNode.id)
                        const includedRaw = String(refNode.data?.plantuml ?? '')
                        const resolved = resolveIncludes(includedRaw, visited)
                        visited.delete(refNode.id)

                        const indented = resolved
                            .split('\n')
                            .map((line: string) => (line === '' ? '' : indent + line))
                            .join('\n')
                        return indented
                    })
                }

                if (srcId && srcNode?.type === 'config') configIdsUsed.add(srcId)

                const resolvedPlantuml = resolveIncludes(plantumlText)

                const varMap: Record<string, string> = {}
                for (const e of edges) {
                    if (configIdsUsed.has(e.target)) {
                        const src = nodes.find((n: any) => n.id === e.source)
                        if (src?.type === 'variable') {
                            const v = src.data?.value
                            varMap[src.id] = v === undefined || v === null ? '' : String(v)
                        }
                    }
                }

                const resolveFunctionCalls = (text: string): string => {
                    const fnCallRegex = /\$\{([\w-]+)\s*\(([^)]*)\)\}/g
                    return text.replace(fnCallRegex, (match, funcId: string, argsStr: string) => {
                        const fnNode = nodes.find((n: any) => n.id === funcId && n.type === 'function')
                        if (!fnNode) return match
                        const body = fnNode.data?.body ?? 'return args[0];'
                        const argIds = argsStr.split(',').map((s: string) => s.trim()).filter(Boolean)
                        const argValues = argIds.map((argId: string) => varMap[argId] ?? '')
                        try {
                            const fn = new Function('args', body)
                            const result = fn(argValues)
                            if (result === undefined || result === null) return ''
                            if (typeof result === 'string' || typeof result === 'number' || typeof result === 'boolean') return String(result)
                            return String(result)
                        } catch {
                            return match
                        }
                    })
                }

                const resolveVariables = (text: string): string =>
                    text.replace(/\$\{([^}]+)\}/g, (_, varId: string) => varMap[varId.trim()] ?? '')

                const afterFunctions = resolveFunctionCalls(resolvedPlantuml)
                let resolvedWithVars = resolveVariables(afterFunctions)
                if (theme === 'dark') {
                    resolvedWithVars = resolvedWithVars.replace(
                        /^(\s*@startuml\s*\n)/i,
                        `$1${DARK_SKINPARAMS}\n`
                    )
                }

                const res = await fetch(KROKI_PLANTUML_SVG, {
                    method: 'POST',
                    headers: { 'Content-Type': 'text/plain' },
                    body: resolvedWithVars,
                })

                if (cancelled || thisRunId !== runIdRef.current) return

                if (!res.ok) {
                    const errText = await res.text()
                    throw new Error(res.status === 400 ? errText || 'Invalid PlantUML' : `Kroki error: ${res.status}`)
                }

                const svg = await res.text()
                if (thisRunId !== runIdRef.current) return
                setSvgContent(svg)
                setError(null)
            } catch (err: any) {
                if (cancelled || thisRunId !== runIdRef.current) return
                const msg = err?.message ?? 'PlantUML render error'
                setSvgContent(null)
                setError({ kind: 'render', message: msg })
            } finally {
                if (!cancelled && thisRunId === runIdRef.current) setLoading(false)
            }
        }

        run()
        return () => { cancelled = true }
        // Only re-run when inputs that affect the resolved diagram change (signatures + source + theme).
        // Do not depend on nodes/edges refs to avoid flicker from unnecessary re-renders.
    }, [id, theme, plantumlText, configSignature, edgesSignature, variablesSignature, functionsSignature])

    return (
        <BaseNode className="min-w-96 min-h-[320px]" style={width != null && height != null && width > 0 && height > 0 ? { width, height } : undefined} resizable nodeId={id} handles={<><InputHandle id="ain" /><OutputHandle id="out" /></>}>
            <BaseNodeHeader className="border-b">
                <Sparkles className="size-4" />
                <BaseNodeHeaderTitle>{id}</BaseNodeHeaderTitle>
            </BaseNodeHeader>

            <BaseNodeContent>
                <div className="min-h-0 flex-1 flex flex-col">
                    {incomingIds.length === 0 ? (
                        <Empty className="min-h-0 flex-1">
                            <EmptyHeader>
                                <EmptyMedia variant="icon">
                                    <Sparkles className="size-6" />
                                </EmptyMedia>
                                <EmptyTitle>No configuration connected</EmptyTitle>
                                <EmptyDescription>Connect a Configuration node or create one. The renderer will display the PlantUML diagram.</EmptyDescription>
                            </EmptyHeader>
                            <EmptyContent>
                                <button
                                    className="inline-flex items-center rounded bg-primary px-3 py-1 text-xs text-primary-foreground"
                                    onClick={() => {
                                        if (!setNodes || !setEdges) return
                                        const genId = () => `node_${Math.random().toString(36).slice(2, 9)}`
                                        const nid = genId()
                                        const thisNode = nodes.find((n: any) => n.id === id)
                                        const pos = thisNode?.position ?? { x: 0, y: 0 }
                                        const newPos = { x: pos.x - 220, y: pos.y }
                                        const newNode = { id: nid, type: 'config', position: newPos, data: { plantuml: '@startuml\n\n@enduml\n', title: `config-${nid}` }, style: DEFAULT_CONFIG_NODE_STYLE }
                                        setNodes((nds: any[]) => nds.concat(newNode))
                                        const edgeId = `e-${nid}-${id}`
                                        setEdges((eds: any[]) => eds.concat({ id: edgeId, source: nid, target: id }))
                                    }}
                                >
                                    Create Config
                                </button>
                            </EmptyContent>
                        </Empty>
                    ) : error ? (
                        srcData?.renderError ? (
                            srcData.renderError(error)
                        ) : srcData?.errorHtml ? (
                            <div className="p-3 text-xs text-red-700 dark:text-red-400" dangerouslySetInnerHTML={{ __html: String(srcData.errorHtml) }} />
                        ) : (
                            <div className="p-3 text-xs text-red-700 dark:text-red-400">{error.message}</div>
                        )
                    ) : loading ? (
                        <div className="flex min-h-0 flex-1 items-center justify-center text-xs text-muted-foreground">Rendering…</div>
                    ) : svgContent ? (
                        <div
                            className="min-h-0 flex-1 w-full overflow-auto rounded border border-input bg-white dark:bg-gray-900 [&_svg]:max-w-full [&_svg]:h-auto"
                            dangerouslySetInnerHTML={{ __html: svgContent }}
                        />
                    ) : null}
                </div>
            </BaseNodeContent>

            <BaseNodeFooter>
                <div className="w-full text-xs text-muted-foreground">
                    {svgContent ? 'PlantUML diagram' : error ? 'Error' : '—'}
                </div>
            </BaseNodeFooter>
        </BaseNode>
    )
})

RenderingNode.displayName = 'RenderingNode'

export default RenderingNode
