// shared.jsx — Maud's Dashboard shared store, data, helpers
// One source of truth so changes in any variant propagate to the others.

// ─── Date helpers (NL) ──────────────────────────────────────────
const TODAY = new Date(2026, 4, 13); // wo 13 mei 2026
const DAY_NAMES_NL  = ['zondag','maandag','dinsdag','woensdag','donderdag','vrijdag','zaterdag'];
const DAY_SHORT_NL  = ['zo','ma','di','wo','do','vr','za'];
const MONTH_NAMES_NL = ['januari','februari','maart','april','mei','juni','juli','augustus','september','oktober','november','december'];
const formatDate = (d) => `${DAY_NAMES_NL[d.getDay()]} ${d.getDate()} ${MONTH_NAMES_NL[d.getMonth()]}`;
const fmtEur = (n, opts = {}) => {
  const sign = n < 0 ? '−' : '';
  const a = Math.abs(n);
  const decimals = opts.short && a >= 100 ? 0 : 2;
  return `${sign}€\u00A0${a.toFixed(decimals).replace('.', ',')}`;
};
const fmtEurShort = (n) => fmtEur(n, { short: true });

// ─── Defaults / seed data ───────────────────────────────────────
const DEFAULTS = {
  mood: 'good', // good | ok | sleepy | stressed | sad | spark
  moodLog: [
    { date: '2026-05-08', mood: 'ok' },
    { date: '2026-05-09', mood: 'spark' },
    { date: '2026-05-10', mood: 'sleepy' },
    { date: '2026-05-11', mood: 'good' },
    { date: '2026-05-12', mood: 'stressed' },
  ],
  top3: [
    { id: 't1', text: 'Verzekering bellen', done: false },
    { id: 't2', text: 'Wandelen 20 min',    done: true  },
    { id: 't3', text: 'Mail naar Anouk',    done: false },
  ],
  appointments: [
    { id: 'a1', time: '09:30', title: 'Daily standup',   kind: 'work',   day: 0 },
    { id: 'a2', time: '12:30', title: 'Lunch met Lisa',  kind: 'social', day: 0 },
    { id: 'a3', time: '18:00', title: 'Training',        kind: 'health', day: 0 },
    { id: 'a4', time: '10:00', title: 'Tandarts',        kind: 'health', day: 1 },
    { id: 'a5', time: '20:00', title: 'Bios met Sara',   kind: 'social', day: 2 },
  ],
  income: 2450,
  bills: [
    { id: 'b1', name: 'Huur',          amount: 850.00, day: 1,  kind: 'fixed', paid: true  },
    { id: 'b2', name: 'Sportschool',   amount: 24.95,  day: 5,  kind: 'fixed', paid: true  },
    { id: 'b3', name: 'Spotify',       amount: 17.99,  day: 15, kind: 'fixed', paid: false },
    { id: 'b4', name: 'ZilverenKruis', amount: 142.00, day: 25, kind: 'fixed', paid: false },
    { id: 'b5', name: 'Internet',      amount: 39.50,  day: 28, kind: 'fixed', paid: false },
  ],
  expenses: [
    { id: 'e1', amount: 4.50,  label: 'koffie',         cat: 'food',      daysAgo: 0 },
    { id: 'e2', amount: 32.40, label: 'AH boodschappen',cat: 'food',      daysAgo: 1 },
    { id: 'e3', amount: 12.00, label: 'lunch op werk',  cat: 'food',      daysAgo: 1 },
    { id: 'e4', amount: 19.99, label: 'kleren H&M',     cat: 'fun',       daysAgo: 2 },
    { id: 'e5', amount: 8.50,  label: 'taxi naar Sara', cat: 'transport', daysAgo: 3 },
    { id: 'e6', amount: 6.40,  label: 'koffie Bagels',  cat: 'food',      daysAgo: 4 },
    { id: 'e7', amount: 24.00, label: 'bios',           cat: 'fun',       daysAgo: 6 },
  ],
  budgets: [
    { cat: 'food',      label: 'Eten',     limit: 400, color: 'mint',     icon: '🥑' },
    { cat: 'fun',       label: 'Leuk',     limit: 150, color: 'peach',    icon: '🎈' },
    { cat: 'transport', label: 'Vervoer',  limit: 80,  color: 'sky',      icon: '🚲' },
    { cat: 'home',      label: 'Thuis',    limit: 60,  color: 'lavendel', icon: '🪴' },
  ],
  savings: [
    { id: 's1', name: 'Lissabon',    target: 800,  current: 320, color: 'peach' },
    { id: 's2', name: 'Buffertje',   target: 1000, current: 740, color: 'sage' },
    { id: 's3', name: 'Nieuwe fiets',target: 450,  current:  90, color: 'lavendel' },
  ],
  groceries: [
    { id: 'g1', label: 'havermout',   done: false },
    { id: 'g2', label: 'bananen',     done: false },
    { id: 'g3', label: 'pindakaas',   done: true  },
    { id: 'g4', label: 'tandpasta',   done: false },
  ],
  brainDump: [
    { id: 'bd1', text: 'planten voor in de keuken',     tag: 'idee' },
    { id: 'bd2', text: 'tandartsafspraak maken',         tag: 'todo' },
    { id: 'bd3', text: 'cadeau verjaardag mama',         tag: 'todo' },
  ],
  reminders: [
    { id: 'r1', label: 'Medicatie',         recur: 'dagelijks',  doneToday: true,  streak: 12 },
    { id: 'r2', label: 'Water — 8 glazen', recur: 'dagelijks',  doneToday: false, streak: 5, progress: 3, max: 8 },
    { id: 'r3', label: 'Saldo checken',    recur: 'wekelijks',  doneToday: false, streak: 3 },
    { id: 'r4', label: '10 min wandelen',  recur: 'dagelijks',  doneToday: false, streak: 7 },
  ],
  // Google Calendar (filled by google-calendar.jsx via ICS feed)
  googleICSUrl: '',
  googleEvents: [],   // [{start: ISO, allDay, summary, location, uid}]
  googleLastSync: null,

  // Monthly cycle (cycle runs from resetDay to next resetDay)
  cycleResetDay: 23,                    // dag van de maand waarop salaris binnenkomt
  currentCycleStart: '2026-04-23',      // ISO date — start van huidige cyclus
  expenseHistory: [],                   // [{ start, end, spent, income, perCategory, savedDelta }]
  lastCycleSnapshot: null,              // snapshot of previous cycle for comparisons

  // Celebrations
  celebrated: [],                       // ids of savings goals already celebrated

  // Notifications
  notifications: {
    enabled: false,
    medicationTime: '08:00',
    top3Time: '09:00',
    billsLeadDays: 2,
    lastShownDate: null,                // YYYY-MM-DD — used to dedupe shown notifications
  },

  // Backup
  lastBackupAt: null,

  // Bank connection (PSD2 via Enable Banking).
  // accounts[] is bron van waarheid voor saldi en metadata.
  // kind: 'personal' | 'shared' | 'savings' | 'other'
  // sharePct: % van uitgaven op dit account dat meetelt voor "veilig uit te geven"
  //   (default per kind: persoonlijk=100, gezamenlijk=50, spaar=0)
  bank: {
    connected: false,
    institution: null,
    institutionLabel: null,
    accounts: [],            // [{ uid, label, kind, iban, balance, sharePct }]
    transactions: [],        // elke tx draagt accountUid
    lastSync: null,
    consentExpiresAt: null,
    // Legacy velden, gehouden voor migratie van oude state:
    accountId: null,
    accountIban: null,
    balance: null,
    config: {
      backendUrl: '',
    },
  },

  // Meal planning (simple: per ISO date → { name, ingredients[] })
  mealPlan: {
    '2026-05-13': { name: 'Pasta pesto met kerstomaatjes', ingredients: ['pasta', 'pesto', 'kerstomaatjes', 'parmezaan'] },
    '2026-05-14': { name: 'Bowl met kip', ingredients: ['kipfilet', 'rijst', 'avocado', 'sla', 'maïs'] },
    '2026-05-15': { name: 'Pizza zelf maken', ingredients: ['pizzabodem', 'tomatensaus', 'mozzarella', 'rucola'] },
    '2026-05-16': { name: '', ingredients: [] },
    '2026-05-17': { name: '', ingredients: [] },
    '2026-05-18': { name: '', ingredients: [] },
    '2026-05-19': { name: '', ingredients: [] },
  },
  recipes: [
    { id: 'rec1', name: 'Pasta pesto',         ingredients: ['pasta', 'pesto', 'parmezaan', 'kerstomaatjes'] },
    { id: 'rec2', name: 'Bowl met kip',        ingredients: ['kipfilet', 'rijst', 'avocado', 'sla'] },
    { id: 'rec3', name: 'Wraps met halloumi',  ingredients: ['wraps', 'halloumi', 'tomaat', 'rode ui', 'yoghurtsaus'] },
    { id: 'rec4', name: 'Pannenkoeken',        ingredients: ['pannenkoekmix', 'melk', 'eieren', 'stroop'] },
  ],
};

