/** DOM側のUI: パレット、従業員パネル、リスク台帳、日報／改善ショップのモーダル。 */
import {
  BREAK_PLANS,
  BUILDS,
  CATWALK_COST,
  HANDLER_EQUIPMENT,
  HAZARDS,
  HAZARD_KINDS,
  INITIAL_RANK,
  MILESTONES,
  SHIFT_TICKS,
  HARM_RULES,
  ORG_ACTIONS,
  SEVERITY_LABEL,
} from '../core/config';
import { PAPER_BACKLOG_LIMIT } from '../core/config';
import { CatState, Phase, type DailyReport, type TileKind } from '../core/types';
import {
  actionTopics,
  buildAdvice,
  canResearch,
  hireCost,
  isActionUnlocked,
  isBuildUnlocked,
  maxMaterialGrade,
} from '../sim/game';
import { MATERIALS, TECHS, type TechId } from '../core/research';
import {
  activeCats,
  isBreakTime,
  milestoneProgress,
  nextMilestone,
  type GameState,
} from '../sim/state';
import { isMuted, playSfx, setMasterVolume, toggleMute } from '../audio';
import type { ViewState } from './render';

export interface UiCallbacks {
  selectBuild(kind: TileKind | null): void;
  setBreakPlan(id: (typeof BREAK_PLANS)[number]['id']): void;
  setMaterial(grade: number): void;
  research(id: TechId): void;
  showTech(): void;
  showStaff(): void;
  selectCatwalk(): void;
  selectStack(): void;
  selectCat(id: number | null): void;
  start(): void;
  setSpeed(n: number): void;
  buy(id: string): void;
  hireCat(): void;
  fireCat(id: number): void;
  setEquipment(catId: number, equipment: string): void;
  toAction(): void;
  nextDay(): void;
  restart(): void;
  togglePaths(on: boolean): void;
  toggleLineFlow(): void;
}

/** パレットの現在の選択。'catwalk' はタイル種別ではないので別値で持つ。 */
export type BuildSelection = TileKind | 'catwalk' | 'stack' | null;

const el = <T extends HTMLElement>(id: string): T => document.getElementById(id) as T;

const STATE_LABEL: Record<CatState, string> = {
  [CatState.Idle]: '待機',
  [CatState.GoToBench]: '持ち場へ移動',
  [CatState.Working]: '作業中',
  [CatState.GoToRest]: '休憩所へ移動',
  [CatState.Resting]: '休憩中',
  [CatState.GoToLitter]: 'トイレへ移動',
  [CatState.UsingLitter]: 'トイレ',
  [CatState.GoToBreak]: '休憩室へ移動',
  [CatState.OnBreak]: '休憩中',
  [CatState.Clerical]: '事務作業中',
  [CatState.Driving]: 'フォークリフト運転中',
  [CatState.Loading]: '積み込み作業中',
  [CatState.GoToPickup]: '荷引取へ移動',
  [CatState.Picking]: '荷積み中',
  [CatState.GoToDropoff]: '荷運搬中',
  [CatState.Dropping]: '荷降ろし中',
  [CatState.Research]: '技術開発中',
  [CatState.GoToFeeder]: '給餌器へ移動',
  [CatState.Eating]: '食事中',
  [CatState.Sleeping]: '居眠り',
  [CatState.GoToScratch]: '爪とぎへ移動',
  [CatState.Scratching]: '爪とぎ中',
  [CatState.Panic]: 'パニック（作業放棄）',
  [CatState.Rage]: '八つ当たり中',
  [CatState.Fighting]: '喧嘩中',
  [CatState.Boxed]: '箱の中',
  [CatState.OffDuty]: '休職中',
};

/** 持ち場の表示名 */
const STATION_LABEL: Partial<Record<string, string>> = {
  bench: '作業台',
  lathe: '旋盤',
  press: 'プレス機',
  robot: 'アームロボット',
  packer: '梱包機',
  office: '事務机',
  handler_post: '荷役ステーション',
  forkdepot: 'フォークリフト',
  dock: 'トラックバース',
};

let cb: UiCallbacks;
let selection: BuildSelection = 'conveyor';
let toastTimer = 0;

export function getSelection(): BuildSelection {
  return selection;
}

export function initHud(callbacks: UiCallbacks): void {
  cb = callbacks;
  buildPalette();
  buildBreakPlans();
  buildMaterials();

  el('btn-tech').addEventListener('click', () => {
    playSfx('click');
    cb.showTech();
  });

  el('btn-staff')?.addEventListener('click', () => {
    playSfx('click');
    cb.showStaff();
  });

  el('btn-start').addEventListener('click', () => cb.start());
  el('btn-help').addEventListener('click', () => showHelp());

  const mute = el<HTMLButtonElement>('btn-mute');
  mute.addEventListener('click', () => {
    const next = !isMuted();
    toggleMute(next);
    mute.textContent = next ? '🔇' : '🔊';
    mute.classList.toggle('off', next);
  });
  el<HTMLInputElement>('vol').addEventListener('input', (e) => {
    setMasterVolume(Number((e.target as HTMLInputElement).value) / 100);
  });
  el('btn-catwalk').addEventListener('click', () => setSelection('catwalk'));
  el('btn-stack').addEventListener('click', () => setSelection('stack'));
  el('btn-flow')?.addEventListener('click', () => {
    playSfx('click');
    cb.toggleLineFlow();
  });
  el<HTMLInputElement>('chk-paths').addEventListener('change', (e) => {
    cb.togglePaths((e.target as HTMLInputElement).checked);
  });

  for (const b of Array.from(document.querySelectorAll<HTMLButtonElement>('button.speed'))) {
    b.addEventListener('click', () => {
      cb.setSpeed(Number(b.dataset.speed));
      for (const o of Array.from(document.querySelectorAll('button.speed'))) {
        o.classList.toggle('active', o === b);
      }
    });
  }

  window.addEventListener('keydown', (e) => {
    if (el('modal-root').classList.contains('hidden') === false) return;
    if (el('title-screen').classList.contains('hidden') === false) return;
    const def = BUILDS.find((b) => b.hotkey === e.key);
    if (def) setSelection(def.kind);
    if (e.key === 'c') setSelection('catwalk');
    if (e.key === 't') setSelection('stack');
    if (e.key === 'l' || e.key === 'L') {
      playSfx('click');
      cb.toggleLineFlow();
    }
    if (e.key === ' ') {
      e.preventDefault();
      cb.start();
    }
  });
}

