// ── Frequency definitions ─────────────────────────────────────
const FREQ = {
  monthly:   { label: 'Monthly',    daysPerPeriod: 30.44, periodsPerMonth: 1 },
  weekly:    { label: 'Weekly',     daysPerPeriod: 7,     periodsPerMonth: 4.345 },
  biweekly:  { label: 'Biweekly',   daysPerPeriod: 14,    periodsPerMonth: 2.173 },
  bimonthly: { label: 'Bi-monthly', daysPerPeriod: 15.22, periodsPerMonth: 2 },
  daily:     { label: 'Daily',      daysPerPeriod: 1,     periodsPerMonth: 30.44 },
  none:      { label: 'Random',     daysPerPeriod: null,  periodsPerMonth: null },
};

// ============================================================
//  MATH ENGINE
//  All dates are JS Date objects. All sums are floats.
//  Frequency-aware. Two-date system. Backdate-safe.
// ============================================================

function getDailyRate(goal) {
  if (goal.paused) return 0;
  const f = FREQ[goal.frequency];
  if (!f || f.daysPerPeriod === null) return 0;
  return goal.contributionAmount / f.daysPerPeriod;
}

function getMonthlyEquivalent(goal) {
  if (goal.paused) return 0;
  const f = FREQ[goal.frequency];
  if (!f || f.periodsPerMonth === null) return 0;
  return goal.contributionAmount * f.periodsPerMonth;
}

function sumContributions(contributions) {
  return (contributions || []).reduce((s, c) => s + (c.amount || 0), 0);
}

function sumMonth(contributions, ymKey) {
  return (contributions || [])
    .filter(c => (c.date || '').slice(0, 7) === ymKey)
    .reduce((s, c) => s + (c.amount || 0), 0);
}

function ymKeyToday() { return new Date().toISOString().slice(0, 7); }

function calcGoal(goal) {
  const balance      = (goal.currentBalance || 0) + sumContributions(goal.contributions);
  const target       = goal.target || 0;
  const remaining    = Math.max(0, target - balance);
  const pct          = target > 0 ? Math.min(100, (balance / target) * 100) : 0;
  const complete     = balance >= target && target > 0;

  const dailyRate    = getDailyRate(goal);
  const monthlyEq    = getMonthlyEquivalent(goal);

  const today        = new Date();
  today.setHours(0, 0, 0, 0);
  const targetDateD  = goal.targetDate ? new Date(goal.targetDate) : null;
  if (targetDateD) targetDateD.setHours(0, 0, 0, 0);
  const daysToTarget = targetDateD ? Math.ceil((targetDateD - today) / 864e5) : null;

  let daysToFinish, projectedDate;
  if (complete) {
    daysToFinish  = 0;
    projectedDate = today;
  } else if (dailyRate > 0) {
    daysToFinish  = Math.ceil(remaining / dailyRate);
    projectedDate = new Date(today.getTime() + daysToFinish * 864e5);
  } else {
    daysToFinish  = null;
    projectedDate = null;
  }

  let status = 'unknown', daysDiff = null;
  if (complete) {
    status = 'complete';
  } else if (goal.paused) {
    status = 'paused';
  } else if (daysToFinish !== null && daysToTarget !== null) {
    daysDiff = daysToTarget - daysToFinish;
    if (daysDiff > 0)       status = 'ahead';
    else if (daysDiff === 0) status = 'ontrack';
    else                    status = 'late';
  }

  let suggestedMonthly = null;
  if (daysToTarget !== null && daysToTarget > 0 && remaining > 0) {
    suggestedMonthly = (remaining / daysToTarget) * 30.44;
  }

  const thisMonthKey   = ymKeyToday();
  const thisMonthTotal = sumMonth(goal.contributions, thisMonthKey);
  let monthlyPlanned = monthlyEq;
  if (monthlyPlanned === 0 && suggestedMonthly !== null) monthlyPlanned = suggestedMonthly;
  const monthlyFilled = Math.min(thisMonthTotal, monthlyPlanned);
  const monthlyBonus  = Math.max(0, thisMonthTotal - monthlyPlanned);
  const monthlyPct    = monthlyPlanned > 0 ? Math.min(100, (thisMonthTotal / monthlyPlanned) * 100) : 0;

  return {
    balance, target, remaining, pct, complete,
    dailyRate, monthlyEq,
    daysToTarget, targetDate: targetDateD,
    daysToFinish, projectedDate,
    status, daysDiff,
    suggestedMonthly,
    thisMonthTotal, monthlyPlanned, monthlyFilled, monthlyBonus, monthlyPct,
  };
}

