import type { I1ChangeSetReviewReadModel } from "@awp/contracts";
import { escapeHtml } from "./html.js";
import { protectedActionDisabled, renderLifecycleShell, renderStateBanner } from "./shell.js";
import { withQuery } from "./router.js";

const reviewTabs = [
  ["review", "Review"],
  ["files", "Files & Diff"],
  ["findings", "Findings"],
  ["evidence", "Evidence"],
  ["provenance", "Provenance"],
] as const;

type ReviewUiState = "reviewing" | "verifying" | "changes-requested" | "ready-to-merge" | "merged";
const reviewState = (model: I1ChangeSetReviewReadModel): ReviewUiState => {
  if (model.status === "merged") return "merged";
  if (model.status === "ready-to-merge") return "ready-to-merge";
  const review = model.reviews.at(-1);
  if (
    model.status === "reviewing" ||
    review?.status === "reviewing" ||
    review?.status === "requested"
  ) {
    return "reviewing";
  }
  if (review?.status === "submitted" && review.disposition === "approved") return "verifying";
  return "changes-requested";
};

export interface ReviewRenderContext {
  readonly projectName?: string;
  readonly factoryRunId?: string;
  readonly diff?: string;
}

const renderHeader = (
  model: I1ChangeSetReviewReadModel,
  state: ReviewUiState,
  tab: string,
  disabled: boolean,
  context: ReviewRenderContext,
): string => {
  const merged = state === "merged";
  const ready = state === "ready-to-merge";
  const review = model.reviews.at(-1);
  const badge = merged
    ? "✓ Merged / completed"
    : ready
      ? "● Trusted merge in progress"
      : state === "reviewing"
        ? "● Independent review in progress"
        : state === "verifying"
          ? "● Required checks pending"
          : "● Changes requested";
  const action = merged
    ? `<a class="btn primary" href="/projects/${encodeURIComponent(model.projectId)}?tab=plans">Continue Plan</a>`
    : ready
      ? `<button class="btn" type="button" disabled aria-disabled="true">Trusted merge automatic</button>`
      : state === "verifying"
        ? `<button class="btn" type="button" disabled aria-disabled="true">Checks reconcile automatically</button>`
        : state === "reviewing"
          ? `<button class="btn" type="button" disabled aria-disabled="true">Review in progress</button>`
          : `<button class="btn primary" type="button" data-command="start-correction" data-candidate="${escapeHtml(model.candidateDigest)}"${disabled ? ' disabled aria-disabled="true"' : ""}>Start correction</button>`;
  const tabs = merged
    ? ([
        ["outcome", "Outcome"],
        ["files", "Files & Diff"],
        ["evidence", "Evidence"],
        ["provenance", "Provenance"],
      ] as const)
    : reviewTabs;
  const identity = merged
    ? `Result: <span class="context-link">${escapeHtml(model.mergedResult?.target.reference ?? model.expectedTarget.reference)}@${escapeHtml(model.mergedResult?.target.revision ?? "Unknown")}</span>`
    : `Base: <span class="context-link">${escapeHtml(model.baseIdentity)}</span> → Candidate: <span class="context-link">${escapeHtml(model.candidateDigest)}</span><span class="divider"></span><span>Target: ${escapeHtml(model.expectedTarget.reference)}</span>`;
  const backHref = context.factoryRunId
    ? `/factory-runs/${encodeURIComponent(context.factoryRunId)}?project=${encodeURIComponent(model.projectId)}`
    : `/projects/${encodeURIComponent(model.projectId)}?tab=reviews`;
  const backLabel = context.factoryRunId ? `FactoryRun ${context.factoryRunId}` : "Project Changes";
  return `<div class="run-head"><div><div class="run-title">ChangeSet / Review <span class="id-badge">${escapeHtml(model.id)}</span></div><div class="context-strip"><span class="pill ${ready || merged ? "green" : "amber"}">${badge}</span><span>${review ? `Review ${escapeHtml(review.id)}` : "No Review"}</span><span class="divider"></span><span>${identity}</span></div></div><div class="actions"><a class="btn purple" href="${backHref}">↗ ${escapeHtml(backLabel)}</a>${action}</div></div><nav class="tabs" aria-label="ChangeSet / Review tabs">${tabs.map(([key, label]) => `<a class="tab${tab === key ? " active" : ""}" href="${withQuery(`/changesets/${model.id}`, { project: model.projectId, tab: key === (merged ? "outcome" : "review") ? undefined : key })}"${tab === key ? ' aria-current="page"' : ""}>${label}</a>`).join("")}</nav>`;
};

