'use strict';

/*
  Frozen STRUC-I v1.0.4 measurement core.
  The named core functions below are copied from the canonical chamber HTML.
  Orchestration wrappers begin after the FROZEN CORE END marker.
*/

/* ════════════════════════════════════════════════════════════
   MATH UTILITIES
   ════════════════════════════════════════════════════════════ */
function median(arr) {
  if (!arr.length) return 0;
  const s = [...arr].sort((a, b) => a - b);
  const m = Math.floor(s.length / 2);
  return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
}
function mean(arr) { return arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0; }
function std(arr) {
  if (arr.length < 2) return 0;
  const mu = mean(arr);
  return Math.sqrt(arr.reduce((a, b) => a + (b - mu) ** 2, 0) / arr.length);
}
function quantile(arr, q) {
  const s = [...arr].sort((a, b) => a - b);
  const pos = q * (s.length - 1);
  const lo = Math.floor(pos), hi = Math.ceil(pos);
  return s[lo] + (s[hi] - s[lo]) * (pos - lo);
}
function logspace(lo, hi, n) {
  const lLo = Math.log10(lo), lHi = Math.log10(hi);
  return Array.from({ length: n }, (_, i) => Math.pow(10, lLo + (lHi - lLo) * i / (n - 1)));
}
function stratifiedSample(arr, maxN) {
  if (arr.length <= maxN) return arr;
  const result = [];
  const step = (arr.length - 1) / (maxN - 1);
  for (let i = 0; i < maxN; i++) result.push(arr[Math.round(i * step)]);
  return result;
}

/* ════════════════════════════════════════════════════════════
   GAP ENGINE
   ════════════════════════════════════════════════════════════ */
function computeGaps(values) {
  const gaps = [];
  for (let i = 0; i < values.length - 1; i++) gaps.push(values[i + 1] - values[i]);
  return gaps;
}
function gapStats(gaps) {
  const nonzero = gaps.filter(g => g > 0);
  return {
    n: gaps.length,
    mean: mean(gaps),
    median: median(gaps),
    std: std(gaps),
    min: Math.min(...gaps),
    max: Math.max(...gaps),
    q10: quantile(gaps, 0.1),
    q25: quantile(gaps, 0.25),
    q75: quantile(gaps, 0.75),
    q90: quantile(gaps, 0.9),
    zeroCount: gaps.filter(g => g === 0).length,
    nonzeroMedian: nonzero.length ? median(nonzero) : 0
  };
}

/* ════════════════════════════════════════════════════════════
   VULNERABILITY ENGINE
   ════════════════════════════════════════════════════════════ */

// Primitive 1: runs depend only on ε and gaps — compute once per κ, not per MC run.
function computeVulnerabilityRuns(gaps, eps) {
  const runs = [];
  let run = 0;
  for (let i = 0; i < gaps.length; i++) {
    if (gaps[i] <= 2 * eps) {
      run++;
    } else {
      if (run > 0) runs.push(run);
      run = 0;
    }
  }
  if (run > 0) runs.push(run);
  return runs;
}

// Primitive 2: independence number from pre-computed run lengths.
function computeNuFromRuns(runs) {
  let nu = 0;
  for (let r of runs) nu += Math.ceil(r / 2);
  return nu;
}

// Wrapper used by the probe UI (updateVulnProbe) — not called inside the MC loop.
function computeVulnerability(gaps, kappa, medGap) {
  const epsilon = kappa * medGap;
  const runs = computeVulnerabilityRuns(gaps, epsilon);
  const nu = computeNuFromRuns(runs);
  const threshold = 2 * epsilon;
  const vulnerable = gaps.map(g => g <= threshold);
  const vulnCount = vulnerable.filter(Boolean).length;
  const runCount = runs.length;
  return { epsilon, nu, vulnCount, runCount, vulnerable };
}

/* ════════════════════════════════════════════════════════════
   MONTE CARLO ENGINE
   ════════════════════════════════════════════════════════════ */
function countInversions(vals) {
  let inv = 0;
  for (let i = 0; i < vals.length - 1; i++) if (vals[i] > vals[i + 1]) inv++;
  return inv;
}

function runMCStep(values, nu, epsilon, M) {
  const n = values.length;
  let admissible = 0;
  let totalInv = 0;
  const invCounts = [];
  for (let m = 0; m < M; m++) {
    const perturbed = values.map(v => v + (Math.random() * 2 - 1) * epsilon);
    const inv = countInversions(perturbed);
    if (inv <= nu) admissible++;
    totalInv += inv;
    if (m < 200) invCounts.push(inv); // sample first 200 for histogram
  }
  return { Ak: admissible / M, meanInv: totalInv / M, invCounts };
}

/* ════════════════════════════════════════════════════════════
   PRESSURE HELPER
   ════════════════════════════════════════════════════════════ */
function pressureLabel(rho) {
  if (rho < 0.25) return { label: 'RELAXED HIERARCHY',    cls: 'sp-relaxed',  color: '#00dd88' };
  if (rho < 0.60) return { label: 'STRUCTURAL TENSION',   cls: 'sp-tension',  color: '#f0a030' };
  if (rho < 0.90) return { label: 'NEAR BOUNDARY',        cls: 'sp-boundary', color: '#e07030' };
  return              { label: 'CRITICAL',                 cls: 'sp-critical', color: '#e04040' };
}