function calcWhatIf(goal, baseCalc, extraMonthly, oneTime) {
  const adjRemaining = Math.max(0, baseCalc.remaining - oneTime);
  const today = new Date(); today.setHours(0, 0, 0, 0);

  const adjDailyRate = baseCalc.dailyRate + (extraMonthly / 30.44);

  let adjDaysToFinish, adjProjectedDate;
  if (adjRemaining === 0) {
    adjDaysToFinish  = 0;
    adjProjectedDate = today;
  } else if (adjDailyRate > 0) {
    adjDaysToFinish  = Math.ceil(adjRemaining / adjDailyRate);
    adjProjectedDate = new Date(today.getTime() + adjDaysToFinish * 864e5);
  } else {
    adjDaysToFinish  = null;
    adjProjectedDate = null;
  }

  const daysSaved = (baseCalc.daysToFinish !== null && adjDaysToFinish !== null)
    ? baseCalc.daysToFinish - adjDaysToFinish
    : null;

  let adjStatus = 'unknown', adjDaysDiff = null;
  if (adjRemaining === 0) {
    adjStatus = 'complete';
  } else if (adjDaysToFinish !== null && baseCalc.daysToTarget !== null) {
    adjDaysDiff = baseCalc.daysToTarget - adjDaysToFinish;
    if (adjDaysDiff > 0)       adjStatus = 'ahead';
    else if (adjDaysDiff === 0) adjStatus = 'ontrack';
    else                       adjStatus = 'late';
  }

  return { adjRemaining, adjDailyRate, adjDaysToFinish, adjProjectedDate, daysSaved, adjStatus, adjDaysDiff };
}

function weeksInYear(yr) {
  const dec28 = new Date(yr, 11, 28);
  return Math.floor((dec28 - new Date(yr, 0, 1)) / (7 * 864e5)) + 1;
}

function calcStreak(contributions) {
  if (!contributions || !contributions.length) return { total: 0, current: 0, longest: 0 };
  const weekKeys = new Set();
  contributions.forEach(c => {
    const d = new Date(c.date);
    const yr = d.getFullYear();
    const wk = Math.floor((d - new Date(yr, 0, 1)) / (7 * 864e5));
    weekKeys.add(`${yr}-${wk}`);
  });
  const sorted = Array.from(weekKeys).sort();
  const now = new Date();
  const curYr = now.getFullYear();
  const curWk = Math.floor((now - new Date(curYr, 0, 1)) / (7 * 864e5));
  let current = 0;
  for (let i = 0; i < 200; i++) {
    let yr = curYr, wk = curWk - i;
    while (wk < 0) { yr -= 1; wk += weeksInYear(yr); }
    if (weekKeys.has(`${yr}-${wk}`)) current++;
    else if (i > 1) break;
  }
  let longest = 0, run = 0, prev = null;
  for (const k of sorted) {
    const [y, w] = k.split('-').map(Number);
    const idx = y * 53 + w;
    if (prev !== null && idx === prev + 1) run++;
    else run = 1;
    if (run > longest) longest = run;
    prev = idx;
  }
  return { total: weekKeys.size, current, longest };
}

function getMonthlyData(contributions) {
  const map = {};
  (contributions || []).forEach(c => {
    const k = (c.date || '').slice(0, 7);
    if (k) map[k] = (map[k] || 0) + (c.amount || 0);
  });
  return Array.from({ length: 6 }, (_, i) => {
    const d = new Date();
    d.setDate(1);
    d.setMonth(d.getMonth() - (5 - i));
    const k = d.toISOString().slice(0, 7);
    return { key: k, label: d.toLocaleDateString('en-US', { month: 'short' }), amount: map[k] || 0 };
  });
}

function relTime(dateStr) {
  if (!dateStr) return '';
  const then = new Date(dateStr);
  const now  = new Date();
  const diffMs = now - then;
  if (diffMs < 0) return then.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
  const m = Math.floor(diffMs / 60000);
  if (m < 1)  return 'just now';
  if (m < 60) return `${m}m ago`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h}h ago`;
  const d = Math.floor(h / 24);
  if (d === 1) return 'yesterday';
  if (d < 7)   return `${d}d ago`;
  return then.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
