import { BUILDINGS, STRUCT, SIM, ZONE, GRID, SEEN_HELP_KEY } from '../config.js';
import { ICONS } from './icons.js';
import { fmtMoney, fmtNum } from '../utils.js';

const $ = (id) => document.getElementById(id);
const el = (html) => {
  const t = document.createElement('template');
  t.innerHTML = html.trim();
  return t.content.firstElementChild;
};

const CITY_NAMES = ['Springvale', 'Port Aurora', 'New Meridian', 'Lakeharbor', 'Ironridge',
  'Sunfield', 'Baycrest', 'Fernwood', 'Stonebrook', 'Havenport'];

const TOOLBAR = [
  { t: 'tool', id: 'query', icon: 'query', label: 'Inspect', key: 'Q', tip: 'Click tiles for details', group: 'Tools' },
  { t: 'tool', id: 'bulldoze', icon: 'bulldoze', label: 'Bulldoze', key: 'B', tip: '$3 per tile', group: 'Tools', costLabel: '$3' },
  { t: 'tool', id: 'road', icon: 'road', label: 'Road', key: 'R', tip: 'Drag an L-shape · bridges $120', group: 'Tools', costLabel: '$10' },
  { t: 'zone', id: 'zone_res', zname: 'res', icon: 'res', label: 'Homes', key: 'Z', tip: 'Free · drag a rectangle', group: 'Zones' },
  { t: 'zone', id: 'zone_com', zname: 'com', icon: 'com', label: 'Shops', key: 'X', tip: 'Free · jobs & taxes', group: 'Zones' },
  { t: 'zone', id: 'zone_ind', zname: 'ind', icon: 'ind', label: 'Industry', key: 'C', tip: 'Free · jobs, pollution', group: 'Zones' },
  { t: 'build', sid: STRUCT.COAL, icon: 'coal', label: 'Coal Plant', group: 'Power' },
  { t: 'build', sid: STRUCT.SOLAR, icon: 'solar', label: 'Solar Farm', group: 'Power' },
  { t: 'build', sid: STRUCT.WIND, icon: 'wind', label: 'Wind Turbine', group: 'Power' },
  { t: 'build', sid: STRUCT.POLICE, icon: 'police', label: 'Police', group: 'Safety' },
  { t: 'build', sid: STRUCT.FIRE, icon: 'fire', label: 'Fire Stn', group: 'Safety' },
  { t: 'build', sid: STRUCT.HOSPITAL, icon: 'hospital', label: 'Hospital', group: 'Health' },
  { t: 'build', sid: STRUCT.SCHOOL, icon: 'school', label: 'School', group: 'Health' },
  { t: 'build', sid: STRUCT.PARK, icon: 'park', label: 'Park', group: 'Leisure' },
  { t: 'build', sid: STRUCT.PLAZA, icon: 'plaza', label: 'Plaza', group: 'Leisure' },
  { t: 'build', sid: STRUCT.STADIUM, icon: 'stadium', label: 'Stadium', group: 'Leisure' }
];

export class UI {
  constructor(game) {
    this.game = game;
    this.city = game.city;
    this.settings = game.settings;
    this.activeBtn = null;
    this._mmTimer = null;

    this.buildToolbar();
    this.bindTopbar();
    this.bindKeys();

    // quests
    this.questDone = new Set();
    this.questDefs = [
      { id: 'zone', label: 'Zone some residential land', test: () => this.anyTile(i => this.city.grid.zone[i] === ZONE.RES) },
      { id: 'road', label: 'Lay your first road', test: () => this.anyTile(i => this.city.grid.struct[i] === STRUCT.ROAD) },
      { id: 'power', label: 'Build any power plant', test: () => this.anyTile(i => [STRUCT.COAL, STRUCT.SOLAR, STRUCT.WIND].includes(this.city.grid.struct[i])) },
      { id: 'grow', label: 'Welcome your first residents (pop ≥ 50)', test: () => this.city.stats.pop >= 50 },
      { id: 'fire', label: 'Protect citizens — build a Fire Station', test: () => this.hasStruct(STRUCT.FIRE) },
      { id: 'happy', label: 'Keep happiness above 70%', test: () => this.city.stats.happiness >= 70 && this.city.stats.pop >= 100 },
      { id: 'pop500', label: 'Grow to 500 citizens', test: () => this.city.stats.pop >= 500 },
      { id: 'stadium', label: 'Celebrate — build the Stadium', test: () => this.hasStruct(STRUCT.STADIUM) }
    ];
    this.renderQuests(true);

    this.city.on('notify', ({ msg, kind }) => this.toast(msg, kind));
    this.city.on('money', () => this.updateHUD());
    this.city.on('stats', () => { this.updateHUD(); this.refreshSidePanel(); });
    this.city.on('date', () => this.updateHUD());
    this.city.on('tilesChanged', () => this.scheduleMinimap());
    this.city.on('milestone', () => this.game.audio.milestone());

    if (!localStorage.getItem(SEEN_HELP_KEY)) {
      setTimeout(() => { this.helpModal(); localStorage.setItem(SEEN_HELP_KEY, '1'); }, 400);
    }
    $('minimapWrap').classList.toggle('hidden', !this.settings.minimap);
    this.updateHUD();
  }