function buildPalette(): void {
  const list = el('build-list');
  list.innerHTML = '';
  for (const def of BUILDS) {
    const btn = document.createElement('button');
    btn.className = 'tool';
    btn.dataset.kind = def.kind;
    btn.innerHTML = `
      <span class="tool-name">${def.label} <span class="tool-key">[${def.hotkey}]</span></span>
      <span class="tool-cost">${def.cost === 0 ? '＋払戻' : `¥${def.cost.toLocaleString()}`}</span>
      <span class="tool-desc">${def.desc}</span>`;
    btn.addEventListener('click', () => {
      playSfx('click');
      setSelection(def.kind);
    });
    list.appendChild(btn);
  }
  el('btn-catwalk').dataset.kind = 'catwalk';
  el('btn-stack').dataset.kind = 'stack';
  setSelection('conveyor');
}

function buildBreakPlans(): void {
  const host = el('break-plans');
  host.innerHTML = '';
  for (const plan of BREAK_PLANS) {
    const btn = document.createElement('button');
    btn.className = 'break-plan';
    btn.dataset.plan = plan.id;
    btn.innerHTML = `
      <span>${plan.label}</span>
      <span class="len">${plan.ticks > 0 ? `${(plan.ticks / 60).toFixed(1)}秒` : '—'}</span>
      <span class="desc">${plan.desc}</span>`;
    btn.addEventListener('click', () => {
      playSfx('click');
      cb.setBreakPlan(plan.id);
    });
    host.appendChild(btn);
  }
}

function buildMaterials(): void {
  const host = el('material-list');
  host.innerHTML = '';
  for (const m of MATERIALS) {
    const btn = document.createElement('button');
    btn.className = 'mat';
    btn.dataset.grade = String(m.grade);
    btn.innerHTML = `
      <span>${m.label} → ${m.product}</span>
      <span class="price">¥${m.price.toLocaleString()}</span>
      <span class="detail">${m.stages}工程 / 必要精度 ${m.precision} / 調達費 ¥${m.cost.toLocaleString()}</span>`;
    btn.addEventListener('click', () => {
      playSfx('click');
      cb.setMaterial(m.grade);
    });
    host.appendChild(btn);
  }
}

export function setSelection(next: BuildSelection): void {
  selection = next;
  for (const b of Array.from(document.querySelectorAll<HTMLElement>('.tool'))) {
    b.classList.toggle('active', b.dataset.kind === next);
  }
  if (next === 'catwalk') cb.selectCatwalk();
  else if (next === 'stack') cb.selectStack();
  else cb.selectBuild(next);
}

// ------------------------------------------------------------------ 毎フレーム更新

export function updateHud(s: GameState, view: ViewState): void {
  const money = el('stat-money');
  money.textContent = `¥${s.money.toLocaleString()}`;
  money.classList.toggle('negative', s.money < 0);
  el('stat-day').textContent = `${s.day}日目`;
  el('stat-trust').textContent = String(Math.round(s.trust));
  el('stat-cats').textContent = `${activeCats(s).length} / ${s.cats.length}`;

  const paper = el('stat-paper');
  paper.textContent = `${s.paperwork.length} 件`;
  paper.classList.toggle('warn', s.paperwork.length > 0 && s.paperwork.length <= PAPER_BACKLOG_LIMIT);
  paper.classList.toggle('bad', s.paperwork.length > PAPER_BACKLOG_LIMIT);

  el('stat-rp').textContent = `${Math.floor(s.researchPoints)}`;
  const powerEl = el('stat-power');
  powerEl.textContent = `${s.powerDemand.toFixed(1)} / ${s.powerSupply.toFixed(1)}`;
  powerEl.classList.toggle('short', s.powerDemand > s.powerSupply);

  const maxGrade = maxMaterialGrade(s);
  for (const b of Array.from(document.querySelectorAll<HTMLElement>('.mat'))) {
    const grade = Number(b.dataset.grade);
    b.classList.toggle('active', grade === s.materialGrade);
    b.classList.toggle('locked', grade > maxGrade);
  }

  // 未開発の設備はパレットで灰色にする
  for (const b of Array.from(document.querySelectorAll<HTMLElement>('#build-list .tool'))) {
    const kind = b.dataset.kind as TileKind;
    b.classList.toggle('locked', !isBuildUnlocked(s, kind));
  }
  el('btn-stack').classList.toggle('locked', !isActionUnlocked(s, 'stack'));

  const safe = el('stat-safe');
  safe.textContent = `${s.safeDays} 日`;
  safe.classList.toggle('warn', s.safeDays === 0);
  safe.classList.toggle('good', s.safeDays >= 5);

  for (const b of Array.from(document.querySelectorAll<HTMLElement>('.break-plan'))) {
    b.classList.toggle('active', b.dataset.plan === s.breakPlan);
  }
  renderRank(s);

  for (const b of Array.from(document.querySelectorAll<HTMLElement>('.badge'))) {
    b.classList.toggle('active', b.dataset.phase === s.phase);
  }
  el('btn-flow')?.classList.toggle('active', view.showLineFlow);
  el('shift-fill').style.width = `${Math.min(100, (s.tick / SHIFT_TICKS) * 100)}%`;

  const start = el<HTMLButtonElement>('btn-start');
  start.disabled = s.phase !== Phase.Plan;
  start.textContent =
    s.phase === Phase.Do ? (isBreakTime(s) ? '☕ 休憩時間' : '稼働中…') : '▶ 稼働開始';

  renderCats(s, view);
  renderRisks(s);
  renderLog(s);
}

function rankName(s: GameState): string {
  return s.rank > 0 ? MILESTONES[s.rank - 1].label : INITIAL_RANK;
}

function renderRank(s: GameState): void {
  el('stat-rank').textContent = rankName(s);
  const next = nextMilestone(s);
  const fill = el('rank-fill');
  const goal = el('rank-goal');
  if (!next) {
    fill.style.width = '100%';
    goal.textContent = '最終ランク到達';
    return;
  }
  const p = next.progress(milestoneProgress(s));
  fill.style.width = `${Math.round(p * 100)}%`;
  goal.textContent = `次: ${next.label} — ${next.goal}`;
}