const renderDiff = (
  model: I1ChangeSetReviewReadModel,
  selectedFile?: string,
  diff?: string,
): string => {
  const files = model.candidate.changedPaths;
  const selected =
    selectedFile && files.includes(selectedFile) ? selectedFile : (files[0] ?? "No changed file");
  const changes = new Map(model.candidate.changes.map((change) => [change.path, change.kind]));
  const blocking = model.findings.find(
    (finding) => finding.severity === "blocking" && !finding.resolved,
  );
  const diffMarkup = diff
    ? diff
        .split("\n")
        .map((line) => {
          const tone =
            line.startsWith("+++") || line.startsWith("---") || line.startsWith("@@")
              ? ""
              : line.startsWith("+")
                ? " add"
                : line.startsWith("-")
                  ? " del"
                  : "";
          return `<div class="code-line${tone}"><span class="ln"></span><span class="ln"></span><span>${escapeHtml(line || " ")}</span></div>`;
        })
        .join("")
    : '<div class="code-line"><span class="ln"></span><span class="ln"></span><span>No stored diff is available.</span></div>';
  return `<section class="panel diff-panel"><nav class="file-tree" aria-label="Changed files"><div class="file-head">Changed files · ${files.length}</div>${files.map((file) => `<a class="file${file === selected ? " active" : ""}" href="${withQuery(`/changesets/${model.id}`, { project: model.projectId, file })}" data-file="${escapeHtml(file)}"${file === selected ? ' aria-current="true"' : ""}><span>${escapeHtml(file.split("/").at(-1) ?? file)}</span><span class="chg">${escapeHtml(changes.get(file) ?? "modify")}</span></a>`).join("")}</nav><div class="diff-view"><div class="diff-toolbar"><span data-selected-file>${escapeHtml(selected)}</span><span class="subtle">Candidate ${escapeHtml(model.candidateDigest)} · authoritative stored patch</span></div><div class="code" tabindex="0" aria-label="Source diff for ${escapeHtml(selected)}">${diffMarkup}${blocking ? `<div class="callout red diff-callout"><div class="callout-title">Blocking finding</div><div class="callout-sub">${escapeHtml(blocking.summary)}${blocking.source ? ` · ${escapeHtml(blocking.source)}` : ""}</div></div>` : ""}</div></div></section>`;
};

const renderEvidence = (model: I1ChangeSetReviewReadModel): string =>
  `<section class="panel"><div class="panel-head"><h2>Verification evidence</h2><p>Bound to candidate ${escapeHtml(model.candidateDigest)}</p></div><div class="panel-body">${model.evidence.map((evidence) => `<div class="gate-row"><span class="gate-mark${evidence.state === "passed" ? "" : " bad"}">${evidence.state === "passed" ? "✓" : "!"}</span><div><div class="gate-name">${escapeHtml(evidence.name)}</div><div class="gate-sub">${escapeHtml(evidence.source)} · ${escapeHtml(evidence.observedAt)}</div></div><span class="pill ${evidence.state === "passed" ? "green" : "red"}">${escapeHtml(evidence.state)}</span></div>`).join("")}</div></section>`;

