/**
 * Context for shared recollection actions: two slots (flux and logos) and layout-level import/export.
 * Consumers use pathname to pick the active slot for title (save status, Save) and Edit/View menus (undo, redo, etc.).
 */

import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
import { useLocation, useParams } from 'react-router-dom'
import type { SaveStatus } from '@/app/canvas/useCanvasGraph'
import {
  getGraph,
  setGraph,
  getLogosContent,
  setLogosContent,
  upsertRenderOutputEntry,
  getFluxSceneTree,
  setFluxSceneTree,
  getSceneGraph,
  setSceneGraph,
  RECOLLECTION_FILE_EXT,
  RECOLLECTION_VERSION,
  type StoredGraphState,
  type StoredLogosContent,
  type RenderOutputCacheEntry,
  type FluxSceneMeta,
} from '../state/recollectionStore'

export type { RenderOutputCacheEntry }
import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
import { backfillEdgeTargetTypes } from '@/app/canvas/canvasGraphUtils'
import { toast } from 'sonner'

export type { SaveStatus }

/** Parsed recollection file format (import/export). */
export type RecollectionFilePayload = {
  version?: number
  graph?: { nodes: unknown[]; edges: unknown[] }
  logos?: StoredLogosContent
  /** Flux scenes (Phase 2+). Each entry has scene metadata + graph. */
  scenes?: Array<{ id: string; title: string; position: number; graph: { nodes: unknown[]; edges: unknown[] } }>
}

export type FluxSlot = {
  saveStatus: SaveStatus
  onSave: () => void
  canSave: boolean
  undo: () => void
  redo: () => void
  canUndo: boolean
  canRedo: boolean
  onRefreshFromStore: (graph: StoredGraphState) => void
  onDuplicate?: () => void
  onCopy?: () => void
  onPaste?: () => void
  canDuplicate?: boolean
  canCopy?: boolean
  onFitView?: () => void
  onRun?: () => void
}

export type LogosSlot = {
  saveStatus: SaveStatus
  onSave: () => void
  canSave: boolean
  undo: () => void
  redo: () => void
  canUndo: boolean
  canRedo: boolean
  onRefreshFromStore: () => void
}

export type RecollectionActionsContextValue = {
  flux: FluxSlot | null
  logos: LogosSlot | null
  setFluxSlot: (slot: FluxSlot | null) => void
  setLogosSlot: (slot: LogosSlot | null) => void
  /** Whether the Flux view is active (pathname ends with /flux). */
  isFluxActive: boolean
  /** Whether the Logos view is active. */
  isLogosActive: boolean
  /** Active slot (flux or logos by pathname). */
  activeSlot: FluxSlot | LogosSlot | null
  onImport: () => void
  onExport: () => void
  /** Upsert a rendering node's output into the cache so Logos "Insert from Flux" block can show it. */
  upsertRenderOutputToLogos: (entry: RenderOutputCacheEntry) => void
}

const RecollectionActionsContext = createContext<RecollectionActionsContextValue | null>(null)

function validateAndWritePayload(
  recollectionId: string,
  payload: RecollectionFilePayload,
  backfillEdges: (nodes: AppNode[], edges: AppEdge[]) => AppEdge[]
): { graph?: StoredGraphState; logos?: StoredLogosContent } {
  const result: { graph?: StoredGraphState; logos?: StoredLogosContent } = {}
  if (payload.graph && Array.isArray(payload.graph.nodes) && Array.isArray(payload.graph.edges)) {
    const nodes = payload.graph.nodes as AppNode[]
    const edges = backfillEdges(nodes, payload.graph.edges as AppEdge[])
    const graphState: StoredGraphState = {
      version: payload.version ?? RECOLLECTION_VERSION,
      nodes,
      edges,
    }
    setGraph(recollectionId, graphState)
    result.graph = graphState
  }
  if (payload.logos != null && Array.isArray(payload.logos)) {
    if (payload.logos.every((item) => item != null && typeof item === 'object')) {
      setLogosContent(recollectionId, payload.logos as StoredLogosContent)
      result.logos = payload.logos as StoredLogosContent
    }
  }
  // Import scenes
  if (payload.scenes && Array.isArray(payload.scenes)) {
    const sceneMetas: FluxSceneMeta[] = []
    for (const s of payload.scenes) {
      if (!s || typeof s.id !== 'string' || typeof s.title !== 'string') continue
      sceneMetas.push({ id: s.id, title: s.title, position: s.position ?? 0 })
      if (s.graph && Array.isArray(s.graph.nodes) && Array.isArray(s.graph.edges)) {
        const nodes = s.graph.nodes as AppNode[]
        const edges = backfillEdges(nodes, s.graph.edges as AppEdge[])
        setSceneGraph(recollectionId, s.id, {
          version: payload.version ?? RECOLLECTION_VERSION,
          nodes,
          edges,
        })
      }
    }
    if (sceneMetas.length > 0) {
      setFluxSceneTree(recollectionId, sceneMetas)
    }
  }
  return result
}

