import { create } from 'zustand'
import { logError } from '../reportError'

/**
 * First-run setup state: whether the managed ffmpeg/ffprobe are present, and the
 * download / locate orchestration for acquiring them. The App gate blocks the app on
 * `ffmpegReady === false`, and Onboarding + the About card drive the actions here.
 *
 * Unlike the settings store, this has no preview-only fallback: `window.api` is the real
 * bridge or the mockApi (which simulates a fake download and honours the `?setup` preview
 * flag), so the same code path works in both. Init is driven from an App effect — not at
 * module load — so it runs after `window.api` is assigned in every context.
 */
interface SetupState {
  /** null until the first status check resolves; then true once both binaries are present */
  ffmpegReady: boolean | null
  ffmpegVersion: string | null
  ffprobeVersion: string | null
  /** phase of an in-flight acquisition (drives the progress UI) */
  phase: 'idle' | 'downloading' | 'extracting'
  /** 0..1 download fraction when known, else undefined (indeterminate bar) */
  fraction: number | undefined
  /** a download or locate is running */
  busy: boolean
  error: string | null
  /** subscribe to progress + run the first status check; returns an unsubscribe fn */
  init: () => () => void
  /** re-query readiness + versions from main */
  check: () => Promise<void>
  /** download + unpack ffmpeg from the pinned upstream archive */
  download: () => Promise<void>
  /** abort the in-flight download */
  cancel: () => void
  /** pick a folder that already holds ffmpeg + ffprobe and install from it */
  locate: () => Promise<void>
}

// Set when the user cancels a download, so the (not-ok) awaited result is treated as a
// cancel rather than surfaced as an error — same idea as the app-update card's canceledRef.
let canceled = false

export const useSetup = create<SetupState>((set, get) => ({
  ffmpegReady: null,
  ffmpegVersion: null,
  ffprobeVersion: null,
  phase: 'idle',
  fraction: undefined,
  busy: false,
  error: null,

  init: () => {
    void get().check()
    return (
      window.api?.onFfmpegSetupProgress?.((p) =>
        set({ phase: p.phase, fraction: p.phase === 'downloading' ? p.fraction : undefined })
      ) ?? (() => {})
    )
  },

  check: async () => {
    try {
      const s = await window.api.getFfmpegSetupStatus()
      set({ ffmpegReady: s.ready, ffmpegVersion: s.ffmpeg, ffprobeVersion: s.ffprobe })
    } catch (e) {
      logError('getFfmpegSetupStatus')(e)
    }
  },

  download: async () => {
    if (get().busy) return
    canceled = false
    set({ busy: true, error: null, phase: 'downloading', fraction: undefined })
    try {
      const r = await window.api.downloadFfmpeg()
      // The user hit Cancel: main aborted the download and cleaned up — don't surface its
      // not-ok result as an error.
      if (canceled) return
      if (!r.ok) {
        set({ error: r.error ?? 'The ffmpeg download failed.' })
        return
      }
      await get().check() // flips ffmpegReady true, clearing the gate
    } catch (e) {
      if (!canceled) set({ error: e instanceof Error ? e.message : String(e) })
    } finally {
      set({ busy: false, phase: 'idle', fraction: undefined })
    }
  },

  cancel: () => {
    canceled = true
    window.api.cancelFfmpegDownload?.().catch(logError('cancelFfmpegDownload'))
  },

  locate: async () => {
    if (get().busy) return
    set({ busy: true, error: null })
    try {
      const r = await window.api.locateFfmpeg()
      if (!r.ok) {
        // A dismissed folder picker ('No folder selected.') isn't an error worth showing.
        if (r.error && !/no folder selected/i.test(r.error)) set({ error: r.error })
        return
      }
      await get().check()
    } catch (e) {
      set({ error: e instanceof Error ? e.message : String(e) })
    } finally {
      set({ busy: false })
    }
  }
}))
