/**
 * uat/cli/lib/access-classify.ts — Pure access/visibility classification (specific to /uat).
 *
 * Two PURE concerns:
 *   1. deriveAccessLabel — the DISPLAY label for a (role, route): allowed / denied /
 *      redirect_login from the plan, PLUS the derived `partial` (page allowed but
 *      >=1 subset legitimately hidden for the role). `partial` is NEVER stored in
 *      the plan — it is computed here for the report's role x route matrix (F5).
 *   2. classifySubsetVisibility — the run verdict for one (role, subset, observed DOM
 *      count): a subset a role may NOT see must be ABSENT (present => BUG-OVEREXPOSED;
 *      absent => CONFIG-LEGITIME); a subset a role MAY see is expected present.
 */
import type { AccessVerdict, Route, Subset } from './plantest-schema.js';

export type AccessLabel = AccessVerdict | 'partial';

export type SubsetVerdictCategory =
  | 'RBAC-OK'
  | 'CONFIG-LEGITIME'
  | 'BUG-OVEREXPOSED'
  | 'BUG-MISSING-ELEMENT'
  | 'INDETERMINATE';

export interface SubsetVerdict {
  pass: boolean;
  category: SubsetVerdictCategory;
}

/** Is `role` expected to see `subset`? */
export function subsetVisibleForRole(subset: Subset, role: string): boolean {
  return subset.visible_for.includes(role);
}

/**
 * Display label for (role, route). `partial` when the page is `allowed` but at
 * least one subset is legitimately hidden for the role — surfaced so the report's
 * matrix can show "allowed, but reduced surface" rather than a flat allowed.
 */
export function deriveAccessLabel(route: Route, role: string): AccessLabel {
  const base: AccessVerdict | undefined = route.access[role];
  if (base !== 'allowed') return base ?? 'denied';
  const anyHidden = route.subsets.some((s) => !subsetVisibleForRole(s, role));
  return anyHidden ? 'partial' : 'allowed';
}

/**
 * Verdict for an observed subset given whether the role may see it and how many
 * matching nodes the DOM exposed.
 *   - role may NOT see it: count>0 => FAIL BUG-OVEREXPOSED ; count==0 => PASS CONFIG-LEGITIME
 *   - role MAY see it:     count>0 => PASS RBAC-OK         ; count==0 => FAIL BUG-MISSING-ELEMENT
 */
export function classifySubsetVisibility(subset: Subset, role: string, domCount: number): SubsetVerdict {
  const maySee = subsetVisibleForRole(subset, role);
  if (!maySee) {
    return domCount > 0
      ? { pass: false, category: 'BUG-OVEREXPOSED' }
      : { pass: true, category: 'CONFIG-LEGITIME' };
  }
  return domCount > 0
    ? { pass: true, category: 'RBAC-OK' }
    : { pass: false, category: 'BUG-MISSING-ELEMENT' };
}

/**
 * Verdict for a click_row_in_parent route whose parent list was empty: there is no
 * row to open, so RBAC cannot be observed — INDETERMINATE, never `denied` (F5).
 */
export function classifyEmptyList(): SubsetVerdict {
  return { pass: true, category: 'INDETERMINATE' };
}
