// Agenda — storage layer.
// Two backends, same async API:
//   - LocalStore  : localStorage. Survives offline. No cross-device sync.
//   - RemoteStore : your own server. Cross-device sync via polling.
//
// Auto-pick: if there's a <meta name="agenda-api-url" content="..."> tag in
// the HTML, RemoteStore is used; otherwise LocalStore.
//
// Public API on each store:
//   await store.load()                    -> { items, updatedAt }
//   await store.save(items)               -> { items, updatedAt }
//   store.subscribe(fn)                   -> unsubscribe()
//     fn({items, updatedAt, status})  whenever the store state changes
//     externally (other tab, server poll, network state).
//   store.status                          'idle' | 'saving' | 'syncing' | 'offline' | 'local'
//
// "status" semantics:
//   local    : LocalStore — no remote sync expected.
//   idle     : RemoteStore — last sync successful, nothing pending.
//   syncing  : RemoteStore — fetching from server now.
//   saving   : RemoteStore — pushing changes now.
//   offline  : RemoteStore — last network attempt failed.

const STORAGE_KEY = 'agenda.items.v2';
const META_KEY    = 'agenda.meta.v2';

/* ──────────────────────────────────────────────────────────────────── */
/* LocalStore                                                          */
/* ──────────────────────────────────────────────────────────────────── */
class LocalStore {
  constructor({ storageKey = STORAGE_KEY, metaKey = META_KEY, demoFactory = makeDemo } = {}) {
    this.storageKey = storageKey;
    this.metaKey = metaKey;
    this.demoFactory = demoFactory;
    this.status = 'local';
    this.subs = new Set();
    this._onStorage = (e) => {
      if (e.key !== this.storageKey) return;
      // Another tab updated. Re-emit so UI refreshes.
      this._emit();
    };
    window.addEventListener('storage', this._onStorage);
  }
  _read() {
    try {
      const raw = localStorage.getItem(this.storageKey);
      if (raw === null) return { items: this.demoFactory(), updatedAt: 0, seeded: true };
      const items = JSON.parse(raw);
      const meta = JSON.parse(localStorage.getItem(this.metaKey) || '{}');
      return { items: Array.isArray(items) ? items : [], updatedAt: meta.updatedAt || 0 };
    } catch {
      return { items: this.demoFactory(), updatedAt: 0, seeded: true };
    }
  }
  _write(items) {
    const updatedAt = Date.now();
    localStorage.setItem(this.storageKey, JSON.stringify(items));
    localStorage.setItem(this.metaKey, JSON.stringify({ updatedAt }));
    return { items, updatedAt };
  }
  async load() {
    const r = this._read();
    if (r.seeded) this._write(r.items); // persist seed so reloads stay stable
    return { items: r.items, updatedAt: r.updatedAt };
  }
  async save(items) {
    const r = this._write(items);
    return r;
  }
  subscribe(fn) {
    this.subs.add(fn);
    return () => this.subs.delete(fn);
  }
  _emit() {
    const r = this._read();
    this.subs.forEach(fn => fn({ ...r, status: this.status }));
  }
  destroy() {
    window.removeEventListener('storage', this._onStorage);
    this.subs.clear();
  }
}

