// store.jsx — Sesión (AuthProvider) y carrito mayorista (CartProvider) + componentes de tienda.
// El carrito vive en localStorage y NUNCA guarda precios: el servidor re-valida y
// re-precia todo en POST /api/orders/validate y POST /api/orders (ver PLAN-MAYORISTA.md).

const { useState, useEffect, useMemo, useContext, useCallback } = React;

const CART_KEY = 'ss_cart';
const DEFAULT_MIN_QTY = 12; // MOQ del sitio ("MOQ: 12 unidades"); la Fase 3 lo traerá por producto
const DEFAULT_QTY_STEP = 1;

const AuthContext = React.createContext(null);
const CartContext = React.createContext(null);

// ---------- Helpers ----------

function productDisplayName(product, lang) {
  if (!product) return '';
  if (lang === 'en') return product.nameEn || product.name;
  if (lang === 'fr') return product.nameFr || product.name;
  return product.name;
}

function formatCents(cents, lang, currency = 'CAD') {
  const locale = lang === 'fr' ? 'fr-CA' : lang === 'es' ? 'es' : 'en-CA';
  try {
    return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(cents / 100);
  } catch {
    return 'CA$ ' + (cents / 100).toFixed(2);
  }
}

function clampQty(qty, minQty = DEFAULT_MIN_QTY, step = DEFAULT_QTY_STEP) {
  let q = Math.max(minQty, Math.floor(Number(qty) || 0));
  if (step > 1) q = Math.ceil(q / step) * step;
  return q;
}

// Convierte una hebilla (de BUCKLE_IMAGES o del catálogo de la BD) en un
// objeto compatible con el carrito.
function buckleToProduct(b) {
  return {
    id: b.productId || null,
    slug: 'hbl-' + b.num,
    sku: b.sku,
    category: 'buckle',
    name: 'Hebilla No.' + b.num,
    nameEn: 'Buckle No.' + b.num,
    nameFr: 'Boucle No.' + b.num,
    hero: b.src,
    finishes: [],
    minOrderQty: b.minOrderQty || DEFAULT_MIN_QTY,
    qtyMultiple: b.qtyMultiple || DEFAULT_QTY_STEP,
    pricing: b.pricing || null,
  };
}

// Punto único donde el backend de catálogo "se enchufa" (Fase 3).
// Con WHOLESALE_FLAGS.prices apagado devuelve el producto local sin pricing.
async function getProductForShop(slug) {
  if (window.WHOLESALE_FLAGS && window.WHOLESALE_FLAGS.prices) {
    try {
      const p = await apiFetch('/api/products/' + encodeURIComponent(slug));
      if (p) return p;
    } catch {}
  }
  const local = (window.PRODUCTS || []).find(p => p.slug === slug);
  return local ? { ...local, pricing: null } : null;
}

// ---------- Caché de catálogo (un solo fetch por sesión) ----------
// La BD es la fuente de verdad del catálogo: lo que el admin crea/edita/desactiva
// se refleja en la web. data.jsx queda como fallback (API caída o flag apagado)
// y como fuente de la riqueza visual local (galerías, lifestyle, descripciones).
let _catalogCache = null; // { token, promise } — promise resuelve los items crudos de la API

function fetchCatalogItems() {
  const token = getStoredToken() || '';
  if (!_catalogCache || _catalogCache.token !== token) {
    _catalogCache = {
      token,
      promise: (async () => {
        const first = await apiFetch('/api/products?limit=500', { auth: !!token });
        let items = first.items || [];
        const total = first.total || items.length;
        let page = 1;
        while (items.length < total && page < 10) {
          page += 1;
          const next = await apiFetch('/api/products?limit=500&page=' + page, { auth: !!token });
          if (!next.items || next.items.length === 0) break;
          items = items.concat(next.items);
        }
        return items;
      })().catch(err => { _catalogCache = null; throw err; }),
    };
  }
  return _catalogCache.promise;
}
window.addEventListener('ss:logout', () => { _catalogCache = null; });

// Pricing por SKU, derivado del mismo fetch del catálogo (uno por sesión).
function fetchPricingMap() {
  if (!getStoredToken()) return Promise.resolve({});
  return fetchCatalogItems().then(items => {
    const map = {};
    items.forEach(p => { if (p.pricing) map[p.sku] = p.pricing; });
    return map;
  }).catch(() => ({}));
}

