/**
 * Codex Plan Parser
 *
 * Pure functions for extracting plan/todo items from various Codex
 * plan payload formats (structured objects, markdown, step arrays).
 */

import type { PlanTodo } from "./codex-types.js";

/**
 * Extract plan todos from a turn/plan/updated params object.
 * Tries structured fields first, falls back to accumulated delta markdown.
 */
export function extractPlanTodos(
  params: Record<string, unknown>,
  turnId: string,
  planDeltaByTurnId: Map<string, string>,
): PlanTodo[] {
  const directPlan = params.plan;
  const turnObj = params.turn as Record<string, unknown> | undefined;
  const nestedPlan = turnObj?.plan;

  const fromPlanObject = extractPlanTodosFromUnknown(
    directPlan !== undefined ? directPlan : nestedPlan,
  );
  if (fromPlanObject.length > 0) {
    return fromPlanObject;
  }

  const fallbackDelta = planDeltaByTurnId.get(turnId);
  if (!fallbackDelta) return [];
  return extractPlanTodosFromMarkdown(fallbackDelta);
}

/**
 * Extract plan todos from an unknown input (structured object or string).
 */
export function extractPlanTodosFromUnknown(input: unknown): PlanTodo[] {
  if (typeof input === "string") {
    return extractPlanTodosFromMarkdown(input);
  }

  if (!input || typeof input !== "object") {
    return [];
  }

  const obj = input as Record<string, unknown>;
  const stepArrayCandidates = [
    obj.steps,
    obj.items,
    obj.planSteps,
    (obj.plan as Record<string, unknown> | undefined)?.steps,
    (obj.plan as Record<string, unknown> | undefined)?.items,
  ];

  for (const candidate of stepArrayCandidates) {
    if (!Array.isArray(candidate)) continue;
    const todos: PlanTodo[] = [];
    for (const step of candidate) {
      if (typeof step === "string") {
        const trimmed = step.trim();
        if (trimmed) todos.push({ content: trimmed, status: "pending" });
        continue;
      }
      if (!step || typeof step !== "object") continue;
      const stepObj = step as Record<string, unknown>;
      const content = firstString(stepObj, ["content", "text", "title", "description", "step", "name"]);
      if (!content) continue;
      const status = normalizePlanStatus(firstString(stepObj, ["status", "state", "phase"]));
      const activeForm = firstString(stepObj, ["activeForm", "active_form", "inProgressText", "in_progress_text"]);
      todos.push({
        content,
        status,
        ...(activeForm ? { activeForm } : {}),
      });
    }
    if (todos.length > 0) return todos;
  }

  const markdown = firstString(obj, ["markdown", "text", "content"]);
  if (markdown) {
    return extractPlanTodosFromMarkdown(markdown);
  }

  return [];
}

/**
 * Parse markdown-formatted plan text into structured PlanTodo items.
 * Supports checkbox lists (- [x], - [ ]), unordered lists, and numbered lists.
 */
export function extractPlanTodosFromMarkdown(markdown: string): PlanTodo[] {
  const todos: PlanTodo[] = [];
  for (const rawLine of markdown.split(/\r?\n/)) {
    const line = rawLine.trim();
    if (!line) continue;

    let match = line.match(/^[-*]\s+\[(x|X|~|>| )\]\s+(.+)$/);
    if (match) {
      const marker = match[1].toLowerCase();
      const status = marker === "x" ? "completed"
        : (marker === "~" || marker === ">") ? "in_progress"
        : "pending";
      todos.push({ content: match[2].trim(), status });
      continue;
    }

    match = line.match(/^[-*]\s+(.+)$/);
    if (match) {
      todos.push({ content: match[1].trim(), status: "pending" });
      continue;
    }

    match = line.match(/^\d+\.\s+(.+)$/);
    if (match) {
      todos.push({ content: match[1].trim(), status: "pending" });
    }
  }
  return todos;
}

/**
 * Return the first non-empty string value found among the given keys.
 */
export function firstString(obj: Record<string, unknown>, keys: string[]): string | null {
  for (const key of keys) {
    const value = obj[key];
    if (typeof value === "string" && value.trim()) {
      return value.trim();
    }
  }
  return null;
}

/**
 * Normalize a raw plan step status string to a canonical PlanTodo status.
 */
export function normalizePlanStatus(statusRaw: string | null): "pending" | "in_progress" | "completed" {
  const status = (statusRaw || "").toLowerCase();
  if (
    status === "completed"
    || status === "done"
    || status === "complete"
    || status === "success"
    || status === "succeeded"
  ) {
    return "completed";
  }
  if (
    status === "in_progress"
    || status === "inprogress"
    || status === "active"
    || status === "running"
    || status === "current"
  ) {
    return "in_progress";
  }
  return "pending";
}
