'use client'

import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import ReactFlow, {
  Background,
  BackgroundVariant,
  Controls,
  useNodesState,
  useEdgesState,
  useReactFlow,
  ReactFlowProvider,
  addEdge,
  Connection,
  Node,
  Edge,
  NodeTypes,
  MarkerType,
} from 'reactflow'
import { nanoid } from 'nanoid'
import 'reactflow/dist/style.css'
import Toolbar from '@/components/editor/Toolbar'
import DialogueNode from '@/components/editor/nodes/DialogueNode'
import ChoiceNode from '@/components/editor/nodes/ChoiceNode'
import VariableNode from '@/components/editor/nodes/VariableNode'
import ProjectSettingsModal from '@/components/editor/ProjectSettingsModal'
import ConditionEditor from '@/components/editor/ConditionEditor'
import ExportValidationModal, { type ValidationIssue } from '@/components/editor/ExportValidationModal'
import { EditorProvider } from '@/components/editor/EditorContext'
import Toast from '@/components/Toast'
import { RealtimeConnection, type ConnectionState } from '@/lib/collaboration/realtime'
import ShareModal from '@/components/editor/ShareModal'
import type { FlowchartData, FlowchartNode, FlowchartEdge, Character, Variable, Condition } from '@/types/flowchart'

type FlowchartEditorProps = {
  projectId: string
  userId: string
  isOwner: boolean
  initialData: FlowchartData
  needsMigration?: boolean
}

// Convert our FlowchartNode type to React Flow Node type
function toReactFlowNodes(nodes: FlowchartNode[]): Node[] {
  return nodes.map((node) => ({
    id: node.id,
    type: node.type,
    position: node.position,
    data: node.data,
  }))
}

// Convert our FlowchartEdge type to React Flow Edge type
function toReactFlowEdges(edges: FlowchartEdge[]): Edge[] {
  return edges.map((edge) => ({
    id: edge.id,
    source: edge.source,
    sourceHandle: edge.sourceHandle,
    target: edge.target,
    targetHandle: edge.targetHandle,
    data: edge.data,
    type: 'smoothstep',
    markerEnd: {
      type: MarkerType.ArrowClosed,
    },
  }))
}

const RANDOM_COLORS = [
  '#EF4444', '#F97316', '#F59E0B', '#10B981',
  '#3B82F6', '#8B5CF6', '#EC4899', '#14B8A6',
  '#6366F1', '#F43F5E', '#84CC16', '#06B6D4',
]

function randomHexColor(): string {
  return RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)]
}

