import type { requirementKind } from '../db/schema/index.js'

/** The six kinds, straight from the enum the schema declares. */
export type Kind = (typeof requirementKind.enumValues)[number]

/**
 * The decision-routing table. Fixed by #15 decision 2.
 *
 * ── THE BAR ─────────────────────────────────────────────────────────────
 *
 *   The model may LOCATE in every lane.
 *   It may DECIDE in only two.
 *
 * ────────────────────────────────────────────────────────────────────────
 *
 * Deliberately NOT the deterministic-first / LLM-fallback pattern used in
 * novelpad-desktop's go-no-go agent. Two cases make routing-by-kind strictly
 * better here:
 *
 *   - Frequency and quantity multipliers hide in footnotes (#22), and dropping
 *     one silently converts an annual cost into a unit price. A fallback path
 *     hands exactly that arithmetic to a model at the moment it is ambiguous,
 *     and the error reads as high-confidence. Arithmetic is code, always.
 *
 *   - A mandatory gate disqualifies regardless of score. An ambiguous gate must
 *     escalate to a HUMAN, not to judgement.
 *
 * Under fallback, which lane a decision took depends on runtime luck — not
 * something you can explain in a protest.
 */
export type Decider = 'code' | 'model_proposes_human_commits'

export const DECIDED_BY: Record<Kind, Decider> = {
  mandatory_gate: 'code',
  certification_form: 'code',
  compliance_schedule: 'code',
  pricing: 'code',
  scored_criterion: 'model_proposes_human_commits',
  narrative: 'model_proposes_human_commits',
}

export function modelMayDecide(kind: Kind): boolean {
  return DECIDED_BY[kind] === 'model_proposes_human_commits'
}

/** Confirmation is required individually exactly where the outcome can move (#16 decision 1). */
export function requiresIndividualConfirmation(
  kind: Kind,
  isCriterionMapped: boolean,
): boolean {
  return kind === 'mandatory_gate' || isCriterionMapped
}

/**
 * Whether a gate can be evaluated against a response document at all.
 *
 * Confirmation is unaffected — the retailer still confirms the requirement
 * exists. What changes is GATE EVALUATION: a requirement whose satisfaction is
 * invisible to the artifact must be excluded rather than failed, or a bidder is
 * disqualified for something their document could never have proven.
 *
 * Found by the corpus. See the column comment on `requirement`.
 */
export function gateEvaluableFromResponse(r: {
  kind: Kind
  determinableFromResponse: boolean
}): boolean {
  return r.kind === 'mandatory_gate' && r.determinableFromResponse
}

/**
 * A score commits at any criterion with NO WEIGHTED CHILDREN.
 *
 * Derived from the rubric rather than configured — see the block comment on
 * `criterion`. A child with `maxPoints` of zero or null is guidance, not a
 * weighted child, and does not make its parent unscoreable.
 */
export function isScoreable(
  criterionId: string,
  all: readonly { id: string; parentId: string | null; maxPoints: number | null }[],
): boolean {
  return !all.some((c) => c.parentId === criterionId && (c.maxPoints ?? 0) > 0)
}

/** The set a committee actually commits scores against. */
export function scoreableCriteria<
  T extends { id: string; parentId: string | null; maxPoints: number | null },
>(all: readonly T[]): T[] {
  return all.filter((c) => isScoreable(c.id, all))
}
