-- Editor mod-awareness (15 testing): with a mod enabled, App.load merges
-- the mod set into Data before the catalogs build, so the species list
-- carries the mod's mon, the flag list carries its MOD_ flags scraped
-- from the mod root, and MonOps stops asserting on the modded species.
-- Runs in its own process (run_tests spawns it): App.load's loader must
-- be the first to merge vanilla records over the singleton Data, or the
-- engine's registrations collide with an earlier loader's merge.
-- Self-contained: own bootstrap, assert-based checks, error() on failure.
package.path = "./?.lua;./?/init.lua;./tools/save-editor/?.lua;"
  .. "./tools/save-editor/panels/?.lua;" .. package.path
love = love or require("tests.love_stub")

local Runtime = require("src.mods.Runtime")
local Assets = require("src.render.Assets")
local SaveData = require("src.core.SaveData")
local Data = require("src.core.Data")

local function check(value, message)
  assert(value, message)
end

-- the fs surface the editor's loader needs, backed by a flat table
local function memfs(files)
  return {
    read = function(path) return files[path] end,
    getInfo = function(path)
      if files[path] then return { type = "file" } end
      local prefix = path .. "/"
      for key in pairs(files) do
        if key:sub(1, #prefix) == prefix then return { type = "directory" } end
      end
      return nil
    end,
    load = function(path)
      if not files[path] then return nil, "no file: " .. path end
      return (loadstring or load)(files[path], path)
    end,
    getDirectoryItems = function(path)
      local seen, items = {}, {}
      local prefix = path .. "/"
      for key in pairs(files) do
        if key:sub(1, #prefix) == prefix then
          local child = key:sub(#prefix + 1):match("^[^/]+")
          if child and not seen[child] then
            seen[child] = true
            items[#items + 1] = child
          end
        end
      end
      table.sort(items)
      return items
    end,
  }
end

local MOD_ROOT = "mods/zz_editor_fixture"

local MOD_MAIN = [[
return function(mod)
  local giftFlag = "MOD_EDITMON_GIFT"
  mod.exports.giftFlag = giftFlag
  mod.content.pokemon:register("EDITMON", {
    id = "EDITMON", name = "EDITMON", dex = 152,
    types = { "NORMAL" },
    baseStats = { hp = 50, attack = 50, defense = 50, speed = 50, special = 50 },
    catchRate = 45, baseExp = 100,
    level1Moves = { "GROWL" },
    growthRate = "MEDIUM_FAST",
    learnset = { { level = 10, move = "TACKLE" } },
    evolutions = {},
    spriteFront = "editmon_front.png", spriteBack = "editmon_back.png",
    frontSize = 5,
  })
end
]]

-- the loader discovers the fixture through love.filesystem, while the
-- flag scrape lists the mod root with io; the disk copy carries no
-- manifest, so a crash that strands it leaves a directory the game ignores
local savedFS = love.filesystem
local savedEvents, savedHooks, savedErrors =
  Runtime.events, Runtime.hooks, Runtime.errors
local savedBridge = Assets.loader

love.filesystem = memfs({
  [MOD_ROOT .. "/manifest.json"] =
    '{"id":"zz_editor_fixture","name":"zz_editor_fixture","version":"1.0.0",'
    .. '"entry":"main.lua","dependencies":[],"api":2}',
  [MOD_ROOT .. "/main.lua"] = MOD_MAIN,
})
-- plain mkdir: cmd reads "-p" as a directory name, and mods/ always exists
os.execute('mkdir "' .. MOD_ROOT .. '"')
local diskMain = assert(io.open(MOD_ROOT .. "/main.lua", "w"))
diskMain:write(MOD_MAIN)
diskMain:close()

local tmpPath = os.tmpname() .. "-editor-modaware.lua"
os.remove(tmpPath)

local ok, err = pcall(function()
  local App = require("App")
  local MonOps = require("MonOps")
  local Growth = require("src.pokemon.Growth")

  App.load(tmpPath)
  local S = App.getState()

  local loaded = S.mods:status().loaded
  check(#loaded == 1 and loaded[1].id == "zz_editor_fixture",
    "fixture mod loads through the editor's loader")
  check(loaded[1].path == MOD_ROOT, "loaded mod reports its root path")

  local hasSpecies = false
  for _, id in ipairs(S.cat.species) do
    if id == "EDITMON" then hasSpecies = true end
  end
  check(hasSpecies, "editor species catalog carries the mod's mon")

  local hasModFlag, hasVanillaFlag = false, false
  for _, name in ipairs(S.events) do
    if name == "MOD_EDITMON_GIFT" then hasModFlag = true end
    if name:match("^EVENT_") then hasVanillaFlag = true end
  end
  check(hasModFlag, "editor flag list carries the mod's MOD_ flag")
  check(hasVanillaFlag, "vanilla EVENT_ flags still scraped beside the mod's")

  -- MonOps reads the same merged Data the catalog was built from
  check(Data.pokemon.EDITMON ~= nil, "merge landed in the Data table MonOps reads")
  local mon = MonOps.create(Data, "PIDGEY", 10)
  MonOps.setSpecies(Data, mon, "EDITMON")
  check(mon.species == "EDITMON", "MonOps.setSpecies accepts the modded species")
  check(mon.exp == Growth.expForLevel("MEDIUM_FAST", 10),
    "setSpecies resyncs exp against the mod's growth curve")
  check(mon.stats.hp > 0, "recalc computes stats from the mod's base stats")
  MonOps.recalc(Data, mon)

  -- and the game-side scrub agrees: a save holding the modded mon passes
  -- clean instead of quarantining it
  local probe = { player = { map = "PALLET_TOWN" },
                  party = { { species = "EDITMON", level = 10,
                              moves = { { id = "TACKLE", pp = 10 } } } } }
  local report = SaveData.validate(probe, Data)
  check(#report.lostMons == 0 and probe.party[1].species == "EDITMON",
    "validate keeps the modded mon while the mod is enabled")

  -- Second editor session in the same process (launcher Close → Edit again):
  -- host teardown must leave Runtime/Assets clean and Data unloaded so the
  -- next App.load remounts mods instead of serving vanilla catalogs.
  App.unload()
  if Data._pristineKeys or Data.pokemon then Data:unloadGenerated() end
  Runtime.reset()
  Assets.installLoader(nil)
  package.loaded["App"] = nil
  package.loaded["Catalog"] = nil
  App = require("App")
  App.load(tmpPath)
  S = App.getState()
  loaded = S.mods:status().loaded
  check(#loaded == 1 and loaded[1].id == "zz_editor_fixture",
    "fixture mod loads again after host-style teardown")
  hasSpecies = false
  for _, id in ipairs(S.cat.species) do
    if id == "EDITMON" then hasSpecies = true end
  end
  check(hasSpecies, "second App.load catalog still carries the mod's mon")
  check(Data.pokemon.EDITMON ~= nil, "second merge lands EDITMON in Data again")

  -- Gold-targeted mods: spa/spd records are usable, and a Gen 1-only
  -- manifest stays out of Gold (no ROM cache / App.load("gold") required).
  local ModTargets = require("src.mods.ModTargets")
  local Ops = require("Ops")
  check(not ModTargets.supports({}, "gold"),
    "legacy gen1-only fixture does not support gold")
  check(not ModTargets.supports({}, "silver"),
    "nor silver")
  check(not ModTargets.supports({ games = { "red" } }, "gold"),
    "explicit gen1 games list does not support gold")
  check(not ModTargets.supports({ games = { "red" } }, "silver"),
    "nor silver")
  check(ModTargets.supports({ games = { "gold" } }, "gold"),
    "gold-targeted manifest supports gold")
  check(not ModTargets.supports({ games = { "gold" } }, "silver"),
    "and names one edition, not the generation")
  check(ModTargets.supports({ games = { "gold", "silver" } }, "silver"),
    "a whole-Gen-2 games list supports silver")
  check(ModTargets.supports({ gen2compat = true }, "gold"),
    "gen2compat legacy still supports gold")
  check(ModTargets.supports({ gen2compat = true }, "silver"),
    "and silver with it")

  local goldS = {
    data = {
      pokemon = {
        EDITMON = {
          baseStats = {
            hp = 50, attack = 50, defense = 50, speed = 50,
            specialAttack = 50, specialDefense = 50,
          },
        },
        G1ONLY = {
          baseStats = {
            hp = 50, attack = 50, defense = 50, speed = 50, special = 50,
          },
        },
      },
    },
  }
  check(Ops.speciesUsable(goldS, "EDITMON"),
    "spa/spd EDITMON is usable on gold")
  check(Ops.speciesUsable(goldS, "G1ONLY"),
    "gen1 special record remains usable (dual-key gate)")
end)

os.remove(MOD_ROOT .. "/main.lua")
if package.config:sub(1, 1) == "\\" then
  os.execute('rmdir "' .. MOD_ROOT .. '"')
else
  os.execute('rmdir "' .. MOD_ROOT .. '" 2>/dev/null')
end
os.remove(tmpPath)
love.filesystem = savedFS
-- leave shared singletons the way we found them (the fixture merged one
-- record into Data.pokemon)
if Data.pokemon then Data.pokemon.EDITMON = nil end
Assets.loader = savedBridge
Assets.invalidate()
Runtime.install(savedEvents, savedHooks, savedErrors)
if not ok then error(err, 0) end

print("ok   save editor mod awareness")
