// Filter sheet — single category at a time.
// Caller decides which tab (collection / model / backdrop / symbol) via `tab` prop.
// Each row shows checkbox + thumbnail + label + rarity% (or floor for collection).

const BACKDROP_COLORS = {
  'Onyx Black':    { edge: '#0a0a10', center: '#3a3a48' },
  'Pure Gold':     { edge: '#7a4d00', center: '#ffd54a' },
  'Ruby Red':      { edge: '#5e0008', center: '#ff3a55' },
  'Cyan Blue':     { edge: '#003a55', center: '#22c4ff' },
  'Emerald Green': { edge: '#004520', center: '#41d182' },
  'Royal Purple':  { edge: '#28004f', center: '#a96cff' },
  'Silver Gray':   { edge: '#33333a', center: '#cdd0d4' },
  'Rose Quartz':   { edge: '#922252', center: '#ff86c0' },
  'Burning Red':   { edge: '#681100', center: '#ff7028' },
  'Ocean Blue':    { edge: '#001f55', center: '#3aa0ff' },
  'Cosmic Indigo': { edge: '#160033', center: '#6a3aff' },
  'Velvet':        { edge: '#1a0033', center: '#7c00d4' },
  'Tropical':      { edge: '#005a4c', center: '#56f5d0' },
  'Sunset':        { edge: '#6a1300', center: '#ffaa66' },
  'Aurora':        { edge: '#003324', center: '#00ffd2' },
};

const TAB_LABELS = {
  collection: 'Коллекция',
  model:      'Модель',
  backdrop:   'Фон',
  symbol:     'Символ',
};

// Per-collection attributes (Model/Backdrop/Symbol) from @GiftChanges
// (api.changes.tg, CORS-open). `?sorted` = by rarity. Memoized per collection+tab.
// Сбой НЕ кешируется и резолвится в null (не []) — чтобы UI показал ошибку/ретрай,
// а не мок-имена, которых нет на бэке (выбор мок-имени = гарантированно пустая выдача).
const _attrCache = {};
function loadGiftAttr(collection, tab) {
  const key = collection + '/' + tab;
  if (_attrCache[key]) return _attrCache[key];
  const ep = tab === 'model' ? 'models' : tab === 'symbol' ? 'symbols' : 'backdrops';
  _attrCache[key] = fetch(`https://api.changes.tg/${ep}/${encodeURIComponent(collection)}?sorted`)
    .then(r => (r.ok ? r.json() : Promise.reject(r.status)))
    .then(list => (Array.isArray(list) ? list : []))
    .catch(() => { delete _attrCache[key]; return null; });
  return _attrCache[key];
}

// Warm a collection's attribute LISTS *and* their icon images into cache, so the
// Model/Backdrop/Symbol filters open instantly with no skeleton and icons never
// pop in. Idempotent: lists are memoized in _attrCache, images deduped below.
const _imgPreloaded = {};
function _preloadImg(url) {
  if (_imgPreloaded[url]) return;
  _imgPreloaded[url] = true;
  const im = new Image();
  im.src = url;
}
function prefetchGiftAttrs(collection) {
  if (!collection) return;
  ['model', 'symbol', 'backdrop'].forEach(tab => {
    loadGiftAttr(collection, tab).then(list => {
      if (tab === 'backdrop') return; // gradient swatches — nothing to preload
      (list || []).forEach(a => _preloadImg(
        `https://api.changes.tg/${tab}/${encodeURIComponent(collection)}/${encodeURIComponent(a.name)}.png?size=256`
      ));
    });
  });
}
window.prefetchGiftAttrs = prefetchGiftAttrs;

function CheckBox({ on }) {
  return (
    <div style={{
      width: 18, height: 18, borderRadius: 6,
      background: on ? 'var(--accent)' : 'transparent',
      border: on ? '1.5px solid var(--accent)' : '1.5px solid rgba(255,255,255,0.22)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      flexShrink: 0,
      transition: 'background 0.15s, border-color 0.15s',
    }}>
      {on && <Icons.check size={12} style={{ color: '#fff' }}/>}
    </div>
  );
}

function ModelThumb({ kind = 'PlushPepe', name }) {
  const [ok, setOk] = React.useState(true);
  return (
    <div style={{ position: 'absolute', inset: 0, background: 'var(--bg-input)' }}>
      {ok && (
        <img src={`https://api.changes.tg/model/${kind}/${encodeURIComponent(name)}.png?size=256`}
             alt="" draggable="false" loading="lazy"
             onError={() => setOk(false)}
             style={{
               position: 'absolute', inset: 0, width: '100%', height: '100%',
               objectFit: 'contain', padding: 4,
             }}/>
      )}
    </div>
  );
}

