/**
 * Game UI — Wiederverwendbare Game-Page-Komponente
 *
 * Bekommt eine Game-Instance, einen Renderer und eine Konfiguration für
 * den Maßnahmen-Shop, und baut die komplette UI auf:
 * - Ressourcen-Panel
 * - Zielepanel
 * - Speed-Controls
 * - Tutorial-Overlay
 * - Maßnahmen-Shop
 * - Events
 * - Graphen
 * - Save / Load / Finish
 * - End-Screen
 */

import type { GameEngine } from '@core/game-engine'
import { INFO_TOPICS, openInfoOverlay } from './info-overlay'
import { persistence } from '@core/persistence'

export interface ShopItem {
  id: string
  name: string
  emoji: string
  description: string
  cost: number
  upkeep?: number
  badges?: Array<{ label: string; type: 'cost' | 'reduction' | 'protection' | 'capacity' | 'quality' | 'neutral' }>
}

export interface GraphZone {
  /** y-Wert ab dem diese Zone beginnt */
  from: number
  /** y-Wert bis wo sie geht */
  to: number
  /** Farbe im RGBA-Format, bevorzugt mit Alpha ~0.15 */
  color: string
  /** Beschriftung die links in der Zone angezeigt wird */
  label?: string
}

export interface GraphLine {
  /** y-Wert der Linie */
  at: number
  /** Farbe */
  color: string
  /** Text-Label rechts neben der Linie */
  label?: string
  /** Strichlinie? */
  dashed?: boolean
}

export interface GraphConfig {
  id: string
  title: string
  field: string         // resource id
  yMin: number
  yMax: number
  color: string
  format?: (v: number) => string
  /** Horizontale Farbbänder im Hintergrund (z.B. Temperaturzonen) */
  zones?: GraphZone[]
  /** Horizontale Referenzlinien (z.B. "Klimaziel 17°C") */
  lines?: GraphLine[]
  /** Einheit für die Anzeige ("°C", "cm", "Mio €", "ppm") */
  unit?: string
  /**
   * Dynamische Skalierung: yMax und optional yMin wandern mit den Daten mit,
   * sodass die Kurve immer den Graph füllt. yMin/yMax aus der Config bleiben
   * als UNTERGRENZEN (yMax wird bei Bedarf nach oben erweitert).
   */
  autoScale?: boolean
  /** Kurzer Achsen-Label für die Tick-Zahl (z.B. "°C" leer lassen weil im Titel) */
  yTickSuffix?: string
}

export type PlayMode = 'free' | 'guided'

export interface GameUIConfig {
  game: GameEngine
  Renderer: new (container: HTMLElement, game: any) => { start(): void; stop(): void }
  shopItems: ShopItem[]
  onBuy: (id: string) => boolean
  /** Optional: Maßnahme abreißen (Rückerstattung). Wird im Shop als 🗑 angezeigt. */
  onDemolish?: (id: string) => boolean
  /** Optional: aktuelle Stückzahl einer Maßnahme abfragen (für "× N"-Badge im Shop) */
  getOwnedCount?: (id: string) => number
  /**
   * Optional: Baueditor-Hook. Wenn gesetzt, wird beim Klick auf eine
   * Maßnahme NICHT direkt onBuy aufgerufen, sondern dieser Callback —
   * der Renderer startet dann den Placement-Mode (Ghost-Mesh, Raycast).
   * Sobald die Anwender*in eine Stelle wählt, ruft der Callback intern
   * `game.buyMeasure(id, {x, z})` auf.
   */
  onStartPlacement?: (id: string) => void
  graphs: GraphConfig[]
  saveKey: string
  finishOnTick?: number  // optional: auto-finish nach diesem Tick
  yearOffset?: number    // z.B. 2025 oder 1975

  /**
   * Spielmodus:
   * - 'free' (Default): Schüler kann Geschwindigkeit frei wählen
   * - 'guided': Lehrperson hat fixe Spieldauer gesetzt; Speed-Buttons ausgeblendet,
   *   Tick-Dauer wird auf totalDurationSec/maxTicks gerechnet, nur Pause erlaubt
   */
  mode?: PlayMode
  /** Im 'guided' Mode: Gesamt-Spieldauer in Sekunden, die das Spiel laufen soll */
  totalDurationSec?: number
  /**
   * Optional: Whitelist welche Resource-IDs in der Status-Box angezeigt werden.
   * Wenn nicht gesetzt, werden alle Resources angezeigt.
   * Werte die in einem Graph stehen, kann man hier weglassen, um Doppelung zu vermeiden.
   */
  statusResources?: string[]
}

export class GameUI {
  private cfg: GameUIConfig
  private game: GameEngine
  private renderer: { start(): void; stop(): void; [key: string]: any } | null = null
  private endShown = false
  private autoSaveTimer = 0
  private autoSavePending = false
  /**
   * Pro Resource ein gedämpft animierter Anzeige-Wert — springt nicht hart
   * beim Tick-Wechsel, sondern zählt weich zum neuen Ziel.
   */
  private displayedValues: Record<string, number> = {}
  private displayAnimRaf = 0
  /** True wenn wir das Spiel wegen einer offenen Bürger-Beschwerde pausiert haben */
  private pausedForCitizen = false
  /** ID der aktuell angezeigten Bürger-Beschwerde — verhindert Re-Render bei Ticks */
  private currentCitizenId: string | null = null
  /** Wird auf true gesetzt wenn Reset läuft — verhindert, dass beforeunload den Save wiederherstellt */
  private resetting = false