const renderBlockedSide = (
  model: I1ChangeSetReviewReadModel,
  disabled: boolean,
  selectedFinding?: string,
): string => {
  const blocking = model.findings.filter(
    (finding) => finding.severity === "blocking" && !finding.resolved,
  );
  return `<aside class="review-side"><section class="panel"><div class="panel-head"><h2>Review disposition</h2><p>Independent Review</p></div><div class="panel-body"><div class="callout red"><div class="callout-title">Changes requested</div><div class="callout-sub">${blocking.length} blocking finding${blocking.length === 1 ? "" : "s"} prevent Merge. Candidate revision is immutable.</div></div>${model.findings.map((finding) => `<a class="finding${selectedFinding === finding.id ? " selected" : ""}" href="${withQuery(`/changesets/${model.id}`, { finding: finding.id })}" data-finding="${escapeHtml(finding.id)}"><div class="finding-top"><span class="finding-title">${escapeHtml(finding.summary)}</span><span class="pill ${finding.severity === "blocking" ? "red" : "blue"}">${escapeHtml(finding.severity === "blocking" ? "Blocking" : "Recommendation")}</span></div><div class="finding-sub">${escapeHtml(finding.source ?? "Source not provided")}</div></a>`).join("")}</div></section>${renderEvidence(model)}<div class="merge-gate side-gate"><div><h2>MergeGate — not ready</h2><p>${escapeHtml(model.mergeGate.action.summary)}</p></div><button class="btn primary" type="button" data-command="start-correction" data-candidate="${escapeHtml(model.candidateDigest)}"${disabled ? ' disabled aria-disabled="true"' : ""}>Start correction</button></div></aside>`;
};

const renderPendingSide = (
  model: I1ChangeSetReviewReadModel,
  state: "reviewing" | "verifying",
): string => {
  const title =
    state === "reviewing" ? "Independent review in progress" : "Repository checks pending";
  const summary =
    state === "reviewing"
      ? "The assigned reviewer AgentRun is evaluating this exact candidate. Merge remains unavailable."
      : "Independent Review approved this candidate, but required verification is not yet fully passing.";
  const action =
    state === "verifying"
      ? `<button class="btn" type="button" disabled aria-disabled="true">Checks reconcile automatically</button>`
      : `<button class="btn" type="button" disabled aria-disabled="true">Review in progress</button>`;
  return `<aside class="review-side"><section class="panel"><div class="panel-head"><h2>${title}</h2><p>Exact candidate ${escapeHtml(model.candidateDigest)}</p></div><div class="panel-body"><div class="callout purple"><div class="callout-title">${title}</div><div class="callout-sub">${summary}</div></div>${model.mergeGate.conditions.map((condition) => `<div class="gate-row"><span class="gate-mark${condition.satisfied ? "" : " bad"}">${condition.satisfied ? "✓" : "!"}</span><div><div class="gate-name">${escapeHtml(condition.code)}</div><div class="gate-sub">${escapeHtml(condition.summary)}</div></div><span class="pill ${condition.satisfied ? "green" : "red"}">${condition.satisfied ? "Satisfied" : "Blocked"}</span></div>`).join("")}</div></section>${renderEvidence(model)}<div class="merge-gate side-gate"><div><h2>MergeGate — not ready</h2><p>${escapeHtml(model.mergeGate.action.summary)}</p></div>${action}</div></aside>`;
};

const renderReadySide = (model: I1ChangeSetReviewReadModel): string =>
  `<aside class="review-side"><section class="panel"><div class="panel-head"><h2>MergeGate</h2><p>Protected conditions are projected from authoritative state.</p></div><div class="panel-body"><div class="callout"><div class="callout-title">✓ Safe to merge this exact candidate</div><div class="callout-sub">The control plane revalidates these conditions and continues trusted Merge automatically.</div></div>${model.mergeGate.conditions.map((condition) => `<div class="gate-row"><span class="gate-mark${condition.satisfied ? "" : " bad"}">${condition.satisfied ? "✓" : "!"}</span><div><div class="gate-name">${escapeHtml(condition.code)}</div><div class="gate-sub">${escapeHtml(condition.summary)}</div></div><span class="pill ${condition.satisfied ? "green" : "red"}">${condition.satisfied ? "Satisfied" : "Blocked"}</span></div>`).join("")}</div></section>${renderEvidence(model)}<section class="panel"><div class="panel-head"><h2>Authority boundary</h2></div><div class="panel-body"><div class="detail-row"><b>AgentRun</b><span>Cannot merge</span></div><div class="detail-row"><b>Merge credential</b><span>Trusted control plane only</span></div><div class="detail-row"><b>Expected target</b><span>${escapeHtml(model.expectedTarget.reference)}@${escapeHtml(model.expectedTarget.revision)}</span></div></div></section><div class="merge-gate side-gate"><div><h2>Automatic protected transition</h2><p>${escapeHtml(model.mergeGate.action.summary)}</p></div><button class="btn" type="button" disabled aria-disabled="true">Trusted merge automatic</button></div></aside>`;