function SymbolThumb({ kind = 'PlushPepe', name }) {
  const [ok, setOk] = React.useState(true);
  return (
    <div style={{ position: 'absolute', inset: 0, background: 'var(--bg-input)' }}>
      {ok && (
        <img src={`https://api.changes.tg/symbol/${kind}/${encodeURIComponent(name)}.png?size=256`}
             alt="" draggable="false" loading="lazy"
             onError={() => setOk(false)}
             style={{
               position: 'absolute', inset: 0, width: '100%', height: '100%',
               objectFit: 'contain', padding: 8, opacity: 0.7,
               filter: 'drop-shadow(0 0 4px rgba(255,255,255,0.18))',
             }}/>
      )}
    </div>
  );
}

function BackdropSwatch({ name, hex }) {
  // Real colors from GiftChanges when available; fall back to the local map.
  const c = hex
    ? { center: hex.centerColor, edge: hex.edgeColor }
    : (BACKDROP_COLORS[name] || { edge: '#222', center: '#666' });
  return (
    <div style={{
      position: 'absolute', inset: 0,
      background: `radial-gradient(circle at 35% 35%, ${c.center}, ${c.edge})`,
    }}/>
  );
}

// Skeleton rows shown while real floor/attribute data loads — avoids flashing
// placeholder prices and avoids a sort jump when data arrives.
function FilterRowsSkeleton({ rows = 9 }) {
  return (
    <div>
      {Array.from({ length: rows }).map((_, i) => (
        <div key={i} className="row aic" style={{ padding: '10px 4px', gap: 12 }}>
          <SkBlock w={18} h={18} r={6}/>
          <SkBlock w={46} h={46} r={11}/>
          <div className="flex1"><SkBlock w={`${52 - (i % 4) * 8}%`} h={14} r={7}/></div>
          <SkBlock w={60} h={14} r={7}/>
        </div>
      ))}
    </div>
  );
}

function FilterRow({ tab, item, on, onClick, idx = 0, collection }) {
  let thumb = null;
  if (tab === 'collection') thumb = <GiftMedia kind={item.value} num={1} rounded={11}/>;
  else if (tab === 'model') thumb = <ModelThumb kind={collection || 'PlushPepe'} name={item.value}/>;
  else if (tab === 'symbol') thumb = <SymbolThumb kind={collection || 'PlushPepe'} name={item.value}/>;
  else if (tab === 'backdrop') thumb = <BackdropSwatch name={item.value} hex={item.hex}/>;

  const right = item.rarity != null
    ? `${item.rarity}%`
    : (item.floor != null ? fmtRub(item.floor) : '—');

  return (
    <div onClick={onClick} className="row aic" style={{
      padding: '10px 4px', gap: 12, cursor: 'pointer', borderRadius: 10,
      animation: `slideInRow 0.32s cubic-bezier(0.2,0.7,0.2,1) both`,
      animationDelay: `${Math.min(idx * 22, 240)}ms`,
    }}>
      <CheckBox on={on}/>
      <div style={{
        position: 'relative', width: 46, height: 46, borderRadius: 11,
        overflow: 'hidden', flexShrink: 0,
        background: 'var(--bg-input)',
      }}>
        {thumb}
      </div>
      <div className="flex1" style={{ minWidth: 0 }}>
        <div style={{
          fontSize: 15, fontWeight: 650, letterSpacing: '-0.005em',
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
        }}>{item.label}</div>
      </div>
      <div style={{
        fontSize: 14, fontWeight: 700,
        color: item.rarity != null ? 'var(--accent)' : 'var(--text)',
        flexShrink: 0,
      }}>{right}</div>
    </div>
  );
}