  anyTile(fn) {
    const g = this.city.grid;
    for (let i = 0; i < g.n; i++) if (fn(i)) return true;
    return false;
  }
  hasStruct(sid) { return this.anyTile(i => this.city.grid.struct[i] === sid); }

  // =================== toolbar ===================

  buildToolbar() {
    const bar = $('toolbar');
    bar.innerHTML = '';

    // floating readout: shows the active tool like SimCity's status chip
    const chip = el('<div id="toolReadout"></div>');
    bar.insertAdjacentElement('beforebegin', chip);

    // group items under labeled sections (Tools · Zones · Power · …)
    const groups = [];
    for (const item of TOOLBAR) {
      let g = groups[groups.length - 1];
      if (!g || g.name !== item.group) { g = { name: item.group, items: [] }; groups.push(g); }
      g.items.push(item);
    }
    for (const g of groups) {
      const box = el(`<div class="tb-group"><div class="tb-cap">${g.name}</div><div class="tb-row"></div></div>`);
      const row = box.querySelector('.tb-row');
      for (const item of g.items) {
        const cost = item.t === 'build' ? BUILDINGS[item.sid].cost
          : (item.t === 'zone' ? 0 : null);
        const costText = item.costLabel ?? (cost === null ? '' : cost === 0 ? 'FREE' : '$' + fmtNum(cost));
        const btn = el(`<button class="tl-btn" data-id="${item.id}">
          ${item.key ? `<span class="tl-key">${item.key}</span>` : ''}
          <span class="tl-ico">${ICONS[item.icon]}</span>
          <span class="tl-label">${item.label}</span>
          <span class="tl-cost">${costText}</span>
        </button>`);
        btn.title = `${item.label}${item.tip ? ' — ' + item.tip : ''}`;
        btn.dataset.kind = item.t === 'build' ? `build_${item.sid}` : item.id;
        if (item.t === 'build') btn.dataset.cost = BUILDINGS[item.sid].cost;
        btn.addEventListener('click', () => {
          this.game.audio.click();
          if (item.t === 'build') this.game.input.setTool('build', item.sid);
          else this.game.input.setTool(item.id);
        });
        row.appendChild(btn);
      }
      bar.appendChild(box);
    }

    // dim unaffordable buildings as funds change
    const affordability = (money) => {
      bar.querySelectorAll('.tl-btn[data-cost]').forEach(b => {
        b.classList.toggle('poor', money < +b.dataset.cost);
      });
    };
    this.city.on('stats', s => affordability(s.money));
    affordability(this.city.money);
  }

  onToolChanged(tool, sid) {
    document.querySelectorAll('#toolbar .tl-btn').forEach(b => b.classList.remove('active'));
    let sel = null;
    if (tool === 'build') sel = document.querySelector(`#toolbar [data-kind="build_${sid}"]`);
    else sel = document.querySelector(`#toolbar [data-kind="${tool}"]`);
    if (sel) sel.classList.add('active');

    // status chip: what am I holding, and how do I use it?
    const kind = tool === 'build' ? `build_${sid}` : tool;
    const item = TOOLBAR.find(i => (i.t === 'build' ? `build_${i.sid}` : i.id) === kind);
    const chip = document.getElementById('toolReadout');
    if (chip) {
      if (!item || tool === 'none') chip.classList.remove('show');
      else {
        let tip = item.tip;
        if (!tip && item.t === 'build') {
          const bmeta = BUILDINGS[item.sid];
          tip = `$${fmtNum(bmeta.cost)} · $${bmeta.upkeep || 0}/mo` +
            (bmeta.radius >= 10 ? ` · covers ${bmeta.radius} tiles around it` : '');
        }
        chip.innerHTML = `<span class="tr-ico">${ICONS[item.icon]}</span>
          <b>${item.label}</b>${tip ? `<span class="tr-tip">${tip}</span>` : ''}`;
        chip.classList.add('show');
      }
    }

    const cursors = { bulldoze: 'not-allowed', query: 'help' };
    this.game.renderer.domElement.style.cursor =
      tool === 'none' ? 'grab' : (cursors[tool] || 'crosshair');
    this.hideInfoPopup();
  }

