/* ANISAN — store reativo + persistência plugável. Clona ANISAN_SEED, expõe
   set/reset/subscribe + o hook useStore(). A persistência real fica num ADAPTER
   (ver Persistence.js): LocalAdapter (localStorage, padrão) ou SupabaseAdapter
   (microserviços). O Store só chama adapter.hydrate()/flush() — não conhece o
   banco. Para migrar: injete window.ANISAN_PERSISTENCE = SupabaseAdapter(client)
   ANTES deste script; nada mais muda.

   • LEITURA: seed imediato (UI nunca vê null) + hidratação assíncrona (1 SELECT
     por tabela no adapter). `hydrated` vira true quando o remoto chega.
   • ESCRITA: set() re-renderiza NA HORA; a gravação é DEBOUNCED (~700ms) e
     coalescida; flush() força o pendente (troca de aba/unload).
   • UPSERT SELETIVO: set(fn, "tabela") marca `dirty`; o adapter grava só elas. */
(function () {
  const KEY = "anisan-sys-v1";
  const SAVE_DEBOUNCE_MS = 700;
  const clone = (x) => JSON.parse(JSON.stringify(x));

  const Store = {
    KEY,
    data: null,
    lastSaved: null,
    saving: false,
    hydrated: false,         // true após o adapter trazer o estado remoto
    dirty: new Set(),        // tabelas alteradas desde a última gravação (p/ upsert seletivo)
    _timer: null,
    listeners: new Set(),
    adapter: null,           // injetado em load() via window.makePersistence()

    // Migrações idempotentes — rodam sobre o seed E sobre o estado hidratado.
    _migrate() {
      if (!this.data || !this.data.contas) this.data = clone(window.ANISAN_SEED);
      if (!Array.isArray(this.data.usuarios)) this.data.usuarios = clone(window.ANISAN_SEED.usuarios || []);
      // Garante o mapa de unidades por usuário (default: comum → todas liberadas).
      (this.data.usuarios || []).forEach((u) => {
        if (!u.unidades || typeof u.unidades !== "object") {
          const m = {}; (window.ANISAN_DATA.units || []).forEach((un) => { m[un] = true; }); u.unidades = m;
        }
      });
      if (window.normalizeContas) window.normalizeContas(this.data);
      if (this.data.metas) {
        const pad12 = (a) => { const out = (a || []).slice(0, 12); while (out.length < 12) out.push(0); return out; };
        this.data.metas.realizado = pad12(this.data.metas.realizado);
        this.data.metas.planejado = pad12(this.data.metas.planejado);
      }
      if (!this.data.contaSaldos) this.data.contaSaldos = {};
      // Histórico & BI: overrides manuais por ano. Migra histAnoAnt (legado) → histManual[ANO-1].fat por unidade.
      if (!this.data.histManual || typeof this.data.histManual !== "object") this.data.histManual = {};
      if (!this.data.dreManual || typeof this.data.dreManual !== "object") this.data.dreManual = {};
      if (Array.isArray(this.data.histAnoAnt)) {
        const yPrev = String(window.YEAR - 1);
        this.data.histManual[yPrev] = this.data.histManual[yPrev] || {};
        const u0 = window.ANISAN_DATA.units[0];
        this.data.histManual[yPrev][u0] = this.data.histManual[yPrev][u0] || {};
        if (!this.data.histManual[yPrev][u0].fat) this.data.histManual[yPrev][u0].fat = this.data.histAnoAnt.slice(0, 12);
        delete this.data.histAnoAnt;
      }
      if (window.autoLancarFixas) window.autoLancarFixas(this.data);
      // Check list de cozinha: garante o objeto de contagem por unidade.
      if (!this.data.checklist) this.data.checklist = {};
      window.ANISAN_DATA.units.forEach((u) => { if (!this.data.checklist[u]) this.data.checklist[u] = {}; });
      if (window.PLANO_CATALOGO && this.data.catalogoVersion !== window.CATALOGO_VERSION) {
        this.data.despesasCadastradas = window.PLANO_CATALOGO.map((x) => ({ ...x }));
        this.data.catalogoVersion = window.CATALOGO_VERSION;
      }
      if (!this.data.receitas) this.data.receitas = {};
      window.ANISAN_DATA.units.forEach((u) => { if (!Array.isArray(this.data.receitas[u])) this.data.receitas[u] = clone((window.ANISAN_SEED.receitas || {})[u] || []); });
      if (window.RECEITA_CATALOGO && this.data.receitasCatalogoVersion !== window.RECEITA_CATALOGO_VERSION) {
        this.data.receitasCadastradas = window.RECEITA_CATALOGO.map((x) => ({ ...x }));
        this.data.receitasCatalogoVersion = window.RECEITA_CATALOGO_VERSION;
      }
    },

    // Boot: seed imediato (UI nunca vê null) + hidratação assíncrona do adapter.
    load() {
      this.adapter = window.makePersistence ? window.makePersistence(KEY) : null;
      this.data = clone(window.ANISAN_SEED);
      this._migrate();
      this._hydrate();
    },
    async _hydrate() {
      if (!this.adapter) { this.hydrated = true; return; }
      try {
        const remote = await this.adapter.hydrate();
        if (remote) this.data = Object.assign(clone(window.ANISAN_SEED), remote);
        this._migrate();
      } catch (e) { /* offline / erro → segue com o seed migrado */ }
      this.hydrated = true;
      this.notify();
    },

    // Notifica a UI (re-render) sem gravar — usado pelo set() para reatividade imediata.
    notify() { this.listeners.forEach((l) => l()); },

    // Gravação REAL — delega ao adapter (localStorage hoje; upsert seletivo no
    // Supabase). Faz snapshot de `dirty` e só o limpa em caso de sucesso.
    async _write() {
      const pending = new Set(this.dirty);
      this.dirty = new Set();
      this.saving = true;
      try {
        if (this.adapter) await this.adapter.flush(pending, this.data);
        this.lastSaved = new Date();
      } catch (e) {
        pending.forEach((t) => this.dirty.add(t));   // reenfileira p/ próxima tentativa
      }
      this.saving = false;
      this.notify();
    },

    // Persistência DEBOUNCED + coalescida: várias edições seguidas = 1 gravação.
    persist() {
      this.saving = true;
      if (this._timer) clearTimeout(this._timer);
      this._timer = setTimeout(() => { this._timer = null; this._write(); }, SAVE_DEBOUNCE_MS);
    },
    flush() { if (this._timer) { clearTimeout(this._timer); this._timer = null; this._write(); } },
    persistNow() { if (this._timer) { clearTimeout(this._timer); this._timer = null; } this._write(); },

    // set(fn[, table]): muta o estado, re-renderiza JÁ e agenda 1 gravação.
    set(fn, table) { fn(this.data); if (table) (Array.isArray(table) ? table : [table]).forEach((t) => this.dirty.add(t)); this.notify(); this.persist(); },
    reset() { this.data = clone(window.ANISAN_SEED); this._migrate(); this.dirty = new Set(["*"]); this.persistNow(); },
    // Re-hidrata sob demanda (ex.: após o login no Supabase, quando passa a haver
    // sessão e o RLS libera os SELECTs). No-op útil também no modo local.
    rehydrate() { return this._hydrate(); },
    subscribe(l) { this.listeners.add(l); return () => this.listeners.delete(l); },
  };

  Store.load();
  window.ANISANStore = Store;
  // Garante que edições pendentes (debounce) sejam gravadas ao sair/ocultar a aba.
  window.addEventListener("beforeunload", () => Store.flush());
  document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") Store.flush(); });

  window.useStore = function () {
    const [, tick] = React.useState(0);
    React.useEffect(() => Store.subscribe(() => tick((x) => x + 1)), []);
    return Store;
  };
})();
