// selfplay.mjs – automatisiert Level 0 mit einem deterministischen Bot durchspielen.
// Nutzt die öffentliche window.__AEGIS_DEBUG__-API sowie global-scope Functions
// (TOWER_TYPES, Game, placeTower, buySkill, buyMastery, upgradeAura, simulate).
//
// Copyright (C) 2026 Lila-Kuh
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
// STRATEGIE:
//   1. ALLE Türme entsperren (Game.completedTowerLevels = 99)
//   2. Sniper (range 390) als Primär-DPS – deckt das gesamte Spielfeld ab
//   3. Kryo (Freeze) + Tesla (Chain) + Quantum (Pierce) für Utility
//   4. Support-Auras (Aegis/Chrono/Range) für +10%/Stufe auf nahe Türme
//   5. Zwischen Wellen: ALLE Skills auf max, Mastery, Aura-Level
//
// WICHTIG: In page.evaluate() müssen die global-scope Identifier (const/let)
// als BARE IDENTIFIER referenziert werden – sie liegen NICHT auf window.
//
// Usage: node selfplay.mjs [episodes=10]
import { chromium } from 'playwright';
import path from 'path';
import fs from 'fs';
import url from 'url';

const HERE = path.dirname(url.fileURLToPath(import.meta.url));
const ROOT = path.resolve(HERE, '..');
const HTML = path.resolve(ROOT, 'Aegis-Labyrinth.html').replace(/\\/g, '/');
const MAX_EPISODES = Math.max(1, parseInt(process.argv[2] || '10', 10));
const TICKS_PER_EPISODE = 7200; // ~120s simulated per episode

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
page.on('pageerror', (e) => console.error('[PAGE ERROR]', e.message));

await page.goto('file:///' + HTML, { waitUntil: 'load' });
await page.waitForFunction(() => window.__AEGIS_DEBUG__ !== undefined, { timeout: 15000 });
console.log('✅ Aegis-Labyrinth geladen');

