// Registro de supermercados en el cliente (`window.Stores`) + hook de
// búsqueda en paralelo (`useStoreSearch`). La lógica pura vive en
// src/stores-core.js (`window.StoresCore`); aquí solo red y React.
//
// Mercadona sigue yendo directo a Algolia (`window.Mercadona.search`); el
// resto de cadenas pasan por nuestras funciones Vercel /api/stores/*. Nunca
// lanza: cada llamada devuelve `{ products }` o `{ error }`.

// Mismo criterio que usaba la resolución de almacén de Mercadona: en localhost prueba primero la
// ruta relativa (`vercel dev`, o el mock de Playwright) y, si no responde
// JSON, cae a producción; en producción y Capacitor, URL absoluta.
async function __storesApi(path) {
  const loc = window.location;
  const isLocalHttp = /^https?:$/.test(loc.protocol) &&
    (loc.hostname === 'localhost' || loc.hostname === '127.0.0.1');
  const prodOrigin = (typeof SHARE_ORIGIN !== 'undefined') ? SHARE_ORIGIN : '';
  let res = null;
  try { res = await fetch(`${isLocalHttp ? '' : prodOrigin}${path}`); } catch (_) { res = null; }
  if (isLocalHttp && (!res || !(res.headers.get('content-type') || '').includes('json'))) {
    try { res = await fetch(`${prodOrigin}${path}`); } catch (_) { res = null; }
  }
  return res;
}

async function __storesSearchMercadona(q, pref) {
  const wh = pref && pref.warehouse;
  if (!wh || !window.Mercadona) return { error: 'error' };
  const hits = await window.Mercadona.search(q, wh, 8);
  if (window.Mercadona.unavailable) return { error: 'unavailable' };
  const now = Date.now();
  return { products: hits.map(h => StoresCore.storeFromLegacyMerca(h, now)).filter(Boolean) };
}

async function __storesSearchRemote(chain, q, pref) {
  const params = new URLSearchParams({ chain, q });
  if (pref && pref.zone) params.set('zone', pref.zone);
  if (pref && pref.shippingZone) params.set('shippingZone', pref.shippingZone);
  const res = await __storesApi(`/api/stores/search?${params.toString()}`);
  if (!res) return { error: 'network' };
  if (res.status === 503) return { error: 'unavailable' };
  if (!res.ok) return { error: 'error' };
  const data = await res.json().catch(() => null);
  // `{ error: 'unavailable' }` con 200 también vale (lo usan los mocks e2e:
  // un 503 real haría que Chromium registre un error de consola).
  if (data && data.error === 'unavailable') return { error: 'unavailable' };
  return data && Array.isArray(data.products) ? { products: data.products } : { error: 'error' };
}

const Stores = {
  // `pref` = app.stores.chains[chain]
  async search(chain, q, pref) {
    try {
      if (chain === 'mercadona') return await __storesSearchMercadona(q, pref);
      return await __storesSearchRemote(chain, q, pref);
    } catch (_) {
      return { error: 'error' };
    }
  },

  // → { ok: true, patch } | { ok: false, code: 'not_served'|'unavailable'|'network' }
  async locate(chain, cp) {
    try {
      const res = await __storesApi(`/api/stores/locate?chain=${encodeURIComponent(chain)}&cp=${encodeURIComponent(cp)}`);
      if (!res) return { ok: false, code: 'network' };
      if (res.status === 400) return { ok: false, code: 'not_served' };
      if (res.status === 503) return { ok: false, code: 'unavailable' };
      if (!res.ok) return { ok: false, code: 'network' };
      const data = await res.json();
      // `{ error: 'unavailable' }` con 200 también vale (mismo motivo que
      // `__storesSearchRemote`: en e2e un 503 real haría que Chromium
      // registre un error de consola).
      if (data && data.error === 'unavailable') return { ok: false, code: 'unavailable' };
      if (!data || data.served === false) return { ok: false, code: 'not_served' };
      return { ok: true, patch: StoresCore.locatePatch(chain, data) };
    } catch (_) {
      return { ok: false, code: 'network' };
    }
  },
};

// Búsqueda en todas las cadenas activas a la vez. Devuelve el estado de
// `StoresCore.startSearch/applyChainResult` (o null sin búsqueda): cada
// cadena se pinta en cuanto llega, sin esperar a las demás, y conserva sus
// productos anteriores mientras recarga. Sin conexión no pide nada y
// devuelve `{ offline: true }`.
function useStoreSearch(query, stores, opts) {
  const debounceMs = (opts && opts.debounceMs) != null ? opts.debounceMs : 300;
  const chains = StoresCore.enabledChains(stores);
  const chainsKey = chains.join(',');
  const chainPrefsKey = JSON.stringify(chains.map(id => stores.chains[id]));
  const [state, setState] = React.useState(null);
  const stateRef = React.useRef(null);
  const seqRef = React.useRef(0);

  React.useEffect(() => {
    const q = (query || '').trim();
    if (!chains.length || q.length < 2) {
      stateRef.current = null;
      setState(null);
      return undefined;
    }
    if (typeof navigator !== 'undefined' && navigator.onLine === false) {
      seqRef.current += 1;
      stateRef.current = { reqId: seqRef.current, query: q, chains: {}, offline: true };
      setState(stateRef.current);
      return undefined;
    }
    const timer = setTimeout(() => {
      const prev = stateRef.current && !stateRef.current.offline ? stateRef.current : null;
      const next = StoresCore.startSearch({ ...(prev || {}), reqId: seqRef.current }, q, chains);
      seqRef.current = next.reqId;
      stateRef.current = next;
      setState(next);
      for (const chain of chains) {
        Stores.search(chain, q, stores.chains[chain]).then((result) => {
          const merged = StoresCore.applyChainResult(stateRef.current, next.reqId, chain, result);
          if (merged === stateRef.current) return;
          stateRef.current = merged;
          setState(merged);
        });
      }
    }, debounceMs);
    return () => clearTimeout(timer);
  }, [query, chainsKey, chainPrefsKey, debounceMs]);

  return state;
}

Object.assign(window, { Stores, useStoreSearch });