// ─── Cross-variant store (subscribable) ──────────────────────────
const STORAGE_KEY = 'maud-dashboard-v3';
const dashboardStore = (() => {
  let state = (() => {
    try {
      const stored = localStorage.getItem(STORAGE_KEY);
      if (stored) return { ...DEFAULTS, ...JSON.parse(stored) };
    } catch (e) {}
    return DEFAULTS;
  })();
  const listeners = new Set();
  const notify = () => listeners.forEach(l => l(state));
  return {
    get: () => state,
    set: (updater) => {
      state = typeof updater === 'function' ? updater(state) : { ...state, ...updater };
      try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch(e){}
      notify();
    },
    subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); },
    reset: () => { state = JSON.parse(JSON.stringify(DEFAULTS)); try { localStorage.removeItem(STORAGE_KEY); } catch(e){} notify(); },
  };
})();

function useDashboard() {
  const [, force] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => dashboardStore.subscribe(force), []);
  return [dashboardStore.get(), dashboardStore.set];
}

// expose reset for tweaks
window.__resetMaud = () => dashboardStore.reset();

// ─── Multi-account helpers ──────────────────────────────────────
const DEFAULT_SHARE_BY_KIND = { personal: 100, shared: 50, savings: 0, other: 100 };
const KIND_META = {
  personal: { label: 'Persoonlijk', icon: '👤', color: '#5E7C6B' },
  shared:   { label: 'Gezamenlijk', icon: '👫', color: '#7A5AF8' },
  savings:  { label: 'Sparen',      icon: '🎯', color: '#D97757' },
  other:    { label: 'Overig',      icon: '•',  color: '#A8998A' },
};

