// SATMELI — capa de datos real. Reemplaza el store mock de index.html.
// Expone useStore() con la misma forma que el mock para que los componentes
// no necesiten cambios: searches/notifs con camelCase y tipos JS nativos.

async function apiFetch(path, opts = {}) {
  const res = await fetch('/api' + path, {
    credentials: 'same-origin',
    headers: { 'content-type': 'application/json', ...(opts.headers || {}) },
    ...opts,
    body: opts.body ? JSON.stringify(opts.body) : undefined,
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({ error: res.statusText }));
    const e = new Error(err.error || `HTTP ${res.status}`);
    e.status = res.status;
    throw e;
  }
  if (res.status === 204) return null;
  return res.json();
}

// ── Normalizadores DB → shape de los componentes ─────────────────────────────

function minutesAgo(ts) {
  if (!ts) return 0;
  return Math.max(0, Math.round((Date.now() - new Date(ts).getTime()) / 60000));
}
function hoursAgo(ts) {
  return minutesAgo(ts) / 60;
}

function normalizeSearch(row) {
  return {
    id:         row.id,
    term:       row.term,
    cat:        row.cat || 'tech',
    condition:  row.cond || 'cualquiera',
    priceMin:   Number(row.price_min) || 0,
    priceMax:   Number(row.price_max) || 0,
    location:   row.location || '',
    freq:       row.freq || '1d',
    active:     !!Number(row.active),
    notify:     !!Number(row.notify),
    lastCheck:  minutesAgo(row.last_check),
    matches:    Number(row.match_count) || 0,
    unread:     Number(row.unread_count) || 0,
    createdAt:  hoursAgo(row.created_at),
    activity:   Array.isArray(row.activity) ? row.activity : Array(14).fill(0),
  };
}

function normalizeNotif(row) {
  return {
    id:         row.id,
    searchId:   row.search_id,
    meliId:     row.meli_id || null,
    title:      row.title || '',
    price:      Number(row.price) || 0,
    target:     Number(row.target) || 0,
    condition:  row.cond || 'cualquiera',
    location:   row.location || '',
    permalink:  row.permalink || 'https://www.mercadolibre.com.ar',
    thumbnail:  row.thumbnail || null,
    sellerRep:  row.seller_rep || '',
    foundAt:    minutesAgo(row.found_at),
    read:       !!Number(row.is_read),
    searchTerm: row.search_term || '',
  };
}

// ── Mapeador inverso (frontend → body de API) ─────────────────────────────────
function searchToBody(s) {
  return {
    term:      s.term,
    cat:       s.cat || '',
    cond:      s.condition || s.cond || 'cualquiera',
    price_min: s.priceMin || 0,
    price_max: s.priceMax || 0,
    location:  s.location || '',
    freq:      s.freq || '1d',
    active:    s.active !== false ? 1 : 0,
    notify:    s.notify !== false ? 1 : 0,
  };
}

// ── useStore: reemplaza el mock con llamadas REST ─────────────────────────────
function useStore() {
  const [searches, setSearches] = React.useState([]);
  const [notifs,   setNotifs]   = React.useState([]);
  const [loading,  setLoading]  = React.useState(true);
  const [error,    setError]    = React.useState(null);

  const refresh = React.useCallback(async () => {
    try {
      const [rawSearches, rawNotifs] = await Promise.all([
        apiFetch('/searches'),
        apiFetch('/notifications'),
      ]);
      setSearches(rawSearches.map(normalizeSearch));
      setNotifs(rawNotifs.map(normalizeNotif));
      setError(null);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);

  // Carga inicial; después refresca cada 60 s automáticamente.
  React.useEffect(() => {
    refresh();
    const id = setInterval(refresh, 60_000);
    return () => clearInterval(id);
  }, [refresh]);

  // ── Acciones ──────────────────────────────────────────────────────────────

  const toggleSearch = React.useCallback(async (id) => {
    const s = searches.find((x) => x.id === id);
    if (!s) return;
    // Optimistic update
    setSearches((p) => p.map((x) => x.id === id ? { ...x, active: !x.active } : x));
    try {
      await apiFetch(`/searches/${id}`, { method: 'PATCH', body: { active: s.active ? 0 : 1 } });
    } catch (_) {
      // Revertir en error
      setSearches((p) => p.map((x) => x.id === id ? { ...x, active: s.active } : x));
    }
  }, [searches]);

  const markRead = React.useCallback(async (id) => {
    const n = notifs.find((x) => x.id === id);
    if (!n) return;
    setNotifs((p) => p.map((x) => x.id === id ? { ...x, read: !x.read } : x));
    try {
      await apiFetch(`/notifications/${id}`, { method: 'PATCH', body: { is_read: n.read ? 0 : 1 } });
    } catch (_) {
      setNotifs((p) => p.map((x) => x.id === id ? { ...x, read: n.read } : x));
    }
  }, [notifs]);

  const markAll = React.useCallback(async () => {
    setNotifs((p) => p.map((n) => ({ ...n, read: true })));
    try {
      await apiFetch('/notifications/read-all', { method: 'POST' });
    } catch (err) {
      console.error('markAll error:', err.message);
    }
  }, []);

  const saveSearch = React.useCallback(async (s) => {
    try {
      if (s.id && searches.some((x) => x.id === s.id)) {
        const updated = await apiFetch(`/searches/${s.id}`, { method: 'PATCH', body: searchToBody(s) });
        setSearches((p) => p.map((x) => x.id === s.id ? normalizeSearch({ ...updated, activity: x.activity }) : x));
      } else {
        const created = await apiFetch('/searches', { method: 'POST', body: searchToBody(s) });
        setSearches((p) => [normalizeSearch(created), ...p]);
      }
    } catch (err) {
      console.error('saveSearch error:', err.message);
    }
  }, [searches]);

  const deleteSearch = React.useCallback(async (id) => {
    setSearches((p) => p.filter((x) => x.id !== id));
    setNotifs((p) => p.filter((n) => n.searchId !== id));
    try {
      await apiFetch(`/searches/${id}`, { method: 'DELETE' });
    } catch (err) {
      console.error('deleteSearch error:', err.message);
    }
  }, []);

  return { searches, notifs, loading, error, refresh, toggleSearch, markRead, markAll, saveSearch, deleteSearch };
}

Object.assign(window, { useStore, apiFetch });
