#!/usr/bin/env bun
/**
 * gstack-decision-search — read active decisions (the curated "what did we decide" view).
 *
 * Usage:
 *   gstack-decision-search [--query KW] [--scope repo|branch|issue]
 *                          [--branch B] [--issue I] [--recent N] [--all] [--json]
 *                          [--semantic]
 *
 * Reads the BOUNDED active snapshot (decisions.active.json) — O(active), not a full
 * history scan — and rebuilds it from the event log if missing. Scope-filtered to the
 * current branch/issue context (recency != relevance). NON-INTERACTIVE. `--all` shows
 * superseded decisions too (from the full log). Exit 0 silently when there are none.
 *
 * `--semantic` (with `--query`) appends an OPTIONAL "related from memory" block from
 * gbrain semantic recall. It is a pure enhancement: when gbrain is off/unconfigured/
 * empty it degrades silently to the reliable file results above. The reliable path
 * never loads gbrain code (the semantic module is imported lazily only here).
 */

import { existsSync } from "fs";
import {
  decisionPaths,
  readSnapshot,
  rebuildSnapshot,
  readEvents,
  filterByScope,
  datamark,
  type ActiveDecision,
} from "../lib/gstack-decision";
import { resolveSlug, gitBranch, flagValue } from "../lib/bin-context";

const HERE = import.meta.dir;
const args = process.argv.slice(2);

const slug = resolveSlug(`${HERE}/gstack-slug`);
const paths = decisionPaths(slug);
const queryRaw = flagValue(args, "--query");
const query = queryRaw?.toLowerCase();
const scope = flagValue(args, "--scope");
const branch = flagValue(args, "--branch") ?? gitBranch();
const issue = flagValue(args, "--issue");
const recentRaw = flagValue(args, "--recent");
const recent = recentRaw ? parseInt(recentRaw, 10) : undefined;
const showAll = args.includes("--all");
const asJson = args.includes("--json");
const semantic = args.includes("--semantic");

let rows: ActiveDecision[];
if (showAll) {
  // --all includes SUPERSEDED decisions (history), but NEVER redacted ones — a redact
  // is an expunge, so it must remove the text from every read path, not just active.
  const events = readEvents(paths);
  const redacted = new Set(
    events.filter((e) => e.kind === "redact" && e.supersedes).map((e) => e.supersedes as string),
  );
  rows = events.filter((e): e is ActiveDecision => e.kind === "decide" && !redacted.has(e.id));
} else {
  rows = readSnapshot(paths);
  // Rebuild only when a snapshot is absent but a log exists (don't write a snapshot
  // into a nonexistent store on an empty read — just return nothing).
  if (!rows.length && existsSync(paths.log)) rows = rebuildSnapshot(paths);
}

rows = filterByScope(rows, { branch, issue });
if (scope) rows = rows.filter((d) => d.scope === scope);
if (query) {
  rows = rows.filter((d) =>
    [d.decision, d.rationale, d.alternatives_considered]
      .filter((s): s is string => typeof s === "string")
      .some((s) => s.toLowerCase().includes(query)),
  );
}
rows.sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0)); // newest first
if (recent && recent > 0) rows = rows.slice(0, recent);

if (asJson) {
  // --json stays reliable-only (semantic recall is a human-facing supplement).
  console.log(JSON.stringify(rows));
  process.exit(0);
}

for (const d of rows) {
  // Datamark all stored free-text (decision, rationale, branch/issue) — it lands in
  // agent context via Context Recovery, so treat it as DATA, not instructions.
  const branchTag = d.branch ? `:${datamark(d.branch)}` : "";
  const issueTag = d.issue ? `:${datamark(d.issue)}` : "";
  const scopeTag = d.scope === "repo" ? "" : ` [${d.scope}${branchTag}${issueTag}]`;
  console.log(`- ${datamark(d.decision ?? "")}${scopeTag} (${d.source}, ${d.date.slice(0, 10)})`);
  if (d.rationale) console.log(`  why: ${datamark(d.rationale)}`);
}

// OPTIONAL gbrain enhancement. Lazy import so the reliable path above never loads
// gbrain code. Degrades silently: null (gbrain off) or [] (nothing found) leaves the
// reliable results above as the answer.
if (semantic && queryRaw) {
  const { semanticRecall } = await import("../lib/gstack-decision-semantic");
  const hits = semanticRecall(queryRaw);
  if (hits && hits.length) {
    console.log("\nRelated from memory (gbrain semantic recall):");
    for (const h of hits) {
      // gbrain hits are EXTERNAL corpus content — datamark slug + snippet too so they
      // can't spoof role markers / fences when printed into agent context.
      const snip = datamark(h.snippet.length > 100 ? `${h.snippet.slice(0, 100)}…` : h.snippet);
      console.log(`  [${h.score.toFixed(2)}] ${datamark(h.slug)}: ${snip}`);
    }
  }
}