// Compute auto-migration of existing free-text values to character/variable definitions
function computeMigration(initialData: FlowchartData, shouldMigrate: boolean) {
  if (!shouldMigrate) {
    return {
      characters: initialData.characters,
      variables: initialData.variables,
      nodes: initialData.nodes,
      edges: initialData.edges,
      toastMessage: null as string | null,
    }
  }

  // Collect unique speaker names from dialogue nodes
  const speakerNames = new Set<string>()
  initialData.nodes.forEach((node) => {
    if (node.type === 'dialogue' && node.data?.speaker) {
      speakerNames.add(node.data.speaker)
    }
  })

  // Create character definitions from unique speaker names
  const newCharacters: Character[] = []
  const speakerToCharacterId = new Map<string, string>()
  speakerNames.forEach((name) => {
    const id = nanoid()
    newCharacters.push({ id, name, color: randomHexColor() })
    speakerToCharacterId.set(name, id)
  })

  // Collect unique variable names from variable nodes, edge conditions, and choice option conditions
  const variableNames = new Set<string>()
  initialData.nodes.forEach((node) => {
    if (node.type === 'variable' && node.data.variableName) {
      variableNames.add(node.data.variableName)
    }
    if (node.type === 'choice' && node.data.options) {
      node.data.options.forEach((opt) => {
        if (opt.condition?.variableName) {
          variableNames.add(opt.condition.variableName)
        }
      })
    }
  })
  initialData.edges.forEach((edge) => {
    if (edge.data?.condition?.variableName) {
      variableNames.add(edge.data.condition.variableName)
    }
  })

  // Create variable definitions from unique variable names
  const newVariables: Variable[] = []
  const varNameToId = new Map<string, string>()
  variableNames.forEach((name) => {
    const id = nanoid()
    newVariables.push({ id, name, type: 'numeric', initialValue: 0 })
    varNameToId.set(name, id)
  })

  // If nothing to migrate, return original data
  if (newCharacters.length === 0 && newVariables.length === 0) {
    return {
      characters: initialData.characters,
      variables: initialData.variables,
      nodes: initialData.nodes,
      edges: initialData.edges,
      toastMessage: null as string | null,
    }
  }

  // Update nodes with characterId/variableId references
  const migratedNodes = initialData.nodes.map((node) => {
    if (node.type === 'dialogue' && node.data.speaker) {
      const characterId = speakerToCharacterId.get(node.data.speaker)
      if (characterId) {
        return { ...node, data: { ...node.data, characterId } }
      }
    }
    if (node.type === 'variable' && node.data.variableName) {
      const variableId = varNameToId.get(node.data.variableName)
      if (variableId) {
        return { ...node, data: { ...node.data, variableId } }
      }
    }
    if (node.type === 'choice' && node.data.options) {
      const updatedOptions = node.data.options.map((opt) => {
        if (opt.condition?.variableName) {
          const variableId = varNameToId.get(opt.condition.variableName)
          if (variableId) {
            return { ...opt, condition: { ...opt.condition, variableId } }
          }
        }
        return opt
      })
      return { ...node, data: { ...node.data, options: updatedOptions } }
    }
    return node
  }) as typeof initialData.nodes

  // Update edges with variableId references
  const migratedEdges = initialData.edges.map((edge) => {
    if (edge.data?.condition?.variableName) {
      const variableId = varNameToId.get(edge.data.condition.variableName)
      if (variableId) {
        return {
          ...edge,
          data: { ...edge.data, condition: { ...edge.data.condition, variableId } },
        }
      }
    }
    return edge
  })

  // Build toast message
  const parts: string[] = []
  if (newCharacters.length > 0) {
    parts.push(`${newCharacters.length} character${newCharacters.length > 1 ? 's' : ''}`)
  }
  if (newVariables.length > 0) {
    parts.push(`${newVariables.length} variable${newVariables.length > 1 ? 's' : ''}`)
  }

  return {
    characters: newCharacters,
    variables: newVariables,
    nodes: migratedNodes,
    edges: migratedEdges,
    toastMessage: `Auto-imported ${parts.join(' and ')} from existing data`,
  }
}

