/**
 * LANE-UI — build bar. Kind tabs, `[`/`]` to page, 1-9 within the active page.
 * Click or hotkey selects for placement; main.ts does the ghost and the actual place.
 */
import type { GameData, MachineDef, SimSnapshot } from '../contracts';
import { attrs, cls, el, panel, text } from './dom';
import { paginate, pageOf, type BuildPage } from './pages';
import { createAccentLookup, machineChassis } from './palette';
import { gateFor, indexTech, type TechIndex } from './research';
import type { BuildSelection } from './selection';
import { COPY, DIR_NAME } from './voice';

export interface BuildBar {
  el: HTMLElement;
  /** Select slot `n` (1-based) on the active page. */
  selectSlot(n: number): void;
  pageBy(delta: number): void;
  sync(): void;
  /** Re-read the lock state; cheap enough to call per frame. */
  update(snap: SimSnapshot): void;
}

export function createBuildBar(data: GameData, sel: BuildSelection): BuildBar {
  const pages: BuildPage[] = paginate(data.machines);
  const accentOf = createAccentLookup(data);
  const techIndex: TechIndex = indexTech(data);
  /** Machine ids currently gated by unfinished research. */
  let locked = new Set<string>();

  const root = panel();
  root.id = 'fk-build';

  const tabRow = el('div', 'fk-tabs');
  const row = el('div', 'fk-build-row');
  const hint = el('div');
  hint.id = 'fk-build-hint';
  root.append(tabRow, row, hint);

  let active = 0;

  const tabs = pages.map((p, i) => {
    const tab = el('button', 'fk-tab', [p.label]);
    attrs(tab, { type: 'button' });
    tab.addEventListener('click', () => {
      active = i;
      render();
    });
    tabRow.append(tab);
    return tab;
  });

  // REMOVE lives beside the tabs, not on a page: it is a mode, not a machine, and it
  // must be reachable whatever page you are on.
  const removeBtn = el('button', 'fk-tab fk-tab-remove', [
    COPY.removeLabel,
    el('span', 'fk-tab-key', [COPY.removeKey]),
  ]);
  attrs(removeBtn, { type: 'button', title: 'REMOVE MODE (X)' });
  removeBtn.addEventListener('click', () => sel.toggleRemove());
  tabRow.append(removeBtn);

  /** Buttons for the active page only; rebuilt on page change (nine at most). */
  let buttons: Array<{ def: string; btn: HTMLButtonElement; lock: HTMLElement }> = [];

  function buildButtons(page: BuildPage) {
    row.replaceChildren();
    buttons = page.machines.map((m: MachineDef, i: number) => {
      const btn = el('button', 'fk-build-btn');

      // Two-material rule (codex §8): grimy body, one impossible element.
      const ico = el('div', 'fk-build-ico');
      ico.style.background = machineChassis(m);
      const accent = el('div', 'fk-build-accent');
      accent.style.background = accentOf(m);
      accent.style.boxShadow = `0 0 5px ${accentOf(m)}`;
      ico.append(accent);
      const lock = el('span', 'fk-build-lock', ['\u{1F512}']);
      ico.append(lock);
      btn.append(ico);

      const key = el('span', 'fk-build-key');
      key.textContent = String(i + 1); // every slot on every page has a hotkey now
      btn.append(key);

      btn.append(el('span', 'fk-build-name', [m.name]));
      btn.addEventListener('click', () => select(m.id));
      row.append(btn);
      return { def: m.id, btn, lock };
    });
    applyLocks();
  }

  /** A locked machine can't be picked — by click or by hotkey. */
  function select(defId: string) {
    if (locked.has(defId)) return;
    sel.select(defId);
  }

  function applyLocks() {
    for (const b of buttons) {
      const isLocked = locked.has(b.def);
      cls(b.btn, 'is-locked', isLocked);
      cls(b.lock, 'is-on', isLocked);

      const m = data.machines.find((x) => x.id === b.def);
      const gate = isLocked ? techIndex.gatedBy.get(b.def) : null;
      attrs(b.btn, {
        title: gate
          ? `${m?.name ?? b.def} — ${COPY.requires} ${gate.id.replace(/-/g, ' ').toUpperCase()}`
          : `${m?.name ?? b.def} — ${m?.kind ?? ''}`,
      });
    }
  }

  function render() {
    buildButtons(pages[active]);
    for (let i = 0; i < tabs.length; i++) cls(tabs[i], 'is-active', i === active);
    // The strip scrolls, so paging with [ ] must bring its tab into view or the player
    // pages blind. `nearest` avoids yanking the strip when the tab is already visible.
    tabs[active]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
    sync();
  }

  function sync() {
    const cur = sel.get();
    const removing = sel.isRemoving();

    for (const b of buttons) cls(b.btn, 'is-sel', cur?.def === b.def);
    cls(removeBtn, 'is-armed', removing);

    if (removing) {
      text(hint, COPY.removeHint);
    } else if (cur) {
      const def = data.machines.find((m) => m.id === cur.def);
      text(hint, `${def?.name ?? cur.def} · FACING ${DIR_NAME[cur.dir]} · ${COPY.placingHint}`);
    } else {
      text(hint, COPY.idleHint);
    }
    cls(hint, 'is-placing', !!cur);
    cls(hint, 'is-removing', removing);
  }

  // A selection made from anywhere (including a future hotkey we don't own) pulls the
  // bar to the page that contains it, so the hot button is always the visible one.
  sel.onChange(() => {
    const cur = sel.get();
    if (cur) {
      const p = pageOf(pages, cur.def);
      if (p >= 0 && p !== active) {
        active = p;
        render();
        return;
      }
    }
    sync();
  });

  render();

  return {
    el: root,
    update(snap) {
      // Recompute only when the unlocked set actually changes — the padlocks then fall
      // off in the same frame the `researched` event lands.
      const next = new Set<string>();
      for (const m of data.machines) {
        if (gateFor(techIndex, snap.research, m.id)) next.add(m.id);
      }
      if (next.size === locked.size && [...next].every((d) => locked.has(d))) return;
      locked = next;
      applyLocks();

      // Never leave the player holding something they can no longer build.
      const cur = sel.get();
      if (cur && locked.has(cur.def)) sel.clear();
    },
    selectSlot(n) {
      const b = buttons[n - 1];
      if (b) select(b.def);
    },
    pageBy(delta) {
      if (pages.length === 0) return;
      active = (active + delta + pages.length) % pages.length;
      render();
    },
    sync,
  };
}
