﻿// BillyHub generated split file. Keep script order in Bills Manager.html.
const { useState, useEffect, useRef } = React;

// â”€â”€ Helpers â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
const uid = () => Math.random().toString(36).slice(2, 11);
const fmt = (n, cents = true) =>
  new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD',
    minimumFractionDigits: cents ? 2 : 0, maximumFractionDigits: cents ? 2 : 0 }).format(n || 0);
const parseLocalDate = value => {
  if (!value) return null;
  if (value instanceof Date) {
    if (Number.isNaN(value.getTime())) return null;
    return new Date(value.getFullYear(), value.getMonth(), value.getDate());
  }
  const [year, month, day] = String(value).split('-').map(Number);
  if (!year || !month || !day) return null;
  return new Date(year, month - 1, day);
};

const toLocalISO = value => {
  const d = parseLocalDate(value) || parseLocalDate(new Date());
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
};

const startOfLocalDay = value => parseLocalDate(value) || parseLocalDate(new Date());
const daysInMonth = (year, month) => new Date(year, month + 1, 0).getDate();
const monthlyDueDate = (year, month, dueDay) => new Date(year, month, Math.min(parseInt(dueDay || 1, 10), daysInMonth(year, month)));
const todayISO = () => toLocalISO(new Date());
const shortDate = d => d ? d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : '-';
const dayOnly = d => d ? String(d.getDate()) : '-';
const longDate = d => d.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });

const FREQ = [
  { value: 'monthly', label: 'Monthly' }, { value: 'weekly', label: 'Weekly' },
  { value: 'biweekly', label: 'Bi-weekly' }, { value: 'quarterly', label: 'Quarterly' },
  { value: 'annually', label: 'Annually' }, { value: 'one-time', label: 'One-time' },
];
const freqLabel = v => FREQ.find(f => f.value === v)?.label || v;
const DEBT_CATEGORIES = ['Credit Card', 'Loan', 'Mortgage', 'Medical', 'Other'];
function inferDebtCategory(debt = {}) {
  const name = (debt.name || '').toLowerCase();
  if (name.includes('loan')) return 'Loan';
  if (name.includes('mortgage')) return 'Mortgage';
  if (name.includes('care')) return 'Medical';
  if (name.includes('card') || name.includes('cc') || name.includes('credit')) return 'Credit Card';
  return 'Other';
}
const debtCategory = debt => debt.category === 'Insurance'
  ? 'Mortgage'
  : DEBT_CATEGORIES.includes(debt.category) ? debt.category : inferDebtCategory(debt);
const MONTHLY = { weekly: 52/12, biweekly: 26/12, monthly: 1, quarterly: 1/3, annually: 1/12, 'one-time': 0 };
const PAY_FREQUENCIES = [
  { value: 'weekly', label: 'Weekly' },
  { value: 'biweekly', label: 'Bi-weekly' },
  { value: 'monthly', label: 'Monthly' },
];
const payFrequencyLabel = value => PAY_FREQUENCIES.find(item => item.value === value)?.label || value || 'Not set';
const normalizeDebtMatchName = value => String(value || '').toLowerCase().replace(/&/g, 'and').replace(/[^a-z0-9]/g, '');
const BILL_DEBT_ALIASES = {};

function findDebtForBill(bill, debts = []) {
  if (!bill || !Array.isArray(debts) || debts.length === 0) return null;
  if (bill.linkedDebtId) {
    const linked = debts.find(debt => debt.id === bill.linkedDebtId);
    if (linked) return linked;
  }
  const billKey = normalizeDebtMatchName(bill.name);
  const aliasKey = BILL_DEBT_ALIASES[billKey] || billKey;
  return debts.find(debt => {
    const debtKey = normalizeDebtMatchName(debt.name);
    return debtKey === aliasKey || aliasKey === debtKey || aliasKey.includes(debtKey) || debtKey.includes(aliasKey);
  }) || null;
}

function getNextDue(bill, from = new Date()) {
  const f = startOfLocalDay(from);
  if (bill.frequency === 'monthly') {
    let d = monthlyDueDate(f.getFullYear(), f.getMonth(), bill.dueDay);
    if (d < f) d = monthlyDueDate(f.getFullYear(), f.getMonth() + 1, bill.dueDay);
    return d;
  }
  if (!bill.startDate) return null;
  const start = parseLocalDate(bill.startDate);
  if (!start) return null;
  if (bill.frequency === 'one-time') return start >= f ? start : null;
  const steps = { weekly: 7, biweekly: 14 };
  if (steps[bill.frequency]) {
    let d = new Date(start); while (d < f) d.setDate(d.getDate() + steps[bill.frequency]); return d;
  }
  if (bill.frequency === 'quarterly') { let d = new Date(start); while (d < f) d.setMonth(d.getMonth() + 3); return d; }
  if (bill.frequency === 'annually')  { let d = new Date(start); while (d < f) d.setFullYear(d.getFullYear() + 1); return d; }
  return null;
}

