import type { ArbiterVerdict } from './types.js';

export type VisibleVerdict =
  | 'step_done'
  | 'task_done'
  | 'done'
  | 'done_with_concerns'
  | 'blocked'
  | 'needs_context'
  | 'continue';

export type ArbiterVerdictResult = ArbiterVerdict | 'unknown';

const VISIBLE_VERDICT_SCAN_LINE_LIMIT = 12;
const ARBITER_VERDICT_SCAN_LINE_LIMIT = 12;

function leadingVisibleLines(text: string, limit: number): string[] {
  const lines: string[] = [];
  let inFence = false;

  for (const rawLine of text.split('\n')) {
    const line = rawLine.trim();
    if (/^(```|~~~)/.test(line)) {
      inFence = !inFence;
      continue;
    }
    if (inFence || line.length === 0) {
      continue;
    }
    lines.push(line);
    if (lines.length >= limit) {
      break;
    }
  }

  return lines;
}

function stripInternalBlocks(text: string): string {
  return text.replace(/<internal>[\s\S]*?(?:<\/internal>|$)/g, '');
}

function parseVisibleVerdictLine(line: string): VisibleVerdict | null {
  if (/^\*{0,2}BLOCKED(?:\*{0,2})?\b/i.test(line)) return 'blocked';
  if (/^\*{0,2}NEEDS_CONTEXT(?:\*{0,2})?\b/i.test(line)) return 'needs_context';
  if (/^\*{0,2}STEP_DONE(?:\*{0,2})?\b/i.test(line)) return 'step_done';
  if (/^\*{0,2}TASK_DONE(?:\*{0,2})?\b/i.test(line)) return 'task_done';
  if (/^\*{0,2}DONE_WITH_CONCERNS(?:\*{0,2})?\b/i.test(line))
    return 'done_with_concerns';
  if (/^\*{0,2}DONE(?:\*{0,2})?\b/i.test(line)) return 'done';
  if (/^\*{0,2}Approved\.?(?:\*{0,2})?/i.test(line)) return 'done';
  if (/^\*{0,2}LGTM(?:\*{0,2})?/i.test(line)) return 'done';
  return null;
}

export function parseVisibleVerdict(
  summary: string | null | undefined,
): VisibleVerdict {
  if (!summary) return 'continue';
  const cleaned = stripInternalBlocks(summary).trim();
  if (!cleaned) return 'continue';
  for (const line of leadingVisibleLines(
    cleaned,
    VISIBLE_VERDICT_SCAN_LINE_LIMIT,
  )) {
    const verdict = parseVisibleVerdictLine(line);
    if (verdict) return verdict;
  }
  return 'continue';
}

/**
 * Remove a leading status/verdict marker line (e.g. "TASK_DONE", "STEP_DONE")
 * from owner output so a held answer can be shown to the user directly without
 * the internal status token. Only strips when the very first non-empty line is
 * a recognised marker; otherwise returns the text untouched.
 */
export function stripLeadingStatusLine(text: string): string {
  const lines = text.split('\n');
  let firstNonEmpty = 0;
  while (firstNonEmpty < lines.length && lines[firstNonEmpty].trim() === '') {
    firstNonEmpty += 1;
  }
  if (
    firstNonEmpty >= lines.length ||
    !parseVisibleVerdictLine(lines[firstNonEmpty].trim())
  ) {
    return text;
  }
  const remainder = lines.slice(firstNonEmpty + 1);
  while (remainder.length > 0 && remainder[0].trim() === '') {
    remainder.shift();
  }
  return remainder.join('\n').trim();
}

const REVIEWER_APPROVAL_LINE =
  /^\*{0,2}(?:VERDICT\s*[:—-]\s*)?PROCEED(?:\*{0,2})?\b/i;

/**
 * Reviewers — especially the Codex reviewer — approve a turn with an
 * arbiter-style "PROCEED" line instead of the documented TASK_DONE. Plain
 * parseVisibleVerdict() does not know "PROCEED" and maps it to 'continue',
 * which resolveReviewerCompletionSignal() treats as a change request. So an
 * *approving* reviewer silently re-opens the owner turn and the two roles
 * ping-pong (owner TASK_DONE ↔ reviewer PROCEED) until the deadlock cap fires —
 * the user sees a burst of near-identical messages that then just stops.
 *
 * Treat a leading PROCEED as an approval (equivalent to DONE) so the state
 * machine routes to owner finalize and the task actually completes after one
 * round instead of looping. Only PROCEED is upgraded; REVISE/RESET/plain text
 * stay 'continue' (a genuine change request).
 */
export function parseReviewerVerdict(
  summary: string | null | undefined,
): VisibleVerdict {
  const verdict = parseVisibleVerdict(summary);
  if (verdict !== 'continue' || !summary) return verdict;
  const cleaned = stripInternalBlocks(summary).trim();
  if (!cleaned) return verdict;
  for (const line of leadingVisibleLines(
    cleaned,
    VISIBLE_VERDICT_SCAN_LINE_LIMIT,
  )) {
    if (REVIEWER_APPROVAL_LINE.test(line)) return 'done';
  }
  return verdict;
}

export function classifyArbiterVerdict(
  summary: string | null | undefined,
): ArbiterVerdictResult {
  if (!summary) return 'unknown';
  const cleaned = stripInternalBlocks(summary).trim();
  if (!cleaned) return 'unknown';
  for (const line of leadingVisibleLines(
    cleaned,
    ARBITER_VERDICT_SCAN_LINE_LIMIT,
  )) {
    const verdictMatch = line.match(
      /\*{0,2}(?:VERDICT\s*[:—-]\s*)?(PROCEED|REVISE|RESET|ESCALATE|CONTINUE)\*{0,2}/i,
    );
    if (verdictMatch) {
      const normalized = verdictMatch[1].toLowerCase();
      return normalized === 'continue'
        ? 'proceed'
        : (normalized as ArbiterVerdict);
    }
  }
  return 'unknown';
}

export function resolveStoredVisibleVerdict(args: {
  verdict?: VisibleVerdict | null;
  outputText?: string | null;
}): VisibleVerdict | null {
  if (args.verdict) {
    return args.verdict;
  }
  if (!args.outputText) {
    return null;
  }
  return parseVisibleVerdict(args.outputText);
}
