/**
 * SIM-05: Klimawächter — Interaktive Treibhauseffekt-Simulation
 *
 * Du bist Klimaminister*in eines Inselstaats. 75 Jahre lang musst du:
 * - die Wirtschaft am Laufen halten (Steuern → Budget)
 * - CO₂-Emissionen kontrollieren (durch Maßnahmen)
 * - die Bevölkerung vor steigendem Meeresspiegel schützen
 *
 * Fachlich korrekt: echte Klimasensitivität (3°C / Verdoppelung CO₂),
 * echte Meeresspiegelanstieg-Werte, echte Maßnahmen-Effekte.
 *
 * Mechanik:
 * - 75 Ticks = 75 Jahre (2025 → 2100)
 * - Pro Tick: Budget +/-, CO₂ steigt/fällt, Temp ändert sich, Meer steigt
 * - Anwender*in baut Maßnahmen (Solar, Wind, Wald, Deiche, …)
 * - Win: bis 2100 überleben mit positivem Budget + Klimazielen
 * - Loss: Pleite ODER > 50% der Stadt überflutet
 */

import { GameEngine, type GameMeta, type TutorialStep, type CitizenEvent } from '@core/game-engine'
import { computeTemperature } from './logic'

const META: GameMeta = {
  id: 'sim-05',
  title: 'Klimawächter',
  description: '2025–2100 als Klimaminister*in eines Inselstaats. Schaffst du es, deine Stadt zu retten?',
  msPerTick: 4000,    // 4 Sekunden = 1 Jahr bei Speed 1 → 5 min Gesamtdauer
  tickUnit: 'Jahr',
  maxTicks: 75,
  tutorialSteps: 5,
}

const START_YEAR = 2025

/**
 * Drei Schwierigkeitsgrade. Anwender*in wählt einen, Lehrperson kann später
 * via URL-Parameter ?level=N erzwingen.
 *
 * Alle Level haben dieselben Maßnahmen + Mechaniken — sie unterscheiden
 * sich nur in: Startgeld, Steuereinnahmen, Schonfristen und der Härte
 * der Folgen.
 */
export interface DifficultyConfig {
  id: 1 | 2 | 3
  label: string
  emoji: string
  description: string
  startBudget: number
  startPopulation: number
  /** Mio € Steuern pro 10.000 Einwohner / Jahr */
  incomePer10k: number
  /** Anzahl Schonjahre, bevor Stromausfall Folgen hat */
  blackoutGrace: number
  /** Bäume pro Stromausfall-Jahr (über Schonfrist hinaus) */
  treesPerBlackout: number
  /** ppm CO₂ pro Stromausfall-Jahr (Holzfeuer) */
  co2BlackoutPerYear: number
  /** Multiplikator für Klima-Folgekosten (Versalzung etc.) */
  climateDamageMul: number
  /** Multiplikator für Bevölkerungs-Verlust durch Klima-Folgen */
  popLossMul: number
  /** Welche Maßnahmen sind in diesem Level kaufbar? Leer = alle. */
  availableMeasures?: string[]
  /** Welche Citizen-Event-IDs sollen NICHT ausgelöst werden? */
  skipCitizenEvents?: string[]
  /** Tick-Verschiebung für Citizen-Events (Level 1 = später, weil weniger Druck) */
  citizenEventTickOffset?: number
}

export const DIFFICULTY_LEVELS: Record<1 | 2 | 3, DifficultyConfig> = {
  1: {
    id: 1, label: 'Lernen', emoji: '🟢',
    description: 'Sanfter Einstieg. Viel Geld, milde Folgen, lange Schonzeit. Nur die wichtigsten Maßnahmen.',
    startBudget: 600, startPopulation: 6000, incomePer10k: 220,
    blackoutGrace: 5, treesPerBlackout: 1, co2BlackoutPerYear: 0.15,
    climateDamageMul: 0.35, popLossMul: 0.35,
    // Reduziert auf 5 Kern-Maßnahmen — keine Fallen, keine Doppelungen
    availableMeasures: ['forest', 'solar', 'wind', 'green-roof', 'dike'],
    // Alle Bürger-Events außer der ersten (Lina) ausblenden
    skipCitizenEvents: ['citizen-scientist', 'citizen-farmer', 'citizen-tourism',
                        'citizen-youth', 'citizen-industry', 'citizen-mountain'],
    // Lina kommt 10 Jahre später als sonst (Tick 25 statt 15)
    citizenEventTickOffset: 10,
  },
  2: {
    id: 2, label: 'Üben', emoji: '🟡',
    description: 'Standard. Strategie nötig, du musst nachdenken. Mehr Auswahl, alle Bürger-Events.',
    startBudget: 480, startPopulation: 6000, incomePer10k: 155,
    blackoutGrace: 0, treesPerBlackout: 1, co2BlackoutPerYear: 0.4,
    climateDamageMul: 1.2, popLossMul: 1.1,
    // Wolken-Impfung und Flughafen weg — die didaktisch fragwürdigsten Optionen
    availableMeasures: ['forest', 'solar', 'wind', 'green-roof', 'dike',
                        'sea-wall', 'coal', 'bikes', 'mangrove', 'sand-fill'],
  },
  3: {
    id: 3, label: 'Profi', emoji: '🔴',
    description: 'Hart. Wenig Spielraum, schnelle Konsequenzen. Alle Maßnahmen — auch die fragwürdigen.',
    startBudget: 220, startPopulation: 7000, incomePer10k: 110,
    blackoutGrace: 0, treesPerBlackout: 2, co2BlackoutPerYear: 0.7,
    climateDamageMul: 2.2, popLossMul: 1.8,
    // Alle Maßnahmen verfügbar (kein Filter)
  },
}

/** Maßnahmen, die die Anwender*in bauen kann */
export interface Measure {
  id: string
  name: string
  emoji: string
  description: string
  cost: number              // Einmalige Kosten (€)
  upkeep: number            // Jährliche Kosten (€)
  co2Reduction: number      // ppm/Jahr Reduktion
  protection: number        // Cm Schutz vor Meeresspiegelanstieg
  powerOutput?: number      // MW Strom-Erzeugung
  prerequisite?: string     // Vorgänger-Maßnahme nötig?
}