  bindTopbar() {
    document.querySelectorAll('#speedControls .spd').forEach(b => {
      b.addEventListener('click', () => this.game.setSpeed(+b.dataset.speed));
    });
    $('btnBudget').addEventListener('click', () => { this.game.audio.click(); this.toggleSidePanel('budget'); });
    $('btnStats').addEventListener('click', () => { this.game.audio.click(); this.toggleSidePanel('stats'); });
    $('btnHelp').addEventListener('click', () => { this.game.audio.click(); this.helpModal(); });
    $('btnMenu').addEventListener('click', () => { this.game.audio.click(); this.menuModal(); });
  }

  setSpeedActive(idx) {
    document.querySelectorAll('#speedControls .spd').forEach(b => {
      b.classList.toggle('active', +b.dataset.speed === idx);
    });
  }

  bindKeys() {
    this._onKey = (e) => {
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
      const k = e.key.toLowerCase();
      switch (k) {
        case ' ': e.preventDefault(); this.game.togglePause(); break;
        case '1': this.game.setSpeed(1); break;
        case '2': this.game.setSpeed(2); break;
        case '3': this.game.setSpeed(3); break;
        case 'q': this.game.input.setTool('query'); break;
        case 'b': this.game.input.setTool('bulldoze'); break;
        case 'r': this.game.input.setTool('road'); break;
        case 'z': this.game.input.setTool('zone_res'); break;
        case 'x': this.game.input.setTool('zone_com'); break;
        case 'c': this.game.input.setTool('zone_ind'); break;
        case 'escape':
          if (this.modalOpen) this.closeModal();
          else this.game.input.setTool('none');
          this.closeInfoPopup();
          break;
        case 'h': this.helpModal(); break;
      }
    };
    window.addEventListener('keydown', this._onKey);
  }

  destroy() {
    window.removeEventListener('keydown', this._onKey);
  }

  // =================== HUD ===================

  updateHUD() {
    const c = this.city, s = c.stats;
    $('statFunds').textContent = fmtMoney(c.money);
    $('statFunds').style.color = c.money < 0 ? '#ff7b72' : '';
    $('statPop').textContent = fmtNum(s.pop);
    $('statDate').textContent = c.dateLabel();
    const h = s.happiness;
    $('statHappy').textContent = h + '%';
    $('happyFace').textContent = h >= 80 ? '😄' : h >= 60 ? '🙂' : h >= 40 ? '😐' : h >= 20 ? '🙁' : '😡';
    $('milestoneTitle').textContent = SIM.milestonePops[c.milestoneIdx] ? SIM.milestonePops[c.milestoneIdx][1] : '';
    this.drawRCI();
  }

  drawRCI() {
    const cv = $('rciBars');
    const ctx = cv.getContext('2d');
    ctx.clearRect(0, 0, cv.width, cv.height);
    const s = this.city.stats;
    const bars = [
      ['resDemand', '#35e08a'],
      ['comDemand', '#4aa8ff'],
      ['indDemand', '#ffb020']
    ];
    const midY = 19, maxH = 11;
    bars.forEach(([key, color], i) => {
      const v = Math.max(-1, Math.min(1, s[key]));
      const x = 10 + i * 24;
      const hgt = Math.abs(v) * maxH;
      ctx.fillStyle = 'rgba(255,255,255,.14)';
      ctx.fillRect(x - 6, midY - maxH, 12, maxH * 2);
      ctx.fillStyle = v >= 0 ? color : '#ff5f56';
      if (v >= 0) ctx.fillRect(x - 6, midY - hgt, 12, hgt);
      else ctx.fillRect(x - 6, midY, 12, hgt);
    });
    ctx.strokeStyle = 'rgba(255,255,255,.35)';
    ctx.beginPath(); ctx.moveTo(2, midY); ctx.lineTo(cv.width - 2, midY); ctx.stroke();
  }

