// Storage layer — abstracts where events live. Swappable for cloud sync later.
//
// Interface:
//   const store = createStore({ adapter });
//   store.getEvents()          → Event[]
//   store.subscribe(fn)        → unsubscribe
//   store.addEvent(evt)
//   store.updateEvent(id, patch)
//   store.deleteEvent(id)
//
// Adapters: localStorage (default), or any object with {load, save} for cloud.

const LS_KEY = "personal-calendar:events:v1";

const localAdapter = {
  load() {
    try {
      const raw = localStorage.getItem(LS_KEY);
      return raw ? JSON.parse(raw) : [];
    } catch (e) { return []; }
  },
  save(events) {
    try { localStorage.setItem(LS_KEY, JSON.stringify(events)); } catch (e) {}
  },
};

// Future: cloud adapter — same shape, just async load/save against an API.
// const cloudAdapter = ({ endpoint, token }) => ({
//   async load() { return fetch(endpoint, { headers: { Authorization: `Bearer ${token}` }}).then(r => r.json()); },
//   async save(events) { await fetch(endpoint, { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, body: JSON.stringify(events) }); },
// });

function createStore({ adapter = localAdapter } = {}) {
  let events = adapter.load() || [];
  const listeners = new Set();

  function notify() {
    adapter.save(events);
    listeners.forEach(fn => fn(events));
  }

  function uid() {
    return "e_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 7);
  }

  return {
    getEvents() { return events; },
    subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
    addEvent(evt) {
      const e = { id: uid(), ...evt };
      events = [...events, e];
      notify();
      return e;
    },
    updateEvent(id, patch) {
      events = events.map(e => e.id === id ? { ...e, ...patch } : e);
      notify();
    },
    deleteEvent(id) {
      events = events.filter(e => e.id !== id);
      notify();
    },
    // Atomic: build the next array fully, then swap and notify once. If newEvents
    // is empty this still wipes prior events tagged with `source`, intentional.
    replaceBySource(source, newEvents) {
      const stamped = newEvents.map(e => ({ id: uid(), ...e, source }));
      const next = events.filter(e => e.source !== source).concat(stamped);
      events = next;
      notify();
    },
    // Used to restore a snapshot (undo).
    replaceAll(newEvents) {
      events = newEvents.slice();
      notify();
    },
    // For recurring events: expand a base event into instances within a range.
    // Pattern: {freq: "daily"|"weekly", count?, until?}
    setAdapter(newAdapter) {
      adapter = newAdapter;
      events = adapter.load() || [];
      notify();
    },
  };
}

// Expand recurring events. Returns a flat list with each occurrence as its own event.
// Supports daily/weekly/monthly/yearly with optional `until` (YYYY-MM-DD) and/or `count`.
// Multi-day events: shifts both `date` and `endDate` so each instance preserves its span.
function expandRecurrence(events, fromKey, toKey) {
  const fmt = (d) =>
    `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;

  function nthOccurrence(base, freq, i) {
    if (freq === "daily")   return new Date(base.getFullYear(), base.getMonth(), base.getDate() + i);
    if (freq === "weekly")  return new Date(base.getFullYear(), base.getMonth(), base.getDate() + i * 7);
    if (freq === "monthly") return new Date(base.getFullYear(), base.getMonth() + i, base.getDate());
    if (freq === "yearly")  return new Date(base.getFullYear() + i, base.getMonth(), base.getDate());
    return null;
  }

  const out = [];
  for (const e of events) {
    if (!e.recur || !e.recur.freq || e.recur.freq === "none") { out.push(e); continue; }
    out.push(e); // include the original

    const [y, m, d] = e.date.split("-").map(Number);
    const base = new Date(y, m - 1, d);
    // Span in days between date and endDate (0 if single-day) — preserved on each instance.
    let spanDays = 0;
    if (e.endDate) {
      const [ey, em, ed] = e.endDate.split("-").map(Number);
      const endBase = new Date(ey, em - 1, ed);
      spanDays = Math.round((endBase - base) / 86400000);
    }

    const until = e.recur.until || null;
    const hardCap = e.recur.count || 520; // safety stop
    for (let i = 1; i < hardCap; i++) {
      const nd = nthOccurrence(base, e.recur.freq, i);
      if (!nd) break;
      const key = fmt(nd);
      if (until && key > until) break;
      if (toKey && key > toKey) break;
      const inst = { ...e, id: `${e.id}__${i}`, date: key, _recurInstance: true, _recurParent: e.id };
      if (spanDays > 0) {
        const endNd = new Date(nd.getFullYear(), nd.getMonth(), nd.getDate() + spanDays);
        inst.endDate = fmt(endNd);
      } else {
        delete inst.endDate;
      }
      if (fromKey && (inst.endDate || inst.date) < fromKey) continue;
      out.push(inst);
    }
  }
  return out;
}

window.CalendarStore = { createStore, localAdapter, expandRecurrence };