// Sehr feine Granularität: kleine Einheiten, viele kaufbar.
// Wirkung pro investiertem Mio € bleibt konstant — nur die Mindest-Schritte
// werden kleiner, sodass mehr Streuung und Reparatur möglich ist.
// Wald hat bewusst die GERINGSTE Wartung pro CO₂-Reduktion (Naturlösung).
export const MEASURES: Measure[] = [
  {
    id: 'forest',
    name: 'Wald aufforsten',
    emoji: '🌲',
    description: 'Bäume binden langfristig CO₂. Sehr günstig im Unterhalt — die Naturlösung.',
    cost: 12, upkeep: 0.5,
    co2Reduction: 0.04, protection: 0,
  },
  {
    id: 'solar',
    name: 'Solaranlage',
    emoji: '☀️',
    description: 'Klein und günstig — sofort kaufbar. Wartung ist allerdings mittelhoch.',
    cost: 40, upkeep: 2,
    co2Reduction: 0.14, protection: 0,
    powerOutput: 1,
  },
  {
    id: 'wind',
    name: 'Windpark',
    emoji: '🌬️',
    description: 'Großes Windrad — hohe Anschaffung, dafür sehr günstig im Betrieb und viel Strom.',
    cost: 150, upkeep: 4,
    co2Reduction: 0.50, protection: 0,
    powerOutput: 3,
  },
  {
    id: 'green-roof',
    name: 'Gründächer',
    emoji: '🏡',
    description: 'Begrünte Dächer kühlen die Stadt. Kleine Wirkung, dafür ohne jährliche Kosten.',
    cost: 30, upkeep: 0,
    co2Reduction: 0.04, protection: 0,
  },
  {
    id: 'bikes',
    name: 'Radwege-Netz',
    emoji: '🚲',
    description: 'Macht die Insel fahrradfreundlich. Günstig, kein Strom nötig — ideal für den Anfang.',
    cost: 18, upkeep: 0.5,
    co2Reduction: 0.06, protection: 0,
  },
  {
    id: 'mangrove',
    name: 'Mangrovenwald',
    emoji: '🪸',
    description: 'Mangroven binden CO₂ UND schützen die Küste vor Erosion. Die Naturlösung — beides in einem.',
    cost: 35, upkeep: 1,
    co2Reduction: 0.06, protection: 6,
  },
  {
    id: 'sand-fill',
    name: 'Sand-Aufschüttung',
    emoji: '🏖',
    description: 'Künstlicher Strand. Gibt sofort viel Schutz — wird aber jedes Jahr vom Meer wieder weggespült.',
    cost: 50, upkeep: 0,
    co2Reduction: 0, protection: 18,
  },
  {
    id: 'dike',
    name: 'Deich bauen',
    emoji: '🌊',
    description: 'Niedrige Hürde — schnell baubar, aber teurer pro cm Schutz.',
    cost: 80, upkeep: 3,
    co2Reduction: 0, protection: 12,
  },
  {
    id: 'sea-wall',
    name: 'Hochwasserschutz',
    emoji: '🛡️',
    description: 'Hohe Hürde, aber deutlich effizienter pro Mio €. Sehr starker Schutz.',
    cost: 240, upkeep: 6,
    co2Reduction: 0, protection: 50,
  },
  // === Maßnahmen, die nur SCHEINBAR helfen — didaktische Fallen ===
  {
    id: 'coal',
    name: 'Kohlekraftwerk',
    emoji: '🏭',
    description: 'Liefert viel Strom auf einmal — billig und sofort. Verbrennt aber Kohle und stößt viel CO₂ aus.',
    cost: 20, upkeep: 1,
    co2Reduction: -0.38, protection: 0,
    powerOutput: 4,
  },
  {
    id: 'airport',
    name: 'Tourismus-Flughafen',
    emoji: '✈️',
    description: 'Bringt Touristen auf die Insel. Mehr Wirtschaft — aber Flugverkehr bedeutet viel CO₂.',
    cost: 62, upkeep: 3,
    co2Reduction: -0.22, protection: 0,
  },
  {
    id: 'cloud-seed',
    name: 'Wolken-Impfung',
    emoji: '🌤️',
    description: 'Geo-Engineering: Wolken werden mit Salz besprüht. Klingt nach Wundermittel — wirkt aber nur ein bisschen.',
    cost: 125, upkeep: 4.5,
    co2Reduction: 0.04, protection: 0,
  },
]

/**
 * Eine vom Spieler gebaute Maßnahme.
 *
 * Das `positions`-Array enthält die Welt-Koordinaten für jeden gebauten Index
 * (in derselben Reihenfolge, wie die Maßnahmen platziert wurden). Ist ein
 * Index nicht enthalten, wird die Position vom Renderer zufällig gewählt
 * (Auto-Platzierung — Rückwärtskompatibilität für Saves vor dem Baueditor).
 */
interface OwnedMeasure {
  measureId: string
  builtAt: number
  count: number
  positions?: Array<{ x: number; z: number }>
}

export class KlimawaechterGame extends GameEngine {
  private measures: OwnedMeasure[] = []
  /** Aktuelle Schwierigkeit — bestimmt Startwerte und Härte der Folgen */
  private diff: DifficultyConfig = DIFFICULTY_LEVELS[2]

  // Echte Klima-Werte
  private co2Ppm = 425              // Aktuelles CO₂ (2025)
  private baselineTemp = 15         // °C vorindustriell
  private currentTemp = 0
  private targetTemp = 0            // Gleichgewichtstemperatur (instantan aus CO₂)
  private seaLevelCm = 0            // cm über Startniveau
  private committedSeaCm = 0        // committed sea level rise (träge)
  private floodedHouses = 0         // %
  /** Discount-Faktor für Maßnahmenkosten (1.0 = normal, 0.8 = 20% billiger) */
  researchDiscount = 1.0
  /**
   * Tourismus-Modus: Wurde durch Bürger-Event "Fischerei → Tourismus" aktiviert.
   * Bringt jährlich Extra-Einnahmen, kostet dafür CO₂ und macht die Insel sehr
   * abhängig vom Strand — sobald der Meeresspiegel den Strand frisst, kollabiert er.
   */
  tourismMode = false
  /** Lawinen-/Murenschutzwall am Bergdorf gebaut (vom Bürger-Event "Anna") */
  hasMountainShield = false