  // =================== toasts / tooltip / popup ===================

  toast(msg, kind = 'info') {
    const icons = { info: 'ℹ️', warn: '⚠️', danger: '🚨', success: '🎉' };
    const t = el(`<div class="toast ${kind}"><span>${icons[kind] || ''}</span><span>${msg}</span></div>`);
    $('toasts').appendChild(t);
    if (kind === 'danger') this.game.audio.disaster();
    setTimeout(() => t.classList.add('gone'), 5200);
    setTimeout(() => t.remove(), 5800);
    while ($('toasts').children.length > 4) $('toasts').firstChild.remove();
  }

  showTooltip(x, y, text) {
    const tt = $('tooltip');
    tt.textContent = text;
    tt.classList.remove('hidden', 'bad');
    tt.style.left = Math.min(x + 14, window.innerWidth - 180) + 'px';
    tt.style.top = (y + 18) + 'px';
  }

  flashTooltip(text) {
    const tt = $('tooltip');
    tt.textContent = text;
    tt.classList.remove('hidden');
    tt.classList.add('bad');
    clearTimeout(this._flashT);
    this._flashT = setTimeout(() => this.hideTooltip(), 1200);
  }

  hideTooltip() { $('tooltip').classList.add('hidden'); }

  showInfoPopup(info, x, y) {
    const p = $('infoPopup');
    let rows = '';
    const row = (k, v) => `<div class="ip-row"><span>${k}</span><b>${v}</b></div>`;
    rows += row('Terrain', info.terrain);
    rows += row('Type', info.type ?? '—');
    if (info.kind === 'service') rows += row('Status', info.active ? '✅ Connected' : '⚠️ No road access (inactive)');
    if (info.outputLabel) rows += row('Output', info.outputLabel);
    if (info.kind === 'zone') {
      rows += row('Development', info.rubble ? '🔥 Rubble' : info.burning ? '🔥 On fire!' : info.level ? `Level ${info.level}` : 'Empty plot');
      rows += row('Power', info.powered ? '⚡ Yes' : '🌑 No');
      if (info.level) rows += row('Condition', info.abandoned ? '🏚️ Abandoned' : 'OK');
      rows += row('Land value', info.landValue != null ? info.landValue : '—');
      rows += row('Pollution', info.pollution != null ? info.pollution : '—');
    }
    p.innerHTML = `<div class="ip-head">Tile ${info.x},${info.z}<button class="ip-close">×</button></div>${rows}`;
    p.classList.remove('hidden');
    p.style.left = Math.min(x + 12, window.innerWidth - 240) + 'px';
    p.style.top = Math.min(y + 12, window.innerHeight - 220) + 'px';
    p.querySelector('.ip-close').addEventListener('click', () => this.closeInfoPopup());
  }

  closeInfoPopup() { $('infoPopup').classList.add('hidden'); }

  // =================== minimap ===================

  scheduleMinimap() {
    if (!this.settings.minimap) return;
    if (this._mmTimer) return;
    this._mmTimer = setTimeout(() => { this._mmTimer = null; this.drawMinimap(); }, 350);
  }

  drawMinimap() {
    const cv = $('minimap');
    const ctx = cv.getContext('2d');
    const g = this.city.grid, S = g.size;
    const sc = cv.width / S;
    const blink = (performance.now() / 500 | 0) % 2 === 0;
    for (let z = 0; z < S; z++) {
      for (let x = 0; x < S; x++) {
        const i = g.idx(x, z);
        let col;
        if (g.burning[i]) col = blink ? '#ff3b30' : '#7a1f1a';
        else if (g.rubble[i]) col = '#3a3a3a';
        else if (g.struct[i] !== 0) col = this.serviceColor(g.struct[i]);
        else if (g.zone[i] !== 0) {
          if (g.level[i] > 0) col = this.zoneColor(g.zone[i], g.level[i]);
          else col = this.zoneTint(g.zone[i]);
        } else if (g.terrain[i] === 1) col = '#1d4f7e';
        else col = g.scenery[i] ? '#35682d' : '#3f7a37';
        ctx.fillStyle = col;
        ctx.fillRect(x * sc, z * sc, sc + 0.5, sc + 0.5);
      }
    }
  }

