import { useCallback, useEffect, useMemo, useState } from 'react'
import { api } from './api'
import type {
  Connection, Health, ItemSelection, JobSnapshot, Options, PackageInfo, Plan, SourceResponse, TargetInventory,
} from './types'
import { Notice } from './ui'
import { Containers } from './Containers'
import { Sidebar } from './Sidebar'
import { Jobs } from './Jobs'
import { Packages } from './Packages'

type View = 'containers' | 'jobs' | 'packages'

export default function App() {
  const [health, setHealth] = useState<Health | null>(null)
  const [source, setSource] = useState<SourceResponse | null>(null)
  const [sel, setSel] = useState<Record<string, ItemSelection>>({})
  const [options, setOptions] = useState<Options>({
    conflict: 'fail', renameSuffix: '-migrated', compress: true,
    compressLevel: 1, dryRun: false, parallelism: 1, verifyAfter: true,
  })
  // Volume sizes come from a separate, slower endpoint so the container list
  // can render immediately; they are merged into the rows when they arrive.
  const [sizes, setSizes] = useState<Record<string, number>>({})
  const [connections, setConnections] = useState<Connection[]>([])
  const [activeConn, setActiveConn] = useState<string>('')
  const [targetInv, setTargetInv] = useState<TargetInventory | null>(null)
  const [jobs, setJobs] = useState<JobSnapshot[]>([])
  const [packages, setPackages] = useState<PackageInfo[]>([])
  const [view, setView] = useState<View>('containers')
  const [activeJob, setActiveJob] = useState<string>('')
  const [error, setError] = useState<string>('')
  const [loading, setLoading] = useState(true)

  const loadSource = useCallback(async () => {
    setLoading(true)
    try {
      const s = await api.source()
      setSource(s)
      // Selections are re-seeded from the server defaults, but any choice the
      // operator already made for a container that still exists is preserved.
      setSel((prev) => {
        const next: Record<string, ItemSelection> = {}
        for (const c of s.inventory.containers) {
          next[c.id] = prev[c.id] ?? s.defaults[c.id]
        }
        return next
      })
      setError('')
      api.volumeSizes()
        .then((r) => setSizes(r.volumes ?? {}))
        .catch(() => undefined) // sizes are a nicety; the list works without them
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e))
    } finally {
      setLoading(false)
    }
  }, [])

  const loadConnections = useCallback(async () => {
    try {
      const list = await api.connections()
      setConnections(list)
      setActiveConn((cur) => (cur && list.some((c) => c.id === cur) ? cur : list[0]?.id ?? ''))
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e))
    }
  }, [])

  const loadJobs = useCallback(async () => {
    try {
      setJobs(await api.jobs())
    } catch { /* the jobs list is refreshed again on the next tick */ }
  }, [])

  const loadPackages = useCallback(async () => {
    try {
      setPackages(await api.packages())
    } catch { /* likewise */ }
  }, [])

  useEffect(() => {
    api.health().then(setHealth).catch(() => undefined)
    loadSource()
    loadConnections()
    loadJobs()
    loadPackages()
  }, [loadSource, loadConnections, loadJobs, loadPackages])

  // A slow poll keeps the job list current without holding a stream open for
  // every job; the detail view subscribes to its own live stream.
  useEffect(() => {
    const t = setInterval(loadJobs, 4000)
    return () => clearInterval(t)
  }, [loadJobs])

  // connectTarget deliberately lets its error escape. The sidebar needs to see
  // an untrusted host key so it can put the fingerprint in front of the
  // operator; swallowing it here left the UI stuck on "not connected yet".
  const connectTarget = useCallback(async (id: string) => {
    setTargetInv(null)
    if (!id) return
    const inv = await api.targetInventory(id)
    setTargetInv(inv)
    setError('')
  }, [])

  const included = useMemo(() => Object.values(sel).filter((s) => s.include), [sel])

  const plan = useMemo<Plan>(() => ({ items: Object.values(sel), options }), [sel, options])

  const runningJobs = jobs.filter((j) => j.state === 'running' || j.state === 'pending').length

  const onJobStarted = useCallback((j: JobSnapshot) => {
    setJobs((prev) => [j, ...prev])
    setActiveJob(j.id)
    setView('jobs')
  }, [])

  return (
    <div className="app">
      <header className="topbar">
        <div className="brand"><img src="/logo-icon.png" alt="" className="brand-logo" />DockMV</div>
        <nav className="tabs">
          <button className={`tab${view === 'containers' ? ' active' : ''}`} onClick={() => setView('containers')}>
            Containers
            <span className="count">{included.length}/{source?.inventory.containers.length ?? 0}</span>
          </button>
          <button className={`tab${view === 'jobs' ? ' active' : ''}`} onClick={() => setView('jobs')}>
            Jobs
            {runningJobs > 0 && <span className="count">{runningJobs} running</span>}
          </button>
          <button className={`tab${view === 'packages' ? ' active' : ''}`} onClick={() => setView('packages')}>
            Packages
            {packages.length > 0 && <span className="count">{packages.length}</span>}
          </button>
        </nav>
        <div className="topbar-right">
          {health && (
            <span className="hostinfo">
              source <b>{source?.inventory.host || health.dockerHost}</b>
              {health.dockerVersion && <> · docker {health.dockerVersion}</>}
            </span>
          )}
          <button className="btn tiny" onClick={loadSource} disabled={loading}>
            {loading ? 'loading…' : 'refresh'}
          </button>
        </div>
      </header>

      {error && (
        <div style={{ padding: '10px 16px' }}>
          <Notice kind="err">
            {error}
            <button className="btn tiny ghost" style={{ marginLeft: 8 }} onClick={() => setError('')}>dismiss</button>
          </Notice>
        </div>
      )}

      {health && !health.ok && (
        <div style={{ padding: '10px 16px' }}>
          <Notice kind="err">
            Cannot reach the source Docker daemon at <span className="mono">{health.dockerHost}</span>
            {health.dockerError && <> — {health.dockerError}</>}
          </Notice>
        </div>
      )}

      <div className="body">
        <main className="main">
          {view === 'containers' && (
            <Containers
              source={source}
              sel={sel}
              setSel={setSel}
              targetInv={targetInv}
              loading={loading}
              sizes={sizes}
            />
          )}
          {view === 'jobs' && (
            <Jobs
              jobs={jobs}
              activeJob={activeJob}
              setActiveJob={setActiveJob}
              reload={loadJobs}
              reloadPackages={loadPackages}
            />
          )}
          {view === 'packages' && <Packages packages={packages} reload={loadPackages} />}
        </main>

        {view === 'containers' && (
          <aside className="sidebar">
            <Sidebar
              source={source}
              plan={plan}
              includedCount={included.length}
              options={options}
              setOptions={setOptions}
              connections={connections}
              activeConn={activeConn}
              setActiveConn={setActiveConn}
              reloadConnections={loadConnections}
              targetInv={targetInv}
              connectTarget={connectTarget}
              onJobStarted={onJobStarted}
              onError={setError}
            />
          </aside>
        )}
      </div>
    </div>
  )
}
