// PumpingStation editor — level-based mode preview SVG.
// Draws zone bands, level markers, the up curve (inflowLevel→maxLevel) and
// the optional shifted-down curve (startLevel→shiftLevel). Computes
// validation issues and stashes them on window._psModeValidationIssues
// for oneditsave to read.

(function () {
  const ns = window.PSEditor = window.PSEditor || {};
  const fNum = (id) => ns.fNum(id);

  // Derive dryRunLevel the same way the basin diagram does.
  // dryRunLevel = outflowLevel × (1 + dryRunThresholdPercent/100).
  // Returns null if either input is missing.
  ns.deriveDryRunLevel = () => {
    const refLow = fNum('outflowLevel');
    const dryPct = fNum('dryRunThresholdPercent');
    if (refLow == null || dryPct == null) return null;
    return refLow * (1 + dryPct / 100);
  };

  ns.modePreview = {
    redraw() {
      const svg = document.getElementById('ps-levelbased-mode-diagram');
      if (!svg) return;
      const start = fNum('startLevel');
      const hold  = fNum('holdLevel');
      const inlet = fNum('inflowLevel');
      const max = fNum('maxLevel');
      // Optional stopLevel — explicit pump-off threshold. Drawn as its
      // own marker line; does NOT shift the ramp foot. Renders as long as
      // the typed value is a non-negative number — the start-vs-stop
      // ordering check belongs to the validation ribbon, not the visual
      // marker (otherwise the line vanishes while the user is mid-edit).
      const stopRaw = fNum('stopLevel');
      const stop = Number.isFinite(stopRaw) && stopRaw >= 0 ? stopRaw : null;
      // dryRunLevel is derived from the basin's outflowLevel + dryRun%
      // (no separate input). Below dryRunLevel the runtime hard-stops;
      // we draw it as the leftmost vertical marker so the user sees
      // exactly where it lands.
      const dryRun = ns.deriveDryRunLevel();
      const overflow = fNum('overflowLevel');
      const shiftEnabled = !!document.getElementById('node-input-enableShiftedRamp')?.checked;
      const shiftRaw = fNum('shiftLevel');
      const shift = Number.isFinite(shiftRaw) && shiftRaw > 0 ? Math.min(shiftRaw, max ?? shiftRaw) : null;
      const armRaw = fNum('shiftArmPercent');
      const armPct = Number.isFinite(armRaw) ? Math.max(0, Math.min(100, armRaw)) : 95;
      const curveType = document.getElementById('node-input-levelCurveType')?.value || 'linear';
      const factorRaw = parseFloat(document.getElementById('node-input-logCurveFactor')?.value);
      const factor = Number.isFinite(factorRaw) && factorRaw > 0 ? factorRaw : 9;

      // Plot window is FIXED relative to basin geometry so that moving any
      // single level slides only that line, not all the others. Lower bound
      // is the basin floor (0); upper bound is overflowLevel (or maxLevel
      // if overflow isn't set) plus a small margin.
      const upperRefs = [max, overflow].filter(Number.isFinite);
      const upperBase = upperRefs.length ? Math.max(...upperRefs) : 1;
      const pad = Math.max(upperBase * 0.05, 0.1);
      const levelMin = 0;
      const levelMax = upperBase + pad;

      // Plot rectangle (viewBox px).
      const x0 = 52, x1 = 390, y0 = 140, y1 = 24;
      const yOffPx = 160;
      const yOffPct = -((yOffPx - y0) / (y0 - y1)) * 100;
      const xFor = (level) => x0 + ((level - levelMin) / (levelMax - levelMin)) * (x1 - x0);
      const yForPct = (pct) => y0 - (pct / 100) * (y0 - y1);
      const scale = (x) => {
        const clamped = Math.max(0, Math.min(1, x));
        if (curveType === 'log') return Math.log1p(factor * clamped) / Math.log1p(factor);
        return clamped;
      };

      // Path with three flat regions and a ramp:
      //   [levelMin..startX]    OFF   (pump off; below startLevel)
      //   [startX..footX]       0 %   (system armed but not yet ramping)
      //   [footX..topX]         ramp  (linear or log scaled 0..100 %)
      //   [topX..levelMax]      100 % (saturated)
      // Up curve: startX=startLevel, footX=inflowLevel, topX=maxLevel.
      // Shifted-down: startX=footX=startLevel, topX=shiftLevel.
      const buildPath = (startX, footX, topX) => {
        if (![startX, footX, topX].every(Number.isFinite) || topX <= footX) return '';
        const pts = [];
        pts.push(`${xFor(levelMin)},${yForPct(yOffPct)}`);
        pts.push(`${xFor(startX)},${yForPct(yOffPct)}`);
        pts.push(`${xFor(startX)},${yForPct(0)}`);
        if (footX > startX) pts.push(`${xFor(footX)},${yForPct(0)}`);
        for (let i = 0; i <= 24; i++) {
          const t = i / 24;
          const level = footX + t * (topX - footX);
          pts.push(`${xFor(level)},${yForPct(scale(t) * 100)}`);
        }
        pts.push(`${xFor(levelMax)},${yForPct(100)}`);
        return pts.join(' ');
      };

      // Up curve. Engagement edge is startLevel (pump-on threshold); the
      // ramp foot is holdLevel, with a Math.max(startLevel, …) safety
      // floor — matching the runtime in levelBased.run.
      //   - holdLevel == startLevel (default): no hold band, 0..100 % across
      //     [startLevel, maxLevel].
      //   - holdLevel > startLevel: pumps engaged across [startLevel,
      //     holdLevel] at 0 % (= MGC flow.min), then 0..100 % across
      //     [holdLevel, maxLevel].
      const up = document.getElementById('ps-mode-curve-up');
      const down = document.getElementById('ps-mode-curve-down');
      const downLabel = document.getElementById('ps-mode-curve-down-label');
      const upFoot = Number.isFinite(hold) && hold > start ? hold : start;
      if (up) up.setAttribute('points', buildPath(start, upFoot, max));

      // Shifted-DOWN curve (only when shift enabled): represents the
      // worst-case held-then-ramp path drawn for hold=100 % (the SVG
      // ideal). Geometry: 100 % flat from levelMax back to shiftLevel,
      // then linear/log ramp from (shiftLevel, 100 %) down to
      // (startLevel, 0 %), then OFF below startLevel.
      // Real runtime hold value depends on where direction flips, so the
      // preview shows the maximum extent.
      const buildShiftedDown = () => {
        if (![start, shift].every(Number.isFinite) || shift <= start) return '';
        const pts = [];
        // OFF baseline far-left to startLevel
        pts.push(`${xFor(levelMin)},${yForPct(yOffPct)}`);
        pts.push(`${xFor(start)},${yForPct(yOffPct)}`);
        // Jump 0 % at startLevel
        pts.push(`${xFor(start)},${yForPct(0)}`);
        // Ramp start→shift = 0..100 % (peak hold = 100 % for this preview)
        for (let i = 0; i <= 24; i++) {
          const t = i / 24;
          const lvl = start + t * (shift - start);
          pts.push(`${xFor(lvl)},${yForPct(scale(t) * 100)}`);
        }
        // Held at 100 % from shift → far-right
        pts.push(`${xFor(levelMax)},${yForPct(100)}`);
        return pts.join(' ');
      };
      if (down) {
        if (shiftEnabled) {
          down.setAttribute('points', buildShiftedDown());
          down.style.display = '';
          if (downLabel) downLabel.style.display = '';
        } else {
          down.setAttribute('points', '');
          down.style.display = 'none';
          if (downLabel) downLabel.style.display = 'none';
        }
      }

      // Horizontal arming-% line — only meaningful when shift enabled.
      const armLine = document.getElementById('ps-mode-line-armPercent');
      const armLabel = document.getElementById('ps-mode-label-armPercent');
      if (armLine && armLabel) {
        if (shiftEnabled) {
          const yArm = yForPct(armPct);
          armLine.setAttribute('y1', yArm);
          armLine.setAttribute('y2', yArm);
          armLabel.setAttribute('y', yArm - 2);
          armLabel.textContent = `arm ${Math.round(armPct)}%`;
          armLine.style.display = '';
          armLabel.style.display = '';
        } else {
          armLine.style.display = 'none';
          armLabel.style.display = 'none';
        }
      }

      // Vertical level markers — line only. Axis labels were removed;
      // identification comes from line colour + side-panel labels +
      // hover coupling.
      [
        ['dryRunLevel', dryRun],
        ['startLevel', start],
        ['stopLevel', stop],
        ['holdLevel', hold],
        ['inflowLevel', inlet],
        ['maxLevel', max],
        ['overflowLevel', overflow],
      ].forEach(([id, level]) => {
        const line = document.getElementById(`ps-mode-line-${id}`);
        if (!line) return;
        if (!Number.isFinite(level)) {
          line.style.display = 'none';
          return;
        }
        const x = xFor(level);
        line.style.display = '';
        line.setAttribute('x1', x); line.setAttribute('x2', x);
      });

      // Background zone bands.
      const plotL = xFor(levelMin);
      const plotR = xFor(levelMax);
      const setBand = (id, a, b) => {
        const r = document.getElementById(id);
        if (!r) return;
        if (!Number.isFinite(a) || !Number.isFinite(b) || b <= a) {
          r.setAttribute('x', 0); r.setAttribute('width', 0);
          return;
        }
        r.setAttribute('x', a);
        r.setAttribute('width', b - a);
      };
      const xMin   = Number.isFinite(dryRun)   ? xFor(dryRun)   : plotL;
      const xStart = Number.isFinite(start)    ? xFor(start)    : xMin;
      const xMax   = Number.isFinite(max)      ? xFor(max)      : plotR;
      const xOvf   = Number.isFinite(overflow) ? xFor(overflow) : xMax;
      setBand('ps-zone-dryRun',     plotL, xMin);
      setBand('ps-zone-safetyLow',  xMin,  xStart);
      setBand('ps-zone-safe',       xStart, xMax);
      setBand('ps-zone-safetyHigh', xMax,  xOvf);
      setBand('ps-zone-overflow',   xOvf,  plotR);

      // Shift level marker (line only).
      const shiftLine = document.getElementById('ps-mode-line-shiftLevel');
      if (shiftLine) {
        if (shiftEnabled && Number.isFinite(shift)) {
          const x = xFor(shift);
          shiftLine.setAttribute('x1', x); shiftLine.setAttribute('x2', x);
          shiftLine.style.display = '';
        } else {
          shiftLine.style.display = 'none';
        }
      }

      // Title + row visibility.
      const curveLabel = document.getElementById('ps-mode-curve-label');
      if (curveLabel) curveLabel.textContent = curveType === 'log' ? 'log curve: fast early response' : 'linear curve';
      const shiftRow = document.getElementById('ps-shiftLevel-row');
      if (shiftRow) shiftRow.style.display = shiftEnabled ? '' : 'none';
      const armRow = document.getElementById('ps-shiftArmPercent-row');
      if (armRow) armRow.style.display = shiftEnabled ? '' : 'none';
      const logRow = document.getElementById('ps-log-factor-row');
      if (logRow) logRow.style.display = curveType === 'log' ? '' : 'none';

      // Auto-default shiftLevel when shift is enabled and current value
      // is missing/out-of-range. Visible default avoids a hidden ramp.
      const shiftInput = document.getElementById('node-input-shiftLevel');
      if (shiftEnabled && shiftInput && Number.isFinite(max)) {
        const cur = parseFloat(shiftInput.value);
        if (!Number.isFinite(cur) || cur <= 0 || cur >= max) {
          shiftInput.value = (max * 0.9).toFixed(2);
        }
      }
      // Auto-default shiftArmPercent to 95 % when shift is enabled and the
      // current value is missing / out of [0, 100].
      const armInput = document.getElementById('node-input-shiftArmPercent');
      if (shiftEnabled && armInput) {
        const cur = parseFloat(armInput.value);
        if (!Number.isFinite(cur) || cur < 0 || cur > 100) {
          armInput.value = 95;
        }
      }

      // Validation: only mode-specific (shift) ordering. Basin-level
      // hierarchy (start ≤ inlet ≤ max ≤ overflow ≤ basinHeight,
      // dryRun < start) is owned by basin-diagram.js so it shows in the
      // basin section near the offending inputs.
      const issues = [];
      if (shiftEnabled) {
        const shiftVal = Number(shiftInput?.value);
        if (Number.isFinite(shiftVal)) {
          if (Number.isFinite(start) && shiftVal <= start)
            issues.push('shiftLevel must be > startLevel');
          if (Number.isFinite(max) && shiftVal > max)
            issues.push('shiftLevel must be ≤ maxLevel');
        } else {
          issues.push('shiftLevel is required when shifted ramp is enabled');
        }
        const armVal = Number(armInput?.value);
        if (!Number.isFinite(armVal) || armVal <= 0 || armVal > 100)
          issues.push('shiftArmPercent must be in (0, 100]');
      }
      const warnBox = document.getElementById('ps-mode-validation');
      if (warnBox) {
        if (issues.length) {
          warnBox.innerHTML = '⚠ Fix before deploy:<ul style="margin:4px 0 0 18px;padding:0;">'
            + issues.map((i) => `<li>${i}</li>`).join('') + '</ul>';
          warnBox.style.display = '';
        } else {
          warnBox.style.display = 'none';
        }
      }
      window._psModeValidationIssues = issues;

      // Read-only readouts in the side panel — number only; the row's
      // .ps-unit span already shows "m".
      const fmt = (v) => Number.isFinite(v) ? v.toFixed(2) : '—';
      const setText = (id, val) => {
        const el = document.getElementById(id);
        if (el) el.textContent = fmt(val);
      };
      setText('ps-mode-readout-dryRun',   dryRun);
      setText('ps-mode-readout-inflow',   inlet);
      setText('ps-mode-readout-overflow', overflow);
    },
  };
})();
