// ViG Command Center — shared lib: hooks, primitives, icons, charts
const { useState, useEffect, useMemo, useRef, useCallback, createContext, useContext } = React;

/* ============ persistence / hooks ============ */
const LS = {
  get(k, d) { try { const v = JSON.parse(localStorage.getItem(k)); return v == null ? d : v; } catch (e) { return d; } },
  set(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) {} },
};

// ownership keyed by "name|type"; seeded once from spreadsheet
const OWN_KEY = 'vig2_ownership';
function ownKey(g) { return g.name + '|' + g.type; }
function seedOwnership(generals) {
  const seed = {};
  generals.forEach(g => { seed[ownKey(g)] = !!g.owned; });
  return seed;
}
const OwnCtx = createContext(null);
function OwnershipProvider({ children }) {
  const generals = window.VIG.generals;
  const [own, setOwn] = useState(() => {
    const stored = LS.get(OWN_KEY, null);
    return stored || seedOwnership(generals);
  });
  useEffect(() => { LS.set(OWN_KEY, own); }, [own]);
  const isOwned = useCallback((g) => !!own[ownKey(g)], [own]);
  const ownedByName = useCallback((name) => generals.some(g => g.name === name && own[ownKey(g)]), [own, generals]);
  const toggle = useCallback((g) => setOwn(p => ({ ...p, [ownKey(g)]: !p[ownKey(g)] })), []);
  const setAll = useCallback((val) => setOwn(() => { const o = {}; generals.forEach(g => o[ownKey(g)] = val); return o; }), [generals]);
  const reset = useCallback(() => setOwn(seedOwnership(generals)), [generals]);

  // role-general ownership (for role-only generals not in the troop sheets)
  const [roleOwn, setRoleOwn] = useState(() => LS.get('vig2_role_owned', {}));
  useEffect(() => { LS.set('vig2_role_owned', roleOwn); }, [roleOwn]);
  const hasTroopRow = useCallback((name) => generals.some(g => g.name === name), [generals]);
  const isOwnedName = useCallback((name) => ownedByName(name) || !!roleOwn[name], [ownedByName, roleOwn]);
  const toggleName = useCallback((name) => {
    if (generals.some(g => g.name === name)) {
      // toggle every troop row of this general to the opposite of current owned state
      const target = !ownedByName(name);
      setOwn(p => { const o = { ...p }; generals.forEach(g => { if (g.name === name) o[ownKey(g)] = target; }); return o; });
    } else {
      setRoleOwn(p => ({ ...p, [name]: !p[name] }));
    }
  }, [generals, ownedByName]);

  return <OwnCtx.Provider value={{ own, isOwned, ownedByName, toggle, setAll, reset, isOwnedName, toggleName, hasTroopRow }}>{children}</OwnCtx.Provider>;
}
const useOwn = () => useContext(OwnCtx);