function renderCats(s: GameState, view: ViewState): void {
  const host = el('cat-list');
  host.innerHTML = '';
  for (const cat of s.cats) {
    const card = document.createElement('div');
    card.className = 'cat-card';
    if (view.selectedCat === cat.id) card.classList.add('selected');
    if (cat.offDutyDays > 0) card.classList.add('offduty');
    const station =
      cat.stationIndex === null
        ? '未割当'
        : STATION_LABEL[s.tiles[cat.stationIndex]?.kind ?? 'floor'] ??
          `作業台#${cat.stationIndex}`;
    const eqLabel =
      cat.equipment && cat.equipment !== 'none'
        ? ` / 🧰${HANDLER_EQUIPMENT[cat.equipment]?.label ?? cat.equipment}`
        : '';
    const carryLabel = cat.carrying.length > 0 ? ` (荷${cat.carrying.length})` : '';

    card.innerHTML = `
      <div class="cat-head">
        <span class="cat-swatch" style="background:${cat.color}"></span>
        <span class="cat-name">${cat.name}${carryLabel}</span>
        <span class="cat-lives">🐾${cat.lives}</span>
        <span class="cat-state">${STATE_LABEL[cat.state]}</span>
      </div>
      ${meter('体力', cat.hp, '#7fd7ff')}
      ${meter('機嫌', cat.mood, cat.mood > 55 ? '#5fd39a' : cat.mood > 28 ? '#e8c05a' : '#e05a5a')}
      ${meter('空腹', cat.hunger, '#e0a05a')}
      ${meter('退屈', cat.boredom, '#b98be0')}
      <div class="meter"><span>担当</span><span style="color:var(--muted)">${station}${cat.helmet ? '' : ' / ⚠ノーヘル'}${eqLabel}</span></div>`;
    card.addEventListener('click', () => {
      cb.selectCat(view.selectedCat === cat.id ? null : cat.id);
    });
    host.appendChild(card);
  }
}

function meter(label: string, value: number, color: string): string {
  return `<div class="meter"><span>${label}</span><span class="meter-bar"><span class="meter-fill" style="width:${Math.max(0, Math.min(100, value))}%;background:${color}"></span></span></div>`;
}

function renderRisks(s: GameState): void {
  const host = el('risk-list');
  host.innerHTML = '';
  const rows = HAZARD_KINDS.map((k) => ({ k, v: s.risk[k] * (1 - s.mitigation[k]) })).sort(
    (a, b) => b.v - a.v,
  );
  for (const { k, v } of rows) {
    const row = document.createElement('div');
    row.className = 'risk-row';
    const color = v > 60 ? '#e05a5a' : v > 30 ? '#e8c05a' : '#5fd39a';
    const mit = s.mitigation[k] > 0 ? ` <span class="risk-mitigated">-${Math.round(s.mitigation[k] * 100)}%</span>` : '';
    row.innerHTML = `
      <span>${HAZARDS[k].label}${mit}</span>
      <span class="risk-bar"><span class="risk-fill" style="width:${Math.min(100, v)}%;background:${color}"></span></span>`;
    host.appendChild(row);
  }
}

let lastLogLength = -1;

function renderLog(s: GameState): void {
  if (s.log.length === lastLogLength) return;
  lastLogLength = s.log.length;
  const host = el('log-list');
  host.innerHTML = '';
  for (const entry of s.log.slice(-60).reverse()) {
    const line = document.createElement('div');
    line.className = `log-line ${entry.tone}`;
    const mm = String(Math.floor(entry.tick / 60)).padStart(2, '0');
    const ss = String(entry.tick % 60).padStart(2, '0');
    line.innerHTML = `<span class="log-time">${mm}:${ss}</span>${entry.text}`;
    host.appendChild(line);
  }
}

export function toast(message: string): void {
  const t = el('toast');
  t.textContent = message;
  t.classList.add('show');
  window.clearTimeout(toastTimer);
  toastTimer = window.setTimeout(() => t.classList.remove('show'), 1800);
}

// ------------------------------------------------------------------ ツールチップ

export function showTooltip(text: string, x: number, y: number): void {
  const tip = el('tooltip');
  tip.textContent = text;
  tip.style.display = 'block';
  tip.style.left = `${x + 14}px`;
  tip.style.top = `${y + 14}px`;
}

export function hideTooltip(): void {
  el('tooltip').style.display = 'none';
}

// ------------------------------------------------------------------ モーダル

function openModal(title: string, body: string, footer: HTMLElement[]): void {
  el('modal-title').textContent = title;
  el('modal-body').innerHTML = body;
  const f = el('modal-footer');
  f.innerHTML = '';
  for (const b of footer) f.appendChild(b);
  el('modal-root').classList.remove('hidden');
}

export function closeModal(): void {
  el('modal-root').classList.add('hidden');
}

function button(label: string, cls: string, onClick: () => void): HTMLButtonElement {
  const b = document.createElement('button');
  b.className = cls;
  b.textContent = label;
  b.addEventListener('click', () => {
    playSfx('click');
    onClick();
  });
  return b;
}

const yen = (n: number): string => `${n < 0 ? '-' : ''}¥${Math.abs(n).toLocaleString()}`;

