/* ============================================================
   VYNDR — LIVE GRADE-SHIFT timeline helper (Wave 4B).
   Pure CommonJS so the client component imports it (allowJs)
   AND the Jest suite requires it directly.

   Builds a grade/line-movement timeline from ALREADY-EMITTED data
   (intradayRefreshService line history {t,line} + revised_from_grade).
   It NEVER fabricates: absent/short history => { show:false }.

   COLOR LAW (mirrors ROW-GRAMMAR + StatStrip.LineSparkline):
   line movement is green = net move TOWARD the graded side,
   amber = AGAINST, dim = flat. Never red — nothing here is settled.
   For an OVER the "toward" sign is the raw line delta; for an UNDER
   it is inverted (a line dropping steams the under).
   ============================================================ */

const TOWARD_COLOR = 'var(--g-a)'; // green
const AGAINST_COLOR = 'var(--amber)'; // amber
const FLAT_COLOR = 'var(--text-1)'; // dim — never red
const MIN_POINTS = 3; // self-hide below this (same floor as LineSparkline)

function isUnder(side) {
  return String(side || 'O').toUpperCase().startsWith('U');
}

/** Classify a signed line delta relative to the graded side. */
function classifyMove(delta, side) {
  const d = typeof delta === 'number' && Number.isFinite(delta) ? delta : 0;
  const toward = isUnder(side) ? -d : d;
  if (toward > 0) return { dir: 'toward', color: TOWARD_COLOR };
  if (toward < 0) return { dir: 'against', color: AGAINST_COLOR };
  return { dir: 'flat', color: FLAT_COLOR };
}

/** Keep only real {t, line} points (strict number guard — Number(null)===0). */
function cleanHistory(history) {
  if (!Array.isArray(history)) return [];
  return history
    .filter((pt) => pt && typeof pt.line === 'number' && Number.isFinite(pt.line))
    .map((pt) => ({ t: pt.t != null ? String(pt.t) : '', line: pt.line }));
}

/**
 * Build the grade-shift timeline.
 * prop: { history:[{t,line}], side, grade, revisedFrom|revised_from_grade, gradedLine }
 * -> { show, points:[{t,line,delta,dir,color}], net:{delta,dir,color},
 *      revision:{from,to}|null, firstLine, lastLine }
 */
function buildGradeTimeline(prop = {}) {
  const history = cleanHistory(prop.history);
  const show = history.length >= MIN_POINTS;
  const side = prop.side;
  const revisedFrom = prop.revisedFrom || prop.revised_from_grade || null;
  const grade = prop.grade || null;

  const points = history.map((pt, i) => {
    if (i === 0) return { t: pt.t, line: pt.line, delta: 0, dir: 'flat', color: FLAT_COLOR };
    const delta = Math.round((pt.line - history[i - 1].line) * 100) / 100;
    const cls = classifyMove(delta, side);
    return { t: pt.t, line: pt.line, delta, dir: cls.dir, color: cls.color };
  });

  let net = { delta: 0, dir: 'flat', color: FLAT_COLOR };
  if (history.length >= 2) {
    const raw = Math.round((history[history.length - 1].line - history[0].line) * 100) / 100;
    const cls = classifyMove(raw, side);
    net = { delta: raw, dir: cls.dir, color: cls.color };
  }

  // A revision is real only when a prior grade was preserved AND it differs.
  const revision = revisedFrom && grade && String(revisedFrom) !== String(grade)
    ? { from: String(revisedFrom), to: String(grade) }
    : null;

  return {
    show,
    points,
    net,
    revision,
    firstLine: history.length ? history[0].line : null,
    lastLine: history.length ? history[history.length - 1].line : null,
  };
}

module.exports = {
  buildGradeTimeline,
  classifyMove,
  cleanHistory,
  isUnder,
  TOWARD_COLOR,
  AGAINST_COLOR,
  FLAT_COLOR,
  MIN_POINTS,
};
