import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { readActiveSessions } from "./active-sessions";
import { parsePlanIndexTable } from "./parse-index";
import { normalizeFileStatus, parsePlanFrontmatter } from "./parse-plan";
import {
  LiveReportDataSchema,
  PlanStatusSchema,
  type LiveReportData,
  type LiveReportPlan,
} from "./schema";

export type AssembleLiveReportOptions = {
  repoRoot: string;
  generatedAt: string;
  sourceRevision: string;
  indexRelPath?: string;
  existsSyncImpl?: typeof existsSync;
  readFileImpl?: (path: string) => string;
  readActiveSessionsImpl?: typeof readActiveSessions;
};

function receiptForPlan(plan: LiveReportPlan): string {
  return plan.planReceipt?.trim() || plan.indexReceipt.trim();
}

export function assembleLiveReport(options: AssembleLiveReportOptions): LiveReportData {
  const {
    repoRoot,
    generatedAt,
    sourceRevision,
    indexRelPath = "docs/plans/INDEX.md",
    existsSyncImpl = existsSync,
    readFileImpl = (path) => readFileSync(path, "utf8"),
    readActiveSessionsImpl = readActiveSessions,
  } = options;

  const indexPath = join(repoRoot, indexRelPath);
  const plansDir = join(repoRoot, "docs", "plans");
  const warnings: string[] = [];

  if (!existsSyncImpl(indexPath)) {
    throw new Error(`plan index missing: ${indexPath}`);
  }

  const indexRows = parsePlanIndexTable(readFileImpl(indexPath));
  const plans: LiveReportPlan[] = indexRows.map((row) => {
    const planPath = join(plansDir, row.href);
    let frontmatter = {
      status: "unknown",
    } as ReturnType<typeof parsePlanFrontmatter>;

    if (existsSyncImpl(planPath)) {
      frontmatter = parsePlanFrontmatter(readFileImpl(planPath));
    } else {
      warnings.push(`plan file missing for index row: ${row.href}`);
    }

    const fileStatus = normalizeFileStatus(frontmatter.status);
    const indexStatus = row.status;
    const fileStatusParsed = PlanStatusSchema.safeParse(fileStatus);
    const statusMismatch =
      fileStatusParsed.success && fileStatusParsed.data !== indexStatus;

    if (statusMismatch) {
      warnings.push(
        `status mismatch for ${row.title}: index=${indexStatus} file=${fileStatusParsed.data}`,
      );
    }

    return {
      priority: row.priority,
      indexStatus,
      fileStatus: frontmatter.status,
      statusMismatch,
      title: row.title,
      href: row.href,
      scope: row.scope,
      indexReceipt: row.indexReceipt,
      ...(frontmatter.worker ? { worker: frontmatter.worker } : {}),
      ...(frontmatter.taskIds ? { taskIds: frontmatter.taskIds } : {}),
      ...(frontmatter.planReceipt ? { planReceipt: frontmatter.planReceipt } : {}),
      ...(frontmatter.nextAction ? { nextAction: frontmatter.nextAction } : {}),
    };
  });

  const activeWorkers = plans
    .filter((plan) => plan.indexStatus === "ACTIVE")
    .map((plan) => ({
      planTitle: plan.title,
      worker: plan.worker?.trim() || "(no worker recorded in plan file)",
      href: plan.href,
      receipt: receiptForPlan(plan),
    }))
    .sort((left, right) => left.planTitle.localeCompare(right.planTitle));

  const blockers = plans
    .filter((plan) => plan.indexStatus === "BLOCKED")
    .map((plan) => ({
      planTitle: plan.title,
      href: plan.href,
      receipt: receiptForPlan(plan),
    }))
    .sort((left, right) => left.planTitle.localeCompare(right.planTitle));

  const activeSessions = readActiveSessionsImpl();

  return LiveReportDataSchema.parse({
    generatedAt,
    sourceRevision,
    indexPath,
    plansDir,
    plans,
    activeWorkers,
    blockers,
    activeSessions,
    warnings: warnings.sort(),
  });
}