  /**
   * Kumulative Anzahl Bäume, die wegen Stromausfall gefällt wurden.
   * Der Renderer liest diesen Counter pro Frame und tötet die nötige
   * Anzahl Bäume sichtbar (damit sie braun/schwarz werden).
   */
  treesChoppedForHeat = 0
  /** Wie viele Jahre in Folge gerade Stromausfall herrscht (für Eskalation) */
  private blackoutStreak = 0

  // CO₂ kann auch mit massivem Klimaschutz nicht unter ~350 ppm fallen (Senken arbeiten langsam)
  private static readonly CO2_FLOOR = 350
  // Untergrenze: vorindustrielles Niveau — kein "Eiszeit-Bug" durch Übermaßnahmen
  private static readonly TEMP_FLOOR = 15.0

  constructor(difficulty: 1 | 2 | 3 = 2) {
    super(META)
    this.diff = DIFFICULTY_LEVELS[difficulty]
    this.setupResources()
    this.setupGoals()
    this.setupTutorial()
    this.setupVariables()
    this.computeClimate()
  }

  /** Aktuelle Schwierigkeitsstufe (für UI-Anzeige). */
  getDifficulty(): DifficultyConfig {
    return this.diff
  }

  private setupResources(): void {
    this.addResource({
      id: 'budget',
      name: 'Budget',
      icon: '💰',
      initial: this.diff.startBudget,
      unit: 'Mio €',
      format: (v) => `${Math.round(v)} Mio €`,
    })
    this.addResource({
      id: 'population',
      name: 'Bevölkerung',
      icon: '👥',
      initial: this.diff.startPopulation,
      unit: 'Menschen',
      format: (v) => `${Math.round(v).toLocaleString('de-AT')}`,
    })
    // Strom-Bilanz: Wert ist die Kapazität (MW), das Format zeigt
    // gleichzeitig Bedarf/Kapazität an. Bedarf wächst mit Bevölkerung
    // (1 MW pro 1.000 Einwohner — kindgerechte Faustzahl).
    this.addResource({
      id: 'power',
      name: 'Strom',
      icon: '⚡',
      initial: 0,
      unit: 'MW',
      format: (v) => {
        const demand = Math.max(0, Math.round(this.getResource('population') / 1000))
        const cap = Math.round(v)
        return `${demand}/${cap} MW`
      },
    })
    this.addResource({
      id: 'co2',
      name: 'CO₂-Konzentration',
      icon: '🌫️',
      initial: 425,
      unit: 'ppm',
      format: (v) => `${v.toFixed(0)} ppm`,
    })
    this.addResource({
      id: 'temperature',
      name: 'Globale Temperatur',
      icon: '🌡️',
      initial: 15.5,
      unit: '°C',
      format: (v) => `${v.toFixed(1)}°C`,
    })
    this.addResource({
      id: 'sealevel',
      name: 'Meeresspiegel',
      icon: '🌊',
      initial: 0,
      unit: 'cm',
      format: (v) => `+${Math.round(v)} cm`,
    })
    this.addResource({
      id: 'flooded',
      name: 'Überflutete Gebiete',
      icon: '🏚️',
      initial: 0,
      unit: '%',
      format: (v) => `${v.toFixed(0)}%`,
    })
  }

  private setupGoals(): void {
    this.addGoal({
      id: 'survive',
      title: 'Bis 2100 überleben',
      description: 'Halte deine Stadt 75 Jahre lang am Leben (2025–2100).',
      check: (g) => (g as KlimawaechterGame).tick >= 75,
      progress: (g) => Math.min(100, ((g as KlimawaechterGame).tick / 75) * 100),
      required: true,
    })
    this.addGoal({
      id: 'temp',
      title: 'Temperatur unter +2°C halten',
      description: 'Die globale Temperatur darf nicht über 17°C steigen.',
      check: (g) => (g as KlimawaechterGame).currentTemp < 17,
      progress: (g) => {
        const t = (g as KlimawaechterGame).currentTemp
        return Math.max(0, Math.min(100, (1 - (t - 15) / 2) * 100))
      },
      required: true,
    })
    this.addGoal({
      id: 'budget',
      title: 'Nicht pleite gehen',
      description: 'Halte ein positives Budget.',
      check: (g) => g.getResource('budget') > 0,
      required: true,
    })
    this.addGoal({
      id: 'flooding',
      title: 'Stadt schützen',
      description: 'Maximal 30% der Stadt dürfen überflutet sein.',
      check: (g) => (g as KlimawaechterGame).floodedHouses < 30,
      progress: (g) => Math.max(0, 100 - (g as KlimawaechterGame).floodedHouses * 3.33),
      required: true,
    })
  }

