// Drives a preview/apply maintenance card whose work runs as a long Celery task
// (re-walks, full-library probes) and whose result must survive the operator
// navigating away or reloading the page — "being able to navigate away from a
// maintenance task is very important" (operator, 2026-06-16).
//
// The card-specific bits (canApply/summaryType/humanBytes, the confirm dialog,
// the result copy) stay in the card; this composable owns the shared task
// lifecycle: dispatch → persist the task id → poll → resurface on mount.
//
// How resurfacing works: on dispatch we stash {taskId, mode, startedAt} in
// localStorage. On mount we re-read it and resume polling the SAME task, so a
// run that finished while the operator was away shows its result the moment
// they come back, and one still in flight re-attaches its spinner. The Celery
// result backend retains the summary well under result_expires, so the re-fetch
// succeeds. localStorage is cleared once the result is observed. (localStorage
// is safe on the plain-HTTP origin — rule 95.)

import { onMounted, ref } from 'vue'

import { useApi } from './useApi.js'
import { toast } from '../utils/toast.js'

// Don't resume a task whose dispatch is older than this — past the longest
// plausible run (the gated purge's hard time_limit is 40min) it's almost
// certainly finished-and-expired or dead, and a stale poll would just spin.
const STALE_RESUME_MS = 3 * 60 * 60 * 1000 // 3h

export function useMaintenanceTask ({ endpoint, storageKey, appliedToast, maxPolls = 300, pollMs = 2000 }) {
  const api = useApi()
  const previewing = ref(false)
  const applying = ref(false)
  const summary = ref(null)
  const applied = ref(false)

  function persist (taskId, mode, startedAt) {
    try {
      localStorage.setItem(storageKey, JSON.stringify({ taskId, mode, startedAt }))
    } catch { /* private mode / quota — degrade to non-resurfacing, no crash */ }
  }
  function clearPersist () {
    try { localStorage.removeItem(storageKey) } catch { /* ignore */ }
  }

  // Poll-then-wait: check immediately first so a resumed-but-already-finished
  // task resolves on the first tick instead of after a needless pollMs delay.
  async function pollResult (taskId) {
    for (let i = 0; i < maxPolls; i++) {
      const r = await api.get(`/api/admin/maintenance/task-result/${taskId}`)
      if (r.ready) {
        clearPersist()
        if (!r.successful) throw new Error('the task failed — check the worker logs')
        return r.result
      }
      await new Promise(res => setTimeout(res, pollMs))
    }
    throw new Error('timed out waiting for the task — check the task dashboard')
  }

  async function run (mode) {
    const { task_id: taskId } = await api.post(endpoint, { body: { dry_run: mode === 'preview' } })
    persist(taskId, mode, Date.now())
    return pollResult(taskId)
  }

  async function preview () {
    previewing.value = true
    applied.value = false
    summary.value = null
    try {
      summary.value = await run('preview')
    } catch (e) {
      toast({ text: e?.body?.detail || e?.message || 'Preview failed', type: 'error' })
    } finally {
      previewing.value = false
    }
  }

  async function apply () {
    applying.value = true
    try {
      summary.value = await run('apply')
      applied.value = true
      toast({ text: appliedToast, type: 'success' })
    } catch (e) {
      toast({ text: e?.body?.detail || e?.message || 'Apply failed', type: 'error' })
    } finally {
      applying.value = false
    }
  }

  // Re-attach to a task left in flight (or finished) while the card was unmounted.
  function resume () {
    let saved = null
    try { saved = JSON.parse(localStorage.getItem(storageKey) || 'null') } catch { saved = null }
    if (!saved?.taskId) return
    if (saved.startedAt && (Date.now() - saved.startedAt) > STALE_RESUME_MS) {
      clearPersist()
      return
    }
    const isApply = saved.mode === 'apply'
    if (isApply) applying.value = true
    else previewing.value = true
    pollResult(saved.taskId)
      .then(result => {
        summary.value = result
        if (isApply) {
          applied.value = true
          toast({ text: appliedToast, type: 'success' })
        }
      })
      .catch(() => { clearPersist() }) // expired/gone — drop quietly, no scary toast on load
      .finally(() => { previewing.value = false; applying.value = false })
  }

  onMounted(resume)

  return { previewing, applying, summary, applied, preview, apply }
}
