// SPEC: PJ2026-010606 GitHub入口 draft-2026-06-25-gh-split
// Moved mechanically from scripts/src/gh.ts:1669-1889.

import { bodySafetySignals, preview, previewLines } from "./auth-and-safety";
import { isMarkdownTableSeparator } from "./board-parser";
import { CODE_QUEUE_BOARD_TARGET_ISSUE, COMMANDER_BRIEF_UPDATE_HEADING_PATTERNS } from "./types";
import type { CommanderBriefDiff, CommanderBriefSection } from "./types";

export function dryRunBody(repo: string, title: string | undefined, body: string): Record<string, unknown> {
  return {
    repo,
    ...(title === undefined ? {} : { title }),
    bodyPreview: preview(body),
    bodyPreviewLines: previewLines(body),
    ...bodySafetySignals(body),
  };
}

export function normalizeNewlines(text: string): string {
  return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
}

export function isTimelineHeading(line: string): boolean {
  return /^##\s+更新\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+北京时间\s*$/u.test(line.trimEnd());
}

export function isCommanderBriefUpdateHeading(line: string): boolean {
  const trimmed = line.trimEnd();
  return COMMANDER_BRIEF_UPDATE_HEADING_PATTERNS.some((pattern) => pattern.test(trimmed));
}

export function commanderBriefUpdateHeadings(body: string): string[] {
  return normalizeNewlines(body)
    .split("\n")
    .map((line) => line.trimEnd())
    .filter((line) => isCommanderBriefUpdateHeading(line));
}