  private setupTutorial(): void {
    this.setTutorial([
      {
        triggerTick: 0,
        title: 'Willkommen, Klimaminister*in!',
        text: 'Es ist das Jahr 2025. Du übernimmst die Verantwortung für eine kleine Inselstadt mit 10.000 Einwohnern. Bis zum Jahr 2100 liegt ihr Schicksal in deiner Hand.\n\nDie Wissenschaft warnt: Das Klima verändert sich. CO₂ in der Atmosphäre steigt. Wenn du nichts tust, wird die Temperatur steigen, das Eis schmelzen — und der Meeresspiegel deine Stadt überfluten.\n\nAber Schutzmaßnahmen kosten Geld. Und du hast nur 450 Mio € zum Start. Plane sparsam!',
        unlocks: ['budget-display', 'population-display'],
      },
      {
        triggerTick: 0,
        title: 'Das Klimasystem',
        text: 'Hier links siehst du die wichtigsten Zahlen:\n\n🌫️ CO₂ steigt aktuell mit ca. 2,5 ppm pro Jahr\n🌡️ Die Temperatur reagiert auf CO₂ — pro Verdoppelung etwa +3°C\n🌊 Wärmer = mehr Eis schmilzt = höherer Meeresspiegel\n\n📌 Wichtig: Diese Insel ist ein vereinfachtes Modell der ganzen Welt. Wenn du hier CO₂ reduzierst, repräsentiert das die globale Klimapolitik. Im echten Leben kann eine einzelne Stadt das Weltklima nicht alleine retten — aber für unsere Lern-Simulation zählt: Was du hier tust, wirkt auch auf das ganze Klima.',
        unlocks: ['climate-display', 'graphs'],
      },
      {
        triggerTick: 0,
        title: 'Strom für die Insel',
        text: 'Deine Insel braucht ⚡ Strom — etwa 1 MW pro 1.000 Einwohner. Oben links siehst du, wie viel gerade gebraucht wird und wie viel deine Kraftwerke liefern (z.B. „6/8 MW").\n\n☀️ Solaranlage = +1 MW (sauber, langsam Wirkung)\n🌬️ Windpark = +3 MW (sauber, teuer)\n🏭 Kohlekraftwerk = +8 MW (sehr viel auf einmal — aber viel CO₂!)\n\nFehlt Strom, wird es kalt: die Leute fällen Bäume und verbrennen das Holz. Das setzt zusätzliches CO₂ frei und tote Bäume binden kein CO₂ mehr.',
        unlocks: ['power-display'],
      },
      {
        triggerTick: 0,
        title: 'Maßnahmen kaufen',
        text: 'Rechts siehst du Maßnahmen, die du kaufen kannst.\n\n🌲 Wald: günstig, kleine Wirkung\n☀️ Solar: mittlerer Preis, sauberer Strom\n🌬️ Wind: teuer, viel sauberer Strom\n🛡️ Deiche: schützen vor Überflutung\n\nJede Maßnahme hat Anschaffungskosten UND jährliche Wartung. Wenn du dich verbaust, kannst du eine Maßnahme mit dem 🗑-Knopf auch wieder abreißen — du bekommst dann die Hälfte des Geldes zurück.',
        unlocks: ['measures'],
      },
      {
        triggerTick: 0,
        title: 'Bereit?',
        text: 'Du kannst die Simulation jederzeit pausieren ⏸️ oder schneller laufen lassen ⏩.\n\nJedes Jahr bekommst du Steuereinnahmen (~150 €, abhängig von der Bevölkerungsgröße) — aber du musst die Wartungskosten deiner Maßnahmen davon bezahlen.\n\nWichtig: Das Klima reagiert träge. Eine Maßnahme heute wirkt sich erst nach Jahren voll aus. Und der Meeresspiegel steigt auch noch, wenn das CO₂ längst sinkt.\n\nDeine Aufgabe: bis 2100 überleben. Halte Temperatur unter +2°C, schütze die Stadt, gehe nicht pleite.\n\nViel Erfolg! 🌍',
        unlocks: ['controls'],
      },
    ])
  }

  private setupVariables(): void {
    this.setVariable('co2Reduction', 0)
    this.setVariable('protection', 0)
    this.setVariable('upkeepTotal', 0)
  }

  private computeClimate(): void {
    // Gleichgewichtstemperatur (was instantan herauskommt)
    this.targetTemp = computeTemperature(this.co2Ppm, 0.3)

    // Aktuelle Temperatur folgt der Zieltemperatur TRÄGE nach
    // (Ozeantraegheit: pro Jahr ~8% Anpassung)
    if (this.currentTemp === 0) {
      this.currentTemp = this.targetTemp
    } else {
      this.currentTemp += (this.targetTemp - this.currentTemp) * 0.08
    }
    // Untergrenze: keine künstliche Abkühlung unter vorindustriell
    if (this.currentTemp < KlimawaechterGame.TEMP_FLOOR) {
      this.currentTemp = KlimawaechterGame.TEMP_FLOOR
    }

    // Meeresspiegel: ~30 cm pro °C über vorindustriell (vereinfacht aus IPCC AR6 SSP3-7.0)
    // Sehr träge: einmal angestiegen, sinkt er kaum (committed rise)
    const deltaT = Math.max(0, this.currentTemp - 15)
    const targetSea = deltaT * 30
    if (targetSea > this.seaLevelCm) {
      // Meer steigt sichtbar
      this.seaLevelCm += (targetSea - this.seaLevelCm) * 0.08
    } else {
      // Aber sinkt nur ganz langsam (Eis-Schmelze nicht reversibel)
      this.seaLevelCm += (targetSea - this.seaLevelCm) * 0.005
    }

    // Überflutung: ab 35 cm effektivem Anstieg verlieren wir Häuser
    const protectionTotal = this.getVariable('protection')
    const effectiveRise = Math.max(0, this.seaLevelCm - protectionTotal)
    if (effectiveRise > 35) {
      this.floodedHouses = Math.min(100, (effectiveRise - 35) * 0.9)
    } else {
      this.floodedHouses = 0
    }

    this.setResource('co2', this.co2Ppm)
    this.setResource('temperature', this.currentTemp)
    this.setResource('sealevel', this.seaLevelCm)
    this.setResource('flooded', this.floodedHouses)
  }

  /** CO₂-Emissionen pro Jahr (BAU). Steigen leicht bis 2050, dann konstant. */
  private getEmissionsThisYear(): number {
    // 2025: 2.5 ppm/Jahr → 2050: 3.2 ppm/Jahr (BAU-Wachstum) → konstant
    const rampYears = 25
    const startEm = 2.5
    const peakEm = 3.2
    if (this.tick < rampYears) {
      return startEm + (peakEm - startEm) * (this.tick / rampYears)
    }
    return peakEm
  }

