'use client'

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import ReactFlow, {
  Background,
  BackgroundVariant,
  Controls,
  useNodesState,
  useEdgesState,
  useReactFlow,
  ReactFlowProvider,
  addEdge,
  Connection,
  Node,
  Edge,
  NodeTypes,
  EdgeTypes,
  MarkerType,
  NodeMouseHandler,
  EdgeMouseHandler,
} from 'reactflow'
import { nanoid } from 'nanoid'
import 'reactflow/dist/style.css'
import Toolbar from '@/components/editor/Toolbar'
import Toast from '@/components/Toast'
import { createClient } from '@/lib/supabase/client'
import DialogueNode from '@/components/editor/nodes/DialogueNode'
import ChoiceNode from '@/components/editor/nodes/ChoiceNode'
import VariableNode from '@/components/editor/nodes/VariableNode'
import ConditionalEdge from '@/components/editor/edges/ConditionalEdge'
import ContextMenu, { ContextMenuType } from '@/components/editor/ContextMenu'
import ConditionEditor from '@/components/editor/ConditionEditor'
import type { FlowchartData, FlowchartNode, FlowchartEdge, Condition } from '@/types/flowchart'

// LocalStorage key prefix for draft saves
const DRAFT_KEY_PREFIX = 'vnwrite-draft-'

// Debounce delay in ms
const AUTOSAVE_DEBOUNCE_MS = 1000

type ContextMenuState = {
  x: number
  y: number
  type: ContextMenuType
  nodeId?: string
  edgeId?: string
} | null

type ConditionEditorState = {
  edgeId: string
  condition?: Condition
} | null

type FlowchartEditorProps = {
  projectId: string
  projectName: string
  initialData: FlowchartData
}

// 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: 'conditional',
    markerEnd: {
      type: MarkerType.ArrowClosed,
    },
  }))
}

// Convert React Flow Node type back to our FlowchartNode type
function fromReactFlowNodes(nodes: Node[]): FlowchartNode[] {
  return nodes.map((node) => ({
    id: node.id,
    type: node.type as 'dialogue' | 'choice' | 'variable',
    position: node.position,
    data: node.data,
  })) as FlowchartNode[]
}

// Convert React Flow Edge type back to our FlowchartEdge type
function fromReactFlowEdges(edges: Edge[]): FlowchartEdge[] {
  return edges.map((edge) => ({
    id: edge.id,
    source: edge.source,
    sourceHandle: edge.sourceHandle ?? undefined,
    target: edge.target,
    targetHandle: edge.targetHandle ?? undefined,
    data: edge.data,
  }))
}

// Get LocalStorage key for a project
function getDraftKey(projectId: string): string {
  return `${DRAFT_KEY_PREFIX}${projectId}`
}

// Save draft to LocalStorage
function saveDraft(projectId: string, data: FlowchartData): void {
  try {
    localStorage.setItem(getDraftKey(projectId), JSON.stringify(data))
  } catch (error) {
    console.error('Failed to save draft to LocalStorage:', error)
  }
}

// Load draft from LocalStorage
function loadDraft(projectId: string): FlowchartData | null {
  try {
    const draft = localStorage.getItem(getDraftKey(projectId))
    if (!draft) return null
    return JSON.parse(draft) as FlowchartData
  } catch (error) {
    console.error('Failed to load draft from LocalStorage:', error)
    return null
  }
}

// Clear draft from LocalStorage
export function clearDraft(projectId: string): void {
  try {
    localStorage.removeItem(getDraftKey(projectId))
  } catch (error) {
    console.error('Failed to clear draft from LocalStorage:', error)
  }
}

// Compare two FlowchartData objects for equality
function flowchartDataEquals(a: FlowchartData, b: FlowchartData): boolean {
  return JSON.stringify(a) === JSON.stringify(b)
}

