import './style.css';

import { City } from './game/city.js';
import { Renderer } from './render/renderer.js';
import { Input } from './input.js';
import { UI } from './ui/ui.js';
import { Audio } from './audio.js';
import { SaveManager } from './save.js';
import { SETTINGS_KEY, START_MONEY } from './config.js';
import { clamp } from './utils.js';

function loadSettings() {
  try {
    return { sound: true, shadows: true, autosave: true, minimap: true, ...JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}') };
  } catch {
    return { sound: true, shadows: true, autosave: true, minimap: true };
  }
}

class Game {
  constructor() {
    this.settings = loadSettings();
    this.audio = new Audio(this.settings);
    this.saves = null;
    this.speedIdx = 1;
    this.paused = false;
    this.lastNonzeroSpeed = 1;

    this._simAcc = 0;
    this._lastT = performance.now();
    this._raf = null;
    this._parts = null; // {renderer, input, ui}
    this._unsubs = [];

    // pick up autosaved city if present
    let initial = null;
    const probeSaves = new SaveManager(null);
    if (probeSaves.hasAutosave()) {
      initial = probeSaves.loadAutosave();
    }
    this.city = initial || new City();
    this.start(this.city);

    document.addEventListener('visibilitychange', () => {
      if (document.hidden && !this.paused) this.togglePause();
    });
  }

  // ---------- lifecycle ----------

  start(city) {
    city.disastersEnabled = this.settings.disasters !== false;
    this.teardown();
    this.city = city;

    const container = document.getElementById('app');
    let renderer;
    try {
      renderer = new Renderer(container, city, this.settings);
    } catch (err) {
      console.error(err);
      container.innerHTML = `<div style="display:flex;height:100%;align-items:center;justify-content:center;text-align:center;padding:20px">
        <div><h2>WebGL unavailable 😢</h2><p>PolyCity needs hardware-accelerated WebGL.<br>Please enable it in your browser settings or try another browser.</p></div></div>`;
      throw err;
    }

    this.saves = new SaveManager(city);
    const ui = new UI(this);
    const input = new Input({ city, renderer, ui, audio: this.audio, game: this });
    this._parts = { renderer, input, ui };

    this._unsubs.push(city.on('autosave', () => {
      if (this.settings.autosave && this.saves.autosave()) {
        console.info('PolyCity autosaved.');
      }
    }));
    this._unsubs.push(city.on('milestone', () => {
      // extra flourish handled by ui toast + sound
    }));

    city.markTilesChanged();
    this._syncSoon();

    if (!this._raf) this.loop();
  }

  teardown() {
    for (const off of this._unsubs) off();
    this._unsubs = [];
    if (this._parts) {
      this._parts.input.destroy?.();
      this._parts.ui.destroy?.();
      this._parts.renderer.dispose?.();
      this._parts = null;
    }
  }

  newCity({ name, seed, money }) {
    const c = new City(seed, name);
    c.money = money ?? START_MONEY;
    this.speedIdx = 1;
    this.paused = false;
    this.start(c);
    this.ui?.toast(`Welcome to ${name}, Mayor!`, 'success');
    this.ui?.resetQuests();
  }

  loadFrom(cityInstance) {
    this.start(cityInstance);
    this.ui?.toast('City loaded.', 'success');
  }

  get ui() { return this._parts?.ui; }
  get input() { return this._parts?.input; }
  get renderer() { return this._parts?.renderer; }

  // ---------- controls ----------

  setSpeed(idx) {
    this.speedIdx = idx;
    this.paused = idx === 0;
    if (idx > 0) this.lastNonzeroSpeed = idx;
    this.ui?.setSpeedActive(idx);
  }

  togglePause() {
    if (this.paused) this.setSpeed(this.lastNonzeroSpeed);
    else this.setSpeed(0);
  }

  applySettings(patch) {
    Object.assign(this.settings, patch);
    localStorage.setItem(SETTINGS_KEY, JSON.stringify(this.settings));
    if ('shadows' in patch && this._parts) this._parts.renderer.setShadows(patch.shadows);
    if ('disasters' in patch && this.city) this.city.disastersEnabled = patch.disasters !== false;
  }

  queryTile(tile, x, y) {
    const info = this.city.getTileInfo(tile.x, tile.z);
    if (info) this.ui?.showInfoPopup(info, x, y);
  }

  checkQuests() { this.ui?.checkQuests(); }

  _syncSoon() {
    if (this.city.tilesDirty && this._parts) {
      this._parts.renderer.sync();
      this.city.tilesDirty = false;
      this.ui?.scheduleMinimap();
    }
  }

  // ---------- main loop ----------

  loop() {
    const tick = (now) => {
      this._raf = requestAnimationFrame(tick);
      const dt = Math.min(0.05, (now - this._lastT) / 1000);
      this._lastT = now;

      // simulation stepping
      const msPerMonth = [0, 5000, 1800, 700][this.speedIdx] || 0;
      if (!this.paused && msPerMonth > 0) {
        this._simAcc += dt * 1000;
        let guard = 0;
        while (this._simAcc >= msPerMonth && guard < 4) {
          this.city.tick();
          this._simAcc -= msPerMonth;
          guard++;
        }
        if (guard >= 4) this._simAcc = 0; // avoid runaway catch-up
      }

      this._syncSoon();
      if (this._parts) {
        this._parts.input.updatePan(dt);
        this._parts.renderer.frame(dt, this.speedIdx, this.paused);
      }
    };
    this._raf = requestAnimationFrame(tick);
  }
}

// ---------------- boot ----------------

window.addEventListener('DOMContentLoaded', () => {
  try {
    window.POLYCITY = new Game();
  } catch (e) {
    console.error(e);
    return;
  }
  const boot = document.getElementById('bootScreen');
  setTimeout(() => {
    boot.classList.add('off');
    setTimeout(() => boot.remove(), 700);
  }, 450);

  if ('serviceWorker' in navigator && import.meta.env.PROD) {
    window.addEventListener('load', () => {
      navigator.serviceWorker.register('./sw.js').catch(() => {});
    });
  }
});

void clamp;
