/**
 * src/core/validity.js — possibilistic validity metadata (Cella FVN / Martin).
 *
 * Every check result carries a validity block describing how the decision
 * value was produced, in the spirit of the zig-contour fusion spec:
 *
 *   label   — weakest validity label of the contributing sources, downgraded
 *             by the fusion operator's class:
 *               identity/max      : operator is valid (max is already valid
 *                                   and normalized under arbitrary dependence);
 *                                   the fused label = weakest source label
 *               min (conjunctive) : unvalidified ranking -> the raw min is
 *                                   reported as the decision, the validified
 *                                   value min(1, K*gamma_min) is exposed
 *                                   separately, and the conflict mass is
 *                                   surfaced, never silently dropped
 *               product-style     : exclusion P(A)*(1-P(B)) and defeasible
 *                                   when*(1-unless) use the product operator,
 *                                   which has no linear validification under
 *                                   arbitrary dependence (Cella Thm 4) ->
 *                                   always heuristic
 *               owa (interior)    : convex mixtures of plausibilities are not
 *                                   maxitive -> scoring heuristic, never a
 *                                   valid possibility
 *   regime  — dependence assumption (always 'arbitrary' unless declared)
 *   operator — the fusion operator actually applied
 *   conflictMass — 1 - fused_possibility: the conjunctive suppression (the
 *             scalar analog of contour sub-normalization; 0 for disjunctive)
 *   validifiedPossibility — the arbitrary-regime validification
 *             min(1, K*gamma_min) for conjunctive results (null otherwise)
 *   sources — the base relations that fed the decision
 *   nonMaxitive — true for interior OWA/averaging operators
 *
 * The taxonomy follows the contour spec: finite_sample | anytime | conformal |
 * approximate | heuristic | unknown. Unlabeled relations default to heuristic.
 */

export const VALIDITY_LABELS = ['finite_sample', 'anytime', 'conformal', 'approximate', 'heuristic', 'unknown'];

// Shared immutable default for unlabeled decisions (all-heuristic): the
// hottest paths attach this constant instead of allocating per check.
export const DEFAULT_VALIDITY = Object.freeze({
  label: 'heuristic',
  operator: 'identity',
  regime: 'arbitrary',
  sources: [],
  conflictMass: 0,
  validifiedPossibility: null,
  nonMaxitive: false
});
export const VALIDITY_RANK = { finite_sample: 0, anytime: 1, conformal: 2, approximate: 3, heuristic: 4, unknown: 5 };

export function normalizeValidity(label) {
  if (label === undefined || label === null) return 'heuristic';
  const l = String(label);
  return VALIDITY_RANK[l] !== undefined ? l : 'unknown';
}

/** Weakest (least valid) of a set of labels. */
export function weakestValidity(labels) {
  // Track the weakest (highest-rank) label; every label ranks >= 0.
  let worst = 'finite_sample';
  for (const l of labels) {
    const n = normalizeValidity(l);
    if (VALIDITY_RANK[n] > VALIDITY_RANK[worst]) worst = n;
  }
  return worst;
}

/**
 * Operator validity classes:
 *   'identity'     — a single source; fused label = source label
 *   'max'          — disjunctive; valid by construction, label = weakest source
 *   'min'          — conjunctive; unvalidified ranking; label downgraded to
 *                    approximate unless already weaker
 *   'product'      — banned operator (Thm 4); always heuristic
 *   'owa'          — interior OWA averaging; non-maxitive; always heuristic
 */
export function operatorValidityClass(operator) {
  switch (operator) {
    case 'identity':
    case 'max':
      return { downgrade: false, nonMaxitive: false };
    case 'min':
      return { downgrade: true, nonMaxitive: false };
    case 'product':
      return { downgrade: true, alwaysHeuristic: true, nonMaxitive: false };
    case 'owa':
      return { downgrade: true, alwaysHeuristic: true, nonMaxitive: true };
    default:
      return { downgrade: true, alwaysHeuristic: true, nonMaxitive: false };
  }
}