/** 業務日報 (CHECKフェーズ) */
export function showReport(s: GameState, report: DailyReport): void {
  const advice = buildAdvice(report);
  const nearmiss = report.incidents.filter((i) => i.severity === 'nearmiss').length;
  const accidents = report.incidents.filter((i) => i.severity !== 'nearmiss').length;
  const inj = report.injuries;
  const injuryTotal = inj.injury + inj.serious + inj.fatal;

  const cells = `
    <div class="report-grid">
      <div class="report-cell"><label>出荷数</label><strong>${report.shipped}</strong></div>
      <div class="report-cell"><label>不良品</label><strong class="${report.defects > 0 ? 'neg' : ''}">${report.defects}</strong></div>
      <div class="report-cell"><label>売上</label><strong>${yen(report.revenue)}</strong></div>
      <div class="report-cell"><label>人件費(おやつ代)</label><strong>${yen(-report.wages)}</strong></div>
      <div class="report-cell"><label>電気代</label><strong>${yen(-report.utilities)}</strong></div>
      <div class="report-cell"><label>事故損害</label><strong class="${report.damages > 0 ? 'neg' : ''}">${yen(-report.damages)}</strong></div>
      <div class="report-cell"><label>書類の是正費用</label><strong class="${report.fines > 0 ? 'neg' : ''}">${yen(-report.fines)}</strong></div>
      <div class="report-cell"><label>事務処理</label><strong>${report.paperworkDone} 件</strong></div>
      <div class="report-cell"><label>未処理書類</label><strong class="${report.paperworkLeft > 0 ? 'neg' : ''}">${report.paperworkLeft} 件</strong></div>
      <div class="report-cell"><label>定時休憩</label><strong>${report.breakTicks > 0 ? `${(report.breakTicks / 60).toFixed(1)}秒` : 'なし'}</strong></div>
      <div class="report-cell"><label>トラック出発</label><strong>${report.truckDepartures} 台</strong></div>
      <div class="report-cell"><label>パレット運搬</label><strong>${report.palletsHauled} 枚</strong></div>
      <div class="report-cell"><label>カゴ荷役</label><strong>${report.basketsHandled ?? 0} 個</strong></div>
      <div class="report-cell"><label>コンテナ運搬</label><strong>${report.containersHauled ?? 0} 箱</strong></div>
      <div class="report-cell"><label>研究ポイント</label><strong>+${report.research}</strong></div>
      <div class="report-cell"><label>素材調達費</label><strong class="${report.materialCost > 0 ? 'neg' : ''}">${yen(-report.materialCost)}</strong></div>
      <div class="report-cell"><label>燃料費</label><strong class="${report.fuel > 0 ? 'neg' : ''}">${yen(-report.fuel)}</strong></div>
      <div class="report-cell"><label>電力 需要/供給</label><strong class="${report.power.demand > report.power.supply ? 'neg' : ''}">${report.power.demand.toFixed(1)} / ${report.power.supply.toFixed(1)}</strong></div>
      <div class="report-cell"><label>本日収支</label><strong class="${report.net >= 0 ? 'pos' : 'neg'}">${yen(report.net)}</strong></div>
      <div class="report-cell"><label>ヒヤリ / 事故</label><strong>${nearmiss} / ${accidents}</strong></div>
      <div class="report-cell"><label>労災の治療費・補償</label><strong class="${report.compensation > 0 ? 'neg' : ''}">${yen(-report.compensation)}</strong></div>
      <div class="report-cell"><label>無災害記録</label><strong class="${report.safeDays >= 5 ? 'pos' : ''}">${report.safeDays} 日</strong></div>
    </div>

    <h2 style="margin-top:0">労働災害</h2>
    <div class="report-grid">
      <div class="report-cell"><label>軽傷</label><strong class="${inj.injury > 0 ? 'neg' : ''}">${inj.injury} 件</strong></div>
      <div class="report-cell"><label>重傷</label><strong class="${inj.serious > 0 ? 'neg' : ''}">${inj.serious} 件</strong></div>
      <div class="report-cell"><label>死亡事故</label><strong class="${inj.fatal > 0 ? 'neg' : ''}">${inj.fatal} 件</strong></div>
      <div class="report-cell"><label>本日の労災</label><strong class="${injuryTotal > 0 ? 'neg' : 'pos'}">${injuryTotal === 0 ? '無災害' : `${injuryTotal} 件`}</strong></div>
    </div>`;

  const incidentRows = report.incidents.length
    ? report.incidents
        .slice(0, 40)
        .map((i) => {
          const label = SEVERITY_LABEL[i.severity];
          const mm = String(Math.floor(i.tick / 60)).padStart(2, '0');
          const ss = String(i.tick % 60).padStart(2, '0');
          return `<tr>
            <td>${mm}:${ss}</td>
            <td><span class="sev ${i.severity}">${label}</span></td>
            <td>${HAZARDS[i.kind].label}</td>
            <td>${i.text}</td>
            <td class="num">${i.cost ? yen(-i.cost) : '-'}</td>
          </tr>`;
        })
        .join('')
    : '<tr><td colspan="5" style="color:var(--muted)">本日は報告事案なし。良い一日でした。</td></tr>';

  const catRows = report.catSummaries
    .map(
      (c) => `<tr>
        <td>${c.name}${c.clerical ? ' <span style="color:var(--accent)">(事務)</span>' : ''}${c.offDuty ? ` <span style="color:var(--danger)">(${c.injury === 'none' ? '休職' : `${HARM_RULES[c.injury].label}`})</span>` : ''}</td>
        <td class="num">${c.produced}</td>
        <td class="num">${c.mood}</td>
        <td class="num">${c.hp}</td>
        <td class="num">🐾${c.lives}</td>
      </tr>`,
    )
    .join('');

  const next = nextMilestone(s);
  const achieved = report.milestoneAchieved
    ? MILESTONES.find((m) => m.id === report.milestoneAchieved)
    : null;
  const milestoneHtml = achieved
    ? `<div class="milestone">🏭 <b>昇格: ${achieved.label}</b><br>
        ${achieved.unlock}${achieved.reward > 0 ? ` / 報奨金 ${yen(achieved.reward)}` : ''}</div>`
    : next
      ? `<div class="milestone">
          次の目標: <b>${next.label}</b> — ${next.goal}<br>
          <span style="color:var(--muted)">累計出荷 ${s.totalShipped}個 / 無事故 ${s.safeStreak}日連続 / 信頼度 ${Math.round(s.trust)}</span>
          <div class="goal-bar"><div class="goal-fill" style="width:${Math.round(next.progress(milestoneProgress(s)) * 100)}%"></div></div>
        </div>`
      : '';

  const adviceHtml = advice.length
    ? advice
        .slice(0, 4)
        .map(
          (a) =>
            `<div class="advice"><b>${HAZARDS[a.kind].label}（${a.count}件）</b><br>${HAZARDS[a.kind].advice}</div>`,
        )
        .join('')
    : '<div class="advice">目立った改善点はありません。生産能力の拡張を検討してください。</div>';

  openModal(
    `業務日報 — ${report.day}日目`,
    `${milestoneHtml}${cells}
     <h2 style="margin-top:0">ヒヤリハット・事故報告書</h2>
     <table class="report">
       <thead><tr><th>時刻</th><th>区分</th><th>分類</th><th>内容</th><th class="num">損害</th></tr></thead>
       <tbody>${incidentRows}</tbody>
     </table>
     <h2>従業員コンディション</h2>
     <table class="report">
       <thead><tr><th>氏名</th><th class="num">処理数</th><th class="num">機嫌</th><th class="num">体力</th><th class="num">残機</th></tr></thead>
       <tbody>${catRows}</tbody>
     </table>
     <h2>改善提言</h2>
     ${adviceHtml}`,
    [button('改善フェーズへ →', 'primary', () => cb.toAction())],
  );
}