  serviceColor(s) {
    switch (s) {
      case STRUCT.ROAD: return '#585c63';
      case STRUCT.COAL: return '#c96f2f';
      case STRUCT.SOLAR: return '#e0c34a';
      case STRUCT.WIND: return '#dddddd';
      case STRUCT.POLICE: return '#4a7dff';
      case STRUCT.FIRE: return '#ff5347';
      case STRUCT.HOSPITAL: return '#ff7ba9';
      case STRUCT.SCHOOL: return '#b388ff';
      default: return '#69f0ae';
    }
  }
  zoneColor(zv, lvl) {
    const base = { 1: ['#4fae74', '#41c487', '#2fdb9b'], 2: ['#4a86c8', '#4a9de0', '#52b4f2'], 3: ['#b28f3c', '#c9a53f', '#e0bb44'] }[zv];
    return base[lvl - 1];
  }
  zoneTint(zv) {
    return { 1: 'rgba(63,220,132,0.45)', 2: 'rgba(74,168,255,0.45)', 3: 'rgba(255,176,32,0.45)' }[zv];
  }

  // =================== side panels ===================

  toggleSidePanel(which) {
    const p = $('sidePanelRight');
    if (!p.classList.contains('hidden') && p.dataset.which === which) { this.closeSidePanel(); return; }
    p.dataset.which = which;
    p.classList.remove('hidden');
    this.renderSidePanel();
  }

  closeSidePanel() { $('sidePanelRight').classList.add('hidden'); }

  renderSidePanel() {
    const p = $('sidePanelRight');
    if (p.classList.contains('hidden')) return;
    if (p.dataset.which === 'budget') this.renderBudget(p);
    else this.renderStats(p);
  }

  refreshSidePanel() { this.renderSidePanel(); }

  renderBudget(p) {
    const c = this.city;
    const lb = c.lastBudget;
    const net = lb.taxRes + lb.taxCom + lb.taxInd - lb.upkeep;
    p.innerHTML = `
      <div class="sp-head"><h3>🏦 Budget</h3><button class="sp-close">×</button></div>
      <div class="sp-body">
        <label class="tax-row">Tax rate <b id="taxVal">${c.taxRate}%</b>
          <input type="range" id="taxSlider" min="0" max="20" step="1" value="${c.taxRate}">
        </label>
        <p class="hint" id="taxHint"></p>
        <table class="ledger">
          <tr><td>Residential taxes</td><td>${fmtMoney(lb.taxRes)}</td></tr>
          <tr><td>Commercial taxes</td><td>${fmtMoney(lb.taxCom)}</td></tr>
          <tr><td>Industrial taxes</td><td>${fmtMoney(lb.taxInd)}</td></tr>
          <tr class="dim"><td>Road upkeep</td><td>-${fmtMoney(lb.roads)}</td></tr>
          <tr class="dim"><td>Service upkeep</td><td>-${fmtMoney(Math.max(0, lb.upkeep - lb.roads))}</td></tr>
          <tr class="${net >= 0 ? 'good' : 'bad'}"><td><b>Net / month</b></td><td><b>${net >= 0 ? '+' : ''}${fmtMoney(net)}</b></td></tr>
          <tr><td>Treasury</td><td><b>${fmtMoney(c.money)}</b></td></tr>
        </table>
        <p class="hint">Figures show last month. Higher taxes fund services but slow growth and hurt happiness.</p>
      </div>`;
    p.querySelector('.sp-close').addEventListener('click', () => this.closeSidePanel());
    const slider = p.querySelector('#taxSlider');
    slider.addEventListener('input', () => {
      c.taxRate = +slider.value;
      p.querySelector('#taxVal').textContent = c.taxRate + '%';
      const hint = p.querySelector('#taxHint');
      if (c.taxRate <= 7) hint.textContent = 'Citizens love low taxes — growth is fast.';
      else if (c.taxRate <= 11) hint.textContent = 'Balanced rate.';
      else if (c.taxRate <= 15) hint.textContent = 'Getting steep — demand is softening.';
      else hint.textContent = 'Crushing! Expect abandonment and anger.';
      this.updateHUD();
    });
  }

