/**
 * capture_work — braindump → a small set of concrete issues. This is the one
 * place the big model earns its keep (decomposition + estimate negotiation).
 * It returns a *proposal*; nothing is filed until the human approves it in the
 * Capture tray and it goes through the create-issue write path. Pure
 * orchestration over an injected `complete` — stubbable, so it's testable offline.
 */

import {
  type EstimateLabel,
  ESTIMATE_LABELS,
  type PriorityLabel,
  PRIORITY_LABELS,
} from '../labels/label-schema.js'
import type { ChatMessage, CompletionResult, ToolDecl } from './chat-client.js'

export interface ProposedIssue {
  title: string
  body: string
  estimate?: EstimateLabel
  priority?: PriorityLabel
}

export interface CaptureProposal {
  issues: ProposedIssue[]
  /** One-line schedule impact, if the model offered one. */
  consequence?: string
}

export const PROPOSE_ISSUES_TOOL: ToolDecl = {
  name: 'propose_issues',
  description: 'Return the decomposed issue set for a braindump. Call this exactly once.',
  parameters: {
    type: 'object',
    properties: {
      issues: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            title: { type: 'string', description: 'a clear imperative title' },
            body: { type: 'string', description: 'one or two lines of detail' },
            estimate: { type: 'string', enum: ['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'] },
            priority: { type: 'string', enum: ['p/1', 'p/2', 'p/3', 'p/4'] },
          },
          required: ['title'],
        },
      },
      consequence: { type: 'string', description: 'one-line note on the schedule impact' },
    },
    required: ['issues'],
  },
}

export const CAPTURE_SYSTEM = [
  'You are Reginald, decomposing a rough braindump into a small set of concrete Gitea issues.',
  'Call propose_issues exactly once. Split genuinely separate work; merge trivially-coupled work; invent no scope.',
  'Each issue gets an imperative title, a one-line body, an estimate (est/1d…8d) and a priority (p/1…4).',
  'Estimate honestly — a "quick" task is rarely one day. Keep the set tight; three good issues beat eight vague ones.',
].join(' ')

function isEstimate(v: unknown): v is EstimateLabel {
  return typeof v === 'string' && (ESTIMATE_LABELS as readonly string[]).includes(v)
}
function isPriority(v: unknown): v is PriorityLabel {
  return typeof v === 'string' && (PRIORITY_LABELS as readonly string[]).includes(v)
}

/** Coerce the model's raw propose_issues args into a validated proposal. */
export function parseCaptureArgs(args: unknown): CaptureProposal {
  const a = (args ?? {}) as { issues?: unknown[]; consequence?: unknown }
  const issues: ProposedIssue[] = []
  for (const raw of Array.isArray(a.issues) ? a.issues : []) {
    const r = (raw ?? {}) as Record<string, unknown>
    const title = typeof r.title === 'string' ? r.title.trim() : ''
    if (!title) continue
    issues.push({
      title,
      body: typeof r.body === 'string' ? r.body : '',
      estimate: isEstimate(r.estimate) ? r.estimate : undefined,
      priority: isPriority(r.priority) ? r.priority : undefined,
    })
  }
  return { issues, consequence: typeof a.consequence === 'string' ? a.consequence : undefined }
}

/**
 * Run one decomposition turn. Forces the model to answer via propose_issues and
 * returns the validated set. An empty set means the model declined to structure it.
 */
export async function captureWork(
  complete: (messages: ChatMessage[], tools?: ToolDecl[]) => Promise<CompletionResult>,
  braindump: string,
): Promise<CaptureProposal> {
  const res = await complete(
    [
      { role: 'system', content: CAPTURE_SYSTEM },
      { role: 'user', content: braindump },
    ],
    [PROPOSE_ISSUES_TOOL],
  )
  const call = res.toolCalls.find((t) => t.name === 'propose_issues')
  if (!call) return { issues: [] }
  try {
    return parseCaptureArgs(JSON.parse(call.arguments || '{}'))
  } catch {
    return { issues: [] }
  }
}