/** 対策カード1枚 */
function counterCard(s: GameState, c: { id: string; label: string; cost: number; desc: string; once: boolean }): string {
  const owned = s.upgrades[c.id] ?? 0;
  const sold = c.once && owned > 0;
  const short = s.money < c.cost;
  return `<div class="shop-card">
    <h4>${c.label}</h4>
    <p>${c.desc}</p>
    <div class="owned">${owned > 0 ? `実施済み ×${owned}` : '&nbsp;'}</div>
    <button data-buy="${c.id}" ${sold || short ? 'disabled' : ''}>
      ${sold ? '導入済み' : `¥${c.cost.toLocaleString()} で実施`}
    </button>
  </div>`;
}

/**
 * 改善フェーズ (ACTION)。
 * Check で挙がった事案ごとに対策を並べる。事案が出ていない項目には手を出せない。
 */
export function showAction(s: GameState, report: DailyReport | null): void {
  const topics = actionTopics(s, report);

  const topicHtml = topics.length
    ? topics
        .map((t) => {
          const def = HAZARDS[t.kind];
          const badge = t.lingering
            ? '<span class="sev nearmiss">継続</span>'
            : `<span class="sev ${t.count > 0 ? 'minor' : 'nearmiss'}">本日 ${t.count}件</span>`;
          const riskColor = t.risk > 60 ? 'var(--danger)' : t.risk > 30 ? 'var(--warn)' : 'var(--good)';
          return `<section class="topic">
            <header>
              ${badge}
              <b>${def.label}</b>
              <span class="topic-risk">リスク <span style="color:${riskColor}">${Math.round(t.risk)}</span></span>
            </header>
            <p class="topic-advice">${def.advice}</p>
            <div class="shop-grid">${t.counters.map((c) => counterCard(s, c)).join('')}</div>
          </section>`;
        })
        .join('')
    : `<div class="advice">本日は対策すべき事案がありませんでした。
        現場は落ち着いています。生産能力の拡張を検討してください。</div>`;

  const orgHtml = ORG_ACTIONS.map((c) => counterCard(s, c)).join('');

  openModal(
    `改善フェーズ — ${report ? `${report.day}日目の事案` : '事案なし'} / 資金 ¥${s.money.toLocaleString()}`,
    `<p class="hint">対策は<b>日報に挙がった事案に対してだけ</b>打てます。
      発生していない事案の対策は買えません（リスクが残っている継続案件は「継続」として並びます）。
      設備の追加設置は次のPLANフェーズで行えます。</p>
     ${topicHtml}
     <h2>体制の見直し</h2>
     <p class="hint">特定の事案に紐付かない施策です。いつでも実施できます。</p>
     <div class="shop-grid">${orgHtml}</div>`,
    [button(`${s.day + 1}日目の計画へ →`, 'primary', () => cb.nextDay())],
  );

  for (const b of Array.from(document.querySelectorAll<HTMLButtonElement>('[data-buy]'))) {
    b.addEventListener('click', () => cb.buy(b.dataset.buy!));
  }
}

/** 開発ツリー */
export function showTechTree(s: GameState): void {
  const columns = Math.max(...TECHS.map((t) => t.column)) + 1;
  const cols: string[] = [];

  for (let c = 0; c < columns; c++) {
    const nodes = TECHS.filter((t) => t.column === c);
    const cards = nodes
      .map((n) => {
        const done = s.techs.includes(n.id);
        const ready = canResearch(s, n.id);
        const affordable = s.researchPoints >= n.cost;
        const missing = n.requires
          .filter((r) => !s.techs.includes(r))
          .map((r) => TECHS.find((t) => t.id === r)?.label ?? r);
        const cls = done ? 'done' : ready ? 'ready' : 'locked';
        return `<div class="tech-node ${cls}">
          <h4>${done ? '✔ ' : ''}${n.label}</h4>
          <p>${n.desc}</p>
          ${missing.length ? `<div class="req">前提: ${missing.join(' / ')}</div>` : ''}
          ${
            done
              ? '<div class="rp">開発済み</div>'
              : `<button data-tech="${n.id}" ${ready && affordable ? '' : 'disabled'}>
                   ${n.cost} RP で開発
                 </button>`
          }
        </div>`;
      })
      .join('');
    cols.push(`<div class="tech-col">${cards}</div>`);
  }

  openModal(
    `技術開発 — 研究ポイント ${Math.floor(s.researchPoints)} RP`,
    `<p class="hint">開発室に猫を割り当てると研究ポイントが貯まります。
      左の列ほど前提が浅く、右へ進むほど強力な設備と高価な素材が解禁されます。</p>
     <div class="tech-tree">${cols.join('')}</div>`,
    [button('閉じる', 'primary', () => closeModal())],
  );

  for (const b of Array.from(document.querySelectorAll<HTMLButtonElement>('[data-tech]'))) {
    b.addEventListener('click', () => cb.research(b.dataset.tech as TechId));
  }
}

// ------------------------------------------------------------------ 従業員管理モーダル