function FilterSheet({ filters, tab = 'collection', collection = null, onApply, onClose }) {
  const [query, setQuery] = React.useState('');
  const [sortDesc, setSortDesc] = React.useState(true);
  const [local, setLocal] = React.useState(filters);
  // Real floor prices from our API (collection tab only); null until loaded.
  const [floors, setFloors] = React.useState(null);
  React.useEffect(() => {
    if (tab === 'collection' && window.loadCollectionFloors) window.loadCollectionFloors().then(setFloors);
  }, [tab]);
  // Real per-collection attributes from GiftChanges (model/backdrop/symbol tabs).
  // null = грузится, 'error' = сбой (ретрай кнопкой), [] / [...] = загружено.
  const [attrs, setAttrs] = React.useState(null);
  const loadAttrs = React.useCallback(() => {
    if (tab !== 'collection' && collection) {
      setAttrs(null);
      loadGiftAttr(collection, tab).then(list => setAttrs(list === null ? 'error' : list));
    }
  }, [tab, collection]);
  React.useEffect(() => { loadAttrs(); }, [loadAttrs]);

  const toggle = (val) => setLocal(prev => {
    const arr = prev[tab] || [];
    return { ...prev, [tab]: arr.includes(val) ? arr.filter(x => x !== val) : [...arr, val] };
  });
  // «Сбросить» применяется СРАЗУ (не теряется при закрытии крестиком/свайпом).
  const reset = () => {
    const next = { ...local, [tab]: [] };
    setLocal(next);
    onApply(next);
  };

  const items = React.useMemo(() => {
    if (tab === 'collection') {
      // Основной источник — живые /collections (реальные имена/floor); хардкод
      // GIFT_LIST только как фоллбэк, пока floors не загрузились/пустые.
      const floorSlugs = floors ? Object.keys(floors) : [];
      const slugs = floorSlugs.length ? floorSlugs : GIFT_LIST;
      return slugs.map(k => {
        const real = floors && floors[k];
        const realFloor = real && real.floor_rub != null ? real.floor_rub : null;
        return {
          value: k,
          label: (real && real.name) || (GIFT_KINDS[k] && GIFT_KINDS[k].name) || k,
          floor: realFloor,                 // null = нет реальной цены → рисуем «—»
          real: realFloor != null,
        };
      });
    }
    // Model / Backdrop / Symbol — ТОЛЬКО реальные имена из GiftChanges.
    // Мок-фоллбэка нет: выбор несуществующего имени ломает реальную выдачу.
    if (Array.isArray(attrs)) {
      return attrs.map(a => ({
        value: a.name, label: a.name,
        rarity: +(((a.rarityPermille || 0) / 10)).toFixed(1),
        hex: a.hex,
      }));
    }
    return [];
  }, [tab, floors, attrs]);

  // While real data is in flight show a skeleton (no mock-price flash, no sort jump).
  const loading =
    (tab === 'collection' && floors === null) ||
    (tab !== 'collection' && !!collection && attrs === null);
  const attrsFailed = tab !== 'collection' && attrs === 'error';

  // Уже выбранные (снапшот на момент ОТКРЫТИЯ шита) закрепляем СВЕРХУ; ниже — по цене/редкости.
  // deps только [tab]: шит перемонтируется при каждом открытии, а обновление filters
  // после Apply не должно дёргать порядок во время анимации закрытия.
  const pinnedSel = React.useMemo(() => new Set(filters[tab] || []), [tab]); // eslint-disable-line
  // Выбранные значения, которых нет в загруженном списке (смена коллекции и т.п.) —
  // дорисовываем призрачными строками, чтобы галку можно было снять.
  const missingSel = (local[tab] || [])
    .filter(v => !items.some(it => it.value === v))
    .map(v => ({ value: v, label: v, floor: null, missing: true }));
  const q = query.trim().toLowerCase();
  const filtered = [...missingSel, ...items]
    .filter(it => !q || it.label.toLowerCase().includes(q))
    .sort((a, b) => {
      const asel = (pinnedSel.has(a.value) || a.missing) ? 1 : 0;
      const bsel = (pinnedSel.has(b.value) || b.missing) ? 1 : 0;
      if (asel !== bsel) return bsel - asel;       // выбранные — первыми
      if (a.real !== b.real && tab === 'collection') return (b.real ? 1 : 0) - (a.real ? 1 : 0); // без цены — в конец
      const av = a.rarity ?? a.floor ?? 0;
      const bv = b.rarity ?? b.floor ?? 0;
      return sortDesc ? bv - av : av - bv;         // затем по цене/редкости
    });

  const tabLabel = TAB_LABELS[tab];
  const rightLabel = tab === 'collection' ? 'Floor price' : 'Rarity';
  const cur = local[tab] || [];
  const count = cur.length;

  return (
    <SheetShell onClose={onClose}>
      <FilterSheetBody {...{
        tabLabel, rightLabel, count, query, setQuery, sortDesc, setSortDesc,
        filtered, tab, cur, toggle, reset, local, onApply, loading, collection,
        attrsFailed, retryAttrs: loadAttrs,
      }}/>
    </SheetShell>
  );
}

