// ── CPAS point roll-off model ────────────────────────────────────────────────
// Handbook rule: points roll off only after the employee goes a full
// ROLLOFF_DAYS (90) with NO new violations. The clock is shared across all of an
// employee's active points and is RESET by any new non-negated violation. Each
// completed clean cycle removes ROLLOFF_POINTS (5), oldest violation first, and
// every successive roll-off requires another fresh clean cycle.
//
// This is intentionally NOT a per-violation "expires 90 days after its own
// incident date" window — a new violation pushes back roll-off for everything.

const ROLLOFF_POINTS = 5;
const ROLLOFF_DAYS   = 90;
const MS_PER_DAY     = 86400000;

// All dates are treated as UTC calendar days to match SQLite's DATE('now').
function dayValue(dateStr) {
  const [y, m, d] = String(dateStr).slice(0, 10).split('-').map(Number);
  return Date.UTC(y, m - 1, d);
}
function daysBetween(fromStr, toStr) {
  return Math.round((dayValue(toStr) - dayValue(fromStr)) / MS_PER_DAY);
}
function addDays(dateStr, n) {
  return new Date(dayValue(dateStr) + n * MS_PER_DAY).toISOString().slice(0, 10);
}
function todayStr() {
  return new Date().toISOString().slice(0, 10);
}

// Compute an employee's CPAS standing as of `asOf` (default today).
//
// `violations` is an array of rows with at least { id, points, incident_date,
// negated }. Negated rows and rows dated after `asOf` are ignored.
//
// Returns:
//   activePoints      — points still counting against the employee
//   totalPoints       — sum of all (non-negated, on-or-before asOf) points
//   rolledOffPoints   — points already retired by completed clean cycles
//   cycles            — completed 90-day clean cycles since the last violation
//   violationCount    — non-negated violations still retaining points
//   lastViolationDate — most recent non-negated incident date (anchors the clock)
//   nextRolloffDate   — date the next 5 points retire (null if nothing left)
//   perViolation      — oldest-first breakdown: each row + { rolledOff, activePoints }
//   schedule          — future roll-off events: { date, points_off, points_after, days_remaining }
function computeStanding(violations, asOf = todayStr()) {
  const asOfVal = dayValue(asOf);
  const active  = violations.filter(
    v => !v.negated && dayValue(v.incident_date) <= asOfVal
  );

  if (active.length === 0) {
    return {
      activePoints: 0, totalPoints: 0, rolledOffPoints: 0, cycles: 0,
      violationCount: 0, lastViolationDate: null, nextRolloffDate: null,
      perViolation: [], schedule: [],
    };
  }

  const sorted = [...active].sort((a, b) => {
    const d = dayValue(a.incident_date) - dayValue(b.incident_date);
    return d !== 0 ? d : (a.id - b.id);
  });

  const totalPoints       = sorted.reduce((s, v) => s + v.points, 0);
  const lastViolationDate = String(sorted[sorted.length - 1].incident_date).slice(0, 10);
  const daysSince         = daysBetween(lastViolationDate, asOf);
  const cycles            = daysSince > 0 ? Math.floor(daysSince / ROLLOFF_DAYS) : 0;
  const rolledOffPoints   = Math.min(totalPoints, cycles * ROLLOFF_POINTS);
  const activePoints      = totalPoints - rolledOffPoints;

  // Retire points oldest-first.
  let toRoll = rolledOffPoints;
  const perViolation = sorted.map(v => {
    const off = Math.min(v.points, toRoll);
    toRoll -= off;
    return { ...v, rolledOff: off, activePoints: v.points - off };
  });
  const violationCount = perViolation.filter(v => v.activePoints > 0).length;

  // Project remaining roll-offs forward. Cycle N retires at lastViolation + N*90.
  const schedule = [];
  let remaining = activePoints;
  let c = cycles;
  while (remaining > 0) {
    c += 1;
    const date = addDays(lastViolationDate, c * ROLLOFF_DAYS);
    const off  = Math.min(ROLLOFF_POINTS, remaining);
    remaining -= off;
    schedule.push({
      date,
      points_off:     off,
      points_after:   remaining,
      days_remaining: daysBetween(asOf, date),
    });
  }

  return {
    activePoints, totalPoints, rolledOffPoints, cycles, violationCount,
    lastViolationDate,
    nextRolloffDate: schedule.length ? schedule[0].date : null,
    perViolation, schedule,
  };
}

module.exports = {
  ROLLOFF_POINTS, ROLLOFF_DAYS,
  computeStanding, daysBetween, addDays, todayStr,
};