  /**
   * Anwender*in baut eine Maßnahme.
   *
   * Wenn `position` übergeben wird (Baueditor-Modus), wird die Maßnahme an
   * dieser Stelle eingetragen — der Renderer liest später die `positions`
   * der OwnedMeasure aus und platziert dort. Ohne Position fällt der
   * Renderer auf seine zufällige Auto-Platzierung zurück.
   */
  buyMeasure(measureId: string, position?: { x: number; z: number }): boolean {
    const m = MEASURES.find(x => x.id === measureId)
    if (!m) return false
    const effectiveCost = Math.round(m.cost * this.researchDiscount)
    const budget = this.getResource('budget')
    if (budget < effectiveCost) {
      this.addEvent('error', `Nicht genug Budget für ${m.name}`, 'warning')
      return false
    }

    this.changeResource('budget', -effectiveCost)

    // Maßnahme registrieren.
    // Spezialfall sand-fill: jede Instanz braucht ein eigenes builtAt
    // (Erosion läuft je Instanz unterschiedlich schnell ab), deshalb
    // immer ein NEUER OwnedMeasure-Eintrag mit count=1.
    let owned: OwnedMeasure
    if (measureId === 'sand-fill') {
      owned = { measureId, builtAt: this.tick, count: 1, positions: [] }
      this.measures.push(owned)
    } else {
      const existing = this.measures.find(x => x.measureId === measureId)
      if (existing) {
        owned = existing
      } else {
        owned = { measureId, builtAt: this.tick, count: 0, positions: [] }
        this.measures.push(owned)
      }
      owned.count++
    }
    if (!owned.positions) owned.positions = []
    if (position) {
      owned.positions.push({ x: position.x, z: position.z })
    } else {
      owned.positions.push(null as unknown as { x: number; z: number })
    }

    // Variablen updaten
    this.recalcMeasureEffects()

    this.addEvent('build', `${m.emoji} ${m.name} gebaut (−${effectiveCost} Mio €)`, 'success')
    this.notify()
    return true
  }

  /**
   * Maßnahme abreißen: entfernt die ZULETZT gebaute Instanz dieser Maßnahme
   * (inkl. ihrer Position), erstattet 50 % der Baukosten. Wartung und
   * Effekte werden neu berechnet.
   */
  demolishMeasure(measureId: string): boolean {
    const m = MEASURES.find(x => x.id === measureId)
    if (!m) return false
    const owned = this.measures.find(x => x.measureId === measureId)
    if (!owned || owned.count <= 0) return false

    owned.count--
    if (owned.positions && owned.positions.length > 0) {
      owned.positions.pop()
    }
    if (owned.count === 0) {
      this.measures = this.measures.filter(x => x.measureId !== measureId)
    }

    const refund = Math.round(m.cost * this.researchDiscount * 0.5)
    this.changeResource('budget', refund)
    this.recalcMeasureEffects()
    this.addEvent('demolish', `🗑 ${m.name} abgerissen (+${refund} Mio €)`, 'info')
    this.notify()
    return true
  }

  /**
   * Berechnet die aggregierten Effekte aller gebauten Maßnahmen neu.
   * Wird bei buy/demolish UND einmal pro Tick aufgerufen, damit zeitabhängige
   * Effekte (Sand-Erosion) korrekt fortlaufen.
   */
  private recalcMeasureEffects(): void {
    let totalCO2Red = 0
    let totalProtection = 0
    let totalUpkeep = 0
    let totalPower = 0
    for (const owned of this.measures) {
      const m = MEASURES.find(x => x.id === owned.measureId)
      if (!m) continue
      totalCO2Red += m.co2Reduction * owned.count
      totalUpkeep += m.upkeep * owned.count
      totalPower += (m.powerOutput ?? 0) * owned.count
      if (owned.measureId === 'sand-fill') {
        // Sand-Aufschüttung erodiert: 2 cm pro Jahr, nach 9 Jahren weg
        const ageYears = Math.max(0, this.tick - owned.builtAt)
        const remaining = Math.max(0, m.protection - ageYears * 2)
        totalProtection += remaining * owned.count
      } else {
        totalProtection += m.protection * owned.count
      }
    }
    this.setVariable('co2Reduction', totalCO2Red)
    this.setVariable('protection', totalProtection)
    this.setVariable('upkeepTotal', totalUpkeep)
    this.setResource('power', totalPower)
  }

  getOwnedMeasures(): OwnedMeasure[] {
    return this.measures
  }

  getStartYear(): number {
    return START_YEAR
  }

  getCurrentYear(): number {
    return START_YEAR + this.tick
  }

  getMeasureCount(measureId: string): number {
    return this.measures.find(x => x.measureId === measureId)?.count ?? 0
  }

  /**
   * Voraussichtliche Jahres-Bilanz für die Status-Box.
   * Liefert Einnahmen, Wartung, sonstige Klima-Folgen und Netto.
   * Wird für die UI-Vorschau berechnet (nicht für die echte Tick-Rechnung).
   */
  getYearlyBalance(): { income: number; tourism: number; upkeep: number; climate: number; net: number } {
    const popRatio = this.getResource('population') / 10000
    const income = Math.round(this.diff.incomePer10k * popRatio)
    const tourism = (this.tourismMode && this.seaLevelCm < 40) ? Math.round(35 * popRatio) : 0
    const upkeep = this.getVariable('upkeepTotal')
    // Klima-Folgekosten (Versalzung etc.) — abhängig vom Schwierigkeitsgrad
    let climate = 0
    if (this.seaLevelCm > 25) {
      const severity = Math.min(1, (this.seaLevelCm - 25) / 40)
      climate = Math.round(severity * 12 * this.diff.climateDamageMul)
    }
    const net = income + tourism - upkeep - climate
    return { income, tourism, upkeep, climate, net }
  }

  /**
   * Hilfsmethode für Anzeige: Steuereinnahmen pro Jahr beim aktuellen Level.
   */
  getIncomeAtCurrentLevel(): number {
    const popRatio = this.getResource('population') / 10000
    return Math.round(this.diff.incomePer10k * popRatio)
  }