function getAccount(state, uid) {
  if (!uid) return null;
  return (state.bank?.accounts || []).find(a => a.uid === uid) || null;
}

function shareFractionFor(state, uid) {
  const a = getAccount(state, uid);
  if (!a) return 1;
  const pct = a.sharePct != null ? a.sharePct : (DEFAULT_SHARE_BY_KIND[a.kind] ?? 100);
  return Math.max(0, Math.min(1, pct / 100));
}

function totalBalance(state, kinds = null) {
  const accounts = state.bank?.accounts || [];
  return accounts
    .filter(a => !kinds || kinds.includes(a.kind))
    .reduce((s, a) => s + (typeof a.balance === 'number' ? a.balance : 0), 0);
}

// ─── Derived calculations ───────────────────────────────────────
function calcMoney(state) {
  const fixedTotal   = state.bills.reduce((s, b) => s + b.amount, 0);
  const fixedUnpaid  = state.bills.filter(b => !b.paid).reduce((s, b) => s + b.amount, 0);
  const manualSpent  = state.expenses
    .filter(e => e.daysAgo <= TODAY.getDate() - 1)
    .reduce((s, e) => s + e.amount, 0);
  // Each bank tx counts according to its account's sharePct (persoonlijk 100%,
  // gezamenlijk default 50%, spaar 0%). Maud past 't aan per rekening.
  const bankTxs = (state.bank?.transactions || []).filter(t => t.amount < 0 && t.cat !== 'ignored');
  const bankSpentShare = bankTxs.reduce((s, t) => s + Math.abs(t.amount) * shareFractionFor(state, t.accountUid), 0);
  const bankSpentRaw   = bankTxs.reduce((s, t) => s + Math.abs(t.amount), 0);
  const spent        = manualSpent + bankSpentShare;
  const spentRaw     = manualSpent + bankSpentRaw;
  const savedTotal   = state.savings.reduce((s, g) => s + g.current, 0);
  const safeToSpend  = state.income - fixedTotal - spent;
  const cycleDays    = daysLeftInCycle(state);
  const dailyAllow   = Math.max(0, Math.round(safeToSpend / Math.max(1, cycleDays)));
  const perCategory  = state.budgets.map(b => {
    const m = state.expenses.filter(e => e.cat === b.cat).reduce((s, e) => s + e.amount, 0);
    const k = (state.bank?.transactions || [])
      .filter(t => t.cat === b.cat && t.amount < 0)
      .reduce((s, t) => s + Math.abs(t.amount) * shareFractionFor(state, t.accountUid), 0);
    return { ...b, spent: m + k };
  });
  const inboxCount = (state.bank?.transactions || [])
    .filter(t => t.amount < 0 && (t.cat == null || t.cat === ''))
    .length;
  return { fixedTotal, fixedUnpaid, spent, spentRaw, savedTotal, safeToSpend, dailyAllow, perCategory, cycleDays, inboxCount };
}

