import { existsSync, readFileSync } from "node:fs";
import { join, resolve } from "node:path";

interface ExplainRoute {
  kind: "page" | "api" | "realtime";
  path: string;
  source: string;
  execution: string;
  canPrerender: boolean;
  needsClientRuntime: boolean;
  needsServerRuntime: boolean;
  hydrationStrategy: string | null;
  reasons?: string[];
  cachePolicy?: Record<string, string>;
  requiredPermission?: string | null;
}

interface ExplainReport {
  frameworkVersion: string;
  adapter: string;
  routes: ExplainRoute[];
  assets: Array<{ file: string; bytes: number }>;
  measurements: Record<string, number>;
  budgetViolations: Array<{ metric: string; budget: number; actual: number }>;
}

export interface Explanation {
  target: string;
  subject: string;
  summary: string;
  reasons: string[];
  evidence: Record<string, unknown>;
}

function loadReport(root: string): ExplainReport {
  const path = join(resolve(root), "dist", "build-report.json");
  if (!existsSync(path)) {
    throw new Error(
      "WRN-EXPLAIN-NO-BUILD: run `wrnexus build` before requesting build explanations.",
    );
  }
  return JSON.parse(readFileSync(path, "utf8")) as ExplainReport;
}

function routeMatch(routes: ExplainRoute[], subject: string): ExplainRoute | undefined {
  const normalized = subject.startsWith("/") ? subject : `/${subject}`;
  return routes.find((route) => route.path === normalized || route.source.includes(subject));
}

export function explainBuildDecision(root: string, target: string, subject = ""): Explanation {
  const report = loadReport(root);
  if (
    target === "route" ||
    target === "hydration" ||
    target === "cache" ||
    target === "permission"
  ) {
    const route =
      target === "permission"
        ? report.routes.find((item) => item.requiredPermission === subject)
        : routeMatch(report.routes, subject);
    if (!route) throw new Error(`WRN-EXPLAIN-NOT-FOUND: no route or source matches '${subject}'.`);
    if (target === "cache") {
      const policy = route.cachePolicy ?? {};
      const entries = Object.entries(policy);
      return {
        target,
        subject: route.path,
        summary: entries.length
          ? `Route cache uses '${policy.strategy ?? "framework-default"}' strategy.`
          : "Route has no explicit cache policy and uses safe framework defaults.",
        reasons: entries.length
          ? entries.map(([name, value]) => `${name} = ${value}`)
          : ["responses remain private/revalidated unless an explicit safe policy enables reuse"],
        evidence: { source: route.source, cachePolicy: policy, execution: route.execution },
      };
    }
    if (target === "permission") {
      const requested = subject.startsWith("/") ? undefined : subject;
      const matches = report.routes.filter((item) =>
        requested ? item.requiredPermission === requested : item.path === route.path,
      );
      return {
        target,
        subject: requested ?? route.path,
        summary: matches.length
          ? `${matches.length} route(s) require this permission.`
          : "No built route declares this permission.",
        reasons: matches.length
          ? matches.map((item) => `${item.path} declares security.permission in ${item.source}`)
          : ["authorization may still be enforced programmatically; inspect authz policies"],
        evidence: { routes: matches },
      };
    }
    const reasons = route.reasons?.length ? route.reasons : ["no dynamic requirement was detected"];
    return {
      target,
      subject: route.path,
      summary:
        target === "hydration"
          ? route.needsClientRuntime
            ? `Hydration uses '${route.hydrationStrategy ?? "load"}' because client runtime is required.`
            : "Hydration is omitted because no client runtime is required."
          : `Route execution is '${route.execution}'${route.canPrerender ? " and can prerender" : " and cannot prerender"}.`,
      reasons,
      evidence: { ...route },
    };
  }
  if (target === "bundle") {
    const assets = [...report.assets].sort((a, b) => b.bytes - a.bytes);
    return {
      target,
      subject: subject || "production bundle",
      summary: `${assets.length} emitted assets; largest is ${assets[0]?.file ?? "none"}.`,
      reasons: assets.slice(0, 10).map((asset) => `${asset.file}: ${asset.bytes} bytes`),
      evidence: { measurements: report.measurements, largestAssets: assets.slice(0, 10) },
    };
  }
  if (target === "build") {
    return {
      target,
      subject: "production build",
      summary: `${report.routes.length} routes target the ${report.adapter} adapter.`,
      reasons: report.budgetViolations.length
        ? report.budgetViolations.map(
            (item) => `${item.metric} exceeds ${item.budget} with ${item.actual}`,
          )
        : ["all configured performance budgets pass"],
      evidence: {
        frameworkVersion: report.frameworkVersion,
        adapter: report.adapter,
        measurements: report.measurements,
      },
    };
  }
  throw new Error(`WRN-EXPLAIN-TARGET: unsupported target '${target}'.`);
}

export function runExplain(root: string, target: string, subject: string, args: string[]): void {
  const explanation = explainBuildDecision(root, target, subject);
  if (args.includes("--json")) {
    console.log(JSON.stringify(explanation, null, 2));
    return;
  }
  console.log(explanation.summary);
  explanation.reasons.forEach((reason, index) => console.log(`  ${index + 1}. ${reason}`));
}
