import React, { useEffect, useState, useMemo, useCallback, useRef } from 'react';
import { DraggableWindow } from './DraggableWindow';
import { apiFetch } from '../../api/client';

interface Props { id: string; charName: string; zIndex: number; inventoryVersion?: number; equipmentCantrips?: any; }

// ── Item normalization (handles both inventory-service snake_case and plugin PascalCase) ──
function normalizeItem(raw: any): any {
  if (!raw) return raw;
  const v = (val: any) => (val !== undefined && val !== null && val !== -1 && val !== -1.0) ? val : undefined;
  const iv = raw.IntValues || {};
  return {
    item_id: raw.item_id ?? raw.Id ?? 0,
    name: raw.name ?? raw.Name ?? (raw.StringValues?.['1']) ?? 'Unknown',
    icon: raw.icon ?? raw.Icon ?? 0,
    object_class: raw.object_class ?? raw.ObjectClass ?? 0,
    current_wielded_location: raw.current_wielded_location ?? v(raw.CurrentWieldedLocation) ?? v(Number(iv['10'])) ?? 0,
    container_id: raw.container_id ?? raw.ContainerId ?? 0,
    items_capacity: raw.items_capacity ?? v(raw.ItemsCapacity) ?? v(Number(iv['6'])) ?? raw.enhanced_properties?.ItemSlots_Decal ?? undefined,
    value: raw.value ?? v(raw.Value) ?? v(Number(iv['19'])) ?? 0,
    burden: raw.burden ?? v(raw.Burden) ?? v(Number(iv['5'])) ?? 0,
    armor_level: raw.armor_level ?? v(raw.ArmorLevel),
    max_damage: raw.max_damage ?? v(raw.MaxDamage),
    material: raw.material ?? raw.material_name ?? raw.Material ?? undefined,
    item_set: raw.item_set ?? raw.ItemSet ?? undefined,
    imbue: raw.imbue ?? raw.Imbue ?? undefined,
    tinks: raw.tinks ?? v(raw.Tinks),
    workmanship: raw.workmanship ?? v(raw.Workmanship),
    equip_skill: raw.equip_skill ?? raw.equip_skill_name ?? raw.EquipSkill ?? undefined,
    wield_level: raw.wield_level ?? v(raw.WieldLevel),
    skill_level: raw.skill_level ?? v(raw.SkillLevel),
    lore_requirement: raw.lore_requirement ?? v(raw.LoreRequirement),
    attack_bonus: raw.attack_bonus ?? v(raw.AttackBonus),
    melee_defense_bonus: raw.melee_defense_bonus ?? v(raw.MeleeDefenseBonus),
    magic_defense_bonus: raw.magic_defense_bonus ?? v(raw.MagicDBonus),
    damage_bonus: raw.damage_bonus ?? v(raw.DamageBonus),
    damage_rating: raw.damage_rating ?? v(raw.DamRating),
    crit_rating: raw.crit_rating ?? v(raw.CritRating),
    heal_boost_rating: raw.heal_boost_rating ?? v(raw.HealBoostRating),
    current_mana: raw.current_mana ?? v(Number(iv['218103815'])) ?? undefined,
    max_mana: raw.max_mana ?? v(Number(iv['218103814'])) ?? undefined,
    spellcraft: raw.spellcraft ?? undefined,
    damage_range: raw.damage_range ?? undefined,
    damage_type: raw.damage_type ?? undefined,
    speed_text: raw.speed_text ?? undefined,
    mana_display: raw.mana_display ?? undefined,
    spells: raw.spells ?? undefined,
    icon_overlay_id: raw.icon_overlay_id ?? v(Number(iv['218103849'])) ?? undefined,
    icon_underlay_id: raw.icon_underlay_id ?? v(Number(iv['218103850'])) ?? undefined,
    _raw: raw,
  };
}

// ── Icon helpers ──
function iconHex(raw: number): string {
  if (!raw || raw <= 0) return '06000133';
  return (raw + 0x06000000).toString(16).toUpperCase().padStart(8, '0');
}

