// Task progress state helpers. Kept DOM-free so Node tests can cover the
// "update_step without title" merge behavior used by the Web UI.

export function cloneProgressSteps(steps) {
  return Array.isArray(steps) ? steps.map(s => ({ ...s })) : [];
}

export function normalizeProgressStatus(status) {
  return ["pending", "in_progress", "completed"].includes(status) ? status : "pending";
}

export function normalizeProgressStep(step) {
  if (!step || typeof step !== "object") return null;
  const id = String(step.id || "").trim();
  const title = String(step.title || "").trim();
  const status = normalizeProgressStatus(step.status);
  if (!id || !title) return null;
  return { id, title, status };
}

// The checklist is a linear progress bar: work advances top-to-bottom. Models
// don't always send a `completed` update before moving the in_progress marker
// on (observed: later steps marked done while earlier ones dangle at
// in_progress), which renders as "green check below, red dot above". Enforce
// monotonic completion — any step before the last completed one is completed
// too — so a missed update self-heals instead of stranding earlier steps.
export function enforceMonotonicProgress(steps) {
  if (!Array.isArray(steps)) return [];
  let lastCompleted = -1;
  for (let i = 0; i < steps.length; i++) {
    if (steps[i] && steps[i].status === "completed") lastCompleted = i;
  }
  if (lastCompleted <= 0) return steps.map(s => ({ ...s }));
  return steps.map((s, i) => (i < lastCompleted ? { ...s, status: "completed" } : { ...s }));
}

export function applyProgressAction(progress, args) {
  const current = cloneProgressSteps(progress);
  if (!args || typeof args !== "object") return current;
  const action = args.action || "";
  if (action === "clear") return [];
  if (action === "set_plan") {
    const planned = Array.isArray(args.steps) ? args.steps.map(normalizeProgressStep).filter(Boolean) : [];
    return enforceMonotonicProgress(planned);
  }
  if (action === "update_step") {
    const raw = args.step;
    if (!raw || typeof raw !== "object") return current;
    const id = String(raw.id || "").trim();
    if (!id) return current;
    let found = false;
    const next = current.map((s) => {
      if (s.id !== id) return s;
      found = true;
      return {
        id: s.id,
        title: String(raw.title || s.title || "").trim(),
        status: normalizeProgressStatus(raw.status || s.status),
      };
    }).filter(s => s.title);
    if (!found && raw.title) {
      next.push({
        id,
        title: String(raw.title).trim(),
        status: normalizeProgressStatus(raw.status),
      });
    }
    return enforceMonotonicProgress(next);
  }
  return current;
}

export function progressActionsFromToolCalls(toolCalls, baseSteps = []) {
  let steps = cloneProgressSteps(baseSteps);
  let sawProgress = false;
  if (!Array.isArray(toolCalls)) return { steps, sawProgress };
  for (const tc of toolCalls) {
    const fn = (tc && tc.function && tc.function.name) || "";
    if (fn !== "task_progress") continue;
    let args = {};
    try {
      args = JSON.parse((tc.function && tc.function.arguments) || "{}");
    } catch (e) {
      args = {};
    }
    steps = applyProgressAction(steps, args);
    sawProgress = true;
  }
  return { steps, sawProgress };
}