// Validate imported flowchart data structure
function isValidFlowchartData(data: unknown): data is FlowchartData {
  if (!data || typeof data !== 'object') return false
  const obj = data as Record<string, unknown>
  if (!Array.isArray(obj.nodes)) return false
  if (!Array.isArray(obj.edges)) return false
  return true
}

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

  // Define custom edge types - memoized to prevent re-renders
  const edgeTypes: EdgeTypes = useMemo(
    () => ({
      conditional: ConditionalEdge,
    }),
    []
  )

  const { getViewport, screenToFlowPosition } = useReactFlow()

  const [contextMenu, setContextMenu] = useState<ContextMenuState>(null)
  const [conditionEditor, setConditionEditor] = useState<ConditionEditorState>(null)
  const [isSaving, setIsSaving] = useState(false)
  const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null)
  const [importConfirmDialog, setImportConfirmDialog] = useState<{
    pendingData: FlowchartData
  } | null>(null)

  // Ref for hidden file input
  const fileInputRef = useRef<HTMLInputElement>(null)

  // Check for saved draft on initial render (lazy initialization)
  const [draftState, setDraftState] = useState<{
    showPrompt: boolean
    savedDraft: FlowchartData | null
  }>(() => {
    // This runs only once on initial render (client-side)
    if (typeof window === 'undefined') {
      return { showPrompt: false, savedDraft: null }
    }
    const draft = loadDraft(projectId)
    if (draft && !flowchartDataEquals(draft, initialData)) {
      return { showPrompt: true, savedDraft: draft }
    }
    return { showPrompt: false, savedDraft: null }
  })

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

  // Track debounce timer
  const saveTimerRef = useRef<NodeJS.Timeout | null>(null)

  // Debounced auto-save to LocalStorage
  useEffect(() => {
    // Don't save while draft prompt is showing
    if (draftState.showPrompt) return

    // Clear existing timer
    if (saveTimerRef.current) {
      clearTimeout(saveTimerRef.current)
    }

    // Set new timer
    saveTimerRef.current = setTimeout(() => {
      const currentData: FlowchartData = {
        nodes: fromReactFlowNodes(nodes),
        edges: fromReactFlowEdges(edges),
      }
      saveDraft(projectId, currentData)
    }, AUTOSAVE_DEBOUNCE_MS)

    // Cleanup on unmount
    return () => {
      if (saveTimerRef.current) {
        clearTimeout(saveTimerRef.current)
      }
    }
  }, [nodes, edges, projectId, draftState.showPrompt])

  // Handle restoring draft
  const handleRestoreDraft = useCallback(() => {
    if (draftState.savedDraft) {
      setNodes(toReactFlowNodes(draftState.savedDraft.nodes))
      setEdges(toReactFlowEdges(draftState.savedDraft.edges))
    }
    setDraftState({ showPrompt: false, savedDraft: null })
  }, [draftState.savedDraft, setNodes, setEdges])

  // Handle discarding draft
  const handleDiscardDraft = useCallback(() => {
    clearDraft(projectId)
    setDraftState({ showPrompt: false, savedDraft: null })
  }, [projectId])

  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: 'conditional',
        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(async () => {
    if (isSaving) return

    setIsSaving(true)

    try {
      const supabase = createClient()

      // Convert React Flow state to FlowchartData
      const flowchartData: FlowchartData = {
        nodes: fromReactFlowNodes(nodes),
        edges: fromReactFlowEdges(edges),
      }

      const { error } = await supabase
        .from('projects')
        .update({
          flowchart_data: flowchartData,
          updated_at: new Date().toISOString(),
        })
        .eq('id', projectId)

      if (error) {
        throw error
      }

      // Clear LocalStorage draft after successful save
      clearDraft(projectId)

      setToast({ message: 'Project saved successfully', type: 'success' })
    } catch (error) {
      console.error('Failed to save project:', error)
      setToast({ message: 'Failed to save project. Please try again.', type: 'error' })
    } finally {
      setIsSaving(false)
    }
  }, [isSaving, nodes, edges, projectId])

  const handleExport = useCallback(() => {
    // Convert React Flow state to FlowchartData
    const flowchartData: FlowchartData = {
      nodes: fromReactFlowNodes(nodes),
      edges: fromReactFlowEdges(edges),
    }

    // Create pretty-printed JSON
    const jsonContent = JSON.stringify(flowchartData, null, 2)

    // Create blob with JSON content
    const blob = new Blob([jsonContent], { type: 'application/json' })

    // Create download URL
    const url = URL.createObjectURL(blob)

    // Create temporary link element and trigger download
    const link = document.createElement('a')
    link.href = url
    link.download = `${projectName}.vnflow`
    document.body.appendChild(link)
    link.click()

    // Cleanup
    document.body.removeChild(link)
    URL.revokeObjectURL(url)
  }, [nodes, edges, projectName])

  // Check if current flowchart has unsaved changes
  const hasUnsavedChanges = useCallback(() => {
    const currentData: FlowchartData = {
      nodes: fromReactFlowNodes(nodes),
      edges: fromReactFlowEdges(edges),
    }
    return !flowchartDataEquals(currentData, initialData)
  }, [nodes, edges, initialData])

  // Load imported data into React Flow
  const loadImportedData = useCallback(
    (data: FlowchartData) => {
      setNodes(toReactFlowNodes(data.nodes))
      setEdges(toReactFlowEdges(data.edges))
      setToast({ message: 'Project imported successfully', type: 'success' })
    },
    [setNodes, setEdges]
  )

  // Handle file selection from file picker
  const handleFileSelect = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const file = event.target.files?.[0]
      if (!file) return

      // Reset file input so same file can be selected again
      event.target.value = ''

      const reader = new FileReader()
      reader.onload = (e) => {
        try {
          const content = e.target?.result as string
          const parsedData = JSON.parse(content)

          // Validate the imported data
          if (!isValidFlowchartData(parsedData)) {
            setToast({
              message: 'Invalid file format. File must contain nodes and edges arrays.',
              type: 'error',
            })
            return
          }

          // Check if current project has unsaved changes
          if (hasUnsavedChanges()) {
            // Show confirmation dialog
            setImportConfirmDialog({ pendingData: parsedData })
          } else {
            // Load data directly
            loadImportedData(parsedData)
          }
        } catch {
          setToast({
            message: 'Failed to parse file. Please ensure it is valid JSON.',
            type: 'error',
          })
        }
      }

      reader.onerror = () => {
        setToast({ message: 'Failed to read file.', type: 'error' })
      }

      reader.readAsText(file)
    },
    [hasUnsavedChanges, loadImportedData]
  )

  // Handle import button click - opens file picker
  const handleImport = useCallback(() => {
    fileInputRef.current?.click()
  }, [])

  // Confirm import (discard unsaved changes)
  const handleConfirmImport = useCallback(() => {
    if (importConfirmDialog?.pendingData) {
      loadImportedData(importConfirmDialog.pendingData)
    }
    setImportConfirmDialog(null)
  }, [importConfirmDialog, loadImportedData])

  // Cancel import
  const handleCancelImport = useCallback(() => {
    setImportConfirmDialog(null)
  }, [])

  // 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))
  }, [])

  // Context menu handlers
  const closeContextMenu = useCallback(() => {
    setContextMenu(null)
  }, [])

  // Handle right-click on canvas (pane)
  const onPaneContextMenu = useCallback(
    (event: React.MouseEvent) => {
      event.preventDefault()
      setContextMenu({
        x: event.clientX,
        y: event.clientY,
        type: 'canvas',
      })
    },
    []
  )

  // Handle right-click on node
  const onNodeContextMenu: NodeMouseHandler = useCallback(
    (event, node) => {
      event.preventDefault()
      setContextMenu({
        x: event.clientX,
        y: event.clientY,
        type: 'node',
        nodeId: node.id,
      })
    },
    []
  )

  // Handle right-click on edge
  const onEdgeContextMenu: EdgeMouseHandler = useCallback(
    (event, edge) => {
      event.preventDefault()
      setContextMenu({
        x: event.clientX,
        y: event.clientY,
        type: 'edge',
        edgeId: edge.id,
      })
    },
    []
  )

  // Add node at specific position (for context menu)
  const handleAddNodeAtPosition = useCallback(
    (type: 'dialogue' | 'choice' | 'variable') => {
      if (!contextMenu) return

      // Convert screen position to flow position
      const position = screenToFlowPosition({
        x: contextMenu.x,
        y: contextMenu.y,
      })

      let newNode: Node

      if (type === 'dialogue') {
        newNode = {
          id: nanoid(),
          type: 'dialogue',
          position,
          data: { speaker: '', text: '' },
        }
      } else if (type === 'choice') {
        newNode = {
          id: nanoid(),
          type: 'choice',
          position,
          data: {
            prompt: '',
            options: [
              { id: nanoid(), label: '' },
              { id: nanoid(), label: '' },
            ],
          },
        }
      } else {
        newNode = {
          id: nanoid(),
          type: 'variable',
          position,
          data: {
            variableName: '',
            operation: 'set',
            value: 0,
          },
        }
      }

      setNodes((nodes) => [...nodes, newNode])
    },
    [contextMenu, screenToFlowPosition, setNodes]
  )

  // Delete selected node from context menu
  const handleDeleteNode = useCallback(() => {
    if (!contextMenu?.nodeId) return
    setNodes((nodes) => nodes.filter((n) => n.id !== contextMenu.nodeId))
  }, [contextMenu, setNodes])

  // Delete selected edge from context menu
  const handleDeleteEdge = useCallback(() => {
    if (!contextMenu?.edgeId) return
    setEdges((edges) => edges.filter((e) => e.id !== contextMenu.edgeId))
  }, [contextMenu, setEdges])

  // Open condition editor for an edge
  const openConditionEditor = useCallback(
    (edgeId: string) => {
      const edge = edges.find((e) => e.id === edgeId)
      if (!edge) return
      setConditionEditor({
        edgeId,
        condition: edge.data?.condition,
      })
    },
    [edges]
  )

  // Add condition to edge (opens ConditionEditor modal)
  const handleAddCondition = useCallback(() => {
    if (!contextMenu?.edgeId) return
    openConditionEditor(contextMenu.edgeId)
  }, [contextMenu, openConditionEditor])

  // Handle double-click on edge to open condition editor
  const onEdgeDoubleClick = useCallback(
    (_event: React.MouseEvent, edge: Edge) => {
      openConditionEditor(edge.id)
    },
    [openConditionEditor]
  )

  // Save condition to edge
  const handleSaveCondition = useCallback(
    (edgeId: string, condition: Condition) => {
      setEdges((eds) =>
        eds.map((edge) =>
          edge.id === edgeId
            ? { ...edge, data: { ...edge.data, condition } }
            : edge
        )
      )
      setConditionEditor(null)
    },
    [setEdges]
  )

  // Remove condition from edge
  const handleRemoveCondition = useCallback(
    (edgeId: string) => {
      setEdges((eds) =>
        eds.map((edge) => {
          if (edge.id !== edgeId) return edge
          // Remove condition from data
          const newData = { ...edge.data }
          delete newData.condition
          return { ...edge, data: newData }
        })
      )
      setConditionEditor(null)
    },
    [setEdges]
  )

  // Close condition editor
  const closeConditionEditor = useCallback(() => {
    setConditionEditor(null)
  }, [])

  return (
    <div className="flex h-full w-full flex-col">
      <Toolbar
        onAddDialogue={handleAddDialogue}
        onAddChoice={handleAddChoice}
        onAddVariable={handleAddVariable}
        onSave={handleSave}
        onExport={handleExport}
        onImport={handleImport}
        isSaving={isSaving}
      />
      <div className="flex-1">
        <ReactFlow
          nodes={nodes}
          edges={edges}
          nodeTypes={nodeTypes}
          edgeTypes={edgeTypes}
          onNodesChange={onNodesChange}
          onEdgesChange={onEdgesChange}
          onEdgesDelete={onEdgesDelete}
          onConnect={onConnect}
          onPaneContextMenu={onPaneContextMenu}
          onNodeContextMenu={onNodeContextMenu}
          onEdgeContextMenu={onEdgeContextMenu}
          onEdgeDoubleClick={onEdgeDoubleClick}
          deleteKeyCode={['Delete', 'Backspace']}
          fitView
        >
          <Background variant={BackgroundVariant.Dots} gap={16} size={1} />
          <Controls position="bottom-right" />
        </ReactFlow>
      </div>

      {contextMenu && (
        <ContextMenu
          x={contextMenu.x}
          y={contextMenu.y}
          type={contextMenu.type}
          onClose={closeContextMenu}
          onAddDialogue={() => handleAddNodeAtPosition('dialogue')}
          onAddChoice={() => handleAddNodeAtPosition('choice')}
          onAddVariable={() => handleAddNodeAtPosition('variable')}
          onDelete={
            contextMenu.type === 'node' ? handleDeleteNode : handleDeleteEdge
          }
          onAddCondition={handleAddCondition}
        />
      )}

      {conditionEditor && (
        <ConditionEditor
          edgeId={conditionEditor.edgeId}
          condition={conditionEditor.condition}
          onSave={handleSaveCondition}
          onRemove={handleRemoveCondition}
          onCancel={closeConditionEditor}
        />
      )}

      {/* Draft restoration prompt */}
      {draftState.showPrompt && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
          <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-zinc-900">
            <h2 className="mb-2 text-lg font-semibold text-zinc-900 dark:text-white">
              Unsaved Draft Found
            </h2>
            <p className="mb-4 text-sm text-zinc-600 dark:text-zinc-400">
              A local draft was found that differs from the saved version. Would
              you like to restore it or discard it?
            </p>
            <div className="flex gap-3">
              <button
                onClick={handleRestoreDraft}
                className="flex-1 rounded-md bg-blue-500 px-4 py-2 text-sm font-medium text-white hover:bg-blue-600"
              >
                Restore Draft
              </button>
              <button
                onClick={handleDiscardDraft}
                className="flex-1 rounded-md border border-zinc-300 bg-white px-4 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700"
              >
                Discard
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Import confirmation dialog */}
      {importConfirmDialog && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
          <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-zinc-900">
            <h2 className="mb-2 text-lg font-semibold text-zinc-900 dark:text-white">
              Unsaved Changes
            </h2>
            <p className="mb-4 text-sm text-zinc-600 dark:text-zinc-400">
              You have unsaved changes. Importing a new file will discard your
              current work. Are you sure you want to continue?
            </p>
            <div className="flex gap-3">
              <button
                onClick={handleConfirmImport}
                className="flex-1 rounded-md bg-red-500 px-4 py-2 text-sm font-medium text-white hover:bg-red-600"
              >
                Discard &amp; Import
              </button>
              <button
                onClick={handleCancelImport}
                className="flex-1 rounded-md border border-zinc-300 bg-white px-4 py-2 text-sm font-medium text-zinc-700 hover:bg-zinc-50 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700"
              >
                Cancel
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Hidden file input for import */}
      <input
        ref={fileInputRef}
        type="file"
        accept=".vnflow,.json"
        onChange={handleFileSelect}
        className="hidden"
      />

      {/* Toast notification */}
      {toast && (
        <Toast
          message={toast.message}
          type={toast.type}
          onClose={() => setToast(null)}
        />
      )}
    </div>
  )
}

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