export function showStaffModal(s: GameState): void {
  const eqKeys = Object.keys(HANDLER_EQUIPMENT) as (keyof typeof HANDLER_EQUIPMENT)[];

  let body = `<div style="max-height:70vh;overflow-y:auto;font-size:11px">`;
  body += `<table style="width:100%;border-collapse:collapse;text-align:left">`;
  body += `<thead><tr style="border-bottom:2px solid #475569">
    <th style="padding:4px">名前</th>
    <th>状態</th>
    <th>担当</th>
    <th>残機</th>
    <th title="体力">💪</th>
    <th title="機嫌">😸</th>
    <th title="集中力">🎯</th>
    <th title="記憶力">🧠</th>
    <th title="空腹">🍽️</th>
    <th title="尿意">🚽</th>
    <th>機材</th>
    <th></th>
  </tr></thead><tbody>`;

  for (const cat of s.cats) {
    const station =
      cat.stationIndex === null
        ? '未割当'
        : STATION_LABEL[s.tiles[cat.stationIndex]?.kind ?? 'floor'] ??
          `#${cat.stationIndex}`;
    const offDuty = cat.offDutyDays > 0;
    const rowStyle = offDuty ? 'opacity:0.5' : '';

    body += `<tr style="border-bottom:1px solid #334155;${rowStyle}">`;
    body += `<td style="padding:3px"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${cat.color};vertical-align:middle;margin-right:4px"></span>${cat.name}</td>`;
    body += `<td>${STATE_LABEL[cat.state]}</td>`;
    body += `<td>${station}${cat.helmet ? '' : ' ⚠'}</td>`;
    body += `<td>🐾${cat.lives}</td>`;
    body += `<td>${miniBar(cat.hp, '#7fd7ff')}</td>`;
    body += `<td>${miniBar(cat.mood, cat.mood > 55 ? '#5fd39a' : cat.mood > 28 ? '#e8c05a' : '#e05a5a')}</td>`;
    body += `<td>${miniBar(cat.focus, '#60a5fa')}</td>`;
    body += `<td>${miniBar(cat.memory, '#a78bfa')}</td>`;
    body += `<td>${miniBar(cat.hunger, '#e0a05a')}</td>`;
    body += `<td>${miniBar(cat.bladder, '#fbbf24')}</td>`;

    // 機材セレクタ
    body += `<td><select data-eq-cat="${cat.id}" style="font-size:10px;padding:1px 2px;background:#1e293b;color:#e2e8f0;border:1px solid #475569;border-radius:3px">`;
    for (const k of eqKeys) {
      const def = HANDLER_EQUIPMENT[k];
      const label = def.cost > 0 ? `${def.label} (¥${def.cost.toLocaleString()})` : def.label;
      body += `<option value="${k}" ${cat.equipment === k ? 'selected' : ''}>${label}</option>`;
    }
    body += `</select></td>`;

    // 解雇ボタン
    const canFire = !offDuty && cat.carrying.length === 0 && s.cats.length > 1;
    body += `<td><button data-fire-cat="${cat.id}" style="font-size:10px;padding:2px 6px;background:#991b1b;color:white;border:none;border-radius:3px;cursor:pointer" ${canFire ? '' : 'disabled'}>解雇</button></td>`;
    body += `</tr>`;
  }

  body += `</tbody></table>`;

  // 雇用セクション
  const cost = hireCost(s);
  const canHire = s.money >= cost;
  body += `<div style="margin-top:12px;padding:8px;background:#1e293b;border-radius:6px;display:flex;align-items:center;justify-content:space-between">`;
  body += `<span>現在 ${s.cats.length} 匹 / 次の雇用費: <strong style="color:${canHire ? '#5fd39a' : '#ef4444'}">¥${cost.toLocaleString()}</strong></span>`;
  body += `<button id="btn-hire" style="padding:4px 12px;background:#166534;color:white;border:none;border-radius:4px;cursor:pointer;font-size:12px" ${canHire ? '' : 'disabled'}>🐱 新しい猫を雇う</button>`;
  body += `</div>`;
  body += `</div>`;

  openModal('👤 従業員管理', body, [button('閉じる', 'primary', () => closeModal())]);

  // イベントバインド: 雇用
  document.getElementById('btn-hire')?.addEventListener('click', () => {
    cb.hireCat();
    showStaffModal(s); // 再描画
  });

  // イベントバインド: 解雇
  for (const b of Array.from(document.querySelectorAll<HTMLButtonElement>('[data-fire-cat]'))) {
    b.addEventListener('click', () => {
      const catId = Number(b.dataset.fireCat);
      cb.fireCat(catId);
      showStaffModal(s); // 再描画
    });
  }

  // イベントバインド: 機材変更
  for (const sel of Array.from(document.querySelectorAll<HTMLSelectElement>('[data-eq-cat]'))) {
    sel.addEventListener('change', () => {
      const catId = Number(sel.dataset.eqCat);
      cb.setEquipment(catId, sel.value);
      showStaffModal(s); // 再描画
    });
  }
}

function miniBar(value: number, color: string): string {
  const w = Math.max(0, Math.min(100, value));
  return `<div style="display:inline-block;width:36px;height:8px;background:#334155;border-radius:2px;vertical-align:middle" title="${Math.round(value)}">` +
    `<div style="width:${w}%;height:100%;background:${color};border-radius:2px"></div></div>`;
}

export function showGameOver(s: GameState): void {
  const totalFatal = s.injuriesTotal.fatal;
  const totalInjured = s.injuriesTotal.injury + s.injuriesTotal.serious + s.injuriesTotal.fatal;
  openModal(
    s.won ? '🚀 GAME CLEAR — 宇宙船部品工場' : 'GAME OVER',
    `<p style="font-size:14px">${s.gameOverReason ?? ''}</p>
     <div class="milestone"><b>最終ランク: ${rankName(s)}</b></div>
     <div class="report-grid">
       <div class="report-cell"><label>操業日数</label><strong>${s.day}日</strong></div>
       <div class="report-cell"><label>累計出荷</label><strong>${s.totalShipped}</strong></div>
       <div class="report-cell"><label>労災件数</label><strong class="${totalInjured > 0 ? 'neg' : 'pos'}">${totalInjured}</strong></div>
       <div class="report-cell"><label>うち死亡事故</label><strong class="${totalFatal > 0 ? 'neg' : ''}">${totalFatal}</strong></div>
       <div class="report-cell"><label>最終資金</label><strong class="${s.money < 0 ? 'neg' : ''}">${yen(s.money)}</strong></div>
     </div>`,
    [button(s.won ? 'もう一度はじめから' : 'もう一度操業する', 'primary', () => cb.restart())],
  );
}