// Inner body that needs SheetCloseContext access for the X button.
function FilterSheetBody({ tabLabel, rightLabel, count, query, setQuery, sortDesc, setSortDesc,
                          filtered, tab, cur, toggle, reset, local, onApply, loading, collection,
                          attrsFailed, retryAttrs }) {
  const closeAnim = React.useContext(SheetCloseContext);

  return (
    <>
      <div className="row aic jcsb" style={{ marginBottom: 14, padding: '4px 2px 0' }}>
        <div style={{ fontSize: 24, fontWeight: 700, letterSpacing: '-0.015em' }}>{tabLabel}</div>
        <button onClick={closeAnim} aria-label="Close" style={{
          width: 32, height: 32, borderRadius: '50%',
          border: 0, padding: 0, cursor: 'pointer',
          background: 'var(--bg-input)', color: 'var(--text-soft)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <Icons.close size={16}/>
        </button>
      </div>

      <div className="row aic gap-8" style={{ marginBottom: 4 }}>
        <div style={{
          flex: 1, padding: '11px 16px', borderRadius: 999,
          background: 'var(--bg-input)',
          display: 'flex', alignItems: 'center', gap: 8,
        }}>
          <Icons.search size={16} style={{ color: 'var(--text-mute)' }}/>
          <input placeholder="Quick find"
                 value={query}
                 onChange={(e) => setQuery(e.target.value)}
                 style={{
                   flex: 1, background: 'transparent',
                   border: 0, outline: 0,
                   color: 'var(--text)', fontSize: 14,
                 }}/>
        </div>
        <button onClick={() => setSortDesc(v => !v)} aria-label="Sort"
                style={{
                  width: 44, height: 44, borderRadius: '50%',
                  border: 0, padding: 0, cursor: 'pointer',
                  background: 'var(--bg-input)', color: 'var(--text-soft)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  flexShrink: 0,
                }}>
          {sortDesc ? <Icons.arrowDown size={16}/> : <Icons.arrowUp size={16}/>}
        </button>
      </div>

      <div className="row aic jcsb" style={{
        padding: '14px 4px 6px', fontSize: 12.5, color: 'var(--text-mute)',
      }}>
        <div className="row aic gap-4">{tabLabel} <Icons.chevD size={11}/></div>
        <div className="row aic gap-4" onClick={() => setSortDesc(v => !v)}
             style={{ cursor: 'pointer' }}>
          {rightLabel} <Icons.chevD size={11} style={{
            transform: sortDesc ? 'none' : 'rotate(180deg)',
            transition: 'transform 0.15s',
          }}/>
        </div>
      </div>

      <div style={{
        maxHeight: 330, overflowY: 'auto',
        marginRight: -4, paddingRight: 4,
        scrollbarWidth: 'none',
      }}>
        {loading ? (
          <FilterRowsSkeleton/>
        ) : attrsFailed ? (
          <div className="col aic" style={{ alignItems: 'center', textAlign: 'center', padding: '28px 0', gap: 12 }}>
            <div className="t-mute" style={{ fontSize: 13 }}>Не удалось загрузить атрибуты</div>
            <button onClick={retryAttrs} style={{
              padding: '10px 20px', borderRadius: 999, background: 'var(--accent)', color: '#fff',
              border: 0, cursor: 'pointer', fontSize: 14, fontWeight: 650,
            }}>Повторить</button>
          </div>
        ) : (
          <div className="anim-fade-up">
            {filtered.map((it, idx) => (
              <FilterRow key={it.value}
                tab={tab}
                item={it}
                idx={idx}
                collection={collection}
                on={cur.includes(it.value)}
                onClick={() => toggle(it.value)}/>
            ))}
            {filtered.length === 0 && (
              <div className="t-mute" style={{ textAlign: 'center', padding: '32px 0', fontSize: 13 }}>
                Ничего не нашлось
              </div>
            )}
          </div>
        )}
      </div>

      <div className="row gap-8" style={{ marginTop: 14 }}>
        <button onClick={reset} style={{
          flex: 1, padding: '14px', borderRadius: 999,
          background: 'var(--bg-input)', color: 'var(--text)',
          border: 0, fontSize: 15, fontWeight: 650, cursor: 'pointer',
        }}>Сбросить</button>
        <button onClick={() => { onApply(local); closeAnim(); }} style={{
          flex: 1.4, padding: '14px', borderRadius: 999,
          background: 'var(--accent)', color: '#fff',
          border: 0, fontSize: 15, fontWeight: 700, cursor: 'pointer',
          boxShadow: '0 6px 18px var(--accent-glow), inset 0 1px 0 rgba(255,255,255,0.18)',
        }}>
          Показать{count > 0 ? ` (${count})` : ''}
        </button>
      </div>
    </>
  );
}

window.FilterSheet = FilterSheet;
window.BACKDROP_COLORS = BACKDROP_COLORS;
