import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react"
import type { ReactNode } from "react"
import { toast } from "sonner"
import * as api from "./api"
import type {
  CloudCompilerConfig,
  CompileResult,
  CompilerProvider,
  DetectParams,
  RunResult,
  ValidationResult,
} from "./types"

const DEFAULT_CLASSES = "person, cat, car, chair, cup"
const DETECTOR_MODEL_VALUES = new Set([
  "yoloe-26n-seg.pt",
  "yoloe-26s-seg.pt",
  "yoloe-26m-seg.pt",
  "yoloe-26l-seg.pt",
  "yoloe-26x-seg.pt",
])
const IMAGE_SIZE_VALUES = new Set([320, 640, 960, 1280])

const DEFAULT_RULES = `rules:
  - name: example-person-enters-lights
    when:
      all:
        - present: {label: person, min_count: 1}
    trigger:
      on: enter
    gate:
      enabled: true
    then:
      - type: simulate
        name: turn on lights
`

const DEFAULT_PARAMS: DetectParams = {
  classes: DEFAULT_CLASSES,
  confidence: 0.25,
  sampleIntervalSec: 0.25,
  maxFrames: 120,
  modelName: "yoloe-26s-seg.pt",
  imageSize: 640,
  device: "auto",
  maxDetections: 0,
  enableWebhooks: false,
  webhookUrl: "",
}

const DEFAULT_COMPILER: CloudCompilerConfig = {
  provider: "anthropic",
  replicateApiKey: "",
  openaiApiKey: "",
  anthropicApiKey: "",
  openaiModel: "gpt-5.5",
  anthropicModel: "claude-sonnet-4-6",
}

interface DashboardState {
  params: DetectParams
  setParam: <K extends keyof DetectParams>(key: K, value: DetectParams[K]) => void
  rulesText: string
  setRulesText: (text: string) => void
  replaceRulesText: (text: string, persist?: boolean) => Promise<ValidationResult>
  videoFile: File | null
  videoPreviewUrl: string | null
  setVideo: (file: File | null) => void
  result: RunResult | null
  running: boolean
  error: string | null
  run: () => Promise<void>
  validation: ValidationResult | null
  validate: () => Promise<void>
  save: () => Promise<void>
  setRuleEnabled: (ruleName: string, enabled: boolean) => Promise<void>
  deleteRule: (ruleName: string) => Promise<void>
  compiler: CloudCompilerConfig
  setCompilerProvider: (provider: CompilerProvider) => void
  setCompilerApiKey: (provider: CompilerProvider, apiKey: string) => void
  setCompilerModel: (provider: "openai" | "anthropic", model: string) => void
  compile: (instruction: string) => Promise<CompileResult | null>
  compiling: boolean
}

const Ctx = createContext<DashboardState | null>(null)