// ── Equipment slots ──
const EQUIP_SLOTS: Record<number, { name: string; row: number; col: number }> = {
  32768:{name:'Neck',row:1,col:1},1:{name:'Head',row:1,col:3},268435456:{name:'Sigil',row:1,col:5},536870912:{name:'Sigil',row:1,col:6},1073741824:{name:'Sigil',row:1,col:7},
  67108864:{name:'Trinket',row:2,col:1},2048:{name:'U.Arm',row:2,col:2},512:{name:'Chest',row:2,col:3},134217728:{name:'Cloak',row:2,col:7},
  65536:{name:'Brace L',row:3,col:1},4096:{name:'L.Arm',row:3,col:2},1024:{name:'Abdomen',row:3,col:3},8192:{name:'U.Leg',row:3,col:4},131072:{name:'Brace R',row:3,col:5},2:{name:'Shirt',row:3,col:7},
  262144:{name:'Ring L',row:4,col:1},32:{name:'Hands',row:4,col:2},16384:{name:'L.Leg',row:4,col:4},524288:{name:'Ring R',row:4,col:5},4:{name:'Pants',row:4,col:7},
  256:{name:'Feet',row:5,col:4},
  2097152:{name:'Shield',row:6,col:1},1048576:{name:'Melee',row:6,col:3},4194304:{name:'Missile',row:6,col:3},16777216:{name:'Held',row:6,col:3},33554432:{name:'2H',row:6,col:3},8388608:{name:'Ammo',row:6,col:7},
};

// Slot colors matching v1
const SLOT_COLORS: Record<string, string> = {};
const purpleSlots = [32768,67108864,65536,131072,262144,524288];
const blueSlots = [1,512,2048,1024,4096,8192,16384,32,256];
const tealSlots = [2,4,134217728,268435456,536870912,1073741824];
const darkblueSlots = [2097152,1048576,4194304,16777216,33554432,8388608];
// Map slot keys to colors
(() => {
  const seen = new Set<string>();
  Object.entries(EQUIP_SLOTS).forEach(([maskStr, def]) => {
    const k = `${def.row}-${def.col}`;
    const m = parseInt(maskStr);
    if (!seen.has(k)) {
      seen.add(k);
      if (purpleSlots.includes(m)) SLOT_COLORS[k] = '#3a2555';
      else if (blueSlots.includes(m)) SLOT_COLORS[k] = '#1e2e55';
      else if (tealSlots.includes(m)) SLOT_COLORS[k] = '#1e3e3e';
      else if (darkblueSlots.includes(m)) SLOT_COLORS[k] = '#142040';
      else SLOT_COLORS[k] = '#2a2a2a';
    }
  });
})();

const gold = '#af7a30';

function ItemIcon({ item, size = 36 }: { item: any; size?: number }) {
  const s: React.CSSProperties = { position: 'absolute', top: 0, left: 0, width: size, height: size, border: 'none', background: 'transparent', imageRendering: 'pixelated' };
  const underlay = item.icon_underlay_id && item.icon_underlay_id > 100 ? `/icons/${iconHex(item.icon_underlay_id)}.png` : null;
  const overlay = item.icon_overlay_id && item.icon_overlay_id > 100 ? `/icons/${iconHex(item.icon_overlay_id)}.png` : null;
  return (
    <div style={{ width: size, height: size, position: 'relative' }}>
      {underlay && <img src={underlay} alt="" style={{ ...s, zIndex: 1 }} onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }} />}
      <img src={`/icons/${iconHex(item.icon)}.png`} alt={item.name} style={{ ...s, zIndex: 2 }} onError={e => { (e.target as HTMLImageElement).src = '/icons/06000133.png'; }} />
      {overlay && <img src={overlay} alt="" style={{ ...s, zIndex: 3 }} onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }} />}
    </div>
  );
}