// Convierte un producto de la API al shape que esperan las páginas, enriquecido
// con el producto local de data.jsx si el SKU coincide (galería, specs, desc).
// El hero de la API solo gana si fue subido desde el admin (/uploads/...).
function apiToShopProduct(p, local) {
  const name = p.name || {};
  const heroFromApi = p.heroImage && p.heroImage.indexOf('/uploads/') === 0;
  return {
    id: p.id,                                  // id real de BD
    localId: local ? local.id : null,          // id de data.jsx (rutas lifestyle)
    slug: p.slug,
    sku: p.sku,
    category: p.category,
    style: p.style || (local && local.style) || null,
    isKids: !!p.isKids,
    name: name.es || (local && local.name) || p.sku,
    nameEn: name.en || name.es || (local && local.nameEn) || p.sku,
    nameFr: name.fr || name.es || (local && local.nameFr) || p.sku,
    hero: heroFromApi ? p.heroImage : ((local && local.hero) || p.heroImage || null),
    gallery: (local && local.gallery) || [],
    finishes: (local && local.finishes) || p.finishes || [],
    specs: (local && local.specs) || {},
    desc: (local && local.desc) || { es: '', en: '', fr: '' },
    minOrderQty: p.minOrderQty || DEFAULT_MIN_QTY,
    qtyMultiple: p.qtyMultiple || DEFAULT_QTY_STEP,
    pricing: p.pricing || null,
    availability: p.availability || null, // 'in_stock' | 'low' | 'backorder' | null
  };
}

// Etiqueta de disponibilidad (null = producto sin control de stock → nada)
function AvailabilityBadge({ availability, lang }) {
  if (!availability) return null;
  const t = I18N[lang].shop;
  const label = availability === 'in_stock' ? t.inStock : availability === 'low' ? t.lowStock : t.backorder;
  return <span className={`stock-badge stock-${availability}`}>{label}</span>;
}

// Conserva el orden curado de data.jsx; los productos solo-BD van al final.
function sortByLocalOrder(arr, orderMap) {
  arr.sort((a, b) => {
    const ia = orderMap[a.sku], ib = orderMap[b.sku];
    if (ia != null && ib != null) return ia - ib;
    if (ia != null) return -1;
    if (ib != null) return 1;
    return (a.id || 0) - (b.id || 0);
  });
  return arr;
}

async function fetchCatalog() {
  const items = await fetchCatalogItems();
  const localBySku = {}, localOrder = {}, buckleBySku = {}, buckleOrder = {};
  (window.PRODUCTS || []).forEach((p, i) => { localBySku[p.sku] = p; localOrder[p.sku] = i; });
  (window.BUCKLE_IMAGES || []).forEach((b, i) => { buckleBySku[b.sku] = b; buckleOrder[b.sku] = i; });

  const hats = [], belts = [], buckles = [];
  for (const p of items) {
    if (p.category === 'buckle') {
      const local = buckleBySku[p.sku];
      const heroFromApi = p.heroImage && p.heroImage.indexOf('/uploads/') === 0;
      buckles.push({
        num: p.sku.replace(/^HBL-/, ''),
        src: heroFromApi ? p.heroImage : ((local && local.src) || p.heroImage || null),
        sku: p.sku,
        productId: p.id,
        minOrderQty: p.minOrderQty || DEFAULT_MIN_QTY,
        qtyMultiple: p.qtyMultiple || DEFAULT_QTY_STEP,
        pricing: p.pricing || null,
        availability: p.availability || null,
      });
    } else {
      const shop = apiToShopProduct(p, localBySku[p.sku]);
      (p.category === 'belt' ? belts : hats).push(shop);
    }
  }
  sortByLocalOrder(hats, localOrder);
  sortByLocalOrder(belts, localOrder);
  sortByLocalOrder(buckles, buckleOrder);
  return { hats, belts, buckles, all: [...hats, ...belts] };
}

// Hook: catálogo de la sesión. Arranca con data.jsx y se sustituye por la BD
// al resolver; si la API falla o el flag está apagado, se queda el local.
function useCatalog() {
  const auth = useAuth();
  const [cat, setCat] = useState(() => ({
    status: 'local',
    hats: window.HAT_PRODUCTS || [],
    belts: window.BELT_PRODUCTS || [],
    buckles: window.BUCKLE_IMAGES || [],
    all: window.PRODUCTS || [],
  }));
  useEffect(() => {
    if (!window.WHOLESALE_FLAGS || !window.WHOLESALE_FLAGS.catalog) return;
    let alive = true;
    fetchCatalog()
      .then(c => { if (alive && c && c.all.length > 0) setCat({ status: 'api', ...c }); })
      .catch(() => {});
    return () => { alive = false; };
  }, [auth.status]);
  return cat;
}