/* ════════════════════════════════════════════════════════════
   REGIME CLASSIFIER
   ════════════════════════════════════════════════════════════ */
function classifyRegime(profile) {
  if (!profile || profile.length === 0) return { regime: 'Unknown', state: 'Unclassified', color: '#2d4560', vcClass: '' };
  const aks = profile.map(p => p.Ak);
  const mu = mean(aks);
  const mn = Math.min(...aks);
  const akHigh = profile.find(p => p.kappa >= 0.7)?.Ak ?? aks[aks.length - 1];
  const akMid  = profile.find(p => p.kappa >= 0.4)?.Ak ?? aks[Math.floor(aks.length / 2)];
  const meanRho = mean(profile.map(p => p.rho ?? 0));
  const maxRho  = Math.max(...profile.map(p => p.rho ?? 0));
  const pl = pressureLabel(meanRho);

  if (mu >= 0.93) {
    const regime = 'Geometric Persistence';
    // Refine state using ρ: high Aκ can hide two very different structural situations
    let state;
    if (mu >= 0.975 && mn >= 0.93 && meanRho < 0.30) {
      state = 'Stable Structure';
    } else if (mu >= 0.975 && mn >= 0.93 && meanRho >= 0.60) {
      state = 'Boundary-Stabilized';
    } else {
      state = 'Weak Persistence';
    }
    const color = state === 'Stable Structure' ? '#00dd88' : state === 'Boundary-Stabilized' ? '#e07030' : '#4ab0f0';
    return { regime, state, color, regimeColor: '#4ab0f0', vcClass: 'vc-gp', meanA: mu, minA: mn, meanRho, maxRho, pressureLabel: pl };
  } else if (mu >= 0.60) {
    const regime = 'Structural Boundary';
    const state = akHigh >= 0.70 ? 'Transitional Structure' : 'Near-Critical Structure';
    const color = '#f0a030';
    return { regime, state, color, regimeColor: '#f0a030', vcClass: 'vc-sb', meanA: mu, minA: mn, meanRho, maxRho, pressureLabel: pl };
  } else {
    const regime = 'Structural Instability';
    const akLow = profile.find(p => p.kappa >= 0.25)?.Ak ?? aks[Math.floor(aks.length * 0.3)];
    const state = akLow < 0.50 ? 'Weak Structure' : 'Random Structure';
    const color = '#e04040';
    return { regime, state, color, regimeColor: '#e04040', vcClass: 'vc-si', meanA: mu, minA: mn, meanRho, maxRho, pressureLabel: pl };
  }
}

/* FROZEN CORE END — orchestration wrappers below do not alter the measurement primitives. */

async function computeRegisteredProfile(values, settings, progressCallback) {
  if (!Array.isArray(values) || values.length < 2) throw new Error('Ladder must contain at least two values.');
  const sorted = [...values].map(Number).sort((a, b) => a - b);
  if (sorted.some(v => !Number.isFinite(v))) throw new Error('Ladder contains a non-finite value.');
  for (let i = 1; i < sorted.length; i++) {
    if (!(sorted[i] > sorted[i - 1])) throw new Error('Ladder must be strictly increasing.');
  }
  const M = settings.mc_runs;
  const grid = logspace(settings.kappa_min, settings.kappa_max, settings.kappa_steps);
  const gaps = computeGaps(sorted);
  const medGap = median(gaps.filter(g => g > 0)) || median(gaps) || 1;
  const profile = [];
  for (let ki = 0; ki < grid.length; ki++) {
    const kappa = grid[ki];
    const epsilon = kappa * medGap;
    const runs = computeVulnerabilityRuns(gaps, epsilon);
    const nu = computeNuFromRuns(runs);
    const vulnCount = runs.reduce((s, r) => s + r, 0);
    const { Ak, meanInv } = runMCStep(sorted, nu, epsilon, M);
    const rho = (nu === 0) ? 0 : meanInv / nu;
    profile.push({
      kappa,
      epsilon,
      nu,
      vulnCount,
      inv: parseFloat(meanInv.toFixed(4)),
      rho: parseFloat(rho.toFixed(6)),
      Ak
    });
    if (progressCallback) progressCallback((ki + 1) / grid.length, profile[profile.length - 1]);
    if (ki % 8 === 7) await new Promise(resolve => setTimeout(resolve, 0));
  }
  return { profile, classification: classifyRegime(profile) };
}

const API = {
  version: '1.0.4',
  median, mean, std, quantile, logspace, stratifiedSample,
  computeGaps, gapStats, computeVulnerabilityRuns, computeNuFromRuns,
  computeVulnerability, countInversions, runMCStep, pressureLabel,
  classifyRegime, computeRegisteredProfile
};

if (typeof self !== 'undefined') self.STRUC_I_V1_0_4 = API;
if (typeof module !== 'undefined' && module.exports) module.exports = API;
