/**
 * `query_project` — Reginald's single read tool. It selects a compact,
 * model-friendly view over the reconciled backlog. Every number comes from
 * deterministic code (scheduler, lifecycle inference, calibration); the model
 * only requests a shape and narrates it — it never computes (decisions.md).
 *
 * v0 serves focus / board / calibration / issue. The remaining views
 * (milestone / runway / standup / search) return a `notImplemented` marker so
 * the model degrades honestly instead of inventing data.
 */

import { fitCalibration, calibrationSamples } from '../calibration/calibration-v0.js'
import type { GiteaIssue } from '../gitea/types.js'
import { inferLifecycle, type LifecycleColumn, type LifecycleEvent } from '../lifecycle/lifecycle-v0.js'
import { type DependencyEdge, schedule, selectFocus } from '../scheduler/scheduler-v0.js'

export type ProjectView =
  | 'focus'
  | 'board'
  | 'calibration'
  | 'issue'
  | 'milestone'
  | 'runway'
  | 'standup'
  | 'search'

export interface QueryFilters {
  issueId?: number
  query?: string
  limit?: number
}

/** The reconciled inputs a view is built from. */
export interface ProjectSnapshot {
  issues: GiteaIssue[]
  timelines: Record<number, LifecycleEvent[]>
  deps: DependencyEdge[]
}

const COLUMN_ORDER: LifecycleColumn[] = ['diagnosis', 'triage', 'steeping', 'review', 'done']

function toSchedulable(issues: GiteaIssue[]) {
  return issues
    .filter((i) => i.state === 'open')
    .map((i) => ({
      number: i.number,
      title: i.title,
      labels: i.labels,
      estimateDays: i.facts.estimateDays,
      priority: i.facts.priority,
    }))
}

function focusView(snap: ProjectSnapshot) {
  const plan = schedule(toSchedulable(snap.issues), snap.deps)
  const f = selectFocus(plan)
  const slot = (item: (typeof plan.items)[number] | null) =>
    item ? { issue: item.number, title: item.title, rationale: item.rationale } : null
  return { now: slot(f.now), next: slot(f.next), later: slot(f.later), openCount: plan.items.length }
}

function boardView(snap: ProjectSnapshot, asOf: Date) {
  const columns: Record<string, { issue: number; title: string; labels: string[] }[]> = {}
  for (const key of COLUMN_ORDER) columns[key] = []
  for (const issue of snap.issues) {
    const inf = inferLifecycle(issue, snap.timelines[issue.number] ?? [], asOf)
    columns[inf.column].push({ issue: issue.number, title: issue.title, labels: issue.labels })
  }
  return {
    columns: COLUMN_ORDER.map((key) => ({ column: key, count: columns[key].length, issues: columns[key] })),
  }
}

function calibrationView(snap: ProjectSnapshot, asOf: Date) {
  const model = fitCalibration(calibrationSamples(snap.issues, snap.timelines, asOf))
  return {
    n: model.n,
    coldStart: model.coldStart,
    globalMultiplier: Number(Math.exp(model.global.mu).toFixed(2)),
    byBucket: Object.entries(model.byBucket).map(([bucket, fit]) => ({
      estimate: `est/${bucket}d`,
      n: fit.n,
      multiplier: Number(Math.exp(fit.mu).toFixed(2)),
    })),
  }
}

function issueView(snap: ProjectSnapshot, filters: QueryFilters, asOf: Date) {
  const issue = snap.issues.find((i) => i.number === filters.issueId)
  if (!issue) return { notFound: filters.issueId ?? null }
  const inf = inferLifecycle(issue, snap.timelines[issue.number] ?? [], asOf)
  const blockedBy = snap.deps.filter((d) => d.issue === issue.number).map((d) => d.dependsOn)
  const blocks = snap.deps.filter((d) => d.dependsOn === issue.number).map((d) => d.issue)
  return {
    issue: issue.number,
    title: issue.title,
    state: issue.state,
    column: inf.column,
    labels: issue.labels,
    assignee: issue.assignee,
    milestone: issue.milestone?.title ?? null,
    estimateDays: issue.facts.estimateDays,
    priority: issue.facts.priority,
    steepingDays: inf.steepingDays,
    blockedBy,
    blocks,
  }
}

function searchView(snap: ProjectSnapshot, filters: QueryFilters) {
  const q = (filters.query ?? '').toLowerCase().trim()
  const limit = Math.min(filters.limit ?? 20, 100)
  const hits = q
    ? snap.issues.filter(
        (i) => i.title.toLowerCase().includes(q) || i.labels.some((l) => l.toLowerCase().includes(q)),
      )
    : []
  return {
    query: filters.query ?? '',
    results: hits.slice(0, limit).map((i) => ({ issue: i.number, title: i.title, state: i.state, labels: i.labels })),
  }
}

/** Build the compact payload for one view. Unknown/unbuilt views return a marker. */
export function buildProjectView(
  view: ProjectView,
  filters: QueryFilters | undefined,
  snap: ProjectSnapshot,
  asOf: Date,
): unknown {
  const f = filters ?? {}
  switch (view) {
    case 'focus':
      return focusView(snap)
    case 'board':
      return boardView(snap, asOf)
    case 'calibration':
      return calibrationView(snap, asOf)
    case 'issue':
      return issueView(snap, f, asOf)
    case 'search':
      return searchView(snap, f)
    default:
      return { notImplemented: view }
  }
}