/* ──────────────────────────────────────────────────────────────────── */
/* RemoteStore                                                         */
/* ──────────────────────────────────────────────────────────────────── */
class RemoteStore {
  constructor(baseUrl, { resource = 'items', pollMs = 6000, storageKey = STORAGE_KEY, demoFactory = makeDemo } = {}) {
    this.base = baseUrl.replace(/\/$/, '');
    this.resource = resource;
    this.storageKey = storageKey;
    this.demoFactory = demoFactory;
    this.pollMs = pollMs;
    this.status = 'syncing';
    this.subs = new Set();
    this.lastSeenUpdatedAt = 0;
    this._cache = { items: [], updatedAt: 0 };
    this._poll = null;
    this._inflightSave = null;
    this._startPolling();
    // also re-sync when tab becomes visible
    document.addEventListener('visibilitychange', () => {
      if (!document.hidden) this._tick();
    });
    window.addEventListener('online', () => this._tick());
  }
  _setStatus(s) {
    if (this.status === s) return;
    this.status = s;
    this._emit();
  }
  async _fetchItems() {
    const r = await fetch(`${this.base}/${this.resource}`, { headers: { 'Accept': 'application/json' }, credentials: 'same-origin' });
    if (r.status === 401) { this._setStatus('auth'); throw new Error('auth'); }
    if (!r.ok) throw new Error(`GET ${this.resource} ${r.status}`);
    return r.json(); // { items, updatedAt }
  }
  async _putItems(items) {
    const r = await fetch(`${this.base}/${this.resource}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify({ items }),
    });
    if (r.status === 401) { this._setStatus('auth'); throw new Error('auth'); }
    if (!r.ok) throw new Error(`PUT ${this.resource} ${r.status}`);
    return r.json(); // { items, updatedAt }
  }
  async load() {
    this._setStatus('syncing');
    try {
      const data = await this._fetchItems();
      // Seed demo data the very first time (server is brand new).
      if ((!data.items || data.items.length === 0) && !data.updatedAt) {
        const seeded = await this._putItems(this.demoFactory());
        this._cache = seeded;
        this.lastSeenUpdatedAt = seeded.updatedAt || 0;
        this._setStatus('idle');
        return seeded;
      }
      this._cache = data;
      this.lastSeenUpdatedAt = data.updatedAt || 0;
      this._setStatus('idle');
      return data;
    } catch (e) {
      // Fallback to local cache so user isn't blocked offline.
      if (e.message !== 'auth') this._setStatus('offline');
      const cached = this._readLocalCache();
      return cached || { items: [], updatedAt: 0 };
    }
  }
  _readLocalCache() {
    try {
      const raw = localStorage.getItem(this.storageKey);
      if (!raw) return null;
      return { items: JSON.parse(raw), updatedAt: 0 };
    } catch { return null; }
  }
  _writeLocalCache(items) {
    try { localStorage.setItem(this.storageKey, JSON.stringify(items)); } catch {}
  }
  async save(items) {
    this._setStatus('saving');
    this._writeLocalCache(items); // optimistic local cache
    try {
      const data = await this._putItems(items);
      this._cache = data;
      this.lastSeenUpdatedAt = data.updatedAt || 0;
      this._setStatus('idle');
      return data;
    } catch (e) {
      if (e.message !== 'auth') this._setStatus('offline');
      // Keep local cache; we'll try again on next tick.
      return { items, updatedAt: Date.now() };
    }
  }
  _startPolling() {
    this._tick(); // initial
    this._poll = setInterval(() => this._tick(), this.pollMs);
  }
  async _tick() {
    if (document.hidden) return;
    if (this.status === 'auth') { if (this._poll) clearInterval(this._poll); return; }
    try {
      const data = await this._fetchItems();
      if ((data.updatedAt || 0) > this.lastSeenUpdatedAt) {
        this._cache = data;
        this.lastSeenUpdatedAt = data.updatedAt || 0;
        this._setStatus('idle');
        this._emit();
      } else {
        this._setStatus('idle');
      }
    } catch (e) {
      if (e.message !== 'auth') this._setStatus('offline');
      else if (this._poll) clearInterval(this._poll);
    }
  }
  subscribe(fn) { this.subs.add(fn); return () => this.subs.delete(fn); }
  _emit() {
    this.subs.forEach(fn => fn({ ...this._cache, status: this.status }));
  }
  destroy() {
    if (this._poll) clearInterval(this._poll);
    this.subs.clear();
  }
}

/* ──────────────────────────────────────────────────────────────────── */
/* Factory                                                             */
/* ──────────────────────────────────────────────────────────────────── */
function createStore({ apiPath = 'items', storageKey, metaKey, demoFactory } = {}) {
  const meta = document.querySelector('meta[name="agenda-api-url"]');
  const url = meta && meta.getAttribute('content');
  if (url) {
    return new RemoteStore(url, {
      resource: apiPath,
      ...(storageKey ? { storageKey } : {}),
      ...(demoFactory ? { demoFactory } : {}),
    });
  }
  return new LocalStore({
    ...(storageKey ? { storageKey } : {}),
    ...(metaKey ? { metaKey } : {}),
    ...(demoFactory ? { demoFactory } : {}),
  });
}

Object.assign(window, { LocalStore, RemoteStore, createStore });