export function showHelp(): void {
  openModal(
    '遊び方',
    `<div class="help-body">
      <h4>PDCAサイクルで回す</h4>
      <p><b>Plan</b> でラインを敷き、<b>Do</b> で猫たちの働きぶりを監視カメラ視点で眺め、<b>Check</b> の日報で原因を分析し、<b>Action</b> で資金を使って対策を打ちます。稼働中はプレイヤーは介入できません。</p>
      <p><b>対策は日報に挙がった事案に対してだけ打てます。</b>
      起きていない事案の対策は買えません。何が起きたかを見てから手を打つ、というのがこのゲームの回し方です。
      前日までのリスクが残っている事案は「継続」として引き続き対策できます。</p>

      <h4>ラインの組み方</h4>
      <ul>
        <li>左の<b>搬入口</b>から右の<b>出荷口</b>まで、コンベアを繋ぎます。</li>
        <li><b>分岐機</b>は背面から受けて正面・左・右へ順番に振り分け、<b>合流機</b>は3方向から受けて正面へ流します。並列ラインはこの2つで組みます。</li>
        <li>途中に<b>作業台を2台</b>通すと、原材料 → 一次加工品 → 完成品になります。完成品でないまま出荷口に入ると不良品扱いです。</li>
        <li>作業台は「自分に向かってくるコンベア」から受け取り、「自分から離れていくコンベア」へ流します。</li>
        <li>コンベアは同じマスを再クリックで向きが回ります。</li>
      </ul>

      <h4>猫は気まぐれです</h4>
      <ul>
        <li><b>動線の分断</b>: 休憩所やトイレへ行けなくなると機嫌が急落し、資材を破壊し始めます。</li>
        <li><b>ラインへの無断侵入</b>: 正規ルートが無いと稼働中のコンベアをショートカットし、抜け毛混入や巻き込みを起こします。<b>キャットウォーク</b>を重ねると安全に横断できます。</li>
        <li><b>オーバーフロー</b>: 猫がトイレや休憩で席を外している間に部品が積み上がると、
        戻ってきた担当猫が殺到した部品に威嚇して作業を放棄します。
        <b>一時保管ストッカー</b>（遊び）で吸収させるか、休憩所を持ち場の近くに置いてください。
        猫が付いていて捌けている状態の満杯は問題ありません。</li>
        <li><b>担当者不在の作業台</b>は赤い「!」で表示されます。そこで荷が止まり、ライン全体が詰まります。</li>
        <li><b>ボトルネック</b>: 部品が届かないと退屈してケーブルを噛みちぎります。<b>爪とぎタワー</b>がヘイトを引き受けます。</li>
        <li><b>段ボール箱</b>は暴走した猫を吸い込んで鎮静化させる最強のトラップです。</li>
        <li>右下の<b>ボイラー室</b>は立入禁止ですが、暖かいので猫が忍び込んで寝ます。重大事故の温床です。</li>
      </ul>

      <h4>技術開発と開発ツリー</h4>
      <p>最初に置けるのは基本的なライン設備だけです。
      <b>開発室</b>に猫を割り当てると<b>研究ポイント(RP)</b>が貯まり、
      画面右上の<b>🔬開発</b>ボタンから開発ツリーを進められます。</p>
      <ul>
        <li>開発すると<b>扱える機械</b>（分岐機・旋盤・プレス機・ロボット・梱包機・パレット・フォークリフト・バース）が増えます</li>
        <li><b>扱える素材と製品</b>も増えます。合金材・電子基板材・特殊合金材と進むほど高く売れます</li>
        <li>電力系統の増強や発電設備も開発の先にあります</li>
      </ul>
      <p>開発室に猫を1匹回すぶん生産は落ちます。<b>いつ研究に人を割くか</b>が中盤の判断になります。</p>

      <h4>素材と製品のグレード</h4>
      <p>左のパネルで<b>調達する素材</b>を選びます。グレードが上がるほど、
      <b>必要な工程数</b>と<b>必要な加工精度</b>が増えますが、単価は跳ね上がります。</p>
      <ul>
        <li>鉄材 → 単純部品: 2工程 / 精度0 / ¥1,400</li>
        <li>合金材 → 精密部品: 3工程 / 精度4 / ¥3,400</li>
        <li>電子基板材 → 電子部品: 4工程 / 精度8 / ¥7,200</li>
        <li>特殊合金材 → 宇宙船部品: 5工程 / 精度13 / ¥15,500</li>
      </ul>
      <p>精度は機械ごとに決まっています（作業台と旋盤 +1、プレス機とアームロボット +3、梱包機 +0）。
      工程数か精度が足りないまま出荷口に入ると<b>不良品</b>になります。
      素材には調達費がかかるので、不良を出すとそのぶん丸損です。</p>

      <h4>電力（発電と送電）</h4>
      <p>機械を動かすには2つ揃っている必要があります。</p>
      <ul>
        <li><b>送電</b>: <b>配電盤</b>の届く範囲（半径5マス）にあること。範囲外の設備は⚡マークが出て動きません。
        ラインを伸ばすときは配電盤も足してください。</li>
        <li><b>発電</b>: 系統契約と自家発電の合計が需要を上回っていること。
        足りないと<b>全設備が一律に減速</b>し、やがて<b>停電事故</b>になります。</li>
      </ul>
      <p>系統電力は使った分だけ課金され、<b>ディーゼル発電機</b>は燃料費が定額なので、
      需要が大きいほど自家発電が得になります。<b>太陽光パネル</b>は燃料費ゼロですが、
      その日の天候で出力が55〜100%に振れます。</p>

      <h4>加工機械の使い分け</h4>
      <p>加工機械はどれも「入力バッファから1個取り、加工段階を1つ上げる」点は同じですが、
      速度・電気代・仕上がり・危険性が違います。</p>
      <ul>
        <li><b>作業台</b>: 遅いが安全で電気も食わない。迷ったらこれ。</li>
        <li><b>旋盤</b>: 作業台の1.5倍速。回転部に<b>尻尾や毛を巻き込まれる労災</b>が起きます。</li>
        <li><b>プレス機</b>: 精度が高く不良品がほとんど出ません。ただし<b>挟まれ</b>は重篤化しやすい。</li>
        <li><b>アームロボット</b>: <b>猫の割り当てが不要</b>で最速。電気代は作業台の4.5倍。
        可動範囲（隣接マス）に猫が入ると激突します。壁で囲うかキャットウォークで動線を上へ逃がしてください。</li>
        <li><b>梱包機</b>: 遅いですが、通した完成品は<b>梱包済みとなり単価が2割上がります</b>。
        猫は箱に入りたがるので、投入口への閉じ込めに注意。</li>
      </ul>

      <h4>パレット輸送とトラック出荷</h4>
      <p>コンベアで出荷口へ直接流すほかに、<b>パレット輸送</b>という高付加価値のルートがあります。</p>
      <ul>
        <li><b>パレット置き場</b>に完成品を溜めます。1段4個で1パレットです。</li>
        <li>満載になると<b>フォークリフト詰所</b>のフォークリフトが取りに来て、<b>トラックバース</b>まで運びます。
        運転手として猫を1匹割り当ててください。</li>
        <li>フォークリフトは<b>コンベアや機械の上を走れません</b>。詰所・パレット・バースを結ぶ
        <b>床の通路</b>を必ず空けておいてください。</li>
        <li>バースでは担当猫が1個ずつ<b>積み込み作業</b>をします。12個でトラックが出発し、
        <b>単価+25%</b>でまとめて出荷されます。終業時は積み残しも出荷されます。</li>
      </ul>
      <p>フォークリフトの進路上で猫が寝ていると轢いてしまいます。
      走行帯と猫の動線が交差する場所は<b>キャットウォークで立体交差</b>させるのが確実です。</p>

      <h4>段積み（二段・三段）</h4>
      <p>パレット置き場と一時保管ストッカーは<b>段積み</b>で容量を増やせます（1段¥900、三段まで）。
      ただし<b>二段目以降は崩落します</b>。満載に近いほど、段が高いほど崩れやすく、
      近くにいた猫が下敷きになると死亡事故にもなります。
      容量が欲しいなら段を積み、安全が欲しいなら平置きを広げる、という選択です。</p>

      <h4>労働災害（労災）</h4>
      <p>事案は物損で済むとは限りません。危険な機械ほど<b>人身災害</b>に発展します。</p>
      <ul>
        <li><b>軽傷</b>: 1〜2日の休業、治療費 ¥3,000</li>
        <li><b>重傷</b>: 3〜5日の休業、補償 ¥15,000、信頼度が下がります</li>
        <li><b>死亡事故</b>: <b>9つの命を1つ消費</b>して5〜7日後に復帰、補償 ¥40,000、信頼度が大きく下がります。
        命を使い切った猫は二度と戻りません。</li>
      </ul>
      <p>重篤化しやすさは猫の状態で決まります。<b>ヘルメット未着用</b>・<b>体力低下</b>・<b>機嫌の悪化</b>は
      そのまま被害の大きさに跳ね返ります。トップバーの<b>無災害記録</b>が途切れないよう回してください。</p>

      <h4>事務室と事務作業</h4>
      <p>ヒヤリハットも事故も、起きるたびに<b>報告書</b>が1件積まれます。
      <b>事務机</b>に猫を割り当てると事務作業が始まり、書類を1件処理するたびに
      その事案の<b>リスクスコアが下がり、信頼度が少し戻ります</b>。
      処理しきれなかった書類は翌日に持ち越され、<b>1件あたり¥450の是正費用</b>が毎日かかります。
      溜めすぎると「書類の滞留」として是正勧告を受けます。</p>
      <p>事務机に付けた猫はラインに立てません。<b>生産に何匹回して、事務に何匹残すか</b>が悩みどころです。</p>

      <h4>休憩室と休憩時間</h4>
      <p>シフトの中ほどに<b>定時休憩</b>が入り、全員が<b>休憩室</b>へ向かいます。
      休憩中はラインが止まりますが、体力・機嫌・空腹がまとめて回復します。</p>
      <ul>
        <li>休憩室は<b>1マスに1匹</b>。従業員の数だけマスが必要です。入れなかった猫は不満を溜めます。</li>
        <li>マスを<b>隣り合わせて敷くほど1つの部屋として広くなり</b>、回復量が上がります。バラバラに置いても効果は薄いです。</li>
        <li>休憩の長さは左パネルで選べます。長いほど回復しますが、その間コンベアは動き続けるので
        作業台の前に部品が溜まります。<b>一時保管ストッカー</b>で吸収させてください。</li>
      </ul>

      <h4>ヒヤリハットを放置しない</h4>
      <p>同じ種類のヒヤリハットが積み重なると<b>リスク台帳</b>のスコアが上がり、やがて事故に発展します。重大事故（Dead Cat 事案）では従業員が9つの命を1つ失って休職します。Actionフェーズでその事案に対策を打つとスコアが下がります。</p>

      <h4>工場を大きくする</h4>
      <p>累計出荷数・資金・信頼度・無事故日数の条件を満たすと<b>工場ランク</b>が上がります。
      ランクが上がるたびに出荷単価と資材の搬入速度が上がるので、それに合わせてラインを増設していきます。
      <b>おやつ代稼ぎ → 町工場 → 中堅工場 → 自動化工場 → 精密機器工場 → 宇宙船部品工場</b> に到達すればクリアです。</p>

      <h4>操作</h4>
      <ul>
        <li>左クリック: 設置 / 撤去。ドラッグでコンベアを連続設置。</li>
        <li>右クリック: そのマスを撤去。</li>
        <li>数字キー <b>1〜0</b>: パレット選択 / <b>W</b>: 壁 / <b>X</b>: 撤去 / <b>C</b>: キャットウォーク / <b>Space</b>: 稼働開始</li>
        <li>作業台を左クリック（設置済みのマス）で猫の割り当てを切り替えられます（先に従業員カードを選択）。</li>
      </ul>
    </div>`,
    [button('閉じる', 'primary', () => closeModal())],
  );
}

export const CATWALK_PRICE = CATWALK_COST;
