import { useEffect, useRef, useState } from 'react'
import type { AppUpdateInfo } from '@shared/ipc'
import { logError } from '../../reportError'

export interface SoftwareUpdateCardController {
  appVersion: string
  appUpd: AppUpdateInfo | null
  appChecking: boolean
  appDownloading: boolean
  appFraction: number | undefined
  appUpdError: string | null
  checkAppUpdate: () => Promise<void>
  installAppUpdate: () => Promise<void>
  cancelDownload: () => void
  openReleasePage: () => void
}

/**
 * View-model for the Software update card (L93/CC13): owns the check → download
 * → run-installer orchestration (including the cancel race, B6) so the card
 * stays presentational.
 */
export function useSoftwareUpdateCard(): SoftwareUpdateCardController {
  const [appVersion, setAppVersion] = useState('')
  const [appUpd, setAppUpd] = useState<AppUpdateInfo | null>(null)
  const [appChecking, setAppChecking] = useState(false)
  const [appDownloading, setAppDownloading] = useState(false)
  const [appFraction, setAppFraction] = useState<number | undefined>(undefined)
  const [appUpdError, setAppUpdError] = useState<string | null>(null)
  // Set when the user cancels the download, so the awaited result (which comes back
  // not-ok) is treated as canceled rather than surfaced as an error (B6).
  const canceledRef = useRef(false)

  useEffect(() => {
    window.api.getAppVersion().then(setAppVersion).catch(logError('getAppVersion'))
    return window.api.onAppUpdateProgress((p) => setAppFraction(p.fraction))
  }, [])

  async function checkAppUpdate(): Promise<void> {
    setAppChecking(true)
    setAppUpd(null)
    setAppUpdError(null)
    try {
      const info = await window.api.checkForAppUpdate()
      // Funnel a failed check into the single error slot rather than storing a
      // second not-ok AppUpdateInfo, so only one error message can ever render.
      if (info.ok) setAppUpd(info)
      else setAppUpdError(info.error ?? 'Update check failed.')
    } catch (e) {
      setAppUpdError(e instanceof Error ? e.message : String(e))
    } finally {
      setAppChecking(false)
    }
  }

  async function installAppUpdate(): Promise<void> {
    if (!appUpd?.downloadUrl) return
    canceledRef.current = false
    setAppDownloading(true)
    setAppFraction(undefined)
    setAppUpdError(null)
    try {
      const dl = await window.api.downloadAppUpdate(appUpd.downloadUrl)
      // The user hit Cancel: main aborted the download and cleaned up the partial —
      // don't surface its not-ok result as an error or try to install (B6).
      if (canceledRef.current) return
      if (!dl.ok || !dl.filePath) {
        setAppUpdError(dl.error ?? 'Download failed.')
        return
      }
      const run = await window.api.runAppUpdate(dl.filePath)
      // On success the app quits as the installer launches; only errors return here.
      if (!run.ok) setAppUpdError(run.error ?? 'Could not start the installer.')
    } catch (e) {
      if (!canceledRef.current) setAppUpdError(e instanceof Error ? e.message : String(e))
    } finally {
      setAppDownloading(false)
    }
  }

  function cancelDownload(): void {
    canceledRef.current = true
    window.api.cancelAppUpdate?.().catch(logError('cancelAppUpdate'))
  }

  function openReleasePage(): void {
    void window.api?.openUrl?.(appUpd?.htmlUrl ?? '')
  }

  return {
    appVersion,
    appUpd,
    appChecking,
    appDownloading,
    appFraction,
    appUpdError,
    checkAppUpdate,
    installAppUpdate,
    cancelDownload,
    openReleasePage
  }
}
