import type { Context } from 'hono'
import { getRawDb } from '../store/db.ts'
import { countThreads, getThreadDetail, listThreads } from '../store/queries.ts'

/**
 * Tag each task (thread) with `errored` — true if it contains a model_call
 * that came back with HTTP status >= 400. Cheap: one indexed `IN` query over
 * just the thread ids on the current page (http_status + thread_id are
 * columns, no payload scan).
 */
async function markErrored<T extends { id: string }>(
  rows: T[],
): Promise<Array<T & { errored: boolean }>> {
  if (rows.length === 0) return []
  const raw = await getRawDb()
  const placeholders = rows.map(() => '?').join(',')
  const hits = raw
    .prepare(
      `SELECT DISTINCT thread_id FROM actions
       WHERE http_status >= 400 AND thread_id IN (${placeholders})`,
    )
    .all(...rows.map((r) => r.id)) as Array<{ thread_id: string }>
  const errored = new Set(hits.map((h) => h.thread_id))
  return rows.map((r) => ({ ...r, errored: errored.has(r.id) }))
}

/** Same `errored` flag, keyed on run id — for the runs nested in a task. */
async function markRunsErrored<T extends { id: string }>(
  rows: T[],
): Promise<Array<T & { errored: boolean }>> {
  if (rows.length === 0) return []
  const raw = await getRawDb()
  const placeholders = rows.map(() => '?').join(',')
  const hits = raw
    .prepare(
      `SELECT DISTINCT run_id FROM actions
       WHERE http_status >= 400 AND run_id IN (${placeholders})`,
    )
    .all(...rows.map((r) => r.id)) as Array<{ run_id: string }>
  const errored = new Set(hits.map((h) => h.run_id))
  return rows.map((r) => ({ ...r, errored: errored.has(r.id) }))
}

export async function handleListThreads(c: Context): Promise<Response> {
  const agent = c.req.query('agent')?.split(',').filter(Boolean)
  const sinceRaw = c.req.query('since')
  const since = sinceRaw ? Number.parseInt(sinceRaw, 10) : undefined
  const limitRaw = c.req.query('limit')
  const offsetRaw = c.req.query('offset')
  const limit = clampInt(limitRaw, 50, 1, 200)
  const offset = clampInt(offsetRaw, 0, 0, 1_000_000)

  // Same shape duality as /runs: a bare array for the legacy/CLI caller, the
  // paged envelope when offset/limit are passed.
  const filters = { agent, since, limit, offset }
  if (offsetRaw == null && limitRaw == null) {
    const rows = await listThreads(filters)
    return c.json(await markErrored(rows))
  }

  const [rows, total] = await Promise.all([listThreads(filters), countThreads({ agent, since })])
  return c.json({ rows: await markErrored(rows), total, limit, offset })
}

export async function handleGetThread(c: Context): Promise<Response> {
  const id = c.req.param('id')
  if (!id) return c.json({ error: 'missing thread id' }, 400)
  const detail = await getThreadDetail(id)
  if (!detail) return c.json({ error: 'task not found' }, 404)
  // Enrich with the errored flag on both the task and its runs, matching the
  // list endpoints so the detail view can badge errors consistently.
  const [thread] = await markErrored([detail.thread])
  const runs = await markRunsErrored(detail.runs)
  return c.json({ thread, runs })
}

function clampInt(raw: string | undefined, dflt: number, lo: number, hi: number): number {
  if (raw == null) return dflt
  const n = Number.parseInt(raw, 10)
  if (!Number.isFinite(n)) return dflt
  return Math.max(lo, Math.min(hi, n))
}
