// Hook de sincronización Supabase ⇄ useAppState.
//
// Diseño:
//  - Se invoca SIEMPRE al final de useAppState (incluso si Supabase no está
//    configurado), pero internamente es un no-op cuando `SUPABASE === null`.
//  - En el primer render con Supabase activo: hace pull de listas y reemplaza
//    el estado local. Después hace pull de productos/historial de la lista
//    activa (y al cambiar de lista). Las semillas locales solo se ven mientras
//    no haya conexión con Supabase.
//  - Las acciones (addItem, updateItem, …) ya empujan a la BD vía SLDb desde
//    core.jsx — aquí nos limitamos a las cargas iniciales y a realtime.
//  - Realtime: suscribe a `products` y `history_logs` filtrados por la lista
//    activa y aplica INSERT/UPDATE/DELETE de forma idempotente (set-merge por id).

function useSupabaseSync(app) {
  const enabled = !!window.SUPABASE && !!window.SLDb?.enabled;
  const adapters = window.__slAdapters;
  const lastListIdRef = React.useRef(null);
  const lastUserIdRef = React.useRef(undefined); // undefined = nunca corrió

  // ─── 1) Pull (re)inicial de listas ─────────────────────────────────────────
  // Se dispara en el arranque y cada vez que cambia la sesión (login/logout).
  React.useEffect(() => {
    if (!enabled) return;
    if (!app.authReady) return; // esperamos a saber si hay sesión
    const userId = app.user?.id || null;
    const prevUserId = lastUserIdRef.current;
    if (prevUserId === userId) return;
    const isLoginTransition = prevUserId == null && userId != null && prevUserId !== undefined;
    lastUserIdRef.current = userId;

    (async () => {
      try {
        // Si acabamos de loguearnos, reclamamos las listas anónimas cuyos
        // tokens conocemos para que entren en el filtro de owner_id.
        if (isLoginTransition) {
          await window.SLDb.claimAnonymousLists();
        }

        const rows = await window.SLDb.getLists();
        const prevById = new Map(app.lists.map(l => [l.id, l]));
        const hidden = new Set(app.hiddenListIds || []);
        const remoteLists = rows
          .filter(r => !hidden.has(r.id))
          .map(r => adapters.listDbToLocal(r, prevById.get(r.id)));

        if (remoteLists.length === 0) {
          // Sin listas por defecto: migramos lo local solo si el usuario YA
          // tenía listas creadas offline (no semillas) en su primer login.
          if (prevUserId === undefined && app.lists.length > 0) {
            await migrateLocalToRemote(app, app.lists);
            return;
          }
          // Vacío total: dejamos el estado vacío. La UI muestra empty state
          // con CTA para crear/unirse.
          app.__internal_setLists([]);
          app.__internal_setProducts({});
          app.__internal_setHistory({});
          app.__internal_setCurrentListId(null);
          return;
        }

        // Reset de productos/historial: el usuario ha cambiado y los datos
        // anteriores ya no aplican.
        const productsByList = {};
        const historyByList = {};
        for (const l of remoteLists) {
          productsByList[l.id] = app.products[l.id] || [];
          historyByList[l.id] = app.history[l.id] || [];
        }
        app.__internal_setLists(remoteLists);
        app.__internal_setProducts(productsByList);
        app.__internal_setHistory(historyByList);
        if (!remoteLists.some(l => l.id === app.currentListId)) {
          app.__internal_setCurrentListId(remoteLists[0].id);
          lastListIdRef.current = null; // fuerza re-pull de productos/historial
        }
      } catch (e) {
        console.error('[shoplist] pull listas', e);
      }
    })();
  }, [enabled, app.authReady, app.user?.id]);

  // Migra las listas indicadas (productos + historial incluidos) a Supabase
  // con UUIDs frescos y reescribe el estado local para usarlos. Las listas no
  // incluidas en `listsToMigrate` se descartan del estado local (típicamente
  // son las semillas demo).
  async function migrateLocalToRemote(app, listsToMigrate) {
    const newLists = [];
    const newProducts = {};
    const newHistory = {};

    for (const oldList of listsToMigrate) {
      const newListData = { ...oldList, id: window.SLIds.uuid() };
      const created = await window.SLDb.createList(newListData);
      const localList = created
        ? adapters.listDbToLocal(created, oldList)
        : newListData;
      newLists.push(localList);

      const oldProducts = app.products[oldList.id] || [];
      const mappedProducts = oldProducts.map(p => ({ ...p, id: window.SLIds.uuid() }));
      newProducts[localList.id] = mappedProducts;
      await Promise.all(mappedProducts.map(p => window.SLDb.createProduct(localList.id, p)));

      const oldHistory = app.history[oldList.id] || [];
      const mappedHistory = oldHistory.map(h => ({ ...h, id: window.SLIds.uuid() }));
      newHistory[localList.id] = mappedHistory;
      await Promise.all(mappedHistory.map(h => window.SLDb.addHistory(localList.id, h)));
    }

    app.__internal_setLists(newLists);
    app.__internal_setProducts(newProducts);
    app.__internal_setHistory(newHistory);
    const firstId = newLists[0]?.id || null;
    if (firstId) app.__internal_setCurrentListId(firstId);
  }

  async function ensureDefaultList(app) {
    const t = app.t || {};
    const defaultName = (t.defaultListName) || { es: 'Mi lista', en: 'My list' };
    const newList = {
      id: window.SLIds.uuid(),
      name: defaultName,
      color: 'cyan',
      members: 1,
      createdAt: Date.now(),
      token: window.SLIds.shortToken(),
    };
    const created = await window.SLDb.createList(newList);
    const localList = created ? adapters.listDbToLocal(created, newList) : newList;
    app.__internal_setLists([localList]);
    app.__internal_setProducts({ [localList.id]: [] });
    app.__internal_setHistory({ [localList.id]: [] });
    app.__internal_setCurrentListId(localList.id);
  }

  // ─── 2) Pull de productos + historial al cambiar de lista activa ───────────
  React.useEffect(() => {
    if (!enabled) return;
    const id = app.currentListId;
    if (!id) return;
    if (lastListIdRef.current === id) return;
    lastListIdRef.current = id;
    (async () => {
      try {
        const [prodRows, histRows] = await Promise.all([
          window.SLDb.getProducts(id),
          window.SLDb.getHistory(id),
        ]);
        const products = prodRows.map(adapters.productDbToLocal);
        const history  = histRows.map(adapters.historyDbToLocal);
        app.__internal_setProducts(p => ({ ...p, [id]: products }));
        app.__internal_setHistory(h => ({ ...h, [id]: history }));
      } catch (e) {
        console.error('[shoplist] pull lista', id, e);
      }
    })();
  }, [enabled, app.currentListId]);

  // ─── 3) Realtime: productos de la lista activa ─────────────────────────────
  React.useEffect(() => {
    if (!enabled) return;
    const id = app.currentListId;
    if (!id) return;

    const unsubProducts = window.SLRealtime.subscribeProducts(id, ({ eventType, new: row, old }) => {
      app.__internal_setProducts(p => {
        const list = p[id] || [];
        if (eventType === 'DELETE') {
          const removeId = old?.id;
          return { ...p, [id]: list.filter(it => it.id !== removeId) };
        }
        if (!row) return p;
        const local = adapters.productDbToLocal(row);
        const idx = list.findIndex(it => it.id === local.id);
        if (idx === -1) return { ...p, [id]: [local, ...list] };
        const next = list.slice();
        next[idx] = { ...next[idx], ...local };
        return { ...p, [id]: next };
      });
    });

    const unsubHistory = window.SLRealtime.subscribeHistory(id, ({ eventType, new: row }) => {
      if (eventType !== 'INSERT' || !row) return;
      app.__internal_setHistory(h => {
        const list = h[id] || [];
        if (list.some(e => e.id === row.id)) return h; // ya está (eco local)
        return { ...h, [id]: [adapters.historyDbToLocal(row), ...list] };
      });
    });

    return () => { unsubProducts(); unsubHistory(); };
  }, [enabled, app.currentListId]);

  // ─── 4) Realtime: cambios en listas (otra sesión crea/borra) ───────────────
  React.useEffect(() => {
    if (!enabled) return;
    const unsub = window.SLRealtime.subscribeLists(({ eventType, new: row, old }) => {
      app.__internal_setLists(prev => {
        if (eventType === 'DELETE') {
          const removeId = old?.id;
          return prev.filter(l => l.id !== removeId);
        }
        if (!row) return prev;
        // Lista oculta localmente: ignorar INSERT/UPDATE para que no reaparezca.
        const hidden = new Set(app.hiddenListIds || []);
        if (hidden.has(row.id)) return prev;
        const prevById = new Map(prev.map(l => [l.id, l]));
        const local = adapters.listDbToLocal(row, prevById.get(row.id));
        const idx = prev.findIndex(l => l.id === local.id);
        if (idx === -1) return [...prev, local];
        const next = prev.slice();
        next[idx] = { ...next[idx], ...local };
        return next;
      });
    });
    return unsub;
  }, [enabled, app.hiddenListIds]);

  // ─── 5) Resume: re-pull al volver al primer plano ──────────────────────────
  // Cuando iOS suspende la app, el runtime JS se congela y el WebSocket de
  // Realtime muere; supabase-js reconecta al volver pero los eventos perdidos
  // durante el suspend no se reenvían. Forzamos un refetch de lists + productos
  // + history de la lista activa cada vez que la app vuelve a primer plano.
  React.useEffect(() => {
    if (!enabled) return;
    let lastRefetch = 0;
    const refetch = async () => {
      // Throttle por si dispara múltiples eventos (Capacitor + visibilitychange
      // a la vez). Ignora si refetch hace <1s.
      const now = Date.now();
      if (now - lastRefetch < 1000) return;
      lastRefetch = now;
      try {
        const rows = await window.SLDb.getLists();
        const hidden = new Set(app.hiddenListIds || []);
        const prevById = new Map(app.lists.map(l => [l.id, l]));
        const remoteLists = rows
          .filter(r => !hidden.has(r.id))
          .map(r => adapters.listDbToLocal(r, prevById.get(r.id)));
        app.__internal_setLists(remoteLists);
        const activeId = app.currentListId;
        if (activeId && remoteLists.some(l => l.id === activeId)) {
          const [prodRows, histRows] = await Promise.all([
            window.SLDb.getProducts(activeId),
            window.SLDb.getHistory(activeId),
          ]);
          app.__internal_setProducts(p => ({ ...p, [activeId]: prodRows.map(adapters.productDbToLocal) }));
          app.__internal_setHistory(h => ({ ...h, [activeId]: histRows.map(adapters.historyDbToLocal) }));
        }
      } catch (e) { console.error('[shoplist] resume refetch', e); }
    };

    // Capacitor App plugin: appStateChange dispara con { isActive: true|false }
    // — disparamos refetch en transición a active. Si el plugin no está (web
    // puro), no estorba.
    let capHandle = null;
    const CapApp = window.Capacitor?.Plugins?.App;
    if (CapApp?.addListener) {
      const subPromise = CapApp.addListener('appStateChange', (state) => {
        if (state?.isActive) refetch();
      });
      // addListener puede devolver una Promise<Handle> o un Handle directo.
      if (subPromise?.then) subPromise.then(h => { capHandle = h; });
      else capHandle = subPromise;
    }

    // Fallback web / extra cobertura: visibilitychange + focus.
    const onVis = () => { if (document.visibilityState === 'visible') refetch(); };
    document.addEventListener('visibilitychange', onVis);
    window.addEventListener('focus', refetch);

    return () => {
      try { capHandle?.remove?.(); } catch (_) {}
      document.removeEventListener('visibilitychange', onVis);
      window.removeEventListener('focus', refetch);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [enabled, app.currentListId, app.hiddenListIds]);
}

window.useSupabaseSync = useSupabaseSync;