// Unified expense list: manual + bank (negative bank tx only, excludes ignored), sorted recent first.
function allExpenses(state) {
  const manual = (state.expenses || []).map(e => ({ ...e, source: e.source || 'manual' }));
  const bank = (state.bank?.transactions || [])
    .filter(t => t.amount < 0 && t.cat !== 'ignored')
    .map(t => ({
      id: 'btx-' + t.id,
      amount: Math.abs(t.amount),
      label: t.description,
      cat: t.cat || null,
      daysAgo: t.daysAgo ?? 0,
      source: 'bank',
      bankRaw: t,
    }));
  return [...manual, ...bank].sort((a, b) => (a.daysAgo || 0) - (b.daysAgo || 0));
}

function daysLeftInMonth(d = TODAY) {
  const last = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
  return Math.max(1, last - d.getDate() + 1);
}

// ─── Cycle helpers (cycle runs from cycleResetDay to next cycleResetDay) ─────
function cycleStartFor(today, resetDay) {
  const d = new Date(today.getFullYear(), today.getMonth(), resetDay);
  if (d > today) d.setMonth(d.getMonth() - 1);
  return d;
}
function cycleEndFor(today, resetDay) {
  const d = new Date(today.getFullYear(), today.getMonth(), resetDay);
  if (d <= today) d.setMonth(d.getMonth() + 1);
  return d;
}
function daysLeftInCycle(state, today = TODAY) {
  const end = cycleEndFor(today, state.cycleResetDay || 23);
  return Math.max(1, Math.ceil((end - today) / 86400000));
}
function cycleProgress(state, today = TODAY) {
  const start = cycleStartFor(today, state.cycleResetDay || 23);
  const end   = cycleEndFor(today, state.cycleResetDay || 23);
  return Math.min(1, Math.max(0, (today - start) / (end - start)));
}
function fmtCycleRange(state, today = TODAY) {
  const start = cycleStartFor(today, state.cycleResetDay || 23);
  const end   = cycleEndFor(today, state.cycleResetDay || 23);
  return `${start.getDate()} ${MONTH_NAMES_NL[start.getMonth()].slice(0,3)} — ${end.getDate()} ${MONTH_NAMES_NL[end.getMonth()].slice(0,3)}`;
}