function ItemTooltip({ item, x, y }: { item: any; x: number; y: number }) {
  const isV = (val: any) => val !== undefined && val !== null && val !== -1 && val !== -1.0;
  const fmt = (n: number) => n.toLocaleString();
  const pct = (v: number) => `${((v - 1) * 100).toFixed(1)}%`;
  return (
    <div style={{ position: 'fixed', left: x + 14, top: y + 14, background: 'rgba(0,0,0,0.96)', border: '1px solid #555', borderRadius: 4, padding: '8px 12px', zIndex: 99999, minWidth: 200, maxWidth: 340, fontSize: 13, color: '#ddd', pointerEvents: 'none', lineHeight: 1.6, fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif' }}>
      <div style={{ color: '#ffcc00', fontWeight: 'bold', fontSize: 14, marginBottom: 4 }}>{item.name}</div>
      <div style={{ color: '#aaa' }}>Value: {fmt(item.value)} &middot; Burden: {item.burden}</div>
      {item.workmanship && <div style={{ color: '#aaa' }}>Workmanship: {item.workmanship}</div>}
      {item.material && <div style={{ color: '#88ff88' }}>Material: {item.material}</div>}
      {isV(item.armor_level) && <div style={{ color: '#88ff88' }}>Armor Level: {item.armor_level}</div>}
      {isV(item.max_damage) && <div style={{ color: '#88ff88' }}>Max Damage: {item.max_damage}</div>}
      {item.damage_range && <div style={{ color: '#88ff88' }}>Damage: {item.damage_range}{item.damage_type ? `, ${item.damage_type}` : ''}</div>}
      {isV(item.attack_bonus) && item.attack_bonus !== 1 && <div style={{ color: '#88ff88' }}>Attack: +{pct(item.attack_bonus)}</div>}
      {isV(item.melee_defense_bonus) && item.melee_defense_bonus !== 1 && <div style={{ color: '#88ff88' }}>Melee Def: +{pct(item.melee_defense_bonus)}</div>}
      {isV(item.magic_defense_bonus) && item.magic_defense_bonus !== 1 && <div style={{ color: '#88ff88' }}>Magic Def: +{pct(item.magic_defense_bonus)}</div>}
      {item.equip_skill && <div style={{ color: '#ddd' }}>Skill: {item.equip_skill}</div>}
      {isV(item.wield_level) && <div style={{ color: '#ffaa00' }}>Wield Level: {item.wield_level}</div>}
      {isV(item.lore_requirement) && <div style={{ color: '#ffaa00' }}>Lore: {item.lore_requirement}</div>}
      {item.imbue && <div style={{ color: '#88ff88' }}>Imbue: {item.imbue}</div>}
      {item.item_set && <div style={{ color: '#88ff88' }}>Set: {item.item_set}</div>}
      {isV(item.tinks) && <div style={{ color: '#88ff88' }}>Tinks: {item.tinks}</div>}
      {isV(item.damage_rating) && <div>Damage Rating: {item.damage_rating}</div>}
      {isV(item.crit_rating) && <div>Crit Rating: {item.crit_rating}</div>}
      {isV(item.heal_boost_rating) && <div>Heal Boost: {item.heal_boost_rating}</div>}
      {item.spellcraft && <div style={{ color: '#dda0dd' }}>Spellcraft: {item.spellcraft}</div>}
      {isV(item.current_mana) && isV(item.max_mana) && <div style={{ color: '#98d7ff' }}>Mana: {item.current_mana} / {item.max_mana}</div>}
      {item.spells?.spells?.length > 0 && <div style={{ color: '#4a90e2', marginTop: 4, fontSize: 12 }}>Spells: {item.spells.spells.map((s: any) => s.name).join(', ')}</div>}
    </div>
  );
}

function PackIcon({ iconSrc, isActive, fillPct, label, onClick }: {
  iconSrc: string; isActive: boolean; fillPct: number; label: string; onClick: () => void;
}) {
  const fillColor = fillPct > 90 ? '#b7432c' : fillPct > 70 ? '#d8a431' : '#00ff00';
  return (
    <div onClick={onClick} title={label}
      style={{ display: 'flex', alignItems: 'flex-start', gap: 2, cursor: 'pointer', flexShrink: 0, marginTop: 3, position: 'relative' }}>
      {isActive && <span style={{ position: 'absolute', left: -11, top: 8, color: gold, fontSize: 10 }}>▶</span>}
      <div style={{ width: 30, height: 30, border: isActive ? '1px solid #00ff00' : '1px solid #333', boxShadow: isActive ? '0 0 4px #00ff00' : 'none', background: '#000', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <img src={iconSrc} alt="" style={{ width: 26, height: 26, objectFit: 'contain', imageRendering: 'pixelated' }}
          onError={e => { (e.target as HTMLImageElement).src = '/icons/06001080.png'; }} />
      </div>
      <div style={{ width: 7, height: 30, background: '#222', border: '1px solid #666', position: 'relative', overflow: 'hidden', borderRadius: 2 }}
        title={`${Math.round(fillPct)}% full`}>
        <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: `${fillPct}%`, background: fillColor, minHeight: fillPct > 0 ? 2 : 0 }} />
      </div>
    </div>
  );
}

export const InventoryWindow: React.FC<Props> = ({ id, charName, zIndex, inventoryVersion, equipmentCantrips }) => {
  const [items, setItems] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);
  const [activePack, setActivePack] = useState<number | null>(null);
  const [tooltip, setTooltip] = useState<{ item: any; x: number; y: number } | null>(null);
  const [charStats, setCharStats] = useState<any>(null);
  const debounceRef = useRef<number>(0);
  const initialLoadDone = useRef(false);

  // Initial fetch
  useEffect(() => {
    setLoading(true);
    Promise.all([
      apiFetch<any>(`/inventory/${encodeURIComponent(charName)}?limit=1000`).catch(() => ({ items: [] })),
      apiFetch<any>(`/character-stats/${encodeURIComponent(charName)}`).catch(() => null),
    ]).then(([inv, stats]) => {
      setItems((inv.items ?? []).map(normalizeItem));
      setCharStats(stats);
      initialLoadDone.current = true;
    }).finally(() => setLoading(false));
  }, [charName]);

  // Debounced re-fetch on inventory_delta (no loading flash)
  useEffect(() => {
    if (!initialLoadDone.current || !inventoryVersion) return;
    clearTimeout(debounceRef.current);
    debounceRef.current = window.setTimeout(() => {
      apiFetch<any>(`/inventory/${encodeURIComponent(charName)}?limit=1000&_t=${Date.now()}`)
        .then(inv => setItems((inv.items ?? []).map(normalizeItem)))
        .catch(() => {});
    }, 2000); // 2s debounce — batch rapid deltas
    return () => clearTimeout(debounceRef.current);
  }, [charName, inventoryVersion]);

  const handleHover = useCallback((item: any | null, e?: React.MouseEvent) => {
    if (item && e) setTooltip({ item, x: e.clientX, y: e.clientY });
    else setTooltip(null);
  }, []);

  const slotPositions = useMemo(() => {
    const seen = new Set<string>();
    const slots: Array<{ key: string; row: number; col: number; mask: number; name: string }> = [];
    Object.entries(EQUIP_SLOTS).forEach(([maskStr, def]) => {
      const k = `${def.row}-${def.col}`;
      if (!seen.has(k)) { seen.add(k); slots.push({ key: k, ...def, mask: parseInt(maskStr) }); }
    });
    return slots;
  }, []);

  const { equippedMap, containers, packItems } = useMemo(() => {
    const equippedMap = new Map<string, any>();
    const containers: any[] = [];
    const containerIds = new Set<number>();
    const packItems = new Map<number, any[]>();

    items.forEach(item => { if (item.object_class === 10) { containers.push(item); containerIds.add(item.item_id); } });
    containers.sort((a: any, b: any) => (a.item_id >>> 0) - (b.item_id >>> 0));

    // Find body container ID (worn items share a container_id that isn't a pack)
    let bodyContainerId: number | null = null;
    items.forEach(item => {
      if (item.current_wielded_location > 0 && item.container_id && !containerIds.has(item.container_id)) {
        bodyContainerId = item.container_id;
      }
    });

    items.forEach(item => {
      if (containerIds.has(item.item_id)) return;
      const wielded = item.current_wielded_location;
      if (wielded > 0) {
        const isArmor = item.object_class === 2;
        if (isArmor) {
          // Armor: ALL matching slots
          Object.entries(EQUIP_SLOTS).forEach(([maskStr, def]) => {
            if ((wielded & parseInt(maskStr)) === parseInt(maskStr)) {
              const key = `${def.row}-${def.col}`;
              if (!equippedMap.has(key)) equippedMap.set(key, item);
            }
          });
        } else {
          // Non-armor: exact match first, then first bit overlap
          let placed = false;
          if (EQUIP_SLOTS[wielded]) {
            const def = EQUIP_SLOTS[wielded];
            const key = `${def.row}-${def.col}`;
            if (!equippedMap.has(key)) { equippedMap.set(key, item); placed = true; }
          }
          if (!placed) {
            for (const [maskStr, def] of Object.entries(EQUIP_SLOTS)) {
              if ((wielded & parseInt(maskStr)) === parseInt(maskStr)) {
                const key = `${def.row}-${def.col}`;
                if (!equippedMap.has(key)) { equippedMap.set(key, item); placed = true; break; }
              }
            }
          }
        }
      } else {
        let cid = item.container_id || 0;
        if (bodyContainerId && cid === bodyContainerId) cid = 0;
        if (!packItems.has(cid)) packItems.set(cid, []);
        packItems.get(cid)!.push(item);
      }
    });
    return { equippedMap, containers, packItems };
  }, [items]);

  // Main backpack: key 0, OR the largest non-container group if bodyContainerId wasn't detected
  let mainItems = packItems.get(0) ?? [];
  let mainPackKey: number = 0;
  if (mainItems.length === 0) {
    // bodyContainerId wasn't detected — find the biggest group that isn't a container
    let biggest = 0;
    for (const [cid, items] of packItems.entries()) {
      if (!containers.some((c: any) => c.item_id === cid) && items.length > biggest) {
        biggest = items.length;
        mainPackKey = cid;
      }
    }
    mainItems = packItems.get(mainPackKey) ?? [];
  }
  const activeItems = activePack !== null ? (packItems.get(activePack) ?? []) : mainItems;

  // Burden
  const burdenUnits = charStats?.burden_units ?? charStats?.stats_data?.burden_units ?? 0;
  const encumbranceCap = charStats?.encumbrance_capacity ?? charStats?.stats_data?.encumbrance_capacity ?? 0;
  const burdenPct = encumbranceCap > 0 ? Math.min(200, (burdenUnits / encumbranceCap) * 100) : 0;
  const burdenColor = burdenPct > 150 ? '#b7432c' : burdenPct > 100 ? '#d8a431' : '#2e8b57';

  if (loading) {
    return <DraggableWindow id={id} title={`Inventory: ${charName}`} zIndex={zIndex} width={572} height={720}>
      <div style={{ padding: 20, color: '#666', fontStyle: 'italic' }}>Loading inventory...</div>
    </DraggableWindow>;
  }

  return (
    <DraggableWindow id={id} title={`Inventory: ${charName}`} zIndex={zIndex} width={572} height={720}>
      <div style={{ display: 'flex', flex: 1, overflow: 'hidden', background: 'rgba(14,14,14,0.96)', fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif', fontSize: 13 }}>
        {/* LEFT: Equipment + Items */}
        <div style={{ width: 316, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
          <div style={{ position: 'relative', height: 270, minHeight: 270, background: '#0a0a0a', borderBottom: `1px solid ${gold}` }}>
            {slotPositions.map(slot => {
              const item = equippedMap.get(slot.key);
              const slotBg = SLOT_COLORS[slot.key] ?? '#2a2a2a';
              return (
                <div key={slot.key}
                  style={{
                    position: 'absolute', left: (slot.col - 1) * 44 + 4, top: (slot.row - 1) * 44 + 4,
                    width: 36, height: 36, background: item ? '#5a5a62' : slotBg,
                    border: item ? '2px solid #00ffff' : '2px outset #6a6a72',
                    boxShadow: item ? '0 0 5px #00ffff, inset 0 0 5px rgba(0,255,255,0.2)' : 'none',
                    display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: item ? 'pointer' : 'default',
                  }}
                  onMouseEnter={e => item && handleHover(item, e)}
                  onMouseMove={e => item && handleHover(item, e)}
                  onMouseLeave={() => handleHover(null)}>
                  {item ? <ItemIcon item={item} size={32} /> :
                    <img src="/icons/06000133.png" alt="" style={{ width: 28, height: 28, opacity: 0.15, filter: 'grayscale(100%)', imageRendering: 'pixelated' }} />}
                </div>
              );
            })}
          </div>
          <div style={{ padding: '3px 6px', fontSize: 11, color: '#ccc', background: '#111', borderBottom: `1px solid ${gold}` }}>
            Contents of {activePack !== null ? (containers.find((c: any) => c.item_id === activePack)?.name ?? 'Pack') : 'Backpack'}
          </div>
          <div style={{ flex: 1, overflowY: 'auto', display: 'grid', gridTemplateColumns: 'repeat(6, 36px)', gridAutoRows: 36, gap: 2, padding: 4, alignContent: 'start' }}>
            {activeItems.map((item: any, i: number) => (
              <div key={item.item_id ?? i}
                style={{ width: 36, height: 36, background: 'linear-gradient(135deg, #3d007a 0%, #1a0033 100%)', border: '1px solid #4a148c', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}
                onMouseEnter={e => handleHover(item, e)}
                onMouseMove={e => handleHover(item, e)}
                onMouseLeave={() => handleHover(null)}>
                <ItemIcon item={item} size={32} />
              </div>
            ))}
            {Array.from({ length: Math.max(0, 24 - activeItems.length) }).map((_, i) => (
              <div key={`e${i}`} style={{ width: 36, height: 36, background: '#0a0a0a', border: '1px solid #1a1a1a' }} />
            ))}
          </div>
        </div>

        {/* SIDEBAR: Burden + Packs */}
        <div style={{ width: 42, display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '4px 2px', borderLeft: `1px solid ${gold}`, borderRight: `1px solid ${gold}` }}>
          <div style={{ textAlign: 'center', fontSize: 8, color: '#ccc', marginBottom: 2 }}>
            {encumbranceCap > 0 ? `${Math.floor(burdenPct)}%` : 'Burden'}
          </div>
          <div style={{ width: 14, height: 40, background: '#111', border: '1px solid #555', position: 'relative', overflow: 'hidden', marginBottom: 6, flexShrink: 0 }}
            title={encumbranceCap > 0 ? `${burdenUnits.toLocaleString()} / ${encumbranceCap.toLocaleString()}` : `Burden: ${items.reduce((s: number, i: any) => s + (i.burden ?? 0), 0).toLocaleString()}`}>
            <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: `${burdenPct / 2}%`, background: burdenColor, transition: 'height 0.3s' }} />
          </div>

          <PackIcon iconSrc="/icons/0600127E.png" isActive={activePack === null}
            fillPct={mainItems.length > 0 ? Math.min(100, (mainItems.length / 102) * 100) : 0}
            label={`Backpack (${mainItems.length}/102)`} onClick={() => setActivePack(null)} />
          {containers.map((c: any) => {
            const cid = c.item_id;
            // Count items directly from normalized items array instead of relying on packItems map
            const childCount = items.filter((i: any) => i.container_id === cid && i.item_id !== cid).length;
            const cap = c.items_capacity || 24;
            const pct = cap > 0 ? Math.min(100, (childCount / cap) * 100) : 0;
            return <PackIcon key={cid} iconSrc={`/icons/${iconHex(c.icon)}.png`} isActive={activePack === cid}
              fillPct={pct}
              label={`${c.name} (${childCount}/${cap})`} onClick={() => setActivePack(cid)} />;
          })}
        </div>

        {/* RIGHT: Mana panel */}
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minWidth: 160 }}>
          <div style={{ padding: '4px 8px', fontSize: '0.72rem', fontWeight: 600, color: '#aaa', background: '#111', borderBottom: `1px solid ${gold}` }}>Mana</div>
          <div style={{ flex: 1, overflowY: 'auto', padding: '2px 0' }}>
            {(() => {
              // Merge real cantrip state data if available
              const cantripItems = equipmentCantrips?.items ?? [];
              const cantripMap: Map<number, any> = new Map(cantripItems.map((c: any) => [c.item_id, c]));
              const snapshotTime = equipmentCantrips?.timestamp ? new Date(equipmentCantrips.timestamp).getTime() : 0;
              const elapsed = snapshotTime > 0 ? Math.max(0, (Date.now() - snapshotTime) / 1000) : 0;

              return Array.from(equippedMap.values())
                .map((item: any) => {
                  const cantrip = cantripMap.get(item.item_id);
                  const curMana = cantrip?.current_mana ?? item.current_mana ?? 0;
                  const maxMana = cantrip?.max_mana ?? item.max_mana ?? 0;
                  const rawRemaining = cantrip?.mana_time_remaining_seconds ?? null;
                  const liveRemaining = rawRemaining != null ? Math.max(0, rawRemaining - elapsed) : null;
                  const state = cantrip?.state ?? (curMana > 0 ? 'active' : 'not_active');
                  return { ...item, current_mana: curMana, max_mana: maxMana, liveRemaining, manaState: state };
                })
                .filter((i: any) => i.current_mana > 0 || i.max_mana > 0)
                .sort((a: any, b: any) => (a.liveRemaining ?? 999999) - (b.liveRemaining ?? 999999))
                .map((item: any, i: number) => {
                  const stateColor = item.manaState === 'active' ? '#4c4' : item.manaState === 'not_active' ? '#c44' : '#da8';
                  return (
                    <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '2px 4px', borderBottom: '1px solid #1a1a1a', cursor: 'pointer' }}
                      onMouseEnter={e => handleHover(item, e)} onMouseMove={e => handleHover(item, e)} onMouseLeave={() => handleHover(null)}>
                      <div style={{ width: 20, height: 20, flexShrink: 0 }}><ItemIcon item={item} size={20} /></div>
                      <div style={{ width: 8, height: 8, borderRadius: '50%', background: stateColor, flexShrink: 0 }} />
                      <div style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: '0.68rem', color: '#ccc' }}>{item.name}</div>
                      <div style={{ fontSize: '0.65rem', color: '#88bbff', whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' }}>{item.current_mana}/{item.max_mana}</div>
                      <div style={{ fontSize: '0.63rem', color: '#9c9', whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums', minWidth: 42, textAlign: 'right' }}>
                        {item.liveRemaining != null ? formatSeconds(item.liveRemaining) : ''}
                      </div>
                    </div>
                  );
                });
            })()}
            {Array.from(equippedMap.values()).filter((i: any) => (i.current_mana > 0 || i.max_mana > 0)).length === 0 && (
              <div style={{ padding: 12, color: '#555', textAlign: 'center', fontSize: '0.7rem' }}>No mana items equipped</div>
            )}
          </div>
        </div>
      </div>
      {tooltip && <ItemTooltip item={tooltip.item} x={tooltip.x} y={tooltip.y} />}
    </DraggableWindow>
  );
};

function formatSeconds(totalSeconds: number): string {
  if (totalSeconds <= 0) return '0h00m';
  const s = Math.floor(totalSeconds);
  const hours = Math.floor(s / 3600);
  const minutes = Math.floor((s % 3600) / 60);
  return `${hours}h${String(minutes).padStart(2, '0')}m`;
}
