/* ============================================================
   api.jsx — DATA ACCESS LAYER  ·  "the backend seam"
   ------------------------------------------------------------
   Every piece of data in the dashboard flows through THIS file.
   Right now it returns mock data. To go live with a real backend
   (build it later with Claude Code — see BACKEND.md):

       1. Set  API.useMock = false
       2. Set  API.baseUrl = "https://your-backend.example.com/api"
       3. Make your backend return the documented JSON shapes.

   Each function below has its REST route + response shape in the
   comment above it. That comment IS the contract your backend must
   honour. Nothing else in the app needs to change.
   ============================================================ */

const API = {
  baseUrl: "https://api.hyperforming.nl/api", // ← your backend base URL (used when useMock = false)
  useMock: false,      // ← flip to false once the backend is live
  latency: 180,        // simulated network delay for the mock (ms)
};

/* ---------- tiny HTTP helpers (used in live mode) ---------- */
function _url(path, params) {
  const u = new URL(API.baseUrl.replace(/\/$/, "") + path, location.origin);
  if (params) Object.entries(params).forEach(([k, v]) => v != null && u.searchParams.set(k, v));
  return u.toString();
}
async function apiGet(path, params) {
  const r = await fetch(_url(path, params), { credentials: "include" });
  if (!r.ok) throw new Error("HTTP " + r.status + " op " + path);
  return r.json();
}
async function apiSend(method, path, body) {
  const r = await fetch(_url(path), {
    method, credentials: "include",
    headers: { "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!r.ok) throw new Error("HTTP " + r.status + " op " + path);
  return r.status === 204 ? null : r.json();
}
const _delay = (ms = API.latency) => new Promise(r => setTimeout(r, ms));

/* ---------- async hook: loading / data / error ---------- */
function useAsync(fn, deps = []) {
  const [state, setState] = useState({ loading: true, data: null, error: null });
  useEffect(() => {
    let alive = true;
    setState(s => ({ loading: true, data: s.data, error: null }));
    Promise.resolve().then(fn).then(
      data => { if (alive) setState({ loading: false, data, error: null }); },
      err => { if (alive) setState({ loading: false, data: null, error: err }); }
    );
    return () => { alive = false; };
  }, deps); // eslint-disable-line
  return state;
}

/* ============================================================
   GOOGLE CALENDAR
   ============================================================ */

/* The connection state lives in localStorage for the mock.
   A real backend stores the OAuth refresh-token server-side and
   exposes the status below. */
const GCAL_KEY = "roan_gcal";
const CAL_DEFS = [
  { id: "werk",       name: "Werk",        cat: "Werk",       color: "var(--blue)" },
  { id: "persoonlijk",name: "Persoonlijk", cat: "Persoonlijk",color: "var(--lilac)" },
  { id: "sport",      name: "Sport",       cat: "Sport",      color: "var(--accent)" },
  { id: "sociaal",    name: "Sociaal",     cat: "Sociaal",    color: "var(--rose)" },
  { id: "groei",      name: "Leren",       cat: "Groei",      color: "var(--amber)" },
  { id: "gezondheid", name: "Gezondheid",  cat: "Gezondheid", color: "var(--sage)" },
];
function _readGcal() {
  try { const s = localStorage.getItem(GCAL_KEY); if (s) return JSON.parse(s); } catch (e) {}
  return { connected: false, email: null, calendars: CAL_DEFS.map(c => ({ ...c, enabled: true })) };
}
function _writeGcal(v) { try { localStorage.setItem(GCAL_KEY, JSON.stringify(v)); } catch (e) {} return v; }

/* GET /calendar/status
   → { connected:bool, email:string|null,
       calendars:[{ id, name, color, enabled:bool }] }            */
async function getCalendarStatus() {
  await _delay(80);
  if (!API.useMock) return apiGet("/calendar/status");
  return _readGcal();
}

/* POST /calendar/connect
   Live: returns { authUrl } → redirect the user to Google's OAuth
   consent screen; your backend handles the callback + token store.
   Mock: simulates a successful connection.                        */
async function connectGoogleCalendar() {
  if (!API.useMock) {
    const { authUrl } = await apiSend("POST", "/calendar/connect");
    window.location.href = authUrl;   // hand off to Google OAuth
    return;
  }
  await _delay(900); // pretend we bounced through Google's consent screen
  const cur = _readGcal();
  return _writeGcal({
    ...cur,
    connected: true,
    email: "roan.devries@gmail.com",
    calendars: cur.calendars && cur.calendars.length ? cur.calendars : CAL_DEFS.map(c => ({ ...c, enabled: true })),
  });
}

/* POST /calendar/disconnect → revokes token server-side */
async function disconnectGoogleCalendar() {
  await _delay(200);
  if (!API.useMock) return apiSend("POST", "/calendar/disconnect");
  return _writeGcal({ connected: false, email: null, calendars: CAL_DEFS.map(c => ({ ...c, enabled: true })) });
}

/* PATCH /calendar/calendars  body:{ id, enabled }
   Toggles whether a sub-calendar's events are shown.              */
async function setCalendarEnabled(id, enabled) {
  await _delay(80);
  if (!API.useMock) return apiSend("PATCH", "/calendar/calendars", { id, enabled });
  const cur = _readGcal();
  return _writeGcal({ ...cur, calendars: cur.calendars.map(c => c.id === id ? { ...c, enabled } : c) });
}

/* Weekly template the mock instantiates into real datetimes.
   (cat → calendar; loc/people/meet enrich the event-detail view) */
const _EVENT_TEMPLATE = [
  { day: 0, s: 9,    e: 10,   title: "Stand-up team",            cat: "Werk",       meet: 1, people: ["Sara", "Daan", "Mei"] },
  { day: 0, s: 12.5, e: 13,   title: "Lunchwandeling",           cat: "Gezondheid", loc: "Vondelpark" },
  { day: 0, s: 18,   e: 19.25,title: "Krachttraining",           cat: "Sport",      loc: "TrainMore Gym" },
  { day: 1, s: 10,   e: 11.5, title: "Designreview",             cat: "Werk",       meet: 1, people: ["Sara", "Tom"] },
  { day: 1, s: 15,   e: 16,   title: "1-op-1 met Sara",          cat: "Werk",       meet: 1, people: ["Sara"] },
  { day: 1, s: 20,   e: 21,   title: "Lezen & thee",             cat: "Persoonlijk" },
  { day: 2, s: 8.5,  e: 9.5,  title: "Hardlopen 8 km",           cat: "Sport",      loc: "Amsterdamse Bos" },
  { day: 2, s: 13,   e: 14,   title: "Lunch met Daan",           cat: "Sociaal",    loc: "Café Modern" },
  { day: 3, s: 9.5,  e: 11,   title: "Sprint planning",          cat: "Werk",       meet: 1, people: ["Team Platform"] },
  { day: 3, s: 19,   e: 20.5, title: "Spaans les",               cat: "Groei",      loc: "Online · iTalki" },
  { day: 4, s: 11,   e: 12,   title: "Demo & retro",             cat: "Werk",       meet: 1, people: ["Hele team"] },
  { day: 4, s: 17.5, e: 18.5, title: "Yoga",                     cat: "Sport",      loc: "Delight Yoga" },
  { day: 5, s: 10,   e: 11.5, title: "Boodschappen & meal prep", cat: "Persoonlijk" },
  { day: 5, s: 20,   e: 22,   title: "Filmavond",                cat: "Sociaal",    loc: "Bij Lisa" },
  { day: 6, s: 9,    e: 10.5, title: "Lange duurloop",           cat: "Sport",      loc: "Amstel" },
  { day: 6, s: 12,   e: 13.5, title: "Brunch familie",           cat: "Sociaal",    loc: "Bij oma" },
];
function _hoursToDate(base, h) {
  const d = new Date(base);
  d.setHours(Math.floor(h), Math.round((h % 1) * 60), 0, 0);
  return d;
}

/* GET /calendar/events?start=ISO&end=ISO
   → [{ id, title, start:ISO, end:ISO, allDay:bool, location, calendarId,
        calendarName, color, attendees:[string], meetingUrl, description }]
   Only events from ENABLED calendars are returned.                */
async function fetchCalendarEvents(startISO, endISO) {
  await _delay();
  if (!API.useMock) return apiGet("/calendar/events", { start: startISO, end: endISO });

  const status = _readGcal();
  if (!status.connected) return [];
  const enabled = new Set(status.calendars.filter(c => c.enabled).map(c => c.cat));
  const weekStart = new Date(startISO);
  const out = [];
  _EVENT_TEMPLATE.forEach((t, i) => {
    if (!enabled.has(t.cat)) return;
    const dayBase = new Date(weekStart);
    dayBase.setDate(dayBase.getDate() + t.day);
    const cal = CAL_DEFS.find(c => c.cat === t.cat);
    const start = _hoursToDate(dayBase, t.s);
    const end = _hoursToDate(dayBase, t.e);
    out.push({
      id: "evt-" + i,
      title: t.title,
      start: start.toISOString(),
      end: end.toISOString(),
      allDay: false,
      location: t.loc || null,
      calendarId: cal.id,
      calendarName: cal.name,
      color: cal.color,
      attendees: t.people || [],
      meetingUrl: t.meet ? "https://meet.google.com/abc-defg-hij" : null,
      description: null,
    });
  });
  return out;
}

/* ============================================================
   FINANCE
   ============================================================ */

function _now() { return new Date(); }
function _monthName(offsetFromNow) {
  const d = _now(); d.setMonth(d.getMonth() + offsetFromNow);
  return NL_MONTHS[d.getMonth()];
}
function _isoDaysAgo(n) { const d = _now(); d.setDate(d.getDate() - n); return d.toISOString(); }

/* GET /networth?range=1M|6M|1J
   → { total, delta, pct, range, currency,
       series:[number], labels:[string]|null,
       breakdown:[{ label, value, color }],
       history:[{ month, value, delta }] }                          */
async function fetchNetWorth(range = "1J") {
  await _delay();
  if (!API.useMock) return apiGet("/networth", { range });

  const series = range === "1M" ? NW_1M : range === "6M" ? NW_6M : NETWORTH_SERIES;
  const total = series[series.length - 1];
  const first = series[0];
  const delta = total - first;
  const pct = (delta / first) * 100;
  const labels = range === "1J" ? NL_MONTHS.map(m => m.slice(0, 1).toUpperCase()) : null;

  const cash = ACCOUNTS.filter(a => /Betaal|Contant/.test(a.name)).reduce((s, a) => s + a.balance, 0);
  const savings = ACCOUNTS.filter(a => /Spaar/.test(a.name)).reduce((s, a) => s + a.balance, 0);
  const invest = ACCOUNTS.filter(a => /Beleg/.test(a.name)).reduce((s, a) => s + a.balance, 0);
  const accountsTotal = ACCOUNTS.reduce((s, a) => s + a.balance, 0);
  const other = Math.max(0, total - accountsTotal);
  const breakdown = [
    { label: "Liquide middelen", value: cash,    color: "var(--blue)" },
    { label: "Spaargeld",        value: savings, color: "var(--sage)" },
    { label: "Beleggingen",      value: invest,  color: "var(--lilac)" },
    { label: "Pensioen / overig",value: other,   color: "var(--amber)" },
  ].filter(b => b.value > 0);

  const history = NETWORTH_SERIES.map((v, i) => {
    const monthIdx = (_now().getMonth() - (NETWORTH_SERIES.length - 1 - i) + 1200) % 12;
    return { month: NL_MONTHS[monthIdx], value: v, delta: i === 0 ? 0 : v - NETWORTH_SERIES[i - 1] };
  }).reverse(); // most recent first

  return { total, delta, pct, range, currency: "EUR", series, labels, breakdown, history };
}

/* Recent transactions per account (mock). d = days ago, amt<0 = uit. */
const _ACCOUNT_TXNS = {
  "Betaalrekening": [
    { d: 0,  name: "Salaris Acme BV",      cat: "Inkomen",      amt: 3650.00 },
    { d: 1,  name: "Albert Heijn",          cat: "Boodschappen", amt: -43.20 },
    { d: 1,  name: "NS Reizen",             cat: "Transport",    amt: -12.40 },
    { d: 3,  name: "Huur — De Key",         cat: "Wonen",        amt: -1180.00 },
    { d: 4,  name: "Spotify",               cat: "Vrije tijd",   amt: -10.99 },
    { d: 6,  name: "Restaurant Toscanini",  cat: "Uit eten",     amt: -68.50 },
    { d: 8,  name: "Naar Spaarrekening",    cat: "Overboeking",  amt: -500.00 },
  ],
  "Spaarrekening": [
    { d: 8,  name: "Van Betaalrekening",    cat: "Overboeking",  amt: 500.00 },
    { d: 30, name: "Rente",                 cat: "Rente",        amt: 24.18 },
    { d: 38, name: "Van Betaalrekening",    cat: "Overboeking",  amt: 500.00 },
  ],
  "Beleggen": [
    { d: 2,  name: "VWRL — maandinleg",     cat: "Aankoop",      amt: -250.00 },
    { d: 11, name: "Dividend VWRL",         cat: "Dividend",     amt: 38.74 },
    { d: 21, name: "Koerswinst (ongereal.)",cat: "Waarde",       amt: 412.30 },
  ],
  "Contant / overig": [
    { d: 5,  name: "Pinnen geldautomaat",   cat: "Opname",       amt: -50.00 },
    { d: 12, name: "Markt — bloemen",       cat: "Vrije tijd",   amt: -8.00 },
  ],
};

/* GET /accounts
   → [{ id, name, bank, balance, currency, color, icon,
        transactions:[{ id, date:ISO, name, category, amount }] }] */
async function fetchAccounts() {
  await _delay();
  if (!API.useMock) return apiGet("/accounts");
  return ACCOUNTS.map((a, i) => ({
    id: "acc-" + i,
    ...a,
    currency: "EUR",
    transactions: (_ACCOUNT_TXNS[a.name] || []).map((t, j) => ({
      id: `acc-${i}-tx-${j}`, date: _isoDaysAgo(t.d), name: t.name, category: t.cat, amount: t.amt,
    })),
  }));
}

/* Transactions per budget category (mock). */
const _BUDGET_TXNS = {
  "Wonen & vast":  [ { d: 3, name: "Huur — De Key", amt: -1180 } ],
  "Boodschappen":  [ { d: 1, name: "Albert Heijn", amt: -43.20 }, { d: 4, name: "Jumbo", amt: -57.80 }, { d: 7, name: "Marqt", amt: -31.10 }, { d: 9, name: "AH to go", amt: -12.60 } ],
  "Uit eten":      [ { d: 6, name: "Toscanini", amt: -68.50 }, { d: 2, name: "Thuisbezorgd", amt: -28.40 }, { d: 10, name: "Café Modern", amt: -19.00 } ],
  "Transport":     [ { d: 1, name: "NS Reizen", amt: -12.40 }, { d: 5, name: "OV-chip opwaarderen", amt: -20.00 }, { d: 12, name: "Shell", amt: -61.60 } ],
  "Sport":         [ { d: 0, name: "TrainMore abonnement", amt: -39.00 }, { d: 8, name: "Delight Yoga", amt: -29.00 } ],
  "Vrije tijd":    [ { d: 4, name: "Spotify", amt: -10.99 }, { d: 7, name: "Pathé bioscoop", amt: -27.00 }, { d: 11, name: "Boekhandel", amt: -42.50 }, { d: 13, name: "Steam", amt: -19.99 } ],
};

/* GET /budget?month=YYYY-MM
   → { month, monthLabel, income, totalSpent, totalBudget, currency,
       categories:[{ name, spent, budget, color,
         transactions:[{ id, date:ISO, name, amount }] }] }          */
async function fetchBudget(monthOffset = 0) {
  await _delay();
  const d = _now(); d.setMonth(d.getMonth() + monthOffset);
  const monthKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
  if (!API.useMock) return apiGet("/budget", { month: monthKey });

  const categories = BUDGET.map((b, i) => ({
    ...b,
    transactions: (_BUDGET_TXNS[b.name] || []).map((t, j) => ({
      id: `bud-${i}-tx-${j}`, date: _isoDaysAgo(t.d), name: t.name, amount: t.amt,
    })),
  }));
  const totalSpent = BUDGET.reduce((s, b) => s + b.spent, 0);
  const totalBudget = BUDGET.reduce((s, b) => s + b.budget, 0);
  return {
    month: monthKey,
    monthLabel: `${NL_MONTHS[d.getMonth()]} ${d.getFullYear()}`,
    income: MONTH_INCOME,
    totalSpent, totalBudget, currency: "EUR",
    categories,
  };
}

Object.assign(window, {
  API, apiGet, apiSend, useAsync,
  getCalendarStatus, connectGoogleCalendar, disconnectGoogleCalendar, setCalendarEnabled, fetchCalendarEvents,
  fetchNetWorth, fetchAccounts, fetchBudget, CAL_DEFS,
});
