import { createSimpleContext } from "@opencode-ai/ui/context"
import { createEffect, createMemo, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { usePlatform } from "@/context/platform"
import { createRendererVoiceCapture } from "./voice-capture"
import { parseVoiceInput } from "./voice-command-parser"
import type {
  RecordingState,
  TranscriptionResult,
  VoiceActions,
  VoiceConfigView,
  VoiceError,
  VoicePlatformAPI,
} from "./types"

const defaultConfig: VoiceConfigView = {
  enabled: true,
  provider: "deepgram",
  language: "en",
  hotkey: "Alt+Space",
  commands: true,
  autoSend: false,
  apiKeyConfigured: false,
  apiKeyEnv: "DEEPGRAM_API_KEY",
}

const noopVoice = {
  available: () => false,
  state: () => "idle" as RecordingState,
  interim: () => "",
  finalText: () => "",
  error: () => undefined as VoiceError | undefined,
  config: () => defaultConfig,
  durationMs: () => 0,
  recording: () => false,
  processing: () => false,
  async start() {},
  async stop() {},
  async toggle() {},
  async setConfig(_patch: Partial<VoiceConfigView>) {},
  async refreshConfig() {},
  registerActions(_next: Partial<VoiceActions>) {
    return () => undefined
  },
  clearError() {},
}

const ctx = createSimpleContext({
  name: "Voice",
  gate: false,
  init: () => {
    const platform = usePlatform()
    const api = () => platform.voice
    const [store, setStore] = createStore({
      available: false,
      state: "idle" as RecordingState,
      interim: "",
      finalText: "",
      error: undefined as VoiceError | undefined,
      config: defaultConfig,
      captureMode: "native" as "native" | "renderer",
      durationMs: 0,
    })

    let actions: Partial<VoiceActions> = {}
    let capture: ReturnType<typeof createRendererVoiceCapture> | undefined
    let timer: ReturnType<typeof setInterval> | undefined
    let startedAt = 0

    const refreshConfig = async () => {
      const voice = api()
      if (!voice) {
        setStore("available", false)
        return
      }
      setStore("available", true)
      const config = await voice.getConfig()
      setStore("config", config)
    }

    createEffect(() => {
      const voice = api()
      if (!voice) {
        setStore("available", false)
        return
      }
      setStore("available", true)
      void refreshConfig()
      void voice.getState().then((state) => setStore("state", state))

      const unsubState = voice.onRecordingState((state) => {
        setStore("state", state)
        if (state === "recording") {
          startedAt = Date.now()
          clearInterval(timer)
          timer = setInterval(() => setStore("durationMs", Date.now() - startedAt), 200)
          return
        }
        clearInterval(timer)
        timer = undefined
        if (state === "idle") {
          setStore("durationMs", 0)
          capture?.stop()
        }
      })

      const unsubTranscription = voice.onTranscription((result) => {
        handleTranscription(result)
      })

      const unsubError = voice.onError((error) => {
        setStore("error", error)
        capture?.stop()
      })

      const unsubCapture = voice.onCaptureMode((mode) => {
        setStore("captureMode", mode)
        if (mode === "renderer") void startRendererCapture(voice)
        if (mode === "native") capture?.stop()
      })

      onCleanup(() => {
        unsubState()
        unsubTranscription()
        unsubError()
        unsubCapture()
        clearInterval(timer)
        capture?.stop()
      })
    })

    const startRendererCapture = async (voice: VoicePlatformAPI) => {
      capture ??= createRendererVoiceCapture(voice)
      try {
        await capture.start()
      } catch (error) {
        setStore("error", {
          code: "no-microphone",
          message: error instanceof Error ? error.message : "Microphone access failed",
        })
        await voice.stop()
      }
    }

    const handleTranscription = (result: TranscriptionResult) => {
      setStore("interim", result.isFinal ? "" : result.text)
      if (!result.isFinal) return
      setStore("finalText", result.text)
      const parsed = parseVoiceInput(result.text, store.config.commands)
      if (parsed.type === "command") {
        dispatchCommand(parsed.command)
        return
      }
      if (!parsed.text) return
      actions.insertText?.(parsed.text)
      if (store.config.autoSend) actions.submit?.()
    }

    const dispatchCommand = (command: string) => {
      if (command === "send") return actions.submit?.()
      if (command === "run") return actions.run?.() ?? actions.submit?.()
      if (command === "accept") return actions.accept?.()
      if (command === "reject") return actions.reject?.()
      if (command === "clear") return actions.clear?.()
      if (command === "stop") return actions.stop?.()
      if (command === "scroll-up") return actions.scrollUp?.()
      if (command === "scroll-down") return actions.scrollDown?.()
      if (command === "focus") return actions.focus?.()
      if (command === "newline") return actions.newline?.()
    }

    return {
      available: createMemo(() => store.available && store.config.enabled),
      state: createMemo(() => store.state),
      interim: createMemo(() => store.interim),
      finalText: createMemo(() => store.finalText),
      error: createMemo(() => store.error),
      config: createMemo(() => store.config),
      durationMs: createMemo(() => store.durationMs),
      recording: createMemo(() => store.state === "recording"),
      processing: createMemo(() => store.state === "processing"),
      async start() {
        const voice = api()
        if (!voice) return
        setStore("error", undefined)
        setStore("interim", "")
        await voice.start()
        if (store.captureMode === "renderer") await startRendererCapture(voice)
      },
      async stop() {
        const voice = api()
        if (!voice) return
        capture?.stop()
        await voice.stop()
      },
      async toggle() {
        const voice = api()
        if (!voice) return
        setStore("error", undefined)
        if (store.state === "recording") {
          capture?.stop()
          await voice.stop()
          return
        }
        await voice.start()
        if (store.captureMode === "renderer") await startRendererCapture(voice)
      },
      async setConfig(patch: Partial<VoiceConfigView>) {
        const voice = api()
        if (!voice) return
        const next = await voice.setConfig(patch)
        setStore("config", next)
      },
      async refreshConfig() {
        await refreshConfig()
      },
      registerActions(next: Partial<VoiceActions>) {
        actions = { ...actions, ...next }
        return () => {
          for (const key of Object.keys(next) as Array<keyof VoiceActions>) {
            if (actions[key] === next[key]) delete actions[key]
          }
        }
      },
      clearError() {
        setStore("error", undefined)
      },
    }
  },
})

export const VoiceProvider = ctx.provider
export const useVoice = ctx.use

/** Safe for storybook / non-app trees that omit VoiceProvider. */
export function useVoiceOptional() {
  try {
    return useVoice()
  } catch {
    return noopVoice
  }
}
