/**
 * @internal
 * classification.mjs — the ground-truth contract for which `.aperant/`
 * subpaths are committable (belong in version control + reviewable) vs
 * ephemeral (runtime/coordination/transient) (C56 B4).
 *
 * This module is the single source of truth consumed by:
 *   - `apt-tools audit pending-artifacts` (bucket committable/ephemeral/unknown)
 *   - self-staging helper (`git add -f` committable writes)
 *   - roadmap/task/pr-review CLIs (call self-staging after mutations)
 *   - future auditors (e.g. retention prune gates)
 *
 * Classification table — see spec.md §B4. Closed-world: any `.aperant/*`
 * path that doesn't hit a rule returns `null`, forcing the caller to pick
 * explicitly. Paths outside `.aperant/` also return `null`.
 *
 * Design constraints:
 *   - Deterministic iteration order (array-of-rules, not object map).
 *   - No I/O — pure string classification, safe to call from tight loops.
 *   - Frozen exports; callers can't mutate the rule set at runtime.
 *   - Glob matcher reused from outputs-guard.mjs so the dialect matches the
 *     task outputs allowlist exactly (important: self-staging checks BOTH).
 */

import { globToRegex } from '../task/outputs-guard.mjs'

/**
 * Artifact classification codomain. Frozen enum-ish object; never add values
 * without updating every consumer of the classifier return shape.
 *
 * @internal
 */
export const ARTIFACT_CLASS = Object.freeze({
	COMMITTABLE: 'committable',
	EPHEMERAL: 'ephemeral',
})