// Hook: pricing de un producto (del propio objeto si ya lo trae, o del caché).
function usePricing(product) {
  const auth = useAuth();
  const [pricing, setPricing] = useState((product && product.pricing) || null);
  useEffect(() => {
    if (!product) { setPricing(null); return; }
    if (product.pricing) { setPricing(product.pricing); return; }
    if (!window.WHOLESALE_FLAGS.prices || !auth.canSeePrices) { setPricing(null); return; }
    let alive = true;
    fetchPricingMap().then(map => { if (alive) setPricing(map[product.sku] || null); });
    return () => { alive = false; };
  }, [product && product.sku, auth.canSeePrices]);
  return pricing;
}

// ---------- AuthProvider ----------

function AuthProvider({ children }) {
  const [user, setUser] = useState(() => getStoredUser());
  const [status, setStatus] = useState(() => (getStoredToken() ? 'loading' : 'anonymous'));

  const refreshUser = useCallback(async () => {
    if (!getStoredToken()) { setUser(null); setStatus('anonymous'); return null; }
    try {
      const me = await apiFetch('/api/auth/me');
      setUser(me);
      setStatus('authed');
      try { localStorage.setItem('ss_dashboard_user', JSON.stringify(me)); } catch {}
      return me;
    } catch (err) {
      if (err.status === 401 || err.status === 403) {
        clearSession();
        setUser(null);
        setStatus('anonymous');
      } else {
        // Error de red: conservar la sesión local y reintentar después
        setStatus(getStoredUser() ? 'authed' : 'anonymous');
      }
      return null;
    }
  }, []);

  useEffect(() => { refreshUser(); }, [refreshUser]);

  // Logout forzado desde apiFetch (401) u otra pestaña
  useEffect(() => {
    const onLogout = () => { setUser(null); setStatus('anonymous'); };
    const onStorage = (e) => {
      if (e.key === 'ss_dashboard_token' && !e.newValue) onLogout();
    };
    window.addEventListener('ss:logout', onLogout);
    window.addEventListener('storage', onStorage);
    return () => {
      window.removeEventListener('ss:logout', onLogout);
      window.removeEventListener('storage', onStorage);
    };
  }, []);

  const login = useCallback(async (email, password) => {
    const data = await apiFetch('/api/auth/login', { method: 'POST', body: { email, password }, auth: false });
    storeSession(data.token, data.user);
    // /login solo deja pasar cuentas activas
    setUser({ ...data.user, status: 'active' });
    setStatus('authed');
    return data.user;
  }, []);

  const register = useCallback(async (payload) => {
    return apiFetch('/api/auth/register', { method: 'POST', body: payload, auth: false });
  }, []);

  const logout = useCallback(() => {
    clearSession();
    setUser(null);
    setStatus('anonymous');
  }, []);

  const canSeePrices = !!(user && user.status !== 'pending' && user.status !== 'suspended'
    && (user.role === 'distributor' || user.role === 'admin'));

  const value = { user, status, login, register, logout, refreshUser, canSeePrices };
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

function useAuth() {
  return useContext(AuthContext) || { user: null, status: 'anonymous', canSeePrices: false };
}

// ---------- CartProvider ----------

function cartItemKey(it) {
  return it.sku + '|' + (it.finish || '');
}

function readCart() {
  try {
    const raw = JSON.parse(localStorage.getItem(CART_KEY) || '[]');
    return Array.isArray(raw) ? raw.filter(it => it && it.sku && it.qty > 0) : [];
  } catch { return []; }
}

function CartProvider({ children }) {
  const [items, setItems] = useState(readCart);

  useEffect(() => {
    try { localStorage.setItem(CART_KEY, JSON.stringify(items)); } catch {}
  }, [items]);

  // Sincronización entre pestañas
  useEffect(() => {
    const onStorage = (e) => { if (e.key === CART_KEY) setItems(readCart()); };
    window.addEventListener('storage', onStorage);
    return () => window.removeEventListener('storage', onStorage);
  }, []);

  const addItem = useCallback((product, { qty = DEFAULT_MIN_QTY, finish = null } = {}) => {
    setItems(prev => {
      const minQty = product.minOrderQty || DEFAULT_MIN_QTY;
      const step = product.qtyMultiple || DEFAULT_QTY_STEP;
      const entry = {
        productId: product.id || null,
        sku: product.sku,
        slug: product.slug || null,
        category: product.category || null,
        name: product.name,
        nameEn: product.nameEn || product.name,
        nameFr: product.nameFr || product.name,
        hero: product.hero || null,
        finish: finish || null,
        minQty, step,
        qty: clampQty(qty, minQty, step),
      };
      const key = cartItemKey(entry);
      const existing = prev.find(it => cartItemKey(it) === key);
      if (existing) {
        return prev.map(it => cartItemKey(it) === key
          ? { ...it, qty: clampQty(it.qty + entry.qty, it.minQty, it.step) }
          : it);
      }
      return [...prev, entry];
    });
  }, []);

  const setQty = useCallback((key, qty) => {
    setItems(prev => prev.map(it => cartItemKey(it) === key
      ? { ...it, qty: clampQty(qty, it.minQty, it.step) }
      : it));
  }, []);

  const removeItem = useCallback((key) => {
    setItems(prev => prev.filter(it => cartItemKey(it) !== key));
  }, []);

  const clear = useCallback(() => setItems([]), []);

  const count = items.length;
  const totalUnits = items.reduce((s, it) => s + it.qty, 0);

  const value = { items, addItem, setQty, removeItem, clear, count, totalUnits };
  return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}

function useCart() {
  return useContext(CartContext) || { items: [], count: 0, totalUnits: 0, addItem: () => {}, setQty: () => {}, removeItem: () => {}, clear: () => {} };
}

// ---------- QtyStepper ----------

function QtyStepper({ value, onChange, min = DEFAULT_MIN_QTY, step = DEFAULT_QTY_STEP, compact = false }) {
  const apply = (v) => onChange(clampQty(v, min, step));
  return (
    <div className={`qty-stepper${compact ? ' compact' : ''}`}>
      <button type="button" aria-label="−" onClick={() => apply(value - (step > 1 ? step : 1))} disabled={value <= min}>−</button>
      <input
        type="number"
        inputMode="numeric"
        min={min}
        step={step}
        value={value}
        onChange={(e) => onChange(Number(e.target.value) || min)}
        onBlur={(e) => apply(e.target.value)}
        aria-label="Qty"
      />
      <button type="button" aria-label="+" onClick={() => apply(value + (step > 1 ? step : 1))}>+</button>
    </div>
  );
}

// ---------- PriceTag ----------
// compact: una sola línea de texto (para tarjetas de catálogo) — nunca hace fetch.
// full: en la página de detalle; cuando la Fase 3 esté activa muestra la tabla de tiers.

function PriceTag({ product, lang, navigate, compact = false }) {
  const t = I18N[lang].shop;
  const auth = useAuth();
  const pricing = usePricing(product);

  if (auth.canSeePrices && pricing && pricing.tiers && pricing.tiers.length > 0) {
    const best = pricing.tiers[0];
    if (compact) {
      return (
        <span className="price-tag-line">
          {t.from} {formatCents(best.unitPriceCents, lang, pricing.currency)} / {t.unit}
        </span>
      );
    }
    return (
      <div className="price-tiers">
        <div className="eyebrow muted" style={{ marginBottom: 10 }}>{t.wholesalePricing}</div>
        <table>
          <thead>
            <tr><th>{t.tierQty}</th><th>{t.tierPrice}</th></tr>
          </thead>
          <tbody>
            {pricing.tiers.map((tier, i) => (
              <tr key={i}>
                <td>{tier.minQty}+</td>
                <td>{formatCents(tier.unitPriceCents, lang, pricing.currency)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }

  if (auth.status === 'loading') return compact ? <span className="price-tag-line">…</span> : null;

  if (!auth.user) {
    if (compact) return <span className="price-tag-line">{t.loginToSeePrices}</span>;
    return (
      <div className="price-gate">
        <p>{t.loginToSeePrices}</p>
        <button
          type="button"
          className="btn btn-gold"
          onClick={() => navigate ? navigate({ page: 'wholesale' }) : (window.location.hash = '#/wholesale')}
        >
          {t.loginCta} <span className="arrow" />
        </button>
      </div>
    );
  }

  if (user_isPending(auth.user)) {
    if (compact) return <span className="price-tag-line">{t.pendingAccount}</span>;
    return <div className="price-gate"><p>{t.pendingAccount}</p></div>;
  }

  // Autenticado pero sin pricing (flag apagado o producto sin tiers cargados)
  if (compact) return <span className="price-tag-line">{t.priceOnRequest}</span>;
  return <div className="price-gate"><p>{t.priceOnRequest}</p></div>;
}

function user_isPending(user) {
  return user && user.status === 'pending';
}

Object.assign(window, {
  AuthProvider, useAuth, CartProvider, useCart, cartItemKey,
  PriceTag, QtyStepper, getProductForShop, buckleToProduct, usePricing, fetchPricingMap,
  fetchCatalog, useCatalog, apiToShopProduct, AvailabilityBadge,
  productDisplayName, formatCents, clampQty,
  DEFAULT_MIN_QTY, DEFAULT_QTY_STEP,
});