  protected simulateTick(): void {
    // 0. Maßnahmen-Effekte neu berechnen (für Sand-Erosion etc.)
    this.recalcMeasureEffects()

    // 1. Steuereinnahmen (skaliert mit Bevölkerung, abhängig vom Schwierigkeitsgrad)
    const popRatio = this.getResource('population') / 10000
    const income = Math.round(this.diff.incomePer10k * popRatio)
    this.changeResource('budget', income)

    // 1b. Tourismus-Modus: solange der Strand existiert, bringt er Extra-Einnahmen
    //     UND zusätzliche CO₂-Belastung. Sobald der Strand vom Meer verschluckt
    //     wird (>40 cm), bricht der Tourismus zusammen → Bevölkerung wandert ab.
    if (this.tourismMode) {
      if (this.seaLevelCm < 40) {
        // Strand noch da — Tourismus liefert
        this.changeResource('budget', Math.round(35 * popRatio))
        this.co2Ppm += 0.6
      } else {
        // Strand weg — Tourismus tot
        this.changeResource('population', -Math.round(60 * popRatio))
        if (!this.eventFired('tourism-collapse')) {
          this.addEvent(
            'tourism-collapse',
            '🏖 Der Strand ist vom Meer verschluckt — der Tourismus bricht zusammen!',
            'danger',
            'sealevel',
          )
        }
      }
    }

    // 2. Wartungskosten
    const upkeep = this.getVariable('upkeepTotal')
    this.changeResource('budget', -upkeep)

    // 3. CO₂ entwickelt sich (Emissionen steigen leicht über die Zeit)
    const emissions = this.getEmissionsThisYear()
    const reduction = this.getVariable('co2Reduction')
    const netChange = emissions - reduction
    this.co2Ppm = Math.max(KlimawaechterGame.CO2_FLOOR, this.co2Ppm + netChange)

    // 4. Klima neu berechnen
    this.computeClimate()

    // 4b. STROM-BILANZ
    // Bedarf = 1 MW pro 1.000 Einwohner. Kapazität = Σ powerOutput.
    // Folgen + Schonfrist hängen vom Schwierigkeitsgrad ab.
    const powerCapacity = this.getResource('power')
    const powerDemand = Math.max(0, Math.round(this.getResource('population') / 1000))
    if (powerDemand > powerCapacity) {
      this.blackoutStreak++
      if (!this.eventFired('blackout-first')) {
        this.addEvent(
          'blackout-first',
          '⚡ Achtung — die Insel hat zu wenig Strom! Baue jetzt ein Kraftwerk, sonst wird es bald kalt.',
          'warning',
          'blackout',
        )
      }
      // Folgen erst NACH der Schonfrist (Level 1: 3 Jahre, Level 2: 1, Level 3: 0)
      if (this.blackoutStreak > this.diff.blackoutGrace) {
        this.treesChoppedForHeat += this.diff.treesPerBlackout
        this.co2Ppm += this.diff.co2BlackoutPerYear
      }
      // Eskalation: 2 Jahre nach Folgenbeginn auch Bevölkerungs-Abwanderung
      const popLossThreshold = this.diff.blackoutGrace + 3
      if (this.blackoutStreak >= popLossThreshold) {
        const drift = Math.round(25 * popRatio * this.diff.popLossMul * (this.blackoutStreak - popLossThreshold + 1))
        this.changeResource('population', -drift)
      }
    } else {
      if (this.blackoutStreak > this.diff.blackoutGrace && !this.eventFired('blackout-recovered')) {
        this.addEvent(
          'blackout-recovered',
          '💡 Der Strom ist wieder da. Aber die gefällten Bäume kommen nicht zurück.',
          'info',
        )
      }
      this.blackoutStreak = 0
    }

    // 5. Schäden bei zu hoher Überflutung
    if (this.floodedHouses > 0) {
      // Bevölkerung sinkt langsam (mit Level-Mul)
      const popLoss = Math.round(this.floodedHouses * 5 * this.diff.popLossMul)
      this.changeResource('population', -popLoss)
    }

    // 5b. Weitere Folgen des Meeresspiegelanstiegs auch OHNE Hausüberflutung.
    // Schwere skaliert mit climateDamageMul / popLossMul des Schwierigkeitsgrads.
    if (this.seaLevelCm > 25) {
      const severity = Math.min(1, (this.seaLevelCm - 25) / 40)
      // Entsalzung kostet Geld
      this.changeResource('budget', -Math.round(severity * 12 * this.diff.climateDamageMul))
      // Langsamer Bevölkerungsrückgang durch schlechtere Lebensbedingungen
      if (Math.random() < severity * this.diff.popLossMul) {
        this.changeResource('population', -Math.round(severity * 15 * this.diff.popLossMul))
      }
    }

    // 6. Events über die 75 Jahre verteilt
    if (this.tick === 2 && !this.eventFired('model-hint')) {
      this.addEvent(
        'model-hint',
        '📚 Diese Insel steht stellvertretend für das gesamte Weltklima.',
        'info',
        'wedge',
      )
    }
    if (this.tick === 5 && !this.eventFired('warning-1')) {
      this.addEvent('warning-1', '⚠️ Wissenschaftler warnen: CO₂ steigt weiter!', 'warning', 'co2')
    }
    if (this.tick === 15 && this.currentTemp > 15.5 && !this.eventFired('warning-temp1')) {
      this.addEvent('warning-temp1', '🌡️ Die Temperatur ist um 0,5°C gestiegen.', 'warning', 'temperature')
    }
    if (this.tick === 25 && this.currentTemp > 16 && !this.eventFired('warning-temp2')) {
      this.addEvent('warning-temp2', '🌡️ +1°C: Erste Hitzewellen werden häufiger.', 'warning', 'temperature')
    }
    if (this.tick === 40 && this.currentTemp > 17 && !this.eventFired('warning-temp3')) {
      this.addEvent('warning-temp3', '🔥 +2°C: Pariser Klimaziel überschritten!', 'danger', 'temperature')
    }
    if (this.floodedHouses > 5 && this.floodedHouses < 10 && !this.eventFired('flood-1')) {
      this.addEvent('flood-1', '🌊 Erste Häuser an der Küste sind betroffen.', 'warning', 'sealevel')
    }
    if (this.floodedHouses > 20 && !this.eventFired('flood-2')) {
      this.addEvent('flood-2', '🌊 Massive Überflutung — viele Bewohner haben ihr Zuhause verloren!', 'danger', 'sealevel')
    }
    // Neue Folgen: Vegetation stirbt, Trinkwasser versalzt
    if (this.seaLevelCm > 20 && !this.eventFired('vegetation-dying')) {
      this.addEvent(
        'vegetation-dying',
        '🌿 Mangroven und Küstenwälder sterben — Salzwasser versalzt die Böden.',
        'warning',
        'vegetation',
      )
    }
    if (this.seaLevelCm > 35 && !this.eventFired('drinking-water')) {
      this.addEvent(
        'drinking-water',
        '💧 Die Brunnen liefern immer mehr salziges Wasser — Trinkwasser wird knapp.',
        'danger',
        'drinking_water',
      )
    }
    if (this.currentTemp > 16.8 && !this.eventFired('glacier-melt')) {
      this.addEvent(
        'glacier-melt',
        '🏔 Der Gletscher am Vulkan schrumpft merklich.',
        'warning',
        'glacier',
      )
    }
    if (this.tick === 30 && this.getResource('budget') > 500 && !this.eventFired('praise-1')) {
      this.addEvent('praise-1', '👏 Die Bevölkerung lobt deine kluge Haushaltsführung.', 'success')
    }
    if (this.tick === 50 && this.getVariable('co2Reduction') > 3 && !this.eventFired('praise-co2')) {
      this.addEvent('praise-co2', '🌿 Deine Klimaschutzmaßnahmen zeigen Wirkung!', 'success')
    }
    if (this.tick === 60 && this.currentTemp < 16 && !this.eventFired('praise-temp')) {
      this.addEvent('praise-temp', '🏆 Internationale Anerkennung für deine Klimapolitik!', 'success')
    }

    // 7. Bürger-Beschwerden zu definierten Zeitpunkten
    this.maybeTriggerCitizenEvent()
  }

