import {
  planStatusCategory,
  type PlanTableItem,
  type ProjectGroupSummary,
} from '@overdeck/deck-ui'
import type { HarnessPlanRun } from '../../lib/panel-data'
import { sortPlanRunsForOverview } from '../../lib/overview-mappers'

export interface ProjectRunGroup {
  summary: ProjectGroupSummary
  runs: HarnessPlanRun[]
}

/** Path segments that mark a throwaway checkout of a project rather than a project itself. */
function isDetachedCheckoutSegment(segment: string): boolean {
  return segment === '.claude' || segment === '.worktrees' || segment === '.product-work' || segment.startsWith('.wt-')
}

export function canonicalRepoRoot(repoRoot: string): string {
  const segments = repoRoot.split('/').filter(Boolean)
  const cut = segments.findIndex(isDetachedCheckoutSegment)
  const kept = cut === -1 ? segments : segments.slice(0, cut)
  return kept.length === 0 ? repoRoot : `/${kept.join('/')}`
}

export function projectLabelForRun(run: HarnessPlanRun): string {
  const registryLabel = run.registry?.projectName?.trim()
  if (registryLabel) return registryLabel
  const pathLabel = canonicalRepoRoot(run.repoRoot).split('/').filter(Boolean).at(-1)
  return pathLabel || 'unassigned'
}

function attemptsOf(run: HarnessPlanRun): number {
  return typeof run.attempts === 'number' && run.attempts > 0 ? run.attempts : 1
}

function isLiveRun(run: HarnessPlanRun): boolean {
  const category = planStatusCategory(run.status, needsYou(run))
  return category === 'running' || category === 'needs-you'
}

function newestOf(runs: HarnessPlanRun[]): HarnessPlanRun {
  return runs.reduce((left, right) =>
    (parsedActivity(right.updatedAt) ?? -Infinity) > (parsedActivity(left.updatedAt) ?? -Infinity) ? right : left,
  )
}

/** A live run always speaks for its plan — a finished attempt stamped later must not hide it. */
function collapseToNewest(runs: HarnessPlanRun[]): HarnessPlanRun[] {
  if (runs.length === 0) return []
  const live = runs.filter(isLiveRun)
  const representative = newestOf(live.length > 0 ? live : runs)
  const attempts = runs.reduce((total, run) => total + attemptsOf(run), 0)
  return [attempts === attemptsOf(representative) ? representative : { ...representative, attempts }]
}

export function planKeyForRun(run: HarnessPlanRun): string {
  return `${projectLabelForRun(run)} ${run.registry?.slug || run.runId}`
}

/**
 * The engine folds attempts per `(repoRoot, slug)`, so a plan re-run from a worktree or a
 * `.product-work` checkout still arrives as several rows describing one plan. Concurrent
 * live runs each keep their row; finished ones collapse into a single history row.
 */
export function foldRunAttempts(runs: HarnessPlanRun[]): HarnessPlanRun[] {
  const byPlan = new Map<string, HarnessPlanRun[]>()
  for (const run of runs) {
    const bucket = byPlan.get(planKeyForRun(run))
    if (bucket) bucket.push(run)
    else byPlan.set(planKeyForRun(run), [run])
  }

  return Array.from(byPlan.values()).flatMap((bucket) => {
    const live = bucket.filter(isLiveRun)
    if (live.length <= 1) return collapseToNewest(bucket)
    return [...live, ...collapseToNewest(bucket.filter((run) => !isLiveRun(run)))]
  })
}

/** Task states the engine reports while a seat is occupied — `running` is the v1 spelling. */
const ACTIVE_TASK_STATUSES = new Set([
  'running',
  'working',
  'dispatching',
  'retrying',
  'verifying',
  'gating',
  'reviewing',
  'resolving',
])

function isActiveTaskStatus(status: string): boolean {
  return ACTIVE_TASK_STATUSES.has(status.trim().toLowerCase())
}

function parsedActivity(updatedAt: string | null): number | null {
  if (!updatedAt) return null
  const value = Date.parse(updatedAt)
  return Number.isFinite(value) ? value : null
}

function needsYou(run: HarnessPlanRun): boolean {
  return run.pendingDecisions > 0 || run.status === 'needs-you'
}

const PROJECT_RUN_PRIORITY = {
  'needs-you': 0,
  running: 1,
  failed: 2,
  queued: 3,
  completed: 4,
  unknown: 5,
}

export function projectRunsForRepo(runs: HarnessPlanRun[], repo: string): HarnessPlanRun[] {
  const normalizedRepo = repo.trim().toLowerCase()
  return foldRunAttempts(
    runs.filter((run) => !run.abandoned && projectLabelForRun(run).trim().toLowerCase() === normalizedRepo),
  )
    .sort((left, right) => {
      const leftCategory = planStatusCategory(left.status, needsYou(left))
      const rightCategory = planStatusCategory(right.status, needsYou(right))
      return PROJECT_RUN_PRIORITY[leftCategory] - PROJECT_RUN_PRIORITY[rightCategory]
    })
}

export function planTableItemForRun(run: HarnessPlanRun): PlanTableItem {
  return {
    runId: run.runId,
    slug: run.registry?.slug || run.runId,
    title: run.title,
    status: run.status,
    ...(run.seq > 0 ? { seq: run.seq } : {}),
    tasksCompleted: run.tasksCompleted,
    tasksTotal: run.tasksTotal,
    ...(run.registry?.created ? { createdDate: run.registry.created } : {}),
    updatedAtMs: parsedActivity(run.updatedAt),
    runningStartedAtMs: parsedActivity(run.currentActivityStartedAt ?? null),
    activeAgents: run.waves.flatMap((wave) => wave.tasks).filter((task) => isActiveTaskStatus(task.status)).length,
    ...(run.failClass ? { failClass: run.failClass } : {}),
    ...(typeof run.attempts === 'number' && run.attempts > 1 ? { attempts: run.attempts } : {}),
    ...(run.alarmed ? { alarmed: true } : {}),
    needsYou: needsYou(run),
    href: `/plans/${encodeURIComponent(run.runId)}`,
  }
}

export function buildProjectGroups(runs: HarnessPlanRun[]): ProjectRunGroup[] {
  const grouped = new Map<string, HarnessPlanRun[]>()
  for (const run of foldRunAttempts(runs.filter((candidate) => !candidate.abandoned))) {
    const key = projectLabelForRun(run)
    const bucket = grouped.get(key)
    if (bucket) bucket.push(run)
    else grouped.set(key, [run])
  }

  return Array.from(grouped, ([key, projectRuns]) => {
    const activities = projectRuns
      .map((run) => parsedActivity(run.updatedAt))
      .filter((value): value is number => value !== null)
    const categories = projectRuns.map((run) => planStatusCategory(run.status, needsYou(run)))
    return {
      summary: {
        key,
        label: projectLabelForRun(projectRuns[0]!),
        plans: new Set(projectRuns.map(planKeyForRun)).size,
        running: categories.filter((category) => category === 'running').length,
        needYou: categories.filter((category) => category === 'needs-you').length,
        failed: categories.filter((category) => category === 'failed').length,
        lastActivityMs: activities.length > 0 ? Math.max(...activities) : null,
      },
      runs: sortPlanRunsForOverview(projectRuns),
    }
  }).sort((left, right) =>
    right.summary.needYou - left.summary.needYou ||
    right.summary.running - left.summary.running ||
    (right.summary.lastActivityMs ?? -Infinity) - (left.summary.lastActivityMs ?? -Infinity) ||
    left.summary.label.localeCompare(right.summary.label),
  )
}
