/* ===================================================================
   hotbar.jsx — player action bar + keyboard bindings

   A bottom-centre toolbar (mirrors the DM's controls) giving every player
   their spells / attacks and a set of harmless throwables, all bound to
   keys they can press even while it isn't their turn ("not in play").

   Rules of the table:
     • Damage actions (attack / damage rolls) require a dice roll BEFORE any
       damage is dealt — either rolled here, or the player can submit the
       total from their own physical dice.
     • Non-damage keys throw a harmless object instead (rock, small flame,
       stick, water). The throw is shown in both layers: a 2D overlay flies
       across the battle map, and a physics prop is tossed in the 3D world
       (App posts embers:throw to the world iframe).

   No-build/global-scope rules: this loads before app.jsx, so it must use
   React.useState/useEffect/etc. (not destructured) to avoid redeclaring the
   shared React hook locals other files already pull in.
   =================================================================== */
const HOTBAR_THROWS = [
  { id: 'rock',  key: 'q', glyph: '🪨', name: 'Rock' },
  { id: 'flame', key: 'w', glyph: '🔥', name: 'Small flame' },
  { id: 'stick', key: 'e', glyph: '🪵', name: 'Stick' },
  { id: 'water', key: 'r', glyph: '💧', name: 'Water' },
];

const HOTBAR_DEFAULT_ACTIONS = [
  { id: 'atk', label: 'Attack', formula: '1d20+5', tag: 'attack' },
  { id: 'dmg', label: 'Damage', formula: '1d8+3',  tag: 'damage' },
  { id: 'cast', label: 'Cantrip', formula: '1d10', tag: 'damage' },
];

const BUFF_STATS = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA', 'AC'];
const hbModKey = (ref, id) => `embers:hbmod:${ref}:${id}`;
const fmtSign = (n) => (n >= 0 ? `+${n}` : `${n}`);