function getDatesInMonth(bill, year, month) {
  const mS = new Date(year, month, 1), mE = new Date(year, month+1, 0);
  const dates = [];
  if (bill.frequency === 'monthly') {
    dates.push(monthlyDueDate(year, month, bill.dueDay));
  } else if (bill.frequency === 'one-time') {
    const d = parseLocalDate(bill.startDate);
    if (!d) return [];
    if (d >= mS && d <= mE) dates.push(d);
  } else if (bill.frequency === 'weekly' || bill.frequency === 'biweekly') {
    const start = parseLocalDate(bill.startDate);
    if (!start) return [];
    const iv = bill.frequency === 'weekly' ? 7 : 14;
    let d = new Date(start);
    while (d < mS) d.setDate(d.getDate() + iv);
    while (d <= mE) { dates.push(new Date(d)); d.setDate(d.getDate() + iv); }
  } else if (bill.frequency === 'quarterly') {
    const start = parseLocalDate(bill.startDate);
    if (!start) return [];
    let d = new Date(start);
    while (d < mS) d.setMonth(d.getMonth() + 3);
    if (d <= mE) dates.push(new Date(d));
  } else if (bill.frequency === 'annually') {
    const start = parseLocalDate(bill.startDate);
    if (!start) return [];
    let d = new Date(start);
    while (d < mS) d.setFullYear(d.getFullYear() + 1);
    if (d <= mE) dates.push(new Date(d));
  }
  return dates;
}

function getSemiMonthlyDays(payday = {}) {
  const first = parseLocalDate(payday.firstDate);
  const second = parseLocalDate(payday.secondDate);
  const day1 = first ? first.getDate() : parseInt(payday.day1 || 1, 10);
  const day2 = second ? second.getDate() : parseInt(payday.day2 || 15, 10);
  return [day1 || 1, day2 || 15].sort((a, b) => a - b);
}

function getPaydatesInRange(payday = {}, rangeStart, rangeEnd) {
  const start = parseLocalDate(rangeStart);
  const end = parseLocalDate(rangeEnd);
  const anchor = parseLocalDate(payday.firstDate);
  const frequency = payday.frequency || 'biweekly';
  const dates = [];
  if (!start || !end || !anchor) return dates;

  if (frequency === 'weekly' || frequency === 'biweekly') {
    const interval = frequency === 'weekly' ? 7 : 14;
    const d = new Date(anchor);
    while (d > start) d.setDate(d.getDate() - interval);
    while (d < start) d.setDate(d.getDate() + interval);
    while (d <= end) {
      dates.push(new Date(d));
      d.setDate(d.getDate() + interval);
    }
    return dates;
  }

  if (frequency === 'monthly') {
    const day = anchor.getDate();
    for (let y = start.getFullYear(); y <= end.getFullYear(); y++) {
      const fromMonth = y === start.getFullYear() ? start.getMonth() : 0;
      const toMonth = y === end.getFullYear() ? end.getMonth() : 11;
      for (let m = fromMonth; m <= toMonth; m++) {
        const d = monthlyDueDate(y, m, day);
        if (d >= start && d <= end) dates.push(d);
      }
    }
    return dates;
  }

  if (frequency === 'semimonthly') {
    const [day1, day2] = getSemiMonthlyDays(payday);
    for (let y = start.getFullYear(); y <= end.getFullYear(); y++) {
      const fromMonth = y === start.getFullYear() ? start.getMonth() : 0;
      const toMonth = y === end.getFullYear() ? end.getMonth() : 11;
      for (let m = fromMonth; m <= toMonth; m++) {
        [monthlyDueDate(y, m, day1), monthlyDueDate(y, m, day2)].forEach(d => {
          if (d >= start && d <= end) dates.push(d);
        });
      }
    }
  }
  return dates.sort((a, b) => a - b);
}

function getAutomaticPayPeriod(payday = {}, from = new Date()) {
  if (!payday.autoEnabled || !payday.firstDate) return null;
  const today = startOfLocalDay(from);
  const rangeStart = new Date(today);
  rangeStart.setFullYear(rangeStart.getFullYear() - 1);
  const rangeEnd = new Date(today);
  rangeEnd.setFullYear(rangeEnd.getFullYear() + 1);
  const dates = getPaydatesInRange(payday, rangeStart, rangeEnd);
  if (dates.length < 2) return null;
  const nextIndex = dates.findIndex(d => d > today);
  if (nextIndex <= 0) return null;
  return { start: dates[nextIndex - 1], end: dates[nextIndex], source: 'auto' };
}

function getPayPeriodOptions(payday = {}, from = new Date(), before = 1, after = 6) {
  if (!payday.autoEnabled || !payday.firstDate) return [];
  const today = startOfLocalDay(from);
  const rangeStart = new Date(today);
  rangeStart.setFullYear(rangeStart.getFullYear() - 1);
  const rangeEnd = new Date(today);
  rangeEnd.setFullYear(rangeEnd.getFullYear() + 1);
  const dates = getPaydatesInRange(payday, rangeStart, rangeEnd);
  const nextIndex = dates.findIndex(d => d > today);
  if (nextIndex <= 0) return [];
  const first = Math.max(1, nextIndex - before);
  const last = Math.min(dates.length - 1, nextIndex + after);
  const options = [];
  for (let i = first; i <= last; i++) {
    options.push({
      key: `${toLocalISO(dates[i - 1])}_${toLocalISO(dates[i])}`,
      start: dates[i - 1],
      end: dates[i],
      isCurrent: i === nextIndex,
    });
  }
  return options;
}

