// ui/app_config.jsx — the shared registries the backend config fills at startup:
// triage statuses, feature switches, and who may assign owners.
/* ------------------------------------------------------------- status */
// Triage states. The backend (triage_status table) is the source of truth; these
// fallbacks match the seed and are used before config loads or if the fetch fails.
// `auto_enriched` is a pipeline-written, non-selectable state — included here so a
// catalog row carrying it never hits an undefined lookup. The key `new` reads "Open"
// (migration 0028): only the label changed, because the key is stored data.
const STATUS_FALLBACK = {
  new:            { dot: "var(--st-new)",  label: "Open",          selectable: true,  sortOrder: 1 },
  triaged:        { dot: "var(--st-tri)",  label: "Triaged",       selectable: true,  sortOrder: 2 },
  "in-progress":  { dot: "var(--st-prog)", label: "In progress",   selectable: true,  sortOrder: 3 },
  remediated:     { dot: "var(--st-done)", label: "Remediated",    selectable: true,  sortOrder: 4 },
  accepted:       { dot: "var(--st-acc)",  label: "Accepted risk", selectable: true,  sortOrder: 5 },
  auto_enriched:  { dot: "var(--st-auto)", label: "Auto-enriched", selectable: false, sortOrder: 6 },
};
// Backing object mutated in place by applyAppConfig(). Wrapped in a Proxy so an
// unknown status key (e.g. one an admin adds without a UI reload) resolves to a safe
// default instead of throwing on `STATUS_META[x].label` — there are many such
// unguarded reads across the screens.
const _statusBacking = Object.assign({}, STATUS_FALLBACK);
const _statusDefault = { dot: "var(--st-new)", label: "Unknown", selectable: false };
const STATUS_META = new Proxy(_statusBacking, {
  get(t, k) { return (typeof k === "string" && !(k in t)) ? _statusDefault : t[k]; },
});
// Selectable status keys in display order — the states a user may set manually
// (drives the ledger's status menu + filter). Mutated in place on config load.
const STATUS_SELECTABLE = Object.keys(STATUS_FALLBACK).filter(k => STATUS_FALLBACK[k].selectable);

// May the signed-in user name an owner or set a remediation deadline? Read from the
// shell's localStorage auth record, the same source ui/collaboration.jsx uses for "is
// this comment mine".
//
// COSMETIC ONLY. The server refuses the write regardless (VulnerabilitiesController's
// ForbidNonAdmin), so a stale or edited record here buys a disabled-looking pill and
// a 403, never an assignment. Its job is to not OFFER a control that would be refused.
//
// Analysts still SEE both pills — who owns a finding and when it is due is how they
// work their own queue. They just do not open the dialog.
function canAssignOwners() {
  try {
    const me = JSON.parse(localStorage.getItem("vc_auth_v1")) || {};
    return !!(me.isAdmin || me.isCompanyAdmin);
  } catch (_) { return false; }
}

/* ------------------------------------------------------------ features */
// Per-company feature switches, served on /api/config and applied by applyAppConfig().
// Mutated IN PLACE (like SEV_ORDER in ui/tags.jsx) so every module that captured this reference
// sees a change without a reload.
//
// EACH SWITCH HAS ONE DEFAULT, and a company nobody has configured, an older backend that
// does not send the field and a config fetch that failed outright all read as it.
// Comments default ON: a switch nobody set must never hide a working feature, so only an
// explicit `false` turns them off. Binary analysis and Jira default OFF, because a
// superadmin grants them company by company, so only an explicit `true` offers them.
const FEATURES = { comments: true, binaryAnalysis: false, jira: false };

// Apply the backend AppConfig (statuses + severity bands + feature switches) to the
// shared registries. Called at startup, by the shell's pull, at sign-out, and by the Admin
// page when it flips a switch, so that session sees the change without a reload.
// Safe to call with a partial config (it leaves everything it does not mention alone).
function applyAppConfig(cfg) {
  if (cfg && Array.isArray(cfg.statuses) && cfg.statuses.length) {
    const ordered = cfg.statuses.slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
    Object.keys(_statusBacking).forEach(k => delete _statusBacking[k]);
    ordered.forEach(s => {
      _statusBacking[s.key] = {
        dot: s.dot || "var(--st-new)",
        label: s.label || s.key,
        selectable: s.selectable !== false,
        sortOrder: s.sortOrder || 0,
      };
    });
    STATUS_SELECTABLE.length = 0;
    ordered.filter(s => s.selectable !== false).forEach(s => STATUS_SELECTABLE.push(s.key));
  }
  if (cfg && Array.isArray(cfg.severityBands) && cfg.severityBands.length) {
    const names = cfg.severityBands.slice().sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)).map(b => b.name);
    SEV_ORDER.length = 0;
    names.forEach(n => SEV_ORDER.push(n));
  }
  // Only a real boolean moves a switch: an older backend omits the field entirely, and
  // reading `undefined` as false would switch the feature off for everyone.
  if (cfg && typeof cfg.commentsEnabled === "boolean") FEATURES.comments = cfg.commentsEnabled;
  if (cfg && typeof cfg.binaryAnalysisEnabled === "boolean") FEATURES.binaryAnalysis = cfg.binaryAnalysisEnabled;
  if (cfg && typeof cfg.jiraEnabled === "boolean") FEATURES.jira = cfg.jiraEnabled;
}

Object.assign(window, { STATUS_META, STATUS_SELECTABLE, applyAppConfig, FEATURES, canAssignOwners });