  renderStats(p) {
    const s = this.city.stats;
    const c = this.city;
    const powerPct = s.powerCap > 0 ? Math.min(100, Math.round(s.powerUse / s.powerCap * 100)) : 0;
    const nextMs = SIM.milestonePops[c.milestoneIdx + 1];
    p.innerHTML = `
      <div class="sp-head"><h3>📊 Statistics</h3><button class="sp-close">×</button></div>
      <div class="sp-body">
        <div class="kv-grid">
          <div><span>Population</span><b>${fmtNum(s.pop)}</b></div>
          <div><span>Jobs</span><b>${fmtNum(s.jobs)}</b></div>
          <div><span>Unemployment</span><b>${Math.round(s.unemployment * 100)}%</b></div>
          <div><span>Happiness</span><b>${s.happiness}%</b></div>
          <div><span>Avg land value</span><b>${Math.round(s.avgLandValue)}</b></div>
          <div><span>Title</span><b>${SIM.milestonePops[c.milestoneIdx][1]}</b></div>
        </div>
        <div class="meter"><label>Power ${s.powerUse}/${s.powerCap}${s.brownouts ? ` ⚠️ ${s.brownouts} brownouts` : ''}</label>
          <div class="meter-bar"><i style="width:${powerPct}%" class="${powerPct >= 95 ? 'hot' : ''}"></i></div>
        </div>
        ${nextMs ? `<p class="hint">Next milestone: <b>${nextMs[1]}</b> at ${fmtNum(nextMs[0])} citizens.</p>` : '<p class="hint">You reached the highest title! 👑</p>'}
        <h4>Population</h4><canvas id="chartPop" width="252" height="72"></canvas>
        <h4>Treasury</h4><canvas id="chartFunds" width="252" height="72"></canvas>
      </div>`;
    p.querySelector('.sp-close').addEventListener('click', () => this.closeSidePanel());
    this.drawChart(p.querySelector('#chartPop'), this.city.history.map(h => h.pop), '#35e08a');
    this.drawChart(p.querySelector('#chartFunds'), this.city.history.map(h => h.funds), '#ffd166');
  }

  drawChart(cv, data, color) {
    const ctx = cv.getContext('2d');
    ctx.clearRect(0, 0, cv.width, cv.height);
    if (data.length < 2) {
      ctx.fillStyle = 'rgba(255,255,255,.4)';
      ctx.font = '11px system-ui';
      ctx.fillText('Not enough data yet — let time flow…', 10, 40);
      return;
    }
    const min = Math.min(...data), max = Math.max(...data);
    const rng = (max - min) || 1;
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    ctx.beginPath();
    data.forEach((v, i) => {
      const x = i / (data.length - 1) * (cv.width - 8) + 4;
      const y = cv.height - 6 - (v - min) / rng * (cv.height - 14);
      i ? ctx.lineTo(x, y) : ctx.moveTo(x, y);
    });
    ctx.stroke();
    ctx.fillStyle = 'rgba(255,255,255,.45)';
    ctx.font = '10px system-ui';
    ctx.fillText(fmtNum(max), 6, 11);
    ctx.fillText(fmtNum(min), 6, cv.height - 2);
  }

  // =================== quests ===================

  renderQuests(initial = false) {
    const box = $('questBox');
    const allDone = this.questDefs.every(q => this.questDone.has(q.id));
    box.classList.toggle('hidden', allDone && !initial ? Date.now() - (this._questAllTime || 0) > 6000 : false);
    if (allDone) {
      if (!this._questAllTime) this._questAllTime = Date.now();
      box.innerHTML = `<div class="q-head">🏆<b>All milestones complete!</b></div>`;
      return;
    }
    box.innerHTML = `<div class="q-head"><b>🎯 City goals</b></div>` +
      this.questDefs.map(q => {
        const done = this.questDone.has(q.id);
        return `<div class="q-item ${done ? 'done' : ''}">${done ? '✅' : '⬜'} ${q.label}</div>`;
      }).join('');
    box.classList.remove('hidden');
  }

  checkQuests() {
    let changed = false;
    for (const q of this.questDefs) {
      if (!this.questDone.has(q.id) && q.test()) { this.questDone.add(q.id); changed = true; }
    }
    if (changed) this.renderQuests();
  }

  resetQuests() {
    this.questDone.clear();
    this._questAllTime = 0;
    this.renderQuests();
  }

  // =================== modals ===================

  get modalOpen() { return !!$('modalRoot').firstChild; }