function getNextPayDateFromSetup(payday = {}, from = new Date()) {
  if (!payday.firstDate) return null;
  const today = startOfLocalDay(from);
  const rangeEnd = new Date(today);
  rangeEnd.setFullYear(rangeEnd.getFullYear() + 1);
  return getPaydatesInRange(payday, today, rangeEnd)[0] || null;
}

// Frequency-based payday days for a month (for calendar highlighting)
function getPaydaysInMonth(paydayOrFirstDate, frequency, year, month) {
  const payday = typeof paydayOrFirstDate === 'object'
    ? paydayOrFirstDate
    : { firstDate: paydayOrFirstDate, frequency };
  const firstDate = payday.firstDate;
  frequency = payday.frequency || frequency;
  const mS = new Date(year, month, 1), mE = new Date(year, month+1, 0);
  const days = new Set();
  if (frequency === 'semimonthly') {
    getPaydatesInRange(payday, mS, mE).forEach(d => days.add(d.getDate()));
    return days;
  }
  const anchor = parseLocalDate(firstDate);
  if (!anchor) return days;
  if (frequency === 'monthly') {
    const dom = anchor.getDate();
    if (dom <= mE.getDate()) days.add(dom);
    return days;
  }
  const iv = frequency === 'weekly' ? 7 : 14;
  let d = new Date(anchor);
  // walk backward to find first occurrence before mS
  while (d >= mS) d.setDate(d.getDate() - iv);
  // walk forward to collect all in month
  d.setDate(d.getDate() + iv);
  while (d <= mE) {
    if (d >= mS) days.add(d.getDate());
    d.setDate(d.getDate() + iv);
  }
  return days;
}

// Two-date pay period: use the exact dates selected in Budget vs. Bills.
function getCurrentPayPeriod(date1, date2) {
  if (!date1 || !date2) return null;
  const first = parseLocalDate(date1);
  const second = parseLocalDate(date2);
  if (!first || !second) return null;
  if (Number.isNaN(first.getTime()) || Number.isNaN(second.getTime())) return null;
  return first <= second ? { start: first, end: second } : { start: second, end: first };
}

function billsDueInPeriod(bills, start, end) {
  return bills.filter(b => {
    const d = getNextDue(b, start);
    return d && d >= start && d <= end;
  });
}

const occurrenceKey = d => toLocalISO(d);

function isOccurrencePaid(bill, dueDate) {
  if (!bill || !dueDate) return false;
  return Array.isArray(bill.paidDates) && bill.paidDates.includes(occurrenceKey(dueDate));
}

function isOccurrencePrepaid(bill, dueDate) {
  if (!bill || !dueDate) return false;
  return Array.isArray(bill.prepaidDates) && bill.prepaidDates.includes(occurrenceKey(dueDate));
}

function calcPayoff(debt, extraPayment = 0) {
  const rate = debt.apr / 100 / 12, pay = Math.max((debt.minPayment || 0) + extraPayment, 1);
  if (!rate) { const m = Math.ceil(debt.balance / pay); return { months: m, totalPaid: m * pay, interestPaid: 0 }; }
  let bal = debt.balance, months = 0, totalPaid = 0;
  while (bal > 0.01 && months < 600) {
    const interest = bal * rate, paid = Math.min(pay, bal + interest);
    bal = bal + interest - paid; totalPaid += paid; months++;
  }
  return { months, totalPaid, interestPaid: totalPaid - debt.balance };
}

const SAMPLE_BILLS = [
  { id: uid(), name: 'Rent', amount: 1800, frequency: 'monthly', dueDay: 1, startDate: '', autoPay: false, notes: '' },
  { id: uid(), name: 'Netflix', amount: 15.99, frequency: 'monthly', dueDay: 15, startDate: '', autoPay: true, notes: 'Shared plan' },
  { id: uid(), name: 'Electric', amount: 120, frequency: 'monthly', dueDay: 22, startDate: '', autoPay: false, notes: '' },
  { id: uid(), name: 'Car Insurance', amount: 180, frequency: 'monthly', dueDay: 5, startDate: '', autoPay: true, notes: '' },
  { id: uid(), name: 'Internet', amount: 65, frequency: 'monthly', dueDay: 18, startDate: '', autoPay: true, notes: '' },
];
const SAMPLE_DEBTS = [
  { id: uid(), name: 'Visa Credit Card', balance: 4200, apr: 24.99, minPayment: 85, notes: '' },
  { id: uid(), name: 'Student Loan', balance: 18500, apr: 5.5, minPayment: 210, notes: '' },
];

// Day picker helpers and deploy builds intentionally ship with no imported user data.
const IMPORTED_BILLS_VERSION = '';
const IMPORTED_BILLS = [];
const IMPORTED_DEBTS_VERSION = '';
const IMPORTED_DEBTS = [];