/* ── tier / plan gate ── */
const TIER_RANK={free:0,commander:1,warlord:2};
const TIER_LABEL={free:'Operative',commander:'Commander',warlord:'Warlord'};
const TIER_FEATURES={ai:'commander',warroom:'commander',discord:'commander',rally:'commander',battlereport:'commander',diplomacy:'commander',subs:'commander',warsim:'warlord',roadmap:'warlord',resourceplanner:'warlord'};
const TIER_COLOR={free:'#6A6A6E',commander:'#FF2A17',warlord:'#E8C271'};
window.tierOf=function(s){const t=s&&s.tier;return TIER_RANK[t]!=null?t:'commander';};
window.canAccessPage=function(id,s){const r=TIER_FEATURES[id];if(!r)return true;return TIER_RANK[window.tierOf(s)]>=TIER_RANK[r];};
function TierGate({page,session,go}){const req=TIER_FEATURES[page],tier=window.tierOf(session);return(<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4 text-center"><div className="flex h-14 w-14 items-center justify-center rounded-2xl" style={{background:'linear-gradient(135deg,#FF3B26,#7a0f0a)',boxShadow:'0 12px 32px -10px rgba(255,42,23,.6)'}}><Icon name="shield" size={26} className="text-white"/></div><div className="text-[20px] font-bold text-zinc-900 dark:text-white">{TIER_LABEL[req]||'Commander'} feature</div><p className="max-w-xs text-[13.5px] text-zinc-500 dark:text-zinc-400">This module requires the <b className="font-semibold" style={{color:TIER_COLOR[req]}}>{TIER_LABEL[req]}</b> plan. You&apos;re on <b>{TIER_LABEL[tier]}</b>.</p><button onClick={()=>go('settings')} className="inline-flex items-center gap-2 rounded-xl px-5 py-2.5 text-[13.5px] font-bold text-white transition-all hover:brightness-110" style={{background:'linear-gradient(135deg,#FF3B26,#7a0f0a)'}}>Upgrade plan →</button></div>);}
window.TierGate=TierGate;

/* ============ theme + role ============ */
function useTheme() {
  const [dark, setDark] = useState(() => LS.get('vig2_dark', false));
  useEffect(() => {
    document.documentElement.classList.toggle('dark', dark);
    LS.set('vig2_dark', dark);
  }, [dark]);
  return [dark, setDark];
}
const ROLE_PERMS = { Admin: { edit: true }, Member: { edit: true }, Guest: { edit: false } };

/* ============ icons (Lucide-style, minimal) ============ */
const ICON = {
  grid: 'M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z',
  database: 'M12 3c4.97 0 9 1.34 9 3s-4.03 3-9 3-9-1.34-9-3 4.03-3 9-3zM3 6v6c0 1.66 4.03 3 9 3s9-1.34 9-3V6M3 12v6c0 1.66 4.03 3 9 3s9-1.34 9-3v-6',
  link: 'M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 0 1 0 10h-2M8 12h8',
  scale: 'M12 3v18M5 7l-3 7h6zM19 7l-3 7h6zM5 7h14',
  flame: 'M12 2c1 4 5 5 5 9a5 5 0 0 1-10 0c0-1 .5-2 1-3 .5 2 2 2 2 0 0-2-1-4 2-6z',
  shield: 'M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z',
  target: 'M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18zM12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM12 11a1 1 0 1 0 0 2 1 1 0 0 0 0-2z',
  search: 'M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM20 20l-3.5-3.5',
  sun: 'M12 4V2M12 22v-2M4 12H2M22 12h-2M6 6 4.5 4.5M19.5 19.5 18 18M18 6l1.5-1.5M4.5 19.5 6 18M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z',
  moon: 'M21 13A9 9 0 1 1 11 3a7 7 0 0 0 10 10z',
  check: 'M5 12l4 4 10-10',
  x: 'M6 6l12 12M18 6 6 18',
  chevron: 'M9 6l6 6-6 6',
  arrow: 'M5 12h14M13 6l6 6-6 6',
  plus: 'M12 5v14M5 12h14',
  download: 'M12 3v12M7 11l5 5 5-5M5 21h14',
  bolt: 'M13 2 4 14h7l-1 8 9-12h-7z',
  trophy: 'M8 21h8M12 17v4M7 4h10v4a5 5 0 0 1-10 0zM7 4H4v2a3 3 0 0 0 3 3M17 4h3v2a3 3 0 0 1-3 3',
  star: 'M12 3l2.7 5.5 6 .9-4.3 4.2 1 6-5.4-2.8L6.6 19.6l1-6L3.3 9.4l6-.9z',
  layers: 'M12 2 2 7l10 5 10-5zM2 12l10 5 10-5M2 17l10 5 10-5',
  users: 'M17 21v-2a4 4 0 0 0-4-4H7a4 4 0 0 0-4 4v2M9 3a4 4 0 1 0 0 8 4 4 0 0 0 0-8M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75',
  trap: 'M3 21v-6a9 9 0 0 1 18 0v6M3 15h18M12 6V3',
  undo: 'M3 7v6h6M21 17a9 9 0 0 0-15-6.7L3 13',
  reset: 'M1 4v6h6M3.51 15a9 9 0 1 0 2.13-9.36L1 10',
  sparkle: 'M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9zM19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z',
  chat: 'M21 12a8 8 0 0 1-11.6 7.1L3 21l1.9-6.4A8 8 0 1 1 21 12z',
  book: 'M4 4h13a2 2 0 0 1 2 2v14H6a2 2 0 0 1-2-2zM4 4v14M9 8h6M9 12h5',
};
function Icon({ name, size = 18, className = '', stroke = 2 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
      strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <path d={ICON[name]} />
    </svg>
  );
}
const TYPE_ICON = { Ground: 'shield', Mounted: 'flame', Ranged: 'target', Siege: 'bolt', 'Sub-City': 'database', Wall: 'shield', Monster: 'trap' };
const TYPE_COLOR = { Ground: '#2563eb', Mounted: '#dc2626', Ranged: '#16a34a', Siege: '#9333ea', 'Sub-City': '#0891b2', Wall: '#ca8a04', Monster: '#db2777' };

/* ============ UI primitives ============ */
// ViG heraldic crest — shield + V monogram, brand-colored
function VigCrest({ size = 34, className = '' }) {
  const id = 'vc' + Math.round(size);
  return (
    <svg width={size} height={size} viewBox="0 0 64 64" className={className} style={{ display: 'block' }}>
      <defs>
        <linearGradient id={id} x1="0" y1="0" x2="1" y2="1">
          <stop offset="0%" stopColor="#ff8a3a"/><stop offset="55%" stopColor="#FF6B00"/><stop offset="100%" stopColor="#c43a00"/>
        </linearGradient>
      </defs>
      {/* shield */}
      <path d="M32 3 L57 12 V32 C57 47 46 56 32 61 C18 56 7 47 7 32 V12 Z" fill={`url(#${id})`} stroke="rgba(255,255,255,.25)" strokeWidth="1"/>
      {/* inner bevel */}
      <path d="M32 9 L51 16 V32 C51 43 43 50 32 54 C21 50 13 43 13 32 V16 Z" fill="none" stroke="rgba(0,0,0,.18)" strokeWidth="1.5"/>
      {/* V monogram */}
      <path d="M21 20 L32 44 L43 20" fill="none" stroke="#fff" strokeWidth="5.5" strokeLinecap="round" strokeLinejoin="round"/>
      {/* spark */}
      <circle cx="32" cy="15" r="2.2" fill="#fff"/>
    </svg>
  );
}
window.VigCrest = VigCrest;

// Animated count-up number — counts to `value` on mount / when it changes
function CountUp({ value, dur = 900, fmt, className, suffix = '', prefix = '' }) {
  const [disp, setDisp] = useState(0);
  const ref = useRef(0);
  useEffect(() => {
    const from = ref.current, to = Number(value) || 0;
    if (from === to) { setDisp(to); return; }
    const t0 = performance.now();
    let raf;
    const step = now => {
      const k = Math.min(1, (now - t0) / dur), e = 1 - Math.pow(1 - k, 3);
      const cur = from + (to - from) * e;
      setDisp(cur);
      if (k < 1) raf = requestAnimationFrame(step); else ref.current = to;
    };
    raf = requestAnimationFrame(step);
    const tmr = setTimeout(() => { setDisp(to); ref.current = to; }, dur + 60);
    return () => { cancelAnimationFrame(raf); clearTimeout(tmr); ref.current = to; };
  }, [value]);
  const out = fmt ? fmt(disp) : Math.round(disp).toLocaleString();
  return <span className={className}>{prefix}{out}{suffix}</span>;
}
window.CountUp = CountUp;

function Card({ className = '', children, ...p }) {
  return <div className={'cyber-card rounded-xl border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900 ' + className} {...p}>{children}</div>;
}
function Btn({ variant = 'default', size = 'md', className = '', children, ...p }) {
  const sz = size === 'sm' ? 'h-8 px-3 text-[13px]' : size === 'lg' ? 'h-11 px-5 text-[15px]' : 'h-9 px-4 text-sm';
  const v = {
    default: 'bg-zinc-900 text-white hover:bg-zinc-700 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-200',
    brand: 'bg-brand text-white hover:bg-brand-600 shadow-sm shadow-brand/30',
    outline: 'border border-zinc-200 bg-white text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800',
    ghost: 'text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800',
  }[variant];
  return <button className={`inline-flex items-center justify-center gap-1.5 rounded-lg font-medium transition-colors disabled:opacity-40 disabled:pointer-events-none ${sz} ${v} ${className}`} {...p}>{children}</button>;
}
function Pill({ tone = 'zinc', children, className = '' }) {
  const tones = {
    zinc: 'bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300',
    brand: 'bg-brand/10 text-brand-700 dark:text-brand',
    green: 'bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400',
    red: 'bg-rose-50 text-rose-600 dark:bg-rose-500/15 dark:text-rose-400',
    blue: 'bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-400',
  };
  return <span className={`inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-[11px] font-medium ${tones[tone]} ${className}`}>{children}</span>;
}
function TypeTag({ type, size = 'sm' }) {
  const c = TYPE_COLOR[type];
  return (
    <span className="inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-[11px] font-medium"
      style={{ background: c + '14', color: c }}>
      <Icon name={TYPE_ICON[type]} size={size === 'sm' ? 12 : 14} stroke={2.2} />{type}
    </span>
  );
}
function Toggle({ on, onClick, disabled }) {
  return (
    <button onClick={disabled ? undefined : onClick} disabled={disabled} aria-pressed={on}
      className={`relative h-5 w-9 shrink-0 rounded-full transition-colors ${on ? 'bg-brand' : 'bg-zinc-200 dark:bg-zinc-700'} ${disabled ? 'opacity-50 cursor-default' : ''}`}>
      <span className={`absolute top-0.5 h-4 w-4 rounded-full bg-white shadow transition-all ${on ? 'left-4' : 'left-0.5'}`}></span>
    </button>
  );
}
function Progress({ value, tone = 'brand', className = '' }) {
  const colors = { brand: 'bg-brand', green: 'bg-emerald-500', blue: 'bg-blue-500' };
  return (
    <div className={`h-1.5 w-full overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800 ${className}`}>
      <div className={`h-full rounded-full transition-all duration-500 ${colors[tone]}`} style={{ width: Math.max(0, Math.min(100, value)) + '%' }}></div>
    </div>
  );
}
function Segmented({ options, value, onChange, className = '' }) {
  return (
    <div className={`inline-flex rounded-lg border border-zinc-200 bg-zinc-50 p-0.5 dark:border-zinc-700 dark:bg-zinc-800/50 ${className}`}>
      {options.map(o => {
        const val = typeof o === 'string' ? o : o.value, label = typeof o === 'string' ? o : o.label;
        return (
          <button key={val} onClick={() => onChange(val)}
            className={`rounded-md px-3 py-1 text-[13px] font-medium transition-colors ${value === val ? 'bg-white text-zinc-900 shadow-sm dark:bg-zinc-700 dark:text-white' : 'text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200'}`}>
            {label}
          </button>
        );
      })}
    </div>
  );
}
function SearchInput({ value, onChange, placeholder, className = '' }) {
  return (
    <div className={`flex items-center gap-2 rounded-lg border border-zinc-200 bg-white px-3 dark:border-zinc-700 dark:bg-zinc-900 focus-within:border-brand focus-within:ring-2 focus-within:ring-brand/15 ${className}`}>
      <Icon name="search" size={16} className="text-zinc-400" />
      <input value={value} onChange={e => onChange(e.target.value)} placeholder={placeholder}
        className="h-9 w-full bg-transparent text-sm text-zinc-800 outline-none placeholder:text-zinc-400 dark:text-zinc-100" />
    </div>
  );
}
function Avatar({ name, size = 32 }) {
  const init = name.split(/[ ']/).filter(Boolean).slice(0, 2).map(s => s[0]).join('').toUpperCase();
  return (
    <div className="flex shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-zinc-700 to-zinc-900 font-semibold text-white dark:from-zinc-600 dark:to-zinc-800"
      style={{ width: size, height: size, fontSize: size * 0.36 }}>{init}</div>
  );
}
function Drawer({ open, onClose, children, title }) {
  return (
    <div className={`fixed inset-0 z-50 ${open ? '' : 'pointer-events-none'}`}>
      <div onClick={onClose} className={`absolute inset-0 bg-zinc-900/30 backdrop-blur-[2px] transition-opacity ${open ? 'opacity-100' : 'opacity-0'}`}></div>
      <div className={`absolute right-0 top-0 h-full w-full max-w-[440px] overflow-y-auto border-l border-zinc-200 bg-white shadow-2xl transition-transform duration-300 dark:border-zinc-800 dark:bg-zinc-900 ${open ? 'translate-x-0' : 'translate-x-full'}`}>
        <div className="sticky top-0 flex items-center justify-between border-b border-zinc-100 bg-white/90 px-5 py-3.5 backdrop-blur dark:border-zinc-800 dark:bg-zinc-900/90">
          <h3 className="text-sm font-semibold text-zinc-900 dark:text-white">{title}</h3>
          <Btn variant="ghost" size="sm" onClick={onClose} className="!px-2"><Icon name="x" size={18} /></Btn>
        </div>
        {children}
      </div>
    </div>
  );
}

/* ============ charts (hand-rolled SVG) ============ */
function Donut({ segments, size = 132, thickness = 16, center }) {
  const r = (size - thickness) / 2, c = 2 * Math.PI * r;
  let off = 0; const total = segments.reduce((a, s) => a + s.value, 0) || 1;
  return (
    <div className="relative" style={{ width: size, height: size }}>
      <svg width={size} height={size} className="-rotate-90">
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" strokeWidth={thickness} className="stroke-zinc-100 dark:stroke-zinc-800" />
        {segments.map((s, i) => {
          const len = (s.value / total) * c;
          const el = <circle key={i} cx={size / 2} cy={size / 2} r={r} fill="none" stroke={s.color} strokeWidth={thickness}
            strokeDasharray={`${len} ${c - len}`} strokeDashoffset={-off} strokeLinecap="butt" />;
          off += len; return el;
        })}
      </svg>
      {center && <div className="absolute inset-0 flex flex-col items-center justify-center">{center}</div>}
    </div>
  );
}
function Ring({ pct, size = 56, thickness = 6, color = '#FF6B00', label }) {
  const r = (size - thickness) / 2, c = 2 * Math.PI * r, len = (pct / 100) * c;
  return (
    <div className="relative" style={{ width: size, height: size }}>
      <svg width={size} height={size} className="-rotate-90">
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" strokeWidth={thickness} className="stroke-zinc-100 dark:stroke-zinc-800" />
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={color} strokeWidth={thickness} strokeDasharray={`${len} ${c}`} strokeLinecap="round" />
      </svg>
      <div className="absolute inset-0 flex items-center justify-center text-[12px] font-semibold tabular-nums text-zinc-800 dark:text-zinc-100">{label ?? Math.round(pct) + '%'}</div>
    </div>
  );
}

Object.assign(window, {
  LS, OwnershipProvider, useOwn, useTheme, ROLE_PERMS, ownKey,
  Icon, TYPE_ICON, TYPE_COLOR, TypeTag,
  Card, Btn, Pill, Toggle, Progress, Segmented, SearchInput, Avatar, Drawer, Donut, Ring,
});