const results = [];
for (let ep = 0; ep < MAX_EPISODES; ep++) {
  const result = await page.evaluate(({ maxTicks }) => {
    const d = window.__AEGIS_DEBUG__;
    const dt = 1 / 60;

    const obs = () => {
      const s = d.getState();
      return {
        state: s.state,
        credits: s.credits,
        lives: s.lives,
        waveIndex: s.waveIndex,
        totalWaves: s.totalWaves,
        waveActive: s.waveActive,
        enemies: s.enemies,
        towers: s.towers,
        kills: s.kills,
        leaked: s.leaked,
      };
    };

    // ─── TOWER PLACEMENT: Sniper-heavy, alle freien Zellen ───
    const placeTowers = () => {
      const towerTypes = TOWER_TYPES.filter(isTowerAvailable).filter((t) => isTowerUnlocked(t));
      if (towerTypes.length === 0) return 0;

      // Pfad-Zellen
      const path = [];
      for (let y = 0; y < MAP_H; y++)
        for (let x = 0; x < MAP_W; x++) if (Game.grid[y][x] === 1) path.push({ x, y });
      if (path.length === 0) return 0;

      // Alle freien Zellen, sortiert nach Distanz zum Pfad
      const candidates = [];
      for (let y = 0; y < MAP_H; y++) {
        for (let x = 0; x < MAP_W; x++) {
          if (Game.grid[y][x] !== 0) continue;
          let minDist = Infinity;
          for (const p of path) {
            const dist = Math.hypot(p.x - x, p.y - y);
            if (dist < minDist) minDist = dist;
          }
          candidates.push({ x, y, dist: minDist });
        }
      }
      candidates.sort((a, b) => a.dist - b.dist);

      // Turm-Zuordnung: Sniper dominiert (range 390 = ganzer Map)
      const sniper = towerTypes.find(t => t.id === 'sniper');
      const quantum = towerTypes.find(t => t.id === 'quantum');
      const tesla = towerTypes.find(t => t.id === 'tesla');
      const kryo = towerTypes.find(t => t.id === 'kryo');
      const nuclear = towerTypes.find(t => t.id === 'nuclear');
      const supports = towerTypes.filter(t => t.support);

      // Reihenfolge: Utility zuerst (nah am Pfad), dann Sniper (weit, deckt alles)
      const queue = [];
      if (kryo) for (let i = 0; i < 6; i++) queue.push(kryo);
      if (tesla) for (let i = 0; i < 6; i++) queue.push(tesla);
      if (quantum) for (let i = 0; i < 4; i++) queue.push(quantum);
      if (nuclear) for (let i = 0; i < 4; i++) queue.push(nuclear);
      if (supports.length > 0) for (let i = 0; i < 6; i++) queue.push(supports[i % supports.length]);
      // Rest: Sniper (range 390!)
      while (queue.length < candidates.length) {
        queue.push(sniper || towerTypes[0]);
      }

      let placed = 0;
      for (let i = 0; i < candidates.length; i++) {
        const c = candidates[i];
        const def = queue[i];
        const ok = placeTower(def.id, c.x, c.y);
        if (ok === true) placed++;
      }
      return placed;
    };

    // ─── UPGRADES: Alle Skills max, Mastery, Aura-Level ───
    const upgradeAll = () => {
      let count = 0;
      for (const t of Game.towers) {
        if (t.def.support) {
          while (upgradeAura(t)) { count++; if (t.auraLevel >= 10) break; }
        } else {
          for (const sk of t.def.skills) {
            const maxR = sk.maxLevel || 5;
            while (t.rank(sk.skillId) < maxR && buySkill(t, sk.skillId)) { count++; }
          }
          while (canBuyMastery(t) && buyMastery(t)) { count++; }
        }
      }
      return count;
    };

    // ─── SETUP ───
    d.selectLevel(0);
    if (Game.state !== 'playing') Game.state = 'playing';
    Game.completedTowerLevels = 99; // *** KRITISCH: ALLE Türme entsperren ***

    // 1. Türme platzieren
    d.setCredits(9999999);
    const placed = placeTowers();

    // 2. Alle Upgrades
    d.setCredits(9999999);
    const ups1 = upgradeAll();

    // 3. Welle 1 starten
    d.startWave();

    // ─── SIMULATION ───
    let maxKills = 0;
    let waveReached = 0;
    let totalUps = ups1;

    for (let tick = 0; tick < maxTicks; tick++) {
      simulate(dt);
      const s = obs();
      maxKills = Math.max(maxKills, s.kills);
      waveReached = Math.max(waveReached, s.waveIndex);

      if (s.state === 'victory' || s.state === 'defeat') break;

      // Zwischenwellen: upgraden + evtl. neue Türme + nächste Welle
      if (!s.waveActive && s.enemies === 0 && Game.state === 'playing') {
        d.setCredits(9999999);
        totalUps += upgradeAll();
        placeTowers();
        d.setCredits(9999999);
        totalUps += upgradeAll();
        d.startWave();
      }
    }

    const final = obs();
    return {
      placed,
      towerCount: Game.towers.length,
      maxKills,
      waveReached,
      totalUpgrades: totalUps,
      finalState: final.state,
      finalKills: final.kills,
      finalLeaked: final.leaked,
      finalLives: final.lives,
    };
  }, { maxTicks: TICKS_PER_EPISODE });

  results.push(result);
  const icon = result.finalState === 'victory' ? '✅' : result.finalState === 'defeat' ? '❌' : '⏸️';
  console.log(
    `${icon} EP ${String(ep + 1).padStart(2)}/${MAX_EPISODES}: state=${result.finalState} ` +
    `wave=${result.waveReached} kills=${result.maxKills} ` +
    `towers=${result.towerCount} ups=${result.totalUpgrades} lives=${result.finalLives} leaked=${result.finalLeaked}`
  );

  // Reset zwischen Episoden
  await page.evaluate(() => {
    const d = window.__AEGIS_DEBUG__;
    d.selectLevel(0);
    Game.state = 'playing';
    Game.towers.length = 0;
    Game.enemies.length = 0;
    Game.projectiles.length = 0;
    Game.particles.length = 0;
    Game.beams.length = 0;
    if (Game.nanites) Game.nanites.length = 0;
  });
}

const wins = results.filter((r) => r.finalState === 'victory').length;
const losses = results.filter((r) => r.finalState === 'defeat').length;
const summary = {
  total: results.length,
  wins,
  losses,
  winRate: results.length ? (wins / results.length) * 100 : 0,
  avgKills: results.length ? Math.round(results.reduce((a, r) => a + r.maxKills, 0) / results.length) : 0,
  avgWaves: results.length ? Math.round((results.reduce((a, r) => a + r.waveReached, 0) / results.length) * 10) / 10 : 0,
};

const outFile = path.join(ROOT, 'selfplay-results.json');
fs.writeFileSync(outFile, JSON.stringify({ timestamp: new Date().toISOString(), episodes: results, summary }, null, 2));
console.log(`✅ Self-Play done → ${outFile}`);
console.log(`   ${summary.wins}/${summary.total} wins (${summary.winRate.toFixed(1)}%) · avg kills=${summary.avgKills} · avg wave=${summary.avgWaves}`);

await browser.close();