  constructor(cfg: GameUIConfig) {
    this.cfg = cfg
    this.game = cfg.game

    // Im 'guided' Mode die Tick-Dauer fix setzen, sodass das Spiel genau totalDurationSec dauert.
    if (cfg.mode === 'guided' && cfg.totalDurationSec && this.game.meta.maxTicks > 0) {
      const msPerTick = (cfg.totalDurationSec * 1000) / this.game.meta.maxTicks
      // Direktes Patchen, weil msPerTick im meta sonst readonly wäre
      ;(this.game.meta as any).msPerTick = msPerTick
    }

    // Vorhandenen Stand automatisch laden, falls vorhanden
    this.tryAutoLoad()

    this.bindControls()
    this.game.subscribe(() => {
      this.renderAll()
      this.scheduleAutoSave()
    })
    this.renderAll()

    // Beim Verlassen der Page sofort speichern
    window.addEventListener('beforeunload', () => this.flushAutoSave())
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'hidden') this.flushAutoSave()
    })
  }

  /** Renderer-Instanz für externe Steuerung (z.B. Kamera-Buttons) */
  getRenderer(): { start(): void; stop(): void; [key: string]: any } | null {
    return this.renderer
  }

  // === Auto-Save ===
  private tryAutoLoad(): void {
    // Synchron aus localStorage laden (schnell), dann async aus API nachladen
    const localSave = localStorage.getItem(this.cfg.saveKey)
    if (localSave && this.game.deserialize(localSave)) {
      // Stand aus localStorage wiederhergestellt
    }
    // Async: Server-Stand pruefen (ueberschreibt ggf. localStorage-Stand)
    if (persistence.hasSession()) {
      persistence.load(this.cfg.saveKey).then(serverSave => {
        if (serverSave && serverSave !== localSave) {
          this.game.deserialize(serverSave)
          this.renderAll()
        }
      })
    }
  }

  private scheduleAutoSave(): void {
    // Debounce: spätestens nach 1.5 s schreiben
    if (this.autoSavePending) return
    this.autoSavePending = true
    this.autoSaveTimer = window.setTimeout(() => {
      this.flushAutoSave()
    }, 1500)
  }

  private flushAutoSave(): void {
    if (this.autoSaveTimer) {
      clearTimeout(this.autoSaveTimer)
      this.autoSaveTimer = 0
    }
    this.autoSavePending = false
    // Während eines Resets NICHT wieder speichern, sonst bleibt der alte Zustand erhalten
    if (this.resetting) return
    const data = this.game.serialize()
    persistence.save(this.cfg.saveKey, data)
  }

  private bindControls(): void {
    // Speed-Modus durchsetzen: im 'guided' nur Pause/Play (Speed 0 und 1) erlaubt.
    // Selector geht auf das ATTRIBUT data-speed (nicht auf eine Klasse), weil
    // verschiedene HTML-Seiten verschiedene Button-Klassen verwenden
    // (game-3d: .icon-btn, game.html: .speed-btn).
    const isGuided = this.cfg.mode === 'guided'
    document.querySelectorAll<HTMLButtonElement>('[data-speed]').forEach(btn => {
      const speed = parseInt(btn.dataset.speed || '1')
      if (isGuided && speed > 1) {
        btn.style.display = 'none'
        return
      }
      btn.addEventListener('click', () => {
        const s = speed as 0 | 1 | 2 | 4
        this.game.setSpeed(s)
        document.querySelectorAll('[data-speed]').forEach(b => b.classList.remove('active'))
        btn.classList.add('active')
      })
    })

    // Save / Load / Finish
    document.getElementById('btn-save')?.addEventListener('click', () => {
      persistence.save(this.cfg.saveKey, this.game.serialize())
      this.toast('💾 Stand gespeichert')
    })
    document.getElementById('btn-load')?.addEventListener('click', async () => {
      const save = await persistence.load(this.cfg.saveKey)
      if (!save) { this.toast('Kein Stand vorhanden'); return }
      if (this.game.deserialize(save)) {
        this.renderAll()
        this.toast('📂 Stand geladen')
      }
    })
    document.getElementById('btn-finish')?.addEventListener('click', () => {
      this.game.finish()
      this.showEndScreen()
    })
    document.getElementById('btn-reset')?.addEventListener('click', () => {
      if (confirm('Stand wirklich löschen und neu starten?')) {
        // Reset-Flag setzt beforeunload-Handler außer Kraft
        this.resetting = true
        localStorage.removeItem(this.cfg.saveKey)
        location.reload()
      }
    })

    // Tutorial next button
    document.getElementById('tut-next')?.addEventListener('click', () => {
      this.game.nextTutorialStep()
      this.renderAll()
    })
  }

  /**
   * Erzwingt ein sofortiges Neuzeichnen aller Graphen — wird z. B. nach
   * dem Zoom-Animation-End aufgerufen, damit sich die SVGs an die neue
   * Card-Größe anpassen.
   */
  redrawGraphs(): void {
    this.renderGraphs()
  }

  private renderAll(): void {
    this.renderResources()
    this.renderGoals()
    this.renderShop()
    this.renderEvents()
    this.renderGraphs()
    this.renderTutorial()
    this.renderCitizenEvent()

    const snap = this.game.getSnapshot()
    if ((snap.state === 'won' || snap.state === 'lost') && !this.endShown) {
      setTimeout(() => this.showEndScreen(), 800)
    }
  }

  private renderCitizenEvent(): void {
    const host = document.getElementById('citizen-event-host')
    if (!host) return
    const ev = this.game.getPendingCitizenEvent()

    // Kein Event → Modal ausblenden, Spiel fortsetzen
    if (!ev) {
      if (this.currentCitizenId !== null) {
        host.innerHTML = ''
        this.currentCitizenId = null
      }
      if (this.pausedForCitizen) {
        this.pausedForCitizen = false
        this.game.setSpeed(1)
      }
      return
    }

    // Gleiches Event wie zuletzt → Modal NICHT neu rendern,
    // sonst flackert es bei jedem Tick-Render
    if (this.currentCitizenId === ev.id) {
      return
    }
    this.currentCitizenId = ev.id

    // Neues Event: Spiel pausieren, Modal einmal aufbauen
    if (!this.pausedForCitizen) {
      this.pausedForCitizen = true
      this.game.setSpeed(0)
    }
    const choicesHtml = ev.choices.map((c, i) => `
      <button class="citizen-choice" data-idx="${i}">
        <div class="citizen-choice-label">${c.label}</div>
        ${c.description ? `<div class="citizen-choice-desc">${c.description}</div>` : ''}
      </button>
    `).join('')
    host.innerHTML = `
      <div class="citizen-overlay">
        <div class="citizen-card">
          <div class="citizen-head">
            <div class="citizen-avatar">${ev.character}</div>
            <div>
              <div class="citizen-title">${ev.title}</div>
              <div class="citizen-sub">Eine Bürgerin meldet sich</div>
            </div>
          </div>
          <div class="citizen-message">„${ev.message}"</div>
          <div class="citizen-choices">${choicesHtml}</div>
        </div>
      </div>
    `
    host.querySelectorAll<HTMLButtonElement>('.citizen-choice').forEach(btn => {
      btn.addEventListener('click', () => {
        const idx = parseInt(btn.dataset.idx || '0')
        this.game.resolveCitizenEvent(idx)
      })
    })
  }

  private renderResources(): void {
    const el = document.getElementById('resources')
    if (!el) return
    let resources = this.game.getResourcesArray()
    if (this.cfg.statusResources && this.cfg.statusResources.length > 0) {
      const allow = new Set(this.cfg.statusResources)
      resources = resources.filter(r => allow.has(r.id))
    }
    // HTML-Gerüst einmal bauen (wenn noch nicht da oder Struktur geändert)
    const existing = el.querySelectorAll<HTMLElement>('.resource')
    const needsRebuild = existing.length !== resources.length
    if (needsRebuild) {
      el.innerHTML = resources.map(r => {
        // Spezialfall STROM: zwei kleine Balken (Erzeugung / Bedarf)
        // visualisieren das Strom-Netz didaktisch.
        let extra = ''
        if (r.id === 'power') {
          extra = `
            <div class="power-grid">
              <div class="pg-row">
                <span class="pg-lbl">⚡ Strom</span>
                <div class="pg-track"><div class="pg-fill pg-cap"></div></div>
                <span class="pg-num pg-cap-num">0</span>
              </div>
              <div class="pg-row">
                <span class="pg-lbl">🏠 Bedarf</span>
                <div class="pg-track"><div class="pg-fill pg-dem"></div></div>
                <span class="pg-num pg-dem-num">0</span>
              </div>
              <div class="pg-status pg-status-ok">Versorgung gesichert</div>
            </div>
          `
        } else if (r.id === 'budget' && typeof (this.game as any).getYearlyBalance === 'function') {
          // Spezialfall BUDGET: Jahres-Bilanz (Einnahmen / Wartung / Netto)
          extra = `
            <div class="budget-balance">
              <div class="bb-row">
                <span class="bb-lbl">+ Steuern</span>
                <span class="bb-num bb-pos bb-income">0</span>
              </div>
              <div class="bb-row bb-tourism" style="display:none">
                <span class="bb-lbl">+ Tourismus</span>
                <span class="bb-num bb-pos bb-tourism-num">0</span>
              </div>
              <div class="bb-row">
                <span class="bb-lbl">− Wartung</span>
                <span class="bb-num bb-neg bb-upkeep">0</span>
              </div>
              <div class="bb-row bb-climate" style="display:none">
                <span class="bb-lbl">− Klima-Schaden</span>
                <span class="bb-num bb-neg bb-climate-num">0</span>
              </div>
              <div class="bb-row bb-net-row">
                <span class="bb-lbl">= pro Jahr</span>
                <span class="bb-num bb-net">0</span>
              </div>
            </div>
          `
        }
        return `
        <div class="resource" data-tip="${r.id}" data-res-id="${r.id}">
          <div class="resource-icon">${r.icon}</div>
          <div class="resource-info">
            <div class="resource-name">${r.name}</div>
            <div class="resource-val"></div>
            ${extra}
          </div>
        </div>
      `}).join('')
    }
    // Werte schreiben (aus displayedValues, nicht aus r.current)
    for (const r of resources) {
      if (this.displayedValues[r.id] === undefined) {
        this.displayedValues[r.id] = r.current
      }
      const row = el.querySelector<HTMLElement>(`[data-res-id="${r.id}"] .resource-val`)
      if (row) {
        const v = this.displayedValues[r.id]
        row.textContent = r.format ? r.format(v) : String(Math.round(v))
      }
    }
    this.updatePowerGrid()
    this.updateBudgetBalance()
    // Animations-Loop starten (einmal)
    this.ensureDisplayAnim()
  }

  /**
   * Aktualisiert die Jahres-Bilanz unter dem Budget-Wert (sim-05-spezifisch).
   * Liest game.getYearlyBalance() — wenn die Methode nicht existiert, NoOp.
   */
  private updateBudgetBalance(): void {
    const host = document.querySelector<HTMLElement>('[data-res-id="budget"] .budget-balance')
    if (!host) return
    const fn = (this.game as any).getYearlyBalance as (() => { income: number; tourism: number; upkeep: number; climate: number; net: number }) | undefined
    if (typeof fn !== 'function') return
    const b = fn.call(this.game)
    const sel = (q: string) => host.querySelector<HTMLElement>(q)
    const incomeEl = sel('.bb-income')
    if (incomeEl) incomeEl.textContent = `${b.income} Mio €`
    const tourismRow = sel('.bb-tourism') as HTMLElement | null
    const tourismEl = sel('.bb-tourism-num')
    if (tourismRow) tourismRow.style.display = b.tourism > 0 ? '' : 'none'
    if (tourismEl) tourismEl.textContent = `${b.tourism} Mio €`
    const upkeepEl = sel('.bb-upkeep')
    if (upkeepEl) upkeepEl.textContent = `${b.upkeep} Mio €`
    const climateRow = sel('.bb-climate') as HTMLElement | null
    const climateEl = sel('.bb-climate-num')
    if (climateRow) climateRow.style.display = b.climate > 0 ? '' : 'none'
    if (climateEl) climateEl.textContent = `${b.climate} Mio €`
    const netEl = sel('.bb-net')
    if (netEl) {
      const sign = b.net >= 0 ? '+' : ''
      netEl.textContent = `${sign}${b.net} Mio €`
      netEl.classList.toggle('bb-pos', b.net >= 0)
      netEl.classList.toggle('bb-neg', b.net < 0)
    }
  }

  /**
   * Aktualisiert die Strom-Netz-Visualisierung in der Status-Box.
   * Liest die displayed-power-Kapazität und die aktuelle Bevölkerung,
   * berechnet Bedarf = pop/1000 und stellt zwei Balken auf gemeinsame Skala.
   */
  private updatePowerGrid(): void {
    const host = document.querySelector<HTMLElement>('[data-res-id="power"] .power-grid')
    if (!host) return
    const cap = Math.round(this.displayedValues['power'] ?? 0)
    const pop = this.displayedValues['population'] ?? this.game.getResource('population')
    const dem = Math.max(0, Math.round(pop / 1000))
    // Skala: höchster Wert + 2 Puffer, mindestens 8
    const scale = Math.max(8, Math.max(cap, dem) + 2)
    const capPct = Math.min(100, (cap / scale) * 100)
    const demPct = Math.min(100, (dem / scale) * 100)
    const capFill = host.querySelector<HTMLElement>('.pg-cap')
    const demFill = host.querySelector<HTMLElement>('.pg-dem')
    const capNum = host.querySelector<HTMLElement>('.pg-cap-num')
    const demNum = host.querySelector<HTMLElement>('.pg-dem-num')
    const status = host.querySelector<HTMLElement>('.pg-status')
    if (capFill) capFill.style.width = `${capPct}%`
    if (demFill) demFill.style.width = `${demPct}%`
    if (capNum) capNum.textContent = `${cap} MW`
    if (demNum) demNum.textContent = `${dem} MW`
    const ok = cap >= dem
    if (capFill) capFill.classList.toggle('pg-cap-low', !ok)
    if (status) {
      status.textContent = ok ? 'Versorgung gesichert' : 'Stromausfall — zu wenig!'
      status.classList.toggle('pg-status-ok', ok)
      status.classList.toggle('pg-status-bad', !ok)
    }
  }

  /**
   * Hält einen rAF-Loop am Laufen, der displayedValues langsam zu den echten
   * Resource-Werten zieht. Pro Sekunde werden ~30% der Differenz überbrückt,
   * bei sehr kleinen Änderungen wird direkt gesetzt.
   */
  private ensureDisplayAnim(): void {
    if (this.displayAnimRaf !== 0) return
    const loop = () => {
      let anyMoving = false
      for (const r of this.game.getResourcesArray()) {
        const target = r.current
        const cur = this.displayedValues[r.id]
        if (cur === undefined) {
          this.displayedValues[r.id] = target
          continue
        }
        const delta = target - cur
        const absDelta = Math.abs(delta)
        if (absDelta < 0.5 && absDelta < Math.abs(target) * 0.001 + 0.5) {
          // Snap wenn nah genug
          if (cur !== target) {
            this.displayedValues[r.id] = target
            anyMoving = true
          }
          continue
        }
        // Gedämpftes Hinzählen: 12% der Distanz pro Frame (~60fps)
        this.displayedValues[r.id] = cur + delta * 0.12
        anyMoving = true
      }
      if (anyMoving) {
        // Kurz UI updaten (nur die Werte-Zellen, nicht das ganze DOM)
        const el = document.getElementById('resources')
        if (el) {
          for (const r of this.game.getResourcesArray()) {
            const row = el.querySelector<HTMLElement>(`[data-res-id="${r.id}"] .resource-val`)
            if (row) {
              const v = this.displayedValues[r.id]
              row.textContent = r.format ? r.format(v) : String(Math.round(v))
            }
          }
          // Strom-Netz-Visualisierung und Bilanz mitziehen
          this.updatePowerGrid()
          this.updateBudgetBalance()
        }
      }
      this.displayAnimRaf = requestAnimationFrame(loop)
    }
    this.displayAnimRaf = requestAnimationFrame(loop)
  }

  private renderGoals(): void {
    // Ziele werden entweder in ein separates #goals-Element gerendert,
    // oder — falls kein separates Element existiert — in das gemeinsame
    // #status-goals Element (dann unter den Resources in der Status-Box).
    const goalsEl = document.getElementById('goals') || document.getElementById('status-goals')
    if (!goalsEl) return
    const snap = this.game.getSnapshot()
    const goalsList = (this.game as any).goals as Array<{ title: string; description: string }>
    goalsEl.innerHTML = snap.goals.map((g, i) => {
      const goal = goalsList?.[i] || { title: g.id, description: '' }
      return `
        <div class="goal">
          <div class="goal-row">
            <div class="goal-check ${g.achieved ? 'done' : ''}">${g.achieved ? '✓' : ''}</div>
            <div>${goal.title || g.id}</div>
          </div>
          <div class="goal-bar"><div class="goal-bar-fill" style="width:${g.progress}%"></div></div>
        </div>
      `
    }).join('')
  }

  private renderShop(): void {
    const el = document.getElementById('measures')
    if (!el) return
    const budget = this.game.getResource('budget')
    const getCount = this.cfg.getOwnedCount
    const canDemolish = !!this.cfg.onDemolish
    el.innerHTML = this.cfg.shopItems.map(item => {
      const canAfford = budget >= item.cost
      const owned = getCount ? getCount(item.id) : 0
      const badgesHtml = (item.badges || []).map(b => `<span class="measure-stat measure-${b.type}">${b.label}</span>`).join('')
      const ownedBadge = owned > 0
        ? `<span class="measure-owned">×${owned}</span>`
        : ''
      const demolishBtn = (owned > 0 && canDemolish)
        ? `<button class="measure-demolish" data-demolish="${item.id}" title="Abreißen (50 % zurück)">🗑</button>`
        : ''
      return `
        <div class="measure-card ${canAfford ? '' : 'disabled'}" data-id="${item.id}">
          <div class="measure-icon">${item.emoji}</div>
          <div class="measure-info">
            <div class="measure-name">${item.name} ${ownedBadge}</div>
            <div class="measure-desc">${item.description}</div>
            <div class="measure-stats">
              <span class="measure-stat measure-cost">💰 ${item.cost} Mio €</span>
              ${badgesHtml}
              ${item.upkeep ? `<span class="measure-stat">⚙ ${item.upkeep} Mio €/J</span>` : ''}
            </div>
          </div>
          ${demolishBtn}
        </div>
      `
    }).join('')

    el.querySelectorAll<HTMLDivElement>('.measure-card').forEach(card => {
      card.addEventListener('click', (ev) => {
        // Klick auf den Abreißen-Button NICHT als Kauf werten
        const target = ev.target as HTMLElement
        if (target.closest('.measure-demolish')) return
        if (card.classList.contains('disabled')) return
        const id = card.dataset.id!
        // Wenn der Baueditor-Hook gesetzt ist, statt sofort zu kaufen den
        // Placement-Mode starten. Sonst direkt kaufen (Auto-Platzierung).
        if (this.cfg.onStartPlacement) {
          this.cfg.onStartPlacement(id)
        } else {
          this.cfg.onBuy(id)
        }
      })
    })
    el.querySelectorAll<HTMLButtonElement>('.measure-demolish').forEach(btn => {
      btn.addEventListener('click', (ev) => {
        ev.stopPropagation()
        const id = btn.getAttribute('data-demolish')
        if (id && this.cfg.onDemolish) this.cfg.onDemolish(id)
      })
    })
  }

  private renderEvents(): void {
    const el = document.getElementById('events')
    if (!el) return
    const events = this.game.getEvents(20)
    if (events.length === 0) {
      el.innerHTML = '<div style="font-size:.65rem;color:var(--text3);text-align:center;padding:.4rem">Noch keine Ereignisse</div>'
      return
    }
    const yearOffset = this.cfg.yearOffset || 0
    el.innerHTML = events.map(e => {
      const prefix = yearOffset > 0 ? yearOffset + e.tick : 'Jahr ' + e.tick
      const infoBtn = e.infoKey && INFO_TOPICS[e.infoKey]
        ? `<button class="event-info-btn" data-event-info="${e.infoKey}" title="Mehr erfahren">i</button>`
        : ''
      return `
        <div class="event event-${e.severity}">
          <div class="event-text"><strong>${prefix}:</strong> ${e.text}</div>
          ${infoBtn}
        </div>
      `
    }).join('')
    el.querySelectorAll<HTMLButtonElement>('.event-info-btn').forEach(btn => {
      btn.addEventListener('click', () => {
        const key = btn.getAttribute('data-event-info')
        if (key && INFO_TOPICS[key]) openInfoOverlay(INFO_TOPICS[key])
      })
    })
  }

  private renderGraphs(): void {
    const snap = this.game.getSnapshot()
    const tl = snap.timeline
    const currentTick = snap.tick

    for (const g of this.cfg.graphs) {
      const points: Array<{ x: number; y: number }> = tl.map(e => ({
        x: e.tick,
        y: e.values[g.field] ?? 0,
      }))
      // Aktuellen Live-Wert anhängen, damit Käufe sofort sichtbar sind —
      // auch wenn noch kein Tick darüber gelaufen ist.
      const liveValue = this.game.getResource(g.field)
      const lastPt = points[points.length - 1]
      if (!lastPt || lastPt.x < currentTick || lastPt.y !== liveValue) {
        points.push({ x: currentTick, y: liveValue })
      }
      this.drawGraph(g, points)
    }
  }

  private drawGraph(cfg: GraphConfig, points: { x: number; y: number }[]): void {
    const svg = document.getElementById(cfg.id) as unknown as SVGElement | null
    if (!svg) return
    const w = (svg as any).clientWidth || 280
    const h = (svg as any).clientHeight || 110
    svg.setAttribute('viewBox', `0 0 ${w} ${h}`)
    const maxTicks = (this.game as any).meta?.maxTicks || 75

    // Platz links für die Y-Achse
    const axisW = 36
    const plotX = axisW
    const plotW = w - axisW - 4
    const plotY0 = 4
    const plotY1 = h - 4
    const plotH = plotY1 - plotY0

    // === Auto-Skalierung ===
    let yMin = cfg.yMin
    let yMax = cfg.yMax
    if (cfg.autoScale && points.length > 0) {
      let maxV = -Infinity, minV = Infinity
      for (const p of points) {
        if (p.y > maxV) maxV = p.y
        if (p.y < minV) minV = p.y
      }
      // yMax dynamisch: mindestens Config-Wert, erweitert wenn Daten darüber
      if (maxV > yMax) {
        yMax = this.niceCeiling(maxV * 1.10)
      }
      // yMin darf nach unten gehen, wenn Daten darunter
      if (minV < yMin) {
        yMin = this.niceFloor(minV * 1.10)
      }
    }
    const yRange = yMax - yMin || 1

    // Helper: y-Wert → Pixel im Plot-Bereich
    const yToPx = (y: number) =>
      plotY0 + plotH - ((y - yMin) / yRange) * plotH

    // === Hintergrund-Zonen (werden nur im Plot-Bereich gezeichnet) ===
    let bgRects = ''
    if (cfg.zones) {
      for (const z of cfg.zones) {
        const y1 = Math.max(plotY0, yToPx(z.to))
        const y2 = Math.min(plotY1, yToPx(z.from))
        const zoneH = Math.max(0, y2 - y1)
        if (zoneH <= 0) continue
        bgRects += `<rect x="${plotX}" y="${y1.toFixed(1)}" width="${plotW}" height="${zoneH.toFixed(1)}" fill="${z.color}" />`
        if (z.label) {
          bgRects += `<text x="${plotX + 4}" y="${(y1 + 10).toFixed(1)}" font-size="9" fill="rgba(40,40,40,.55)" font-weight="600">${this.escapeXml(z.label)}</text>`
        }
      }
    }

    // === Y-Achse mit Tick-Labels (links) ===
    let yAxis = ''
    // 4 Ticks: yMin, 1/3, 2/3, yMax
    const ticks = [yMin, yMin + yRange / 3, yMin + (yRange * 2) / 3, yMax]
    for (const t of ticks) {
      const y = yToPx(t)
      // Tick-Text
      const labelText = this.formatTick(t, cfg)
      yAxis += `<text x="${(axisW - 4).toFixed(1)}" y="${(y + 3).toFixed(1)}" text-anchor="end" font-size="9" fill="rgba(40,40,40,.55)" font-weight="600">${this.escapeXml(labelText)}</text>`
      // Dezenter Gitterstrich
      yAxis += `<line x1="${plotX}" y1="${y.toFixed(1)}" x2="${plotX + plotW}" y2="${y.toFixed(1)}" stroke="rgba(0,0,0,0.06)" stroke-width="0.8" />`
    }

    // === Referenzlinien ===
    let refLines = ''
    if (cfg.lines) {
      for (const l of cfg.lines) {
        const y = yToPx(l.at)
        if (y < plotY0 || y > plotY1) continue
        const dash = l.dashed ? 'stroke-dasharray="3 3"' : ''
        refLines += `<line x1="${plotX}" y1="${y.toFixed(1)}" x2="${plotX + plotW}" y2="${y.toFixed(1)}" stroke="${l.color}" stroke-width="1.3" ${dash} />`
        if (l.label) {
          refLines += `<text x="${(plotX + plotW - 4).toFixed(1)}" y="${(y - 2).toFixed(1)}" text-anchor="end" font-size="9" font-weight="700" fill="${l.color}">${this.escapeXml(l.label)}</text>`
        }
      }
    }

    // === Linie + Punkt + aktueller Wert ===
    let linePath = ''
    let valueLabel = ''
    if (points.length >= 1) {
      const path = points.map((p, i) => {
        const x = plotX + (p.x / maxTicks) * plotW
        const y = Math.max(plotY0 + 2, Math.min(plotY1 - 2, yToPx(p.y)))
        return `${i === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${y.toFixed(1)}`
      }).join(' ')
      if (points.length >= 2) {
        linePath = `<path d="${path}" fill="none" stroke="${cfg.color}" stroke-width="2.3" stroke-linejoin="round" stroke-linecap="round" />`
      }
      const lastPt = points[points.length - 1]
      const xLast = plotX + (lastPt.x / maxTicks) * plotW
      const yLast = Math.max(plotY0 + 6, Math.min(plotY1 - 6, yToPx(lastPt.y)))
      // Pulsierender Halo (SMIL-Animation, läuft browserseitig völlig autonom)
      linePath += `
        <circle cx="${xLast.toFixed(1)}" cy="${yLast.toFixed(1)}" r="5" fill="${cfg.color}" opacity="0.45">
          <animate attributeName="r" values="4;10;4" dur="1.8s" repeatCount="indefinite" />
          <animate attributeName="opacity" values="0.55;0.05;0.55" dur="1.8s" repeatCount="indefinite" />
        </circle>
      `
      linePath += `<circle cx="${xLast.toFixed(1)}" cy="${yLast.toFixed(1)}" r="3.5" fill="${cfg.color}" stroke="#fff" stroke-width="1.2" />`
      // Live-Wert direkt aus den Resources holen, damit auch Format mit Einheit funktioniert
      const liveResource = this.game.getResourcesArray().find(r => r.id === cfg.field)
      let valText: string
      if (liveResource?.format) {
        valText = liveResource.format(lastPt.y)
      } else if (cfg.format) {
        valText = cfg.format(lastPt.y)
      } else {
        const num = lastPt.y.toFixed(Math.abs(lastPt.y) > 50 ? 0 : 1)
        valText = cfg.unit ? `${num} ${cfg.unit}` : num
      }
      const textW = Math.max(40, Math.min(108, valText.length * 6.2 + 14))
      const badgeX = plotX + plotW - textW
      valueLabel = `
        <rect x="${badgeX.toFixed(1)}" y="4" width="${textW}" height="16" rx="4" fill="${cfg.color}" />
        <text x="${(plotX + plotW - 6).toFixed(1)}" y="15" text-anchor="end" font-size="10" font-weight="800" fill="#fff">${this.escapeXml(valText)}</text>
      `
    }

    svg.innerHTML = `
      ${bgRects}
      ${yAxis}
      ${refLines}
      ${linePath}
      ${valueLabel}
    `
  }

  /** Kurz-Format für Y-Achsen-Ticks */
  private formatTick(v: number, cfg: GraphConfig): string {
    // Sehr kompakt halten — Platz ist limitiert
    if (Math.abs(v) >= 1000) return (v / 1000).toFixed(1).replace('.0', '') + 'k'
    if (Math.abs(v) >= 100) return Math.round(v).toString()
    if (Number.isInteger(v)) return v.toString()
    return v.toFixed(1)
  }

  /** Rundet nach oben auf schöne Werte: 10, 20, 50, 100, 200, 500, 1000, ... */
  private niceCeiling(v: number): number {
    if (v <= 0) return 10
    const exp = Math.floor(Math.log10(v))
    const base = Math.pow(10, exp)
    const n = v / base
    let nice: number
    if (n <= 1) nice = 1
    else if (n <= 2) nice = 2
    else if (n <= 5) nice = 5
    else nice = 10
    return nice * base
  }

  private niceFloor(v: number): number {
    if (v >= 0) return 0
    return -this.niceCeiling(-v)
  }

  private escapeXml(s: string): string {
    return s
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;')
  }

  private renderTutorial(): void {
    const el = document.getElementById('tutorial')
    if (!el) return
    const step = this.game.getCurrentTutorialStep()
    const snap = this.game.getSnapshot()

    if (!step || snap.state !== 'tutorial') {
      el.style.display = 'none'
      if (!this.renderer) {
        const wrap = document.getElementById('canvas-wrap')!
        this.renderer = new this.cfg.Renderer(wrap, this.game)
        this.renderer.start()
      }
      return
    }
    el.style.display = 'flex'
    document.getElementById('tut-title')!.textContent = step.title
    document.getElementById('tut-text')!.textContent = step.text

    const totalSteps = (this.game as any).meta?.tutorialSteps || 4
    const dotsEl = document.getElementById('tut-dots')!
    dotsEl.innerHTML = ''
    for (let i = 0; i < totalSteps; i++) {
      const d = document.createElement('div')
      d.className = 'dot' + (i === snap.tutorialStep ? ' active' : '')
      dotsEl.appendChild(d)
    }
    const nextBtn = document.getElementById('tut-next')!
    nextBtn.textContent = snap.tutorialStep === totalSteps - 1 ? 'Simulation starten 🚀' : 'Weiter →'
  }

  private showEndScreen(): void {
    if (this.endShown) return
    this.endShown = true
    const snap = this.game.getSnapshot()
    const meta = (this.game as any).meta
    const maxTicks = meta?.maxTicks || 0
    const tickUnit = meta?.tickUnit || 'Ticks'

    const won = snap.state === 'won'
    const lost = snap.state === 'lost'
    const finishedEarly = !won && !lost && maxTicks > 0 && snap.tick < maxTicks

    // Verpflichtende Ziele auswerten
    const goalsList = (this.game as any).goals as Array<{ title: string; required?: boolean }>
    const requiredGoals = (goalsList || []).map((g, i) => ({
      title: g.title,
      required: g.required !== false,
      achieved: snap.goals[i]?.achieved ?? false,
    })).filter(g => g.required)
    const requiredAchieved = requiredGoals.filter(g => g.achieved).length
    const requiredTotal = requiredGoals.length

    // Icon + Title sind ehrlich
    let icon: string
    let title: string
    let summary: string
    if (won) {
      icon = '🏆'
      title = 'Mission erfüllt!'
      summary = `Du hast bis ${this.cfg.yearOffset ? this.cfg.yearOffset + snap.tick : snap.tick} alle ${requiredTotal} Hauptziele erreicht. Glückwunsch!`
    } else if (lost) {
      icon = '💔'
      title = 'Simulation gescheitert'
      summary = `Im Jahr ${this.cfg.yearOffset ? this.cfg.yearOffset + snap.tick : snap.tick} ist deine Mission gescheitert. ${requiredAchieved} von ${requiredTotal} Zielen waren erreicht.`
    } else if (finishedEarly) {
      icon = '⏹'
      title = 'Vorzeitig beendet'
      const remaining = maxTicks - snap.tick
      summary = `Du hast nach ${snap.tick} von ${maxTicks} ${tickUnit} abgebrochen — noch ${remaining} ${tickUnit} wären übrig gewesen. ${requiredAchieved} von ${requiredTotal} Zielen waren zu diesem Zeitpunkt erreicht. <strong>Das ist kein Sieg.</strong>`
    } else {
      icon = '🏁'
      title = 'Simulationsdauer zu Ende'
      summary = `Du hast ${snap.tick} ${tickUnit} überstanden, aber nicht alle Hauptziele erreicht (${requiredAchieved}/${requiredTotal}).`
    }

    // Goal-Liste mit ✓ / ✗
    const goalsHtml = requiredGoals.map(g => `
      <div class="end-goal ${g.achieved ? 'done' : 'fail'}">
        <span class="end-goal-icon">${g.achieved ? '✓' : '✗'}</span>
        <span>${g.title}</span>
      </div>
    `).join('')

    // Resourcen
    const statsHtml = this.game.getResourcesArray().slice(0, 4).map(r => `
      <div class="end-stat">
        <div class="end-stat-val">${r.format ? r.format(r.current) : r.current}</div>
        <div class="end-stat-lbl">${r.name}</div>
      </div>
    `).join('')

    const html = `
      <div class="end-screen">
        <div class="end-card">
          <div class="end-icon">${icon}</div>
          <h2>${title}</h2>
          <p class="end-summary">${summary}</p>
          <div class="end-goals">${goalsHtml}</div>
          <div class="end-stats">${statsHtml}</div>
          <button class="tut-btn" id="end-replay">🔄 Nochmal spielen</button>
        </div>
      </div>
    `
    document.getElementById('end-screen-host')!.innerHTML = html
    document.getElementById('end-replay')?.addEventListener('click', () => {
      this.resetting = true
      localStorage.removeItem(this.cfg.saveKey)
      location.reload()
    })
  }

  private toast(msg: string): void {
    // Simple alert for now
    alert(msg)
  }
}