  /**
   * Schaut, ob ein Bürger-Event passend zur Simulationslage gefeuert werden soll.
   * Jedes Event nur einmal — `eventFired` verhindert Duplikate.
   *
   * Pro Schwierigkeitsgrad können Events ausgeblendet (skipCitizenEvents) und
   * zeitlich verschoben (citizenEventTickOffset) werden.
   */
  private maybeTriggerCitizenEvent(): void {
    if (this.getPendingCitizenEvent()) return  // schon eines offen

    // Hilfsfunktion: prüft alle Voraussetzungen (Tick, eventFired, Skip-Liste)
    const offset = this.diff.citizenEventTickOffset || 0
    const skipped = new Set(this.diff.skipCitizenEvents || [])
    const shouldFire = (id: string, baseTick: number, extraCondition: boolean = true): boolean => {
      if (skipped.has(id)) return false
      if (this.tick !== baseTick + offset) return false
      if (this.eventFired(id)) return false
      return extraCondition
    }

    // === Lina, Fischerin ===
    if (shouldFire('citizen-fisher', 15)) {
      this.triggerCitizenEvent({
        id: 'citizen-fisher',
        character: '🎣',
        title: 'Lina, die Fischerin',
        message: 'Bürgermeister*in! Das Wasser wird wärmer, die Fische ziehen weg. Mein Boot ist alt — wir brauchen Hilfe oder wir müssen aufgeben.',
        choices: [
          {
            label: '120 Mio € für moderne Boote zahlen',
            description: 'Bevölkerung bleibt stabil, Budget −120 Mio €',
            apply: (g) => g.changeResource('budget', -120),
          },
          {
            label: 'Sich auf Tourismus umstellen lassen',
            description: 'Kostet nichts. Bringt jährlich Extra-Einnahmen — solange der Strand bleibt. Aber: Mehr Touristen = mehr CO₂. Verschwindet der Strand, kollabiert die Wirtschaft.',
            apply: (g) => {
              g.changeResource('population', -200)
              ;(g as KlimawaechterGame).tourismMode = true
              g.addEvent(
                'tourism-on',
                '🏖 Die Insel stellt sich auf Tourismus um — Hotels werden gebaut.',
                'info',
              )
            },
          },
          {
            label: 'Nichts tun',
            description: '−400 Bevölkerung, dafür kein Geldverlust',
            apply: (g) => g.changeResource('population', -400),
          },
        ],
      })
    }

    // === Dr. Hassan, Forscherin ===
    if (shouldFire('citizen-scientist', 9)) {
      this.triggerCitizenEvent({
        id: 'citizen-scientist',
        character: '👩‍🔬',
        title: 'Dr. Hassan, Klimaforscherin',
        message: 'Wir haben einen neuen Tonnen-Zähler entwickelt, der CO₂-Senken effizienter macht. Mit einem Forschungszentrum schaffen wir es, alle künftigen Maßnahmen günstiger zu bauen.',
        choices: [
          {
            label: 'Forschungszentrum bauen (300 Mio €)',
            description: 'Alle künftigen Maßnahmen 20 % billiger',
            apply: (g) => {
              g.changeResource('budget', -300)
              ;(g as KlimawaechterGame).researchDiscount = 0.8
            },
          },
          {
            label: 'Antrag ablehnen',
            description: 'Nichts passiert',
            apply: () => {},
          },
        ],
      })
    }

    // === Yusuf, Bauer ===
    if (shouldFire('citizen-farmer', 14, this.currentTemp > 15.8)) {
      this.triggerCitizenEvent({
        id: 'citizen-farmer',
        character: '👨‍🌾',
        title: 'Yusuf, Bauer aus dem Süden',
        message: 'Die Felder vertrocknen! Letztes Jahr hatten wir kaum Ernte. Wenn wir keine Bewässerung bekommen, müssen wir abwandern.',
        choices: [
          {
            label: 'Bewässerungssystem bauen (180 Mio €)',
            description: 'Bevölkerung stabil',
            apply: (g) => g.changeResource('budget', -180),
          },
          {
            label: 'Salzresistente Sorten subventionieren (60 Mio €)',
            description: '−100 Menschen, aber langfristig stabiler',
            apply: (g) => {
              g.changeResource('budget', -60)
              g.changeResource('population', -100)
            },
          },
          {
            label: 'Nichts tun',
            description: '−500 Menschen ziehen weg',
            apply: (g) => g.changeResource('population', -500),
          },
        ],
      })
    }

    // === Maria, Hotel ===
    if (shouldFire('citizen-tourism', 20, this.seaLevelCm > 15)) {
      this.triggerCitizenEvent({
        id: 'citizen-tourism',
        character: '🏖️',
        title: 'Maria, Hotelbesitzerin',
        message: 'Der Strand wird immer kleiner. Touristen bleiben weg, meine Hotels stehen halbleer. Wir müssen etwas gegen den Meeresspiegel tun!',
        choices: [
          {
            label: 'Strand künstlich aufschütten (50 Mio €)',
            description: 'Einmaliger Effekt: +5 cm Schutz',
            apply: (g) => {
              g.changeResource('budget', -50)
              const cur = g.getVariable('protection')
              ;(g as any).variables['protection'] = cur + 5
            },
          },
          {
            label: '"Wir können das Meer nicht aufhalten."',
            description: '−300 Menschen wandern ab',
            apply: (g) => g.changeResource('population', -300),
          },
        ],
      })
    }

    // === Lia, 14 Jahre ===
    if (shouldFire('citizen-youth', 28, this.getVariable('co2Reduction') < 1)) {
      this.triggerCitizenEvent({
        id: 'citizen-youth',
        character: '👧',
        title: 'Lia, 14 Jahre',
        message: 'Sie planen unsere Zukunft! Wir haben heute gestreikt. Wir wollen, dass Sie endlich ernst machen mit dem Klimaschutz — sonst werden wir weiterstreiken.',
        choices: [
          {
            label: '"Ich verspreche, mehr zu tun" — sofort 1 Solar gratis',
            description: '+1 kostenlose Solaranlage',
            apply: (g) => {
              const sub = g as KlimawaechterGame
              const existing = sub['measures'].find((m: any) => m.measureId === 'solar')
              if (existing) existing.count++
              else sub['measures'].push({ measureId: 'solar', builtAt: sub['tick'], count: 1 })
              ;(sub as any).recalcMeasureEffects()
            },
          },
          {
            label: 'Streik aussitzen',
            description: '−150 Bevölkerung (Jugend wandert ab)',
            apply: (g) => g.changeResource('population', -150),
          },
        ],
      })
    }

    // === Konzernchef Vogel ===
    if (shouldFire('citizen-industry', 38)) {
      this.triggerCitizenEvent({
        id: 'citizen-industry',
        character: '🏭',
        title: 'Konzernchef Vogel',
        message: 'Wir bringen Arbeitsplätze auf die Insel — wenn Sie uns Steuererleichterung geben. Sonst müssen wir abwandern.',
        choices: [
          {
            label: 'Subventionen zahlen (250 Mio €)',
            description: '+500 Bevölkerung, +5 ppm CO₂ einmalig',
            apply: (g) => {
              g.changeResource('budget', -250)
              g.changeResource('population', 500)
              const sub = g as KlimawaechterGame
              sub['co2Ppm'] += 5
            },
          },
          {
            label: 'Klar nein',
            description: '−200 Bevölkerung verlässt die Insel',
            apply: (g) => g.changeResource('population', -200),
          },
          {
            label: 'Bedingung: Nur wenn klimaneutral',
            description: 'Konzern lehnt ab, nichts passiert',
            apply: () => {},
          },
        ],
      })
    }

    // === Anna, Bergdorf ===
    if (shouldFire('citizen-mountain', 50, this.currentTemp > 16.5)) {
      this.triggerCitizenEvent({
        id: 'citizen-mountain',
        character: '🏔',
        title: 'Anna vom Bergdorf',
        message: 'Der Gletscher am Vulkan schmilzt rasant. Bei Starkregen fließt das Wasser in unser Dorf. Wir brauchen Schutzwälle!',
        choices: [
          {
            label: 'Lawinen-Schutzwall bauen (55 Mio €)',
            description: '+4 cm Schutz, Dorf bleibt sicher',
            apply: (g) => {
              g.changeResource('budget', -55)
              const cur = g.getVariable('protection')
              ;(g as any).variables['protection'] = cur + 4
              ;(g as KlimawaechterGame).hasMountainShield = true
            },
          },
          {
            label: 'Bergdorf evakuieren',
            description: '−400 Menschen, aber Geld gespart',
            apply: (g) => g.changeResource('population', -400),
          },
        ],
      })
    }
  }