// Rule table. Ordered — first match wins, so more-specific rules must come
// before their more-general parents (e.g. the round-snapshot committable rule
// is listed before the generic pr-reviews rounds ephemeral rule).
//
// Each rule: { pattern: glob string, class: 'committable' | 'ephemeral', why: string }.
// `why` is kept for documentation & diagnostic output — the classifier returns
// only the class; callers needing the reason use `classifyArtifactPathDetailed`.
const RULES = Object.freeze([
	// ── Committable: long-term reviewable state ───────────────────────────
	{
		pattern: '.aperant/roadmap/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Roadmap — reviewable long-term state; covered by roadmap sync helper.',
	},
	{
		pattern: '.aperant/context/notes/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Decision ledger the team consumes.',
	},
	{
		pattern: '.aperant/decisions/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Roundtable outcomes, stress-test reports.',
	},
	{
		pattern: '.aperant/features/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Features registry (docs-coherence policy).',
	},
	{
		pattern: '.aperant/seeds.json',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Seeds register (cross-scope thematic initiatives).',
	},
	{
		pattern: '.aperant/project.json',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Product context.',
	},
	{
		pattern: '.aperant/project_index.json',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Derived-but-reviewable feature-surface index (docs-coherence).',
	},
	{
		pattern: '.aperant/axes.json',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Product taxonomy (axes) — reviewable project metadata.',
	},
	{
		pattern: '.aperant/config.json',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Durable project preferences (per-PR keys migrate out via A3).',
	},
	{
		pattern: '.aperant/rationalizations/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Per-model failure-mode tables (reviewed artifact).',
	},
	{
		pattern: '.aperant/templates/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Shared templates (rationalization skeletons, etc.).',
	},
	{
		pattern: '.aperant/prompts/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Agent prompt templates (versioned + reviewable).',
	},
	{
		pattern: '.aperant/benchmarks/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Benchmark definitions + eval suites (reviewable).',
	},
	{
		pattern: '.aperant/stress-tests/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Planner stress-test fixtures (reviewable).',
	},
	{
		pattern: '.aperant/roundtables/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Structured debate outputs (reviewable decision records).',
	},
	{
		pattern: '.aperant/context/installed/**',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Installed-context snapshots (reviewable).',
	},
	// ── Ephemeral: pr-review state is local framework coordination ─────────
	// pr-state.json + per-round snapshots are LOCAL coordination data, not
	// audit trail. Rationale (2026-04 design re-evaluation):
	//   1. pr-state.json embeds a PID in `lock.owner` — same security
	//      reasoning that classifies state.json ephemeral.
	//   2. Empirically, the prior COMMITTABLE classification was never
	//      honored — across 9 prior merged PRs in this repo, every state
	//      file accumulated locally and zero made it into git history. The
	//      `git add -f` self-staging without a follow-up commit step was a
	//      designed-in leak.
	//   3. Field-by-field, the snapshot is a denormalized cache of GitHub
	//      data: head_sha_by_round and comment_timeline reconstruct from
	//      `gh pr view --json commits,timelineItems` and the comments API.
	//      `superseded_findings` is computable from rounds/N/review.json
	//      diffs. Optional fields were empty in 9/9 snapshots checked.
	//   4. Architectural separation: GitHub PR API owns PR-level audit;
	//      `.aperant/` owns local framework coordination; richer
	//      cross-machine AI-performance telemetry belongs in Aperant the
	//      app, not in every consumer's git repo.
	{
		pattern: '.aperant/pr-reviews/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Per-PR coordination + scratch; rebuildable from GitHub PR API. Forensic insights belong in Aperant the app, not git.',
	},
	// ── Ephemeral: coordination, runtime, transient ────────────────────────
	{
		pattern: '.aperant/state.json',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Coordination board (contains PIDs).',
	},
	{
		pattern: '.aperant/active.json',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Legacy-shim aggregate of team-status/*.json (C31).',
	},
	{
		pattern: '.aperant/team.json',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Team-mode flag + metadata (runtime coordination).',
	},
	{
		pattern: '.aperant/pending-effects.jsonl',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Effects outbox — drained in-process; transient.',
	},
	{
		pattern: '.aperant/events/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Append-only daily log of lifecycle transitions.',
	},
	{
		pattern: '.aperant/install/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Install telemetry/summaries (last-init/last-install) — runtime artifacts.',
	},
	{
		pattern: '.aperant/team-status/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Per-agent liveness board (PID + incarnation guard).',
	},
	{
		pattern: '.aperant/workflows/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Workflow run files (post-A1: unlinked or archived on destroy).',
	},
	{
		pattern: '.aperant/tasks/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Per-task artifacts (spec, plan, progress) — gitignored.',
	},
	{
		pattern: '.aperant/proof/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Screenshots, videos, HTML reports.',
	},
	{
		pattern: '.aperant/logs/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Route-dropped, route-classify, etc.',
	},
	{
		pattern: '.aperant/worktrees/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Gitignored via .gitignore; never commit.',
	},
	{
		pattern: '.aperant/locks/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Coordination file locks.',
	},
	{
		pattern: '.aperant/digests/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Narrator bookkeeping digests.',
	},
	{
		pattern: '.aperant/quick/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Quick-task scratch dirs (ephemeral, recycled).',
	},
	{
		pattern: '.aperant/paused/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Paused-session checkpoints (restored by /apt:resume).',
	},
	{
		pattern: '.aperant/reviews/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Legacy review scratch (round artifacts).',
	},
	{
		pattern: '.aperant/plans/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Plan-mode scratch before task-id assignment.',
	},
	{
		pattern: '.aperant/tmp/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Transient scratch; never commit.',
	},
	{
		pattern: '.aperant/wfrun/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'YAML workflow run artifacts (C17).',
	},
	{
		pattern: '.aperant/design/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Design-scan outputs (tokens, extracted repo, synthesis) — rebuildable.',
	},
	{
		pattern: '.aperant/product-context.json',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Derived/read-through sidecar used by CLI flows; rebuilt from onboarding context.',
	},
	// ── Explicit ephemeral: secrets/tokens MUST never classify committable ─
	{
		pattern: '.aperant/secrets/**',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'Secrets — never committable (R-02 belt).',
	},
	{
		pattern: '.aperant/tokens.json',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'OAuth/integration tokens — never committable (R-02 belt).',
	},
	// ── Dotfiles at `.aperant/` root (gitignore meta) ──────────────────────
	{
		pattern: '.aperant/.gitignore',
		class: ARTIFACT_CLASS.COMMITTABLE,
		why: 'Gitignore meta — committable project policy artifact.',
	},
	{
		pattern: '.aperant/.gitignore.pre-c31.backup',
		class: ARTIFACT_CLASS.EPHEMERAL,
		why: 'One-shot migration backup — not versioned long-term.',
	},
])

// Precompile regexes for hot-path classification. Keep the original rule
// reference so callers that want `why` can find it by index.
const COMPILED = RULES.map((rule) => ({ ...rule, regex: globToRegex(rule.pattern) }))

/**
 * Return the ordered list of committable globs. Deterministic. Frozen array
 * (consumers should not mutate it).
 *
 * @returns {readonly string[]}
 * @internal
 */
export function listCommittableGlobs() {
	return Object.freeze(
		RULES.filter((r) => r.class === ARTIFACT_CLASS.COMMITTABLE).map((r) => r.pattern),
	)
}

/**
 * Return the ordered list of ephemeral globs. Deterministic. Frozen array.
 *
 * @returns {readonly string[]}
 * @internal
 */
export function listEphemeralGlobs() {
	return Object.freeze(
		RULES.filter((r) => r.class === ARTIFACT_CLASS.EPHEMERAL).map((r) => r.pattern),
	)
}

/**
 * Classify a repo-relative path.
 *
 *   - Returns 'committable' or 'ephemeral' when a rule matches.
 *   - Returns `null` when the path is outside `.aperant/` OR when it's under
 *     `.aperant/` but unmatched (unknown → forces the caller to pick).
 *
 * The glob matcher is the same one used by task outputs (`**` matches any
 * sequence incl. `/`, `*` matches non-`/`). No negation, no braces.
 *
 * @param {string} relPath path relative to the repo root (e.g.
 *   `.aperant/roadmap/framework/milestones.json`). Leading `./` stripped.
 * @param {{}} [_opts] reserved for future options (no-op today).
 * @returns {'committable' | 'ephemeral' | null}
 * @internal
 */
export function classifyArtifactPath(relPath, _opts) {
	if (typeof relPath !== 'string' || relPath.length === 0) return null
	const path = normalizeRel(relPath)
	if (!path.startsWith('.aperant/') && path !== '.aperant') return null
	for (const rule of COMPILED) {
		if (rule.regex.test(path)) return rule.class
	}
	return null
}

/**
 * Detailed variant — returns `{ class, pattern, why }` or `null`. Intended for
 * diagnostics (unknown-path reports, audit output).
 *
 * @param {string} relPath
 * @returns {{ class: string, pattern: string, why: string } | null}
 * @internal
 */
export function classifyArtifactPathDetailed(relPath) {
	if (typeof relPath !== 'string' || relPath.length === 0) return null
	const path = normalizeRel(relPath)
	if (!path.startsWith('.aperant/') && path !== '.aperant') return null
	for (const rule of COMPILED) {
		if (rule.regex.test(path)) {
			return Object.freeze({ class: rule.class, pattern: rule.pattern, why: rule.why })
		}
	}
	return null
}

/**
 * Filter: return paths that classify committable. Drops unknown/null + ephemeral.
 *
 * @param {string[]} paths
 * @returns {string[]} filtered subset (preserves input order; no dedupe).
 * @internal
 */
export function filterCommittable(paths) {
	if (!Array.isArray(paths)) return []
	return paths.filter((p) => classifyArtifactPath(p) === ARTIFACT_CLASS.COMMITTABLE)
}

/**
 * Filter: return paths that classify ephemeral.
 *
 * @param {string[]} paths
 * @returns {string[]} filtered subset (preserves input order; no dedupe).
 * @internal
 */
export function filterEphemeral(paths) {
	if (!Array.isArray(paths)) return []
	return paths.filter((p) => classifyArtifactPath(p) === ARTIFACT_CLASS.EPHEMERAL)
}

/**
 * Normalize a repo-relative path for classification:
 *   - drop a leading `./`
 *   - normalize backslashes to forward slashes (Windows paths from git porcelain)
 *   - trim trailing slash
 *
 * @param {string} p
 * @returns {string}
 * @internal
 */
function normalizeRel(p) {
	let s = p.replace(/\\/g, '/')
	if (s.startsWith('./')) s = s.slice(2)
	if (s.endsWith('/') && s.length > 1) s = s.slice(0, -1)
	return s
}
