-- Driver: one STADIUM screenshot per species, every one of the 151 standing
-- on the ENEMY mark in its standby loop.
--
-- stadium_shots.lua photographs a handful of deliberately awkward cases; this
-- is the exhaustive sweep behind it. What it exists to catch is the class of
-- fault that is per-MODEL and invisible to the headless QA pass -- a texture
-- that comes out magenta, a mon standing in the floor or floating over it, a
-- silhouette that is subtly the wrong shape -- none of which is a number
-- stadium_anim_qa.lua can measure.
--
--   SHOT_DIR=mods/DramaticShapeVoxelMod/.claude/stadium_verify \
--   POKEPORT_SPEED=4 \
--   POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/stadium_all_shots.lua \
--   lovec.exe .
--
-- Files land as NNN_species.png in dex order. Run from the PROJECT ROOT.
--
-- ------- everything but the foe is nailed down
--
-- The whole value of 151 shots is that they are comparable, so the staging is
-- identical in every one: same map, same cells, same hour (the day/night
-- cycle would otherwise relight every shot), same rung, and the same small
-- Pokemon on the player's mark -- DIGLETT, chosen because it is the shortest
-- model in the set and so hides the least of the arena behind it. Whatever
-- differs between two of these shots is the foe.
--
-- ------- the model/sprite verdict is the other half
--
-- A species whose pack will not load, or whose animation data the packer
-- declined, does not error: StadiumMon.setSpecies answers false and the mode
-- quietly stands the Game Boy's flat pic on the tile instead. That is the
-- correct behaviour and it is nearly invisible in a screenshot at a glance --
-- which is exactly why it needs counting rather than looking. Stadium.showing
-- is asked for every species and the ones that decline are listed at the end.
return function(game)
  local U = dofile("tests/drivers/util.lua")
  local DIR = os.getenv("SHOT_DIR") or ".claude/stadium_verify"
  local Pokemon = require("src.pokemon.Pokemon")
  local BattleState = require("src.battle.BattleState")

  local exports = game.mods and game.mods.exports
  local lib = exports and exports.DRAMATIC_SHAPE and exports.DRAMATIC_SHAPE.lib
  if not lib then
    U.log("DRAMATIC_SHAPE is not loaded -- enable it and run again")
    return
  end
  local Battles = lib.require("OverworldBattle")
  local Stadium = lib.require("Stadium")
  local Pack = lib.require("StadiumPack")
  local Install = lib.require("StadiumInstall")

  -- every real species the merged data carries, in dex order
  local species = {}
  for id, def in pairs(game.data.pokemon) do
    if type(id) == "string" and type(def) == "table"
       and def.dex and def.dex >= 1 and def.dex <= 151 then
      species[#species + 1] = { id = id, dex = def.dex }
    end
  end
  table.sort(species, function(a, b) return a.dex < b.dex end)

  game.save.player.name = "RED"

  -- ONTO THE MAP FIRST: the model build (if one is owed) is pushed from the
  -- mod's update hook on the first frame the overworld is the top state, so
  -- waiting for it before teleporting waits forever.
  U.teleport(game, "ROUTE_1", 5, 8, "down")

  local waited = 0
  while (Install.pending() or Install.status.state == "building")
        and waited < 5400 do
    U.wait(10)
    waited = waited + 10
  end
  if waited > 0 then
    U.log(("waited %.1fs for the model build -- %s")
          :format(waited / 60, tostring(Install.status.state)))
  end

  Battles.setting:setValue("stadium", game)
  Battles.backSetting:setValue(false, game)
  lib.require("DayNight").setting:setValue(os.getenv("DS_TOD") or "day", game)

  local have = 0
  for dex = 1, 151 do
    if Pack.available(dex) then have = have + 1 end
  end
  U.log(("%d species, %d/151 packs on disk, rung = %s")
        :format(#species, have, tostring(Battles.setting:get())))

  -- let the neighbourhood's meshes land, so the first fight opens on the
  -- real arena rather than the flat fallback
  U.wait(90)

  local asPic, stuck, missed = {}, {}, {}

  for _, s in ipairs(species) do
    -- the player's side is a constant (see the header), so the only thing
    -- that changes between two shots is the Pokemon being verified
    game.save.party = { Pokemon.new(game.data, "DIGLETT", 40) }

    local battle = BattleState.newWild(game, s.id, 30)
    battle.onFinish = function() end
    game.overworld:pushBattle(battle)

    -- the wipe and the intro chatter, then far enough past it that the
    -- player's own Pokemon has been sent out -- before that the player's
    -- side is legitimately the trainer's back sprite and the foe is still
    -- sliding in
    U.wait(70)
    for _ = 1, 40 do
      if battle.phase == "menu" then break end
      U.tap(game, "a")
      U.wait(10)
    end
    if battle.phase ~= "menu" then
      stuck[#stuck + 1] = s.id
      U.log(("STUCK before menu: %s (phase %s)")
            :format(s.id, tostring(battle.phase)))
    end
    -- and let the send-out beat finish so the mon is standing still in its
    -- standby loop rather than mid-arrival
    U.wait(45)

    -- model or flat pic? asked here, with the fight actually on screen
    if not Stadium.showing("enemy") then
      asPic[#asPic + 1] = ("%03d %s"):format(s.dex, s.id)
    end

    local path = ("%s/%03d_%s.png"):format(DIR, s.dex, s.id:lower())
    if not U.shot(game, path) then
      missed[#missed + 1] = s.id
    end

    while game.stack:top() and game.stack:top() ~= game.overworld do
      game.stack:pop()
    end
    U.wait(10)
  end

  U.log(("---- %d shots -> %s"):format(#species - #missed, DIR))
  if #asPic > 0 then
    U.log(("%d species declined to a model and were shot as FLAT PICS: %s")
          :format(#asPic, table.concat(asPic, ", ")))
  else
    U.log("all species stood as MODELS -- none fell back to a flat pic")
  end
  if #stuck > 0 then
    U.log(("%d never reached the battle menu: %s")
          :format(#stuck, table.concat(stuck, ", ")))
  end
  if #missed > 0 then
    U.log(("%d screenshots did not reach disk: %s")
          :format(#missed, table.concat(missed, ", ")))
  end
  U.log("done -- " .. DIR)
end