export function codeQueueBoardHwlabProductRoutingFindings(body: string | undefined): Array<Record<string, unknown>> {
  if (body === undefined) return [];
  const findings: Array<Record<string, unknown>> = [];
  const directIssuePatterns = [
    { kind: "hwlab-repo-issue-url", pattern: /(?:https?:\/\/github\.com\/)?pikasTech\/HWLAB\/issues\/\d+/iu },
    { kind: "hwlab-repo-issue-short-ref", pattern: /\bpikasTech\/HWLAB#\d+\b/iu },
    { kind: "hwlab-short-issue-ref", pattern: /\bHWLAB#\d+\b/iu },
  ];
  const hwlabContextPattern = /\bHWLAB\b|pikasTech\/HWLAB|hwlab-/iu;
  const strongProductPattern = /hwlab-cloud-web|Cloud Workbench|DEV-LIVE|res_boxsimu|hwlab-patch-panel|patch panel|M3\s*(?:虚拟硬件|可信闭环)/iu;
  const genericProductPattern = /用户反馈|user feedback/iu;
  const governanceContextPattern = /\b(?:commander|Code Queue|CLI|infra|governance|guard|guardrail|routing|misfile|board)\b|治理|调度|基础设施|守卫|边界|分流|看板/iu;
  const lines = normalizeNewlines(body).split("\n");
  for (let index = 0; index < lines.length; index += 1) {
    const line = lines[index];
    const isTableRow = line.trim().startsWith("|") && !isMarkdownTableSeparator(line);
    const strongProductSignal = strongProductPattern.test(line);
    const genericProductSignal = genericProductPattern.test(line);
    const governanceContext = governanceContextPattern.test(line);
    for (const definition of directIssuePatterns) {
      const match = definition.pattern.exec(line);
      if (match !== null && !governanceContext && (isTableRow || strongProductSignal || (genericProductSignal && hwlabContextPattern.test(line)))) {
        findings.push({
          kind: definition.kind,
          lineNumber: index + 1,
          match: match[0],
          snippet: preview(line.trim()),
        });
      }
    }
    if (isTableRow && !governanceContext && (strongProductSignal || (genericProductSignal && hwlabContextPattern.test(line)))) {
      const match = strongProductSignal ? strongProductPattern.exec(line) : genericProductPattern.exec(line);
      findings.push({
        kind: "hwlab-product-signal",
        lineNumber: index + 1,
        match: match?.[0] ?? "HWLAB product signal",
        snippet: preview(line.trim()),
      });
    }
    if (findings.length >= 12) break;
  }
  return findings;
}

export function codeQueueBoardHwlabProductRoutingHint(boardIssueNumber: number, body?: string): Record<string, unknown> | null {
  if (boardIssueNumber !== CODE_QUEUE_BOARD_TARGET_ISSUE) return null;
  const findings = codeQueueBoardHwlabProductRoutingFindings(body);
  return {
    warning: "#20 is only for UniDesk commander/Code Queue/CLI/infra governance; HWLAB user/product issues belong in pikasTech/HWLAB.",
    route: "Create or update the corresponding issue in pikasTech/HWLAB; keep #20 rows limited to UniDesk governance or infrastructure support work.",
    forbiddenPatterns: [
      "pikasTech/HWLAB#<N>",
      "github.com/pikasTech/HWLAB/issues/<N>",
      "HWLAB#<N>",
      "HWLAB product/live validation rows",
    ],
    allowedScope: ["commander governance", "Code Queue supervision", "UniDesk CLI guardrails", "UniDesk infrastructure"],
    detected: findings.length > 0,
    findings,
  };
}

export function codeQueueBoardHintHasHwlabProductRouting(hint: Record<string, unknown> | null): boolean {
  if (hint === null) return false;
  const routing = hint.hwlabProductRouting;
  return typeof routing === "object" && routing !== null && (routing as { detected?: unknown }).detected === true;
}

export function codeQueueBoardHintHasCommanderBriefUpdates(hint: Record<string, unknown> | null): boolean {
  if (hint === null) return false;
  const commanderBrief = hint.commanderBrief;
  return typeof commanderBrief === "object" && commanderBrief !== null && (commanderBrief as { detected?: unknown }).detected === true;
}

export function codeQueueBoardCommanderBriefHint(boardIssueNumber: number, body?: string): Record<string, unknown> | null {
  if (boardIssueNumber !== CODE_QUEUE_BOARD_TARGET_ISSUE) return null;
  const headings = body === undefined ? [] : commanderBriefUpdateHeadings(body);
  const hwlabProductRouting = codeQueueBoardHwlabProductRoutingHint(boardIssueNumber, body);
  const commanderBriefDetected = headings.length > 0;
  const hwlabProductDetected = codeQueueBoardHintHasHwlabProductRouting({ hwlabProductRouting });
  return {
    warning: "#20 is the long-term UniDesk commander/Code Queue/CLI/infra governance board only; do not write daily commander brief updates or HWLAB product/user issue tracking into #20.",
    route: commanderBriefDetected
      ? "Move daily progress notes to the daily rolling commander brief issue referenced in #20's 指挥简报索引, using --body-profile commander-brief."
      : hwlabProductDetected
        ? "Move HWLAB product/user issue tracking to pikasTech/HWLAB; keep #20 limited to UniDesk commander/Code Queue/CLI/infra governance."
        : "Use #20 only for UniDesk commander/Code Queue/CLI/infra governance rows.",
    forbiddenHeadings: ["## 更新 YYYY-MM-DD HH:mm 北京时间", "## YYYY-MM-DD HH:mm 北京时间指挥更新", "### YYYY-MM-DD HH:mm CST：..."],
    suggestedCommand: "bun scripts/cli.ts gh issue update <daily-brief-issue> --mode replace --body-stdin --body-profile commander-brief --expect-body-sha <sha> <<'EOF'\n<reviewed body>\nEOF",
    detected: commanderBriefDetected || hwlabProductDetected,
    detectedHeadings: headings,
    commanderBrief: {
      detected: commanderBriefDetected,
      detectedHeadings: headings,
    },
    hwlabProductRouting,
  };
}

export function extractTimelineSections(markdown: string): CommanderBriefSection[] {
  const normalized = normalizeNewlines(markdown);
  const lines = normalized.split("\n");
  const sections: CommanderBriefSection[] = [];
  let currentStart = -1;
  for (let index = 0; index < lines.length; index += 1) {
    if (!isTimelineHeading(lines[index])) continue;
    if (currentStart !== -1) {
      sections.push({
        heading: lines[currentStart].trimEnd(),
        text: lines.slice(currentStart, index).join("\n").trimEnd(),
        startLine: currentStart + 1,
      });
    }
    currentStart = index;
  }
  if (currentStart !== -1) {
    sections.push({
      heading: lines[currentStart].trimEnd(),
      text: lines.slice(currentStart).join("\n").trimEnd(),
      startLine: currentStart + 1,
    });
  }
  return sections.filter((section) => section.text.length > 0);
}

export function commanderBriefDiff(oldBodyRaw: string, newBodyRaw: string): CommanderBriefDiff {
  const oldBody = normalizeNewlines(oldBodyRaw);
  const newBody = normalizeNewlines(newBodyRaw);
  if (oldBody === newBody) {
    return {
      ok: false,
      mode: "identical",
      message: "",
      chars: 0,
      sections: [],
      sectionCount: 0,
      skippedReason: "issue body is unchanged",
    };
  }

  if (newBody.startsWith(oldBody)) {
    const suffix = newBody.slice(oldBody.length);
    const sections = extractTimelineSections(suffix).map((section) => section.text);
    const message = sections.join("\n\n").trim();
    if (message.length === 0) {
      return {
        ok: false,
        mode: "append-only",
        message: "",
        chars: 0,
        sections: [],
        sectionCount: 0,
        skippedReason: "append-only suffix contains no new commander brief update section",
      };
    }
    return { ok: true, mode: "append-only", message, chars: message.length, sections, sectionCount: sections.length };
  }

  const oldSections = new Set(extractTimelineSections(oldBody).map((section) => section.text));
  const allNewSections = extractTimelineSections(newBody);
  const newSections = allNewSections.map((section) => section.text).filter((section) => !oldSections.has(section));
  if (newSections.length === 0) {
    return {
      ok: false,
      mode: "heading-diff",
      message: "",
      chars: 0,
      sections: [],
      sectionCount: 0,
      skippedReason: "no new commander brief update section found; non-timeline edits are not notified",
    };
  }
  const duplicateNewHeadings = allNewSections.some((section, index) => allNewSections.findIndex((candidate) => candidate.heading === section.heading) !== index);
  const oldHeadings = new Set(extractTimelineSections(oldBody).map((section) => section.heading));
  const ambiguousChangedHeadings = allNewSections.some((section) => oldHeadings.has(section.heading) && !oldSections.has(section.text));
  if (duplicateNewHeadings || ambiguousChangedHeadings) {
    return {
      ok: false,
      mode: "unreliable",
      message: "",
      chars: 0,
      sections: [],
      sectionCount: 0,
      skippedReason: duplicateNewHeadings
        ? "duplicate update headings make commander brief diff unreliable"
        : "an existing update section changed; cannot reliably isolate only new timeline content",
    };
  }
  const message = newSections.join("\n\n").trim();
  return { ok: true, mode: "heading-diff", message, chars: message.length, sections: newSections, sectionCount: newSections.length };
}