export function DashboardProvider({ children }: { children: ReactNode }) {
  const [params, setParams] = useState<DetectParams>(DEFAULT_PARAMS)
  const [rulesText, setRulesText] = useState<string>(DEFAULT_RULES)
  const [videoFile, setVideoFile] = useState<File | null>(null)
  const [videoPreviewUrl, setVideoPreviewUrl] = useState<string | null>(null)
  const [result, setResult] = useState<RunResult | null>(null)
  const [running, setRunning] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [validation, setValidation] = useState<ValidationResult | null>(null)
  const [compiler, setCompiler] = useState<CloudCompilerConfig>(() => ({
    ...DEFAULT_COMPILER,
    provider:
      (localStorage.getItem("tiny-trigger-compiler-provider") as CompilerProvider | null) ??
      DEFAULT_COMPILER.provider,
    openaiModel: localStorage.getItem("tiny-trigger-openai-model") ?? DEFAULT_COMPILER.openaiModel,
    anthropicModel:
      localStorage.getItem("tiny-trigger-anthropic-model") ?? DEFAULT_COMPILER.anthropicModel,
  }))
  const [compiling, setCompiling] = useState(false)
  const previewRef = useRef<string | null>(null)
  const compileInFlightRef = useRef(false)


  const applyRulesText = useCallback(async (nextRulesText: string) => {
    setRulesText(nextRulesText)
    const nextValidation = await api.validateRules(nextRulesText)
    setValidation(nextValidation)
    return nextValidation
  }, [])

  const replaceRulesText = useCallback(
    async (nextRulesText: string, persist = false) => {
      const nextValidation = await applyRulesText(nextRulesText)
      if (persist && nextValidation.ok) {
        await api.saveRules(nextRulesText)
      }
      return nextValidation
    },
    [applyRulesText],
  )

  // Hydrate defaults from backend config + saved rules on mount.
  useEffect(() => {
    api
      .getConfig()
      .then((cfg) => {
        setParams((p) => ({
          ...p,
          classes: cfg.default_classes ?? p.classes,
          modelName:
            cfg.default_detector_model && DETECTOR_MODEL_VALUES.has(cfg.default_detector_model)
              ? cfg.default_detector_model
              : p.modelName,
          device: cfg.default_device ?? p.device,
          imageSize:
            cfg.default_image_size && IMAGE_SIZE_VALUES.has(cfg.default_image_size)
              ? cfg.default_image_size
              : p.imageSize,
          maxDetections: cfg.default_max_detections ?? p.maxDetections,
          webhookUrl: cfg.webhook_url ?? p.webhookUrl,
        }))
        if (cfg.llm_provider === "replicate" || cfg.llm_provider === "openai" || cfg.llm_provider === "anthropic") {
          setCompilerProvider(cfg.llm_provider)
        }
        setCompiler((current) => ({
          ...current,
          openaiModel: cfg.openai_model ?? current.openaiModel,
          anthropicModel: cfg.anthropic_model ?? current.anthropicModel,
        }))
      })
      .catch(() => void 0)
    api
      .loadRules()
      .then((r) => {
        void applyRulesText(r.rules_text || DEFAULT_RULES)
      })
      .catch(() => {
        void applyRulesText(DEFAULT_RULES)
      })
  }, [applyRulesText])

  const setParam = useCallback(
    <K extends keyof DetectParams>(key: K, value: DetectParams[K]) => {
      setParams((p) => ({ ...p, [key]: value }))
    },
    [],
  )

  const setCompilerProvider = useCallback((provider: CompilerProvider) => {
    localStorage.setItem("tiny-trigger-compiler-provider", provider)
    setCompiler((current) => ({ ...current, provider }))
  }, [])

  const setCompilerApiKey = useCallback((provider: CompilerProvider, apiKey: string) => {
    const keyByProvider: Record<CompilerProvider, keyof CloudCompilerConfig> = {
      replicate: "replicateApiKey",
      openai: "openaiApiKey",
      anthropic: "anthropicApiKey",
    }
    setCompiler((current) => ({ ...current, [keyByProvider[provider]]: apiKey }))
  }, [])

  const setCompilerModel = useCallback((provider: "openai" | "anthropic", model: string) => {
    const key = provider === "openai" ? "openaiModel" : "anthropicModel"
    localStorage.setItem(`tiny-trigger-${provider}-model`, model)
    setCompiler((current) => ({ ...current, [key]: model }))
  }, [])

  const setVideo = useCallback((file: File | null) => {
    if (previewRef.current) URL.revokeObjectURL(previewRef.current)
    if (file) {
      const url = URL.createObjectURL(file)
      previewRef.current = url
      setVideoPreviewUrl(url)
    } else {
      previewRef.current = null
      setVideoPreviewUrl(null)
    }
    setVideoFile(file)
  }, [])

  const run = useCallback(async () => {
    if (!videoFile) {
      toast.error("Upload a video first")
      return
    }
    setRunning(true)
    setError(null)
    try {
      const res = await api.detectAndAutomate(videoFile, rulesText, params)
      setResult(res)
      if (res.status === "fired") {
        toast.success(`${res.stats.actions} action${res.stats.actions === 1 ? "" : "s"} fired`)
      } else {
        toast.message("No frame matched the automation", {
          description: `${res.stats.detections} detections across ${res.stats.processed_frames} frames`,
        })
      }
    } catch (e) {
      const message = e instanceof Error ? e.message : String(e)
      setError(message)
      toast.error("Run failed", { description: message })
    } finally {
      setRunning(false)
    }
  }, [videoFile, rulesText, params])

  const validate = useCallback(async () => {
    try {
      const v = await applyRulesText(rulesText)
      if (v.ok) toast.success(`Validated — ${v.rules.length} rule${v.rules.length === 1 ? "" : "s"}`)
      else toast.error("Validation failed")
    } catch (e) {
      toast.error("Validation error", {
        description: e instanceof Error ? e.message : String(e),
      })
    }
  }, [applyRulesText, rulesText])

  const save = useCallback(async () => {
    try {
      const r = await api.saveRules(rulesText)
      toast.success(`Saved ${r.rule_count} rule${r.rule_count === 1 ? "" : "s"}`)
    } catch (e) {
      toast.error("Save failed", {
        description: e instanceof Error ? e.message : String(e),
      })
    }
  }, [rulesText])

  const setRuleEnabled = useCallback(
    async (ruleName: string, enabled: boolean) => {
      try {
        const result = await api.setRuleEnabled(rulesText, ruleName, enabled)
        await applyRulesText(result.rules_text)
        toast.success(`${enabled ? "Enabled" : "Disabled"} ${ruleName}`)
      } catch (e) {
        toast.error("Rule update failed", {
          description: e instanceof Error ? e.message : String(e),
        })
      }
    },
    [applyRulesText, rulesText],
  )

  const deleteRule = useCallback(
    async (ruleName: string) => {
      try {
        const result = await api.deleteRule(rulesText, ruleName)
        await applyRulesText(result.rules_text)
        toast.success(`Deleted ${ruleName}`)
      } catch (e) {
        toast.error("Delete failed", {
          description: e instanceof Error ? e.message : String(e),
        })
      }
    },
    [applyRulesText, rulesText],
  )

  const compile = useCallback(
    async (instruction: string): Promise<CompileResult | null> => {
      if (compileInFlightRef.current) return null
      compileInFlightRef.current = true
      setCompiling(true)
      try {
        const c = await api.compileRules(
          instruction,
          params.classes,
          rulesText,
          compiler,
        )
        await applyRulesText(c.rules_text)
        toast.success(`Added rule. ${c.rule_count} total.`)
        return c
      } catch (e) {
        toast.error("Compile failed", {
          description: e instanceof Error ? e.message : String(e),
        })
        return null
      } finally {
        compileInFlightRef.current = false
        setCompiling(false)
      }
    },
    [applyRulesText, compiler, params.classes, rulesText],
  )

  const value = useMemo<DashboardState>(
    () => ({
      params,
      setParam,
      rulesText,
      setRulesText,
      replaceRulesText,
      videoFile,
      videoPreviewUrl,
      setVideo,
      result,
      running,
      error,
      run,
      validation,
      validate,
      save,
      setRuleEnabled,
      deleteRule,
      compiler,
      setCompilerProvider,
      setCompilerApiKey,
      setCompilerModel,
      compile,
      compiling,
    }),
    [
      params,
      setParam,
      rulesText,
      replaceRulesText,
      videoFile,
      videoPreviewUrl,
      setVideo,
      result,
      running,
      error,
      run,
      validation,
      validate,
      save,
      setRuleEnabled,
      deleteRule,
      compiler,
      setCompilerModel,
      compile,
      compiling,
    ],
  )

  return <Ctx.Provider value={value}>{children}</Ctx.Provider>
}

export function useDashboard(): DashboardState {
  const ctx = useContext(Ctx)
  if (!ctx) throw new Error("useDashboard must be used within DashboardProvider")
  return ctx
}