function PlayerHotbar({ activeRef, getEnt, addRoll, onThrow, stageView, isDM, myTurn }) {
  const in3D = stageView === 'vr';        // in 3D the iframe HUD owns the visible action bar
  const allRolls = (typeof SAVED_ROLLS !== 'undefined' && SAVED_ROLLS) || [];
  const myRolls = allRolls.filter(r => r.who === activeRef);
  const actions = (myRolls.length ? myRolls : HOTBAR_DEFAULT_ACTIONS).slice(0, 5);
  const slots = actions.map((a, i) => ({ ...a, key: String(i + 1) }));

  const [pending, setPending] = React.useState(null);   // damage action awaiting a roll
  const [manual, setManual] = React.useState('');
  const [sit, setSit] = React.useState(0);              // situational +N the player adds for one roll
  const [mods, setMods] = React.useState({});           // per-action auto-buff { [id]: { stat, bonus } }

  // Load any DM-assigned auto-buffs for the current seat (persisted so they're
  // never forgotten between sessions). Reloads when the seat changes.
  React.useEffect(() => {
    const next = {};
    slots.forEach(a => {
      try { const raw = localStorage.getItem(hbModKey(activeRef, a.id)); if (raw) next[a.id] = JSON.parse(raw); } catch {}
    });
    setMods(next);
  }, [activeRef]);                                       // eslint-disable-line react-hooks/exhaustive-deps

  const setActionMod = (id, patch) => setMods(m => {
    const cur = { ...(m[id] || {}), ...patch };
    try { localStorage.setItem(hbModKey(activeRef, id), JSON.stringify(cur)); } catch {}
    return { ...m, [id]: cur };
  });

  // Resolve an action's standing auto-buff: a DM-chosen stat (pulled as its 5e
  // ability modifier — AC pulled as its own modifier) plus any flat bonus. The
  // stored override wins over a stat baked into the action's data.
  const ent = (getEnt && getEnt(activeRef)) || {};
  const sheet = ent.sheet || {};
  const modOfFn = window.modOf || ((s) => Math.floor((s - 10) / 2));
  const autoBuff = React.useCallback((a) => {
    const m = mods[a.id] || {};
    const stat = m.stat !== undefined ? m.stat : (a.stat || null);
    const flat = m.bonus !== undefined ? (m.bonus || 0) : (a.bonus || 0);
    let s = 0;
    if (stat) s = stat === 'AC' ? modOfFn(ent.ac ?? 10) : modOfFn(sheet[stat] ?? 10);
    return s + flat;
  }, [mods, ent, sheet, modOfFn]);

  const fireRoll = React.useCallback((a, manualTotal, situational = 0) => {
    const extra = autoBuff(a) + (situational || 0);     // standing buff + this-roll +N
    const extraTxt = extra ? fmtSign(extra) : '';
    let res;
    if (manualTotal != null && manualTotal !== '') {
      const n = parseInt(manualTotal, 10);
      res = { total: (isNaN(n) ? 0 : n) + extra, dice: [], formula: `${a.formula}${extraTxt} (physical)` };
    } else {
      res = window.rollFormula(`${a.formula}${extraTxt}`);
    }
    addRoll({ ...res, label: a.label, who: activeRef });
    setPending(null);
    setManual('');
    setSit(0);
  }, [addRoll, activeRef, autoBuff]);

  const triggerAction = React.useCallback((a) => {
    const dealsDamage = a.tag === 'damage' || a.tag === 'attack';
    if (dealsDamage) { setSit(0); setPending(a); }   // gate: must roll before damage is dealt
    else fireRoll(a);                                 // heal/util resolves immediately (auto-buff still applies)
  }, [fireRoll]);

  const doThrow = React.useCallback((t) => { onThrow && onThrow(t); }, [onThrow]);

  // Driving an action slot from the 3D iframe bar. On your turn it runs the real
  // action (damage actions open the roll gate); pressed OUT of turn the first
  // four slots toss their harmless throwable instead — a fidget that can't affect
  // play (rock=1, flame=2, stick=3, water=4).
  const on3DAction = React.useCallback((idx) => {
    const a = slots[idx]; if (!a) return;
    if (!myTurn && idx < HOTBAR_THROWS.length) { doThrow(HOTBAR_THROWS[idx]); return; }
    triggerAction(a);
  }, [slots, myTurn, doThrow, triggerAction]);

  // Keep the latest values for the iframe's one-off "give me the actions" request.
  const slotsRef = React.useRef(slots);  slotsRef.current = slots;
  const turnRef  = React.useRef(myTurn); turnRef.current  = myTurn;
  const dmRef    = React.useRef(isDM);   dmRef.current    = isDM;
  const postActions = React.useCallback(() => {
    if (!window.__embersPostWorld) return;
    window.__embersPostWorld({
      type: 'embers:setActions',
      actions: slotsRef.current.map(s => ({ label: s.label, formula: s.formula, key: s.key, tag: s.tag })),
      myTurn: !!turnRef.current,
      role: dmRef.current ? 'dm' : 'player',
    });
  }, []);

  // Mirror the player's real actions into the 3D world's bottom bar (with retries
  // to cover the iframe's async boot), and re-send whenever they change.
  React.useEffect(() => {
    if (!in3D) return;
    postActions();
    const t1 = setTimeout(postActions, 700);
    const t2 = setTimeout(postActions, 1600);
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [in3D, activeRef, myTurn, isDM, postActions]);

  // The iframe bar forwards slot clicks back here; it can also ask for a refresh.
  React.useEffect(() => {
    const onMsg = (e) => {
      const d = e.data; if (!d) return;
      if (d.type === 'embers:hotbar') on3DAction(d.index);
      else if (d.type === 'embers:reqActions') postActions();
    };
    window.addEventListener('message', onMsg);
    return () => window.removeEventListener('message', onMsg);
  }, [on3DAction, postActions]);

  // Global keybindings — ignored while typing in a field or with a modifier held.
  React.useEffect(() => {
    const onKey = (e) => {
      const el = document.activeElement;
      if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable)) return;
      if (e.metaKey || e.ctrlKey || e.altKey) return;
      const k = (e.key || '').toLowerCase();
      const i = slots.findIndex(x => x.key === k);
      if (i >= 0) { e.preventDefault(); in3D ? on3DAction(i) : triggerAction(slots[i]); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [slots, triggerAction, on3DAction, in3D]);

  return (
    <div className={`hotbar leather stitched${in3D ? ' hb-headless' : ''}`} role="toolbar" aria-label="Your action bar">
      {!in3D && (
        <div className="hb-group hb-actions">
          {slots.map(s => {
            const b = autoBuff(s);
            return (
              <button key={s.id} className="hb-slot" onClick={() => triggerAction(s)}
                aria-label={`${s.label}, ${s.formula}${b ? ` ${fmtSign(b)} buff` : ''} (key ${s.key})`}
                title={`${s.label} — ${s.formula}${b ? ` ${fmtSign(b)}` : ''}`}>
                <span className="hb-key" aria-hidden="true">{s.key}</span>
                {b ? <span className="hb-buff-badge" aria-hidden="true">{fmtSign(b)}</span> : null}
                <span className="hb-name">{s.label}</span>
                <span className="hb-sub">{s.formula}</span>
              </button>
            );
          })}
        </div>
      )}

      {pending && (
        <div className="hb-roll" role="dialog" aria-label={`Roll for ${pending.label}`}>
          <div className="hb-roll-hd">{pending.label} — roll to deal damage</div>
          <div className="hb-roll-buffs">
            <label className="hb-buff">
              <span>{isDM ? 'DM auto-buff' : 'Auto-buff stat'}</span>
              <select value={(mods[pending.id] && mods[pending.id].stat) ?? pending.stat ?? ''}
                onChange={e => setActionMod(pending.id, { stat: e.target.value || null })}
                aria-label="Stat that auto-buffs this ability on every roll">
                <option value="">None</option>
                {BUFF_STATS.map(s => <option key={s} value={s}>{s}</option>)}
              </select>
            </label>
            <div className="hb-buff">
              <span>This roll</span>
              <div className="stepper">
                <button aria-label="Decrease modifier" onClick={() => setSit(s => s - 1)}>－</button>
                <b aria-live="polite">{fmtSign(sit)}</b>
                <button aria-label="Increase modifier" onClick={() => setSit(s => s + 1)}>＋</button>
              </div>
            </div>
            {autoBuff(pending) ? <span className="hb-buff-note">auto {fmtSign(autoBuff(pending))}</span> : null}
          </div>
          <div className="hb-roll-row">
            <button className="btn" onClick={() => fireRoll(pending, null, sit)}>
              Roll {pending.formula}{(autoBuff(pending) + sit) ? fmtSign(autoBuff(pending) + sit) : ''}
            </button>
            <span className="hb-or">or</span>
            <input className="hb-manual" type="number" inputMode="numeric" placeholder="physical total"
              value={manual} onChange={e => setManual(e.target.value)}
              aria-label="Enter the total from your physical dice" />
            <button className="btn ghost" disabled={manual === ''} onClick={() => fireRoll(pending, manual, sit)}>Submit</button>
            <button className="composer-x" aria-label="Cancel roll" onClick={() => { setPending(null); setManual(''); setSit(0); }}>✕</button>
          </div>
        </div>
      )}
    </div>
  );
}
window.PlayerHotbar = PlayerHotbar;