  showModal(contentEl) {
    const root = $('modalRoot');
    root.innerHTML = '';
    const wrap = el('<div class="modal-back"><div class="modal"></div></div>');
    wrap.querySelector('.modal').appendChild(contentEl);
    wrap.addEventListener('pointerdown', (e) => { if (e.target === wrap) this.closeModal(); });
    root.appendChild(wrap);
    return () => this.closeModal();
  }

  closeModal() { $('modalRoot').innerHTML = ''; }

  helpModal() {
    const b = BUILDINGS;
    const modal = el(`<div class="modal-card">
      <h2>🏙️ Welcome to PolyCity</h2>
      <p>You are the mayor. Zone land, provide power and services, keep the budget alive, and grow a village into a megalopolis.</p>
      <div class="help-cols">
        <div>
          <h3>Quick start</h3>
          <ol>
            <li>Lay a <b>Road</b> (R) — drag an L-shape.</li>
            <li>Paint <b>Homes</b> (Z), <b>Shops</b> (X), <b>Industry</b> (C) beside it.</li>
            <li>Build a <b>power plant</b> and connect it near your zones.</li>
            <li>Zones develop on their own when there is demand!</li>
            <li>Add police/fire/schools/parks to raise land value.</li>
          </ol>
        </div>
        <div>
          <h3>Controls</h3>
          <ul>
            <li><b>Left-drag</b> — use tool · <b>Right-drag</b> — orbit camera</li>
            <li><b>Middle-drag / WASD</b> — pan · <b>Wheel / pinch</b> — zoom</li>
            <li><b>Space</b> pause · <b>1·2·3</b> speed · <b>Esc</b> cancel</li>
            <li><b>Q</b> inspect · <b>B</b> bulldoze · <b>R</b> road · <b>Z/X/C</b> zones</li>
          </ul>
        </div>
      </div>
      <h3>Handbook</h3>
      <div class="help-book">
        ${[STRUCT.ROAD, STRUCT.COAL, STRUCT.SOLAR, STRUCT.WIND, STRUCT.POLICE, STRUCT.FIRE, STRUCT.HOSPITAL, STRUCT.SCHOOL, STRUCT.PARK, STRUCT.PLAZA, STRUCT.STADIUM]
        .map(sid => `<div class="hb-item"><span class="hb-ico">${ICONS[b[sid].id === 'road' ? 'road' : b[sid].id]}</span><div><b>${b[sid].name}</b> <em>$${fmtNum(b[sid].cost)}${b[sid].upkeep ? ` · $${b[sid].upkeep}/mo` : ''}</em><br>${b[sid].desc}</div></div>`).join('')}
      </div>
      <p class="dim" style="margin:10px 0 0">🌪️ Once your city matures, tornadoes can strike — they flatten buildings into rubble. Bulldoze the rubble to rebuild. Disasters can be disabled in ☰ Menu → Settings.</p>
      <button class="btn primary" id="helpClose">Let’s build! 🔨</button>
    </div>`);
    this.showModal(modal);
    modal.querySelector('#helpClose').addEventListener('click', () => this.closeModal());
  }