// Reset the cycle: archive the current cycle's snapshot, clear expenses,
// reset bills.paid and top3.done. Returns the new state (caller must apply).
function performCycleReset(state, today = new Date()) {
  const correctStart = cycleStartFor(today, state.cycleResetDay || 23);
  const snapshot = {
    start: state.currentCycleStart,
    end: correctStart.toISOString().slice(0, 10),
    income: state.income,
    spent: state.expenses.reduce((s, e) => s + e.amount, 0),
    perCategory: state.budgets.map(b => ({
      cat: b.cat,
      label: b.label,
      limit: b.limit,
      spent: state.expenses.filter(e => e.cat === b.cat).reduce((s, e) => s + e.amount, 0),
    })),
    savedTotal: state.savings.reduce((s, g) => s + g.current, 0),
  };
  return {
    ...state,
    expenseHistory: [...(state.expenseHistory || []), snapshot],
    lastCycleSnapshot: snapshot,
    expenses: [],
    bills: state.bills.map(b => ({ ...b, paid: false })),
    top3: state.top3.map(t => ({ ...t, done: false })),
    currentCycleStart: correctStart.toISOString().slice(0, 10),
  };
}

// Auto-detect a missed cycle reset and apply it.
function maybeAutoReset(state, today = new Date()) {
  if (!state.currentCycleStart) return state;
  const correct = cycleStartFor(today, state.cycleResetDay || 23);
  const stored  = new Date(state.currentCycleStart);
  if (correct > stored) return performCycleReset(state, today);
  return state;
}

// ─── Mood definitions ───────────────────────────────────────────
const MOODS = [
  { id: 'good',     emoji: '😊', label: 'goed' },
  { id: 'ok',       emoji: '😐', label: 'oké' },
  { id: 'sleepy',   emoji: '😴', label: 'moe' },
  { id: 'stressed', emoji: '😤', label: 'druk' },
  { id: 'sad',      emoji: '🥲', label: 'down' },
  { id: 'spark',    emoji: '⚡️', label: 'aan' },
];

// ─── Tailwind-ish color tokens, per variant ─────────────────────
const PEACH = {
  bg:        '#FBF4ED',
  bgWarm:    '#F8E9DB',
  card:      '#FFFFFF',
  ink:       '#2A1F18',
  inkSoft:   '#6B5B4E',
  inkFaint:  '#A8998A',
  accent:    '#D97757',
  accentSoft:'#FDE6D8',
  hairline:  'rgba(42,31,24,0.08)',
};
const SAGE = {
  bg:        '#F4F2EC',
  bgWarm:    '#E8E5DA',
  card:      '#FFFFFF',
  ink:       '#1C2A24',
  inkSoft:   '#566963',
  inkFaint:  '#9AA8A2',
  accent:    '#5E7C6B',
  accentSoft:'#DDE8E1',
  hairline:  'rgba(28,42,36,0.09)',
};
const LAVENDEL = {
  bg:        '#FAF6F1',
  bgWarm:    '#EFE7F5',
  card:      '#FFFFFF',
  ink:       '#221E2B',
  inkSoft:   '#6A6177',
  inkFaint:  '#A89FB5',
  accent:    '#7A5AF8',
  accentSoft:'#E9E1FB',
  hairline:  'rgba(34,30,43,0.09)',
};

// Add some shared bank helpers that work without bank.jsx loaded.
// matchRule: find a rule whose `match` substring is in the description.
function matchRule(description, rules) {
  if (!description || !rules) return null;
  const d = description.toLowerCase();
  return rules.find(r => r.match && d.includes(r.match.toLowerCase())) || null;
}

Object.assign(window, {
  TODAY, DAY_NAMES_NL, DAY_SHORT_NL, MONTH_NAMES_NL,
  formatDate, fmtEur, fmtEurShort, daysLeftInMonth,
  DEFAULTS, dashboardStore, useDashboard, calcMoney, allExpenses,
  MOODS, PEACH, SAGE, LAVENDEL,
  cycleStartFor, cycleEndFor, daysLeftInCycle, cycleProgress,
  fmtCycleRange, performCycleReset, maybeAutoReset,
  matchRule,
  // Multi-account helpers
  DEFAULT_SHARE_BY_KIND, KIND_META, getAccount, shareFractionFor, totalBalance,
});