const renderMerged = (model: I1ChangeSetReviewReadModel): string =>
  `<div class="outcome-grid"><section class="panel"><div class="panel-head"><h2>Merge outcome</h2><p>Read-only terminal result for the exact candidate.</p></div><div class="panel-body"><div class="callout"><div class="callout-title">✓ Trusted Merge completed successfully</div><div class="callout-sub">The provider result was reconciled to canonical state.</div></div><div class="detail-list"><div class="detail-row"><b>Resulting commit</b><a class="link" href="#provider-commit">${escapeHtml(model.mergedResult?.target.revision ?? "Unknown")}</a></div><div class="detail-row"><b>Target</b><span>${escapeHtml(model.mergedResult?.target.reference ?? model.expectedTarget.reference)}</span></div><div class="detail-row"><b>ChangeSet</b><span>${escapeHtml(model.id)}</span></div><div class="detail-row"><b>Evidence</b><span>${model.evidence.filter((evidence) => evidence.state === "passed").length} / ${model.evidence.length} passed</span></div></div></div></section><section class="panel"><div class="panel-head"><h2>Plan / Goal effect</h2><p>Completion advances the Project without collapsing Goal and Plan state.</p></div><div class="panel-body"><div class="callout purple"><div class="callout-title">Goal advanced</div><div class="callout-sub">Dogfood the first complete lifecycle.</div></div><div class="detail-row"><b>Next</b><a class="link" href="/projects/${encodeURIComponent(model.projectId)}/queue">Open dependency-aware Queue</a></div></div></section></div>`;

const renderLoadingReview = (): string =>
  `<div class="review-layout" aria-busy="true" aria-label="Loading ChangeSet Review"><section class="panel skeleton-panel"><div class="panel-head"><h2>Files &amp; Diff</h2></div><div class="skeleton-lines"><span></span><span></span><span></span></div></section><aside class="review-side"><section class="panel skeleton-panel"><div class="panel-head"><h2>Review / MergeGate</h2></div><div class="skeleton-lines"><span></span><span></span></div></section></aside></div>`;

export function renderReviewPage(
  model: I1ChangeSetReviewReadModel,
  surfaceState = "ready",
  tab = "review",
  selectedFile?: string,
  selectedFinding?: string,
  context: ReviewRenderContext = {},
): string {
  const state = reviewState(model);
  const effectiveSurface = ["ready", "loading", "error", "stale", "permission"].includes(
    surfaceState,
  )
    ? surfaceState
    : "ready";
  const defaultTab = state === "merged" ? "outcome" : "review";
  const validTabs =
    state === "merged"
      ? new Set(["outcome", "files", "evidence", "provenance"])
      : new Set(reviewTabs.map(([key]) => key));
  const activeTab = validTabs.has(tab) ? tab : defaultTab;
  const disabled = protectedActionDisabled(effectiveSurface);
  let content = renderHeader(model, state, activeTab, disabled, context);
  if (effectiveSurface === "loading") {
    content += renderLoadingReview();
    return renderLifecycleShell({
      active: "reviews",
      breadcrumbs: ["AWP", context.projectName ?? "Project", "ChangeSet", "Review"],
      content,
      project: { id: model.projectId, name: context.projectName ?? "Project" },
      ...(context.factoryRunId === undefined ? {} : { latestFactoryRunId: context.factoryRunId }),
      latestChangeSetId: model.id,
    });
  }
  content += renderStateBanner(effectiveSurface, "review");
  if (state === "merged") content += renderMerged(model);
  else
    content += `<div class="review-layout">${renderDiff(model, selectedFile)}${state === "ready-to-merge" ? renderReadySide(model) : state === "reviewing" || state === "verifying" ? renderPendingSide(model, state) : renderBlockedSide(model, disabled, selectedFinding)}</div>`;
  return renderLifecycleShell({
    active: "reviews",
    breadcrumbs: ["AWP", context.projectName ?? "Project", "ChangeSet", "Review"],
    content,
    project: { id: model.projectId, name: context.projectName ?? "Project" },
    ...(context.factoryRunId === undefined ? {} : { latestFactoryRunId: context.factoryRunId }),
    latestChangeSetId: model.id,
  });
}