  menuModal() {
    const m = el(`<div class="modal-card">
      <h2>☰ Menu</h2>
      <div class="menu-grid">
        <button class="btn" id="mNew">🌍 New City</button>
        <button class="btn" id="mExport">💾 Export Save (.json)</button>
        <button class="btn" id="mImport">📂 Import Save</button>
      </div>
      <h3>Save slots</h3>
      <div id="slots"></div>      <h3>Settings</h3>
      <div class="settings">
        <label><input type="checkbox" id="setSound" ${this.settings.sound ? 'checked' : ''}> Sound effects</label>
        <label><input type="checkbox" id="setShadow" ${this.settings.shadows ? 'checked' : ''}> Shadows (pretty, costs FPS)</label>
        <label><input type="checkbox" id="setAutosave" ${this.settings.autosave ? 'checked' : ''}> Autosave every year</label>
        <label><input type="checkbox" id="setMinimap" ${this.settings.minimap ? 'checked' : ''}> Minimap</label>
        <label><input type="checkbox" id="setDisasters" ${this.settings.disasters !== false ? 'checked' : ''}> Tornado disasters</label>
      </div>
      <p class="hint">PolyCity v1.0 — built with Three.js. Everything runs locally in your browser.</p>
      <button class="btn primary" id="mClose">Back to city</button>
    </div>`);
    this.showModal(m);

    const slotsDiv = m.querySelector('#slots');
    const renderSlots = () => {
      slotsDiv.innerHTML = '';
      for (let n = 1; n <= 3; n++) {
        const info = this.game.saves.slotInfo(n);
        const row = el(`<div class="slot-row">
          <b>Slot ${n}</b>
          <span class="dim">${info ? `${info.name} · pop ${fmtNum(info.pop)} · ${new Date(info.savedAt).toLocaleString()}` : '— empty —'}</span>
          <button class="btn sm" data-a="save" data-n="${n}">Save</button>
          <button class="btn sm" data-a="load" data-n="${n}" ${info ? '' : 'disabled'}>Load</button>
          <button class="btn sm danger" data-a="del" data-n="${n}" ${info ? '' : 'disabled'}>✕</button>
        </div>`);
        slotsDiv.appendChild(row);
      }
    };
    renderSlots();
    slotsDiv.addEventListener('click', (e) => {
      const btn = e.target.closest('button');
      if (!btn) return;
      const n = +btn.dataset.n;
      if (btn.dataset.a === 'save') { this.game.saves.saveSlot(n); this.toast(`Saved to slot ${n}.`, 'success'); renderSlots(); }
      if (btn.dataset.a === 'load') { this.game.loadFrom(this.game.saves.loadSlot(n)); this.closeModal(); }
      if (btn.dataset.a === 'del') { this.game.saves.deleteSlot(n); renderSlots(); }
    });

    m.querySelector('#mNew').addEventListener('click', () => this.newCityModal());
    m.querySelector('#mExport').addEventListener('click', () => this.game.saves.exportFile());
    m.querySelector('#mImport').addEventListener('click', () => {
      const inp = document.createElement('input');
      inp.type = 'file';
      inp.accept = '.json,application/json';
      inp.addEventListener('change', async () => {
        try {
          const city = await this.game.saves.importFile(inp.files[0]);
          this.game.loadFrom(city);
          this.closeModal();
          this.toast('Save imported!', 'success');
        } catch { this.toast('That file is not a valid save.', 'danger'); }
      });
      inp.click();
    });
    m.querySelector('#setSound').addEventListener('change', e => this.game.applySettings({ sound: e.target.checked }));
    m.querySelector('#setShadow').addEventListener('change', e => this.game.applySettings({ shadows: e.target.checked }));
    m.querySelector('#setDisasters').addEventListener('change', e => this.game.applySettings({ disasters: e.target.checked }));
    m.querySelector('#setAutosave').addEventListener('change', e => this.game.applySettings({ autosave: e.target.checked }));
    m.querySelector('#setMinimap').addEventListener('change', e => {
      this.game.applySettings({ minimap: e.target.checked });
      $('minimapWrap').classList.toggle('hidden', !e.target.checked);
      if (e.target.checked) this.drawMinimap();
    });
    m.querySelector('#mClose').addEventListener('click', () => this.closeModal());
  }

  newCityModal() {
    const name = CITY_NAMES[(Math.random() * CITY_NAMES.length) | 0];
    const seed = (Math.random() * 2 ** 31) | 0;
    const m = el(`<div class="modal-card">
      <h2>🌍 Found a New City</h2>
      <label class="fld">Mayor & city name
        <input id="ncName" type="text" maxlength="24" value="${name}">
      </label>
      <label class="fld">Map seed
        <span class="seedrow"><input id="ncSeed" type="number" value="${seed}">
        <button class="btn sm" id="ncDice">🎲</button></span>
      </label>
      <label class="fld">Starting funds
        <select id="ncMoney">
          <option value="8000">💰 $8,000 — Hard</option>
          <option value="20000" selected>💰💰 $20,000 — Classic</option>
          <option value="45000">💰💰💰 $45,000 — Casual</option>
        </select>
      </label>
      <button class="btn primary" id="ncGo">Break ground! 🚧</button>
    </div>`);
    this.showModal(m);
    m.querySelector('#ncDice').addEventListener('click', () => {
      m.querySelector('#ncSeed').value = (Math.random() * 2 ** 31) | 0;
    });
    m.querySelector('#ncGo').addEventListener('click', () => {
      this.game.newCity({
        name: m.querySelector('#ncName').value.trim() || 'New City',
        seed: (+m.querySelector('#ncSeed').value | 0) || seed,
        money: +m.querySelector('#ncMoney').value
      });
      this.closeModal();
    });
  }
}