  private firedEvents = new Set<string>()
  private eventFired(id: string): boolean {
    if (this.firedEvents.has(id)) return true
    this.firedEvents.add(id)
    return false
  }

  protected checkLossCondition(): boolean {
    if (this.getResource('budget') < -500) return true
    if (this.floodedHouses > 50) return true
    return false
  }

  // === Subclass-State persistieren ===
  protected serializeSubclass(): Record<string, unknown> {
    return {
      co2Ppm: this.co2Ppm,
      currentTemp: this.currentTemp,
      targetTemp: this.targetTemp,
      seaLevelCm: this.seaLevelCm,
      committedSeaCm: this.committedSeaCm,
      floodedHouses: this.floodedHouses,
      researchDiscount: this.researchDiscount,
      tourismMode: this.tourismMode,
      hasMountainShield: this.hasMountainShield,
      treesChoppedForHeat: this.treesChoppedForHeat,
      blackoutStreak: this.blackoutStreak,
      difficulty: this.diff.id,
      measures: this.measures,
      firedEvents: Array.from(this.firedEvents),
    }
  }

  protected deserializeSubclass(data: Record<string, unknown>): void {
    if (typeof data.co2Ppm === 'number')         this.co2Ppm = data.co2Ppm
    if (typeof data.currentTemp === 'number')    this.currentTemp = data.currentTemp
    if (typeof data.targetTemp === 'number')     this.targetTemp = data.targetTemp
    if (typeof data.seaLevelCm === 'number')     this.seaLevelCm = data.seaLevelCm
    if (typeof data.committedSeaCm === 'number') this.committedSeaCm = data.committedSeaCm
    if (typeof data.floodedHouses === 'number')  this.floodedHouses = data.floodedHouses
    if (typeof data.researchDiscount === 'number') this.researchDiscount = data.researchDiscount
    if (typeof data.tourismMode === 'boolean')      this.tourismMode = data.tourismMode
    if (typeof data.hasMountainShield === 'boolean') this.hasMountainShield = data.hasMountainShield
    if (typeof data.treesChoppedForHeat === 'number') this.treesChoppedForHeat = data.treesChoppedForHeat
    if (typeof data.blackoutStreak === 'number')      this.blackoutStreak = data.blackoutStreak
    if (data.difficulty === 1 || data.difficulty === 2 || data.difficulty === 3) {
      this.diff = DIFFICULTY_LEVELS[data.difficulty]
    }
    if (Array.isArray(data.measures))            this.measures = data.measures as OwnedMeasure[]
    if (Array.isArray(data.firedEvents))         this.firedEvents = new Set(data.firedEvents as string[])
    // Variablen aus den Maßnahmen neu berechnen (upkeep, co2Reduction, protection)
    this.recalcMeasureEffects()
  }
}