export function RecollectionActionsProvider({ children }: { children: React.ReactNode }) {
  const { recollectionId } = useParams<{ recollectionId: string }>()
  const { pathname } = useLocation()
  const [flux, setFluxSlotState] = useState<FluxSlot | null>(null)
  const [logos, setLogosSlotState] = useState<LogosSlot | null>(null)
  const importInputRef = useRef<HTMLInputElement>(null)
  const fluxRef = useRef<FluxSlot | null>(null)
  const logosRef = useRef<LogosSlot | null>(null)
  const pathnameRef = useRef(pathname)
  fluxRef.current = flux
  logosRef.current = logos
  pathnameRef.current = pathname

  const isFluxActive = pathname.endsWith('/flux')
  const isLogosActive = pathname.includes('/logos') || /\/recollections\/[^/]+\/?$/.test(pathname)
  const activeSlot = isFluxActive ? flux : isLogosActive ? logos : null

  const setFluxSlot = useCallback((slot: FluxSlot | null) => {
    setFluxSlotState(() => slot)
  }, [])
  const setLogosSlot = useCallback((slot: LogosSlot | null) => {
    setLogosSlotState(() => slot)
  }, [])

  useEffect(() => {
    const onKeyDown = (ev: KeyboardEvent) => {
      const mod = ev.ctrlKey || ev.metaKey
      if (mod && ev.key.toLowerCase() === 's') {
        const slot = activeSlot
        if (slot?.onSave && slot?.canSave) {
          ev.preventDefault()
          ev.stopPropagation()
          slot.onSave()
        }
      }
    }
    window.addEventListener('keydown', onKeyDown, true)
    return () => window.removeEventListener('keydown', onKeyDown, true)
  }, [activeSlot])

  const onExport = useCallback(() => {
    if (!recollectionId) return
    const graph = getGraph(recollectionId)
    const logosContent = getLogosContent(recollectionId)
    // Export scenes
    const sceneTree = getFluxSceneTree(recollectionId)
    const scenes = sceneTree.map((s) => {
      const sg = getSceneGraph(recollectionId, s.id)
      return { ...s, graph: sg ? { nodes: sg.nodes, edges: sg.edges } : { nodes: [], edges: [] } }
    })
    const payload: RecollectionFilePayload = {
      version: RECOLLECTION_VERSION,
      ...(graph && { graph: { nodes: graph.nodes, edges: graph.edges } }),
      ...(logosContent && { logos: logosContent }),
      ...(scenes.length > 0 && { scenes }),
    }
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' })
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `recollection${RECOLLECTION_FILE_EXT}`
    a.click()
    URL.revokeObjectURL(url)
    toast.success('Recollection exported')
  }, [recollectionId])

  const onImport = useCallback(() => {
    importInputRef.current?.click()
  }, [])

  const upsertRenderOutputToLogos = useCallback(
    (entry: RenderOutputCacheEntry) => {
      if (recollectionId) upsertRenderOutputEntry(recollectionId, entry)
    },
    [recollectionId]
  )

  const onImportFileChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files?.[0]
      e.target.value = ''
      if (!file || !recollectionId) return
      const reader = new FileReader()
      reader.onload = () => {
        try {
          const text = reader.result as string
          const payload = JSON.parse(text) as RecollectionFilePayload
          if (!payload || typeof payload !== 'object') {
            toast.error('Invalid file: not valid JSON')
            return
          }
          const written = validateAndWritePayload(recollectionId, payload, (nodes, edges) =>
            backfillEdgeTargetTypes(nodes, edges)
          )
          const currentPath = pathnameRef.current
          const fluxActive = currentPath.endsWith('/flux')
          const logosActive = currentPath.includes('/logos') || /\/recollections\/[^/]+\/?$/.test(currentPath)
          if (written.graph && fluxActive && fluxRef.current?.onRefreshFromStore) {
            fluxRef.current.onRefreshFromStore(written.graph)
          }
          if ((written.graph != null || written.logos != null) && logosActive && logosRef.current?.onRefreshFromStore) {
            logosRef.current.onRefreshFromStore()
          }
          if (payload.version != null && payload.version > RECOLLECTION_VERSION) {
            toast.error('Recollection was created with a newer app version')
          } else {
            toast.success('Recollection loaded')
          }
        } catch {
          toast.error('Invalid file: not valid JSON')
        }
      }
      reader.readAsText(file)
    },
    [recollectionId]
  )

  const value: RecollectionActionsContextValue = React.useMemo(
    () => ({
      flux,
      logos,
      setFluxSlot,
      setLogosSlot,
      isFluxActive,
      isLogosActive,
      activeSlot,
      onImport,
      onExport,
      upsertRenderOutputToLogos,
    }),
    [flux, logos, setFluxSlot, setLogosSlot, isFluxActive, isLogosActive, activeSlot, onImport, onExport, upsertRenderOutputToLogos]
  )

  return (
    <RecollectionActionsContext.Provider value={value}>
      {children}
      <input
        ref={importInputRef}
        type="file"
        accept=".json,application/json"
        className="hidden"
        aria-hidden
        onChange={onImportFileChange}
      />
    </RecollectionActionsContext.Provider>
  )
}

export function useRecollectionActions(): RecollectionActionsContextValue {
  const ctx = useContext(RecollectionActionsContext)
  if (!ctx) throw new Error('useRecollectionActions must be used within RecollectionActionsProvider')
  return ctx
}

/** Returns the context value or null when outside RecollectionActionsProvider. Use when the consumer may render outside recollection layout. */
export function useOptionalRecollectionActions(): RecollectionActionsContextValue | null {
  return useContext(RecollectionActionsContext)
}