/**
 * Build the validity block for a fused decision.
 *
 * @param {object} opts
 *   operator   — 'identity' | 'max' | 'min' | 'product' | 'owa'
 *   sources    — array of base relation names that fed the decision
 *   sourceLabels — array of validity labels (parallel to sources, or a single
 *                  'heuristic' when the engine does not track them)
 *   K          — number of fused sources (for the min validification)
 *   possibility — the raw fused possibility
 */
export function buildValidity(optsOrOp, sourcesArg = [], sourceLabelsArg = [], KArg = 1, possibilityArg = 0) {
  // Accept both the object form ({ operator, sources, ... }) and the
  // positional form (operator, sources, sourceLabels, K, possibility).
  const opts = (typeof optsOrOp === 'object' && optsOrOp !== null)
    ? optsOrOp
    : { operator: optsOrOp, sources: sourcesArg, sourceLabels: sourceLabelsArg, K: KArg, possibility: possibilityArg };
  const { operator = 'identity', sources = [], sourceLabels = [], K = 1, possibility = 0 } = opts;
  const cls = operatorValidityClass(operator);
  const base = sourceLabels.length > 0 ? weakestValidity(sourceLabels) : 'heuristic';
  let label = base;
  if (cls.downgrade) {
    if (cls.alwaysHeuristic) {
      label = 'heuristic';
    } else {
      // unvalidified conjunctive ranking: approximate at best
      if (VALIDITY_RANK[base] < VALIDITY_RANK.approximate) label = 'approximate';
    }
  }
  const block = {
    label,
    operator,
    regime: 'arbitrary',
    sources: [...sources],
    nonMaxitive: cls.nonMaxitive
  };
  if (operator === 'min') {
    const k = Math.max(1, K);
    block.conflictMass = Math.max(0, 1 - possibility);
    block.validifiedPossibility = Math.min(1, k * possibility);
  } else if (operator === 'identity' || operator === 'max') {
    block.conflictMass = 0;
    block.validifiedPossibility = null;
  } else {
    block.conflictMass = null;
    block.validifiedPossibility = null;
  }
  return block;
}

/** Merge several rule-level validity blocks into one (weakest label, unioned sources). */
/**
 * Minimal public form: label + operator only. Conflict mass, the
 * validified value, sources, and the non-maxitive flag are debugging /
 * internal-logging detail (re-entrant tracing practice: verbose traces are
 * opt-in, never on the default result path).
 */
export function minimalValidity(block) {
  if (!block) return { label: 'heuristic', operator: 'identity', regime: 'arbitrary' };
  return {
    label: block.label || 'heuristic',
    operator: block.operator || 'identity',
    regime: block.regime || 'arbitrary'
  };
}

export function mergeValidity(blocks) {
  const present = (blocks || []).filter(Boolean);
  if (present.length === 0) {
    return buildValidity({});
  }
  const labels = present.map(b => b.label);
  const sources = [];
  for (const b of present) {
    for (const s of b.sources || []) {
      if (!sources.includes(s)) sources.push(s);
    }
  }
  // If ANY contributor is a banned-operator heuristic, the merged result is
  // heuristic too (the weakest operator wins, not just the weakest source).
  const anyBanned = present.some(b => b.operator === 'product' || b.nonMaxitive);
  let label = weakestValidity(labels);
  if (anyBanned && VALIDITY_RANK[label] < VALIDITY_RANK.heuristic) label = 'heuristic';
  return {
    label,
    operator: present.length === 1 ? present[0].operator : 'mixed',
    regime: 'arbitrary',
    sources,
    nonMaxitive: present.some(b => b.nonMaxitive),
    conflictMass: present.some(b => b.operator === 'min') ? Math.max(0, 1 - Math.max(...present.map(b => b.conflictMass ?? 0))) : null,
    validifiedPossibility: null
  };
}