// Inner component that uses useReactFlow hook
function FlowchartEditorInner({ projectId, userId, isOwner, initialData, needsMigration }: FlowchartEditorProps) {
  // Define custom node types - memoized to prevent re-renders
  const nodeTypes: NodeTypes = useMemo(
    () => ({
      dialogue: DialogueNode,
      choice: ChoiceNode,
      variable: VariableNode,
    }),
    []
  )

  const { getViewport } = useReactFlow()

  // Compute migrated data once on first render using a lazy state initializer
  const [migratedData] = useState(() => computeMigration(initialData, !!needsMigration))

  const [nodes, setNodes, onNodesChange] = useNodesState(
    toReactFlowNodes(migratedData.nodes)
  )
  const [edges, setEdges, onEdgesChange] = useEdgesState(
    toReactFlowEdges(migratedData.edges)
  )

  const [characters, setCharacters] = useState<Character[]>(migratedData.characters)
  const [variables, setVariables] = useState<Variable[]>(migratedData.variables)
  const [showSettings, setShowSettings] = useState(false)
  const [showShare, setShowShare] = useState(false)
  const [selectedEdgeId, setSelectedEdgeId] = useState<string | null>(null)
  const [toastMessage, setToastMessage] = useState<string | null>(migratedData.toastMessage)
  const [validationIssues, setValidationIssues] = useState<ValidationIssue[] | null>(null)
  const [warningNodeIds, setWarningNodeIds] = useState<Set<string>>(new Set())
  const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected')
  const realtimeRef = useRef<RealtimeConnection | null>(null)

  // Connect to Supabase Realtime channel on mount, disconnect on unmount
  useEffect(() => {
    const connection = new RealtimeConnection(projectId, userId, {
      onConnectionStateChange: setConnectionState,
    })
    realtimeRef.current = connection
    connection.connect()

    return () => {
      connection.disconnect()
      realtimeRef.current = null
    }
  }, [projectId, userId])

  const handleAddCharacter = useCallback(
    (name: string, color: string): string => {
      const id = nanoid()
      const newCharacter: Character = { id, name, color }
      setCharacters((prev) => [...prev, newCharacter])
      return id
    },
    []
  )

  const handleAddVariableDefinition = useCallback(
    (name: string, type: 'numeric' | 'string' | 'boolean', initialValue: number | string | boolean): string => {
      const id = nanoid()
      const newVariable: Variable = { id, name, type, initialValue }
      setVariables((prev) => [...prev, newVariable])
      return id
    },
    []
  )

  const editorContextValue = useMemo(
    () => ({ characters, onAddCharacter: handleAddCharacter, variables, onAddVariable: handleAddVariableDefinition }),
    [characters, handleAddCharacter, variables, handleAddVariableDefinition]
  )

  const getCharacterUsageCount = useCallback(
    (characterId: string) => {
      return nodes.filter((n) => n.type === 'dialogue' && n.data?.characterId === characterId).length
    },
    [nodes]
  )

  const getVariableUsageCount = useCallback(
    (variableId: string) => {
      const nodeCount = nodes.filter(
        (n) => n.type === 'variable' && n.data?.variableId === variableId
      ).length
      const edgeCount = edges.filter(
        (e) => e.data?.condition?.variableId === variableId
      ).length
      const choiceOptionCount = nodes.filter(
        (n) => n.type === 'choice'
      ).reduce((count, n) => {
        const options = n.data?.options || []
        return count + options.filter(
          (opt: { condition?: { variableId?: string } }) => opt.condition?.variableId === variableId
        ).length
      }, 0)
      return nodeCount + edgeCount + choiceOptionCount
    },
    [nodes, edges]
  )

  const onConnect = useCallback(
    (params: Connection) => {
      if (!params.source || !params.target) return
      const newEdge: Edge = {
        id: nanoid(),
        source: params.source,
        target: params.target,
        sourceHandle: params.sourceHandle,
        targetHandle: params.targetHandle,
        type: 'smoothstep',
        markerEnd: {
          type: MarkerType.ArrowClosed,
        },
      }
      setEdges((eds) => addEdge(newEdge, eds))
    },
    [setEdges]
  )

  // Get center position of current viewport for placing new nodes
  const getViewportCenter = useCallback(() => {
    const viewport = getViewport()
    // Calculate center based on viewport dimensions (assume ~800x600 visible area)
    // Adjust based on zoom level
    const centerX = (-viewport.x + 400) / viewport.zoom
    const centerY = (-viewport.y + 300) / viewport.zoom
    return { x: centerX, y: centerY }
  }, [getViewport])

  // Add dialogue node at viewport center
  const handleAddDialogue = useCallback(() => {
    const position = getViewportCenter()
    const newNode: Node = {
      id: nanoid(),
      type: 'dialogue',
      position,
      data: { speaker: '', text: '' },
    }
    setNodes((nodes) => [...nodes, newNode])
  }, [getViewportCenter, setNodes])

  const handleAddChoice = useCallback(() => {
    const position = getViewportCenter()
    const newNode: Node = {
      id: nanoid(),
      type: 'choice',
      position,
      data: {
        prompt: '',
        options: [
          { id: nanoid(), label: '' },
          { id: nanoid(), label: '' },
        ],
      },
    }
    setNodes((nodes) => [...nodes, newNode])
  }, [getViewportCenter, setNodes])

  const handleAddVariable = useCallback(() => {
    const position = getViewportCenter()
    const newNode: Node = {
      id: nanoid(),
      type: 'variable',
      position,
      data: {
        variableName: '',
        operation: 'set',
        value: 0,
      },
    }
    setNodes((nodes) => [...nodes, newNode])
  }, [getViewportCenter, setNodes])

  const handleSave = useCallback(() => {
    // TODO: Implement in US-034
  }, [])

  const performExport = useCallback(() => {
    // TODO: Actual export logic in US-035
    setValidationIssues(null)
    setWarningNodeIds(new Set())
  }, [])

  const handleExport = useCallback(() => {
    const issues: ValidationIssue[] = []
    const characterIds = new Set(characters.map((c) => c.id))
    const variableIds = new Set(variables.map((v) => v.id))

    // Scan nodes for undefined references
    nodes.forEach((node) => {
      if (node.type === 'dialogue' && node.data?.characterId) {
        if (!characterIds.has(node.data.characterId)) {
          issues.push({
            nodeId: node.id,
            nodeType: 'dialogue',
            contentSnippet: node.data.text
              ? `"${node.data.text.slice(0, 40)}${node.data.text.length > 40 ? '...' : ''}"`
              : 'Empty dialogue',
            undefinedReference: node.data.characterId,
            referenceType: 'character',
          })
        }
      }
      if (node.type === 'variable' && node.data?.variableId) {
        if (!variableIds.has(node.data.variableId)) {
          issues.push({
            nodeId: node.id,
            nodeType: 'variable',
            contentSnippet: node.data.variableName
              ? `Variable: ${node.data.variableName}`
              : 'Variable node',
            undefinedReference: node.data.variableId,
            referenceType: 'variable',
          })
        }
      }
      if (node.type === 'choice' && node.data?.options) {
        node.data.options.forEach((opt: { id: string; label: string; condition?: { variableId?: string; variableName?: string } }) => {
          if (opt.condition?.variableId && !variableIds.has(opt.condition.variableId)) {
            issues.push({
              nodeId: node.id,
              nodeType: 'choice',
              contentSnippet: opt.label
                ? `Option: "${opt.label.slice(0, 30)}${opt.label.length > 30 ? '...' : ''}"`
                : 'Choice option',
              undefinedReference: opt.condition.variableId,
              referenceType: 'variable',
            })
          }
        })
      }
    })

    // Scan edges for undefined variable references in conditions
    edges.forEach((edge) => {
      if (edge.data?.condition?.variableId && !variableIds.has(edge.data.condition.variableId)) {
        issues.push({
          nodeId: edge.id,
          nodeType: 'edge',
          contentSnippet: edge.data.condition.variableName
            ? `Condition on: ${edge.data.condition.variableName}`
            : 'Edge condition',
          undefinedReference: edge.data.condition.variableId,
          referenceType: 'variable',
        })
      }
    })

    if (issues.length === 0) {
      performExport()
    } else {
      setValidationIssues(issues)
      setWarningNodeIds(new Set(issues.map((i) => i.nodeId)))
    }
  }, [nodes, edges, characters, variables, performExport])

  const handleExportAnyway = useCallback(() => {
    performExport()
  }, [performExport])

  const handleExportCancel = useCallback(() => {
    setValidationIssues(null)
    setWarningNodeIds(new Set())
  }, [])

  const handleImport = useCallback(() => {
    // TODO: Implement in US-036
  }, [])

  // Handle edge deletion via keyboard (Delete/Backspace)
  const onEdgesDelete = useCallback((deletedEdges: Edge[]) => {
    // Edges are already removed from state by onEdgesChange
    // This callback can be used for additional logic like logging or dirty state
    console.log('Deleted edges:', deletedEdges.map((e) => e.id))
  }, [])

  // Handle edge click to open condition editor
  const onEdgeClick = useCallback((_event: React.MouseEvent, edge: Edge) => {
    setSelectedEdgeId(edge.id)
  }, [])

  // Handle condition change from ConditionEditor
  const handleConditionChange = useCallback(
    (edgeId: string, condition: Condition | undefined) => {
      setEdges((eds) =>
        eds.map((edge) =>
          edge.id === edgeId
            ? { ...edge, data: condition ? { condition } : undefined }
            : edge
        )
      )
    },
    [setEdges]
  )

  // Apply warning styles to nodes with undefined references
  const styledNodes = useMemo(
    () =>
      warningNodeIds.size === 0
        ? nodes
        : nodes.map((node) =>
            warningNodeIds.has(node.id)
              ? { ...node, className: 'export-warning-node' }
              : node
          ),
    [nodes, warningNodeIds]
  )

  // Get the selected edge's condition data
  const selectedEdge = useMemo(
    () => (selectedEdgeId ? edges.find((e) => e.id === selectedEdgeId) : null),
    [selectedEdgeId, edges]
  )

  return (
    <EditorProvider value={editorContextValue}>
      <div className="flex h-full w-full flex-col">
        <Toolbar
          onAddDialogue={handleAddDialogue}
          onAddChoice={handleAddChoice}
          onAddVariable={handleAddVariable}
          onSave={handleSave}
          onExport={handleExport}
          onImport={handleImport}
          onProjectSettings={() => setShowSettings(true)}
          onShare={() => setShowShare(true)}
          connectionState={connectionState}
        />
        <div className="flex-1">
          <ReactFlow
            nodes={styledNodes}
            edges={edges}
            nodeTypes={nodeTypes}
            onNodesChange={onNodesChange}
            onEdgesChange={onEdgesChange}
            onEdgesDelete={onEdgesDelete}
            onEdgeClick={onEdgeClick}
            onConnect={onConnect}
            deleteKeyCode={['Delete', 'Backspace']}
            fitView
          >
            <Background variant={BackgroundVariant.Dots} gap={16} size={1} />
            <Controls position="bottom-right" />
          </ReactFlow>
        </div>
        {showSettings && (
          <ProjectSettingsModal
            projectId={projectId}
            characters={characters}
            variables={variables}
            onCharactersChange={setCharacters}
            onVariablesChange={setVariables}
            onClose={() => setShowSettings(false)}
            getCharacterUsageCount={getCharacterUsageCount}
            getVariableUsageCount={getVariableUsageCount}
          />
        )}
        {showShare && (
          <ShareModal
            projectId={projectId}
            isOwner={isOwner}
            onClose={() => setShowShare(false)}
          />
        )}
        {selectedEdge && (
          <ConditionEditor
            edgeId={selectedEdge.id}
            condition={selectedEdge.data?.condition}
            onChange={handleConditionChange}
            onClose={() => setSelectedEdgeId(null)}
          />
        )}
        {validationIssues && (
          <ExportValidationModal
            issues={validationIssues}
            onExportAnyway={handleExportAnyway}
            onCancel={handleExportCancel}
          />
        )}
        {toastMessage && (
          <Toast
            message={toastMessage}
            type="success"
            onClose={() => setToastMessage(null)}
          />
        )}
      </div>
    </EditorProvider>
  )
}

// Outer wrapper component with ReactFlowProvider
export default function FlowchartEditor(props: FlowchartEditorProps) {
  return (
    <ReactFlowProvider>
      <FlowchartEditorInner {...props} />
    </ReactFlowProvider>
  )
}
