-- Android mod / save Import must open the SAF picker (love.system.pickFile
-- with kind) and consume picked_mod.zip / picked_save.sav on focus, mirroring
-- the ROM flow.  Self-contained: `luajit tests/rom_importer_android_mod_pick_test.lua`.
package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end

local S = require("tests.harness").suite("rom importer android mod/save pick")
local eq = S.eq
local check = S.check

local RomImporter = require("src.import.RomImporter")

love.system = love.system or {}
local saved = {
  getOS = love.system.getOS,
  pickFile = love.system.pickFile,
  pickFileKinds = love.system.pickFileKinds,
}

local pickCalls, pickDestinations = {}, {}
love.system.getOS = function() return "Android" end
love.system.pickFile = function(kind, destination)
  pickCalls[#pickCalls + 1] = kind or "rom"
  pickDestinations[#pickCalls] = destination
  return true
end
love.system.pickFileKinds = function() return "rom,mod,sav,required_import" end

local function freshImporter(ready)
  return setmetatable({
    android = true,
    workState = nil,
    tab = "mods",
    ready = {
      red = ready.red and true or false,
      blue = ready.blue and true or false,
    },
    saveNotice = {},
    modNotice = nil,
    androidPendingVersion = nil,
    _installMod = function(self, source)
      self._installed = source
      self.modNotice = { ok = true, text = "Installed test" }
    end,
    _importSave = function(self, version, source)
      self._imported = { version = version, source = source }
      self.saveNotice[version] = { ok = true, text = "Imported" }
    end,
    _savedropTarget = RomImporter._savedropTarget,
    _refreshMods = function() end,
    _refreshSlots = function() end,
  }, RomImporter)
end

-- Choose mod with nothing pending opens the mod picker.
pickCalls = {}
local ri = freshImporter({ red = true, blue = true })
ri:chooseMod()
eq(#pickCalls, 1, "chooseMod opens the picker when no pending zip exists")
eq(pickCalls[1], "mod", "chooseMod asks pickFile for a mod")

-- Pending USB zip installs without opening the picker.
love.filesystem.write("usb_mod.zip", "PK\0fake")
pickCalls = {}
ri = freshImporter({ red = true, blue = true })
ri:chooseMod()
eq(#pickCalls, 0, "chooseMod installs a pending zip without opening the picker")
eq(ri._installed, "usb_mod.zip", "chooseMod consumed the USB zip")
check(love.filesystem.getInfo("usb_mod.zip") == nil,
  "successful install removes the pending zip")

-- Focus consumes picked_mod.zip even when both ROMs are already ready.
love.filesystem.write("picked_mod.zip", "PK\0saf")
ri = freshImporter({ red = true, blue = true })
ri:focus(true)
eq(ri._installed, "picked_mod.zip", "focus installs the SAF mod drop")
check(love.filesystem.getInfo("picked_mod.zip") == nil,
  "successful focus install removes picked_mod.zip")

-- Choose save opens the sav picker when nothing is pending.
pickCalls = {}
ri = freshImporter({ red = true, blue = true })
ri.tab = "blue"
ri:chooseSaveImport("blue")
eq(#pickCalls, 1, "chooseSaveImport opens the picker when no pending sav exists")
eq(pickCalls[1], "sav", "chooseSaveImport asks pickFile for a sav")
eq(ri.androidPendingVersion, "blue", "pending version is remembered for focus")

-- Focus consumes picked_save.sav into the remembered version.
love.filesystem.write("picked_save.sav", string.rep("S", 32))
ri = freshImporter({ red = true, blue = true })
ri.androidPendingVersion = "blue"
ri:focus(true)
check(ri._imported ~= nil, "focus imports the SAF save drop")
eq(ri._imported.version, "blue", "focus imports into the pending version")
eq(ri._imported.source, "picked_save.sav", "focus reads the SAF save filename")
check(love.filesystem.getInfo("picked_save.sav") == nil,
  "successful focus import removes picked_save.sav")

-- Required mod files use their own safe picker kind and pending filename.
pickCalls = {}
ri = freshImporter({ red = true, blue = true })
ri.nativePicker = true
ri.mobileFileBridge = true
ri.mods = { {
  id = "needs_source",
  manifest = { id = "needs_source", name = "Needs Source", path = "mods/needs_source",
    required_imports = { { id = "source", name = "Source", file = "source.bin",
      format = "raw", md5 = { "00000000000000000000000000000000" } } } },
} }
ri:chooseRequiredImport("needs_source", "source")
eq(pickCalls[1], "required_import",
  "required file asks for the dedicated picker kind")
eq(pickDestinations[1], "mods/needs_source/baseroms/source.bin",
  "raw required file receives its direct private baseroms destination")
eq(ri.pickerPendingModId, "needs_source", "pending mod is remembered")
eq(ri.pickerPendingImportId, "source", "pending import is remembered")

-- A rejected selection stays on the imported-files page, where the player can
-- see it before choosing another file, instead of behind the modal.
ri.nativePicker = false
ri._importRequiredData = RomImporter._importRequiredData
local savedData = love.data
love.data = {
  hash = function() return "not accepted" end,
  encode = function() return "ffffffffffffffffffffffffffffffff" end,
}
ri:_importRequiredData("needs_source", "source", "wrong source bytes")
love.data = savedData
check(ri.requiredImportNotice ~= nil,
  "required import rejection creates an in-modal notice")
eq(ri.requiredImportNotice.modId, "needs_source",
  "required import notice identifies its mod")
eq(ri.requiredImportNotice.importId, "source",
  "required import notice identifies its file")
check(ri.requiredImportNotice.text:find("MD5 mismatch", 1, true) ~= nil,
  "required import notice includes the MD5 failure")
check(ri.modNotice == nil,
  "required import rejection is not hidden in the general Mods notice")

-- Reported size is checked before the selected file is read into Lua.
local savedGetInfo = love.filesystem.getInfo
love.filesystem.getInfo = function(name, kind)
  if name == "oversized_required.bin" then
    return { type = "file", size = 10 }
  end
  return savedGetInfo(name, kind)
end
ri.mods[1].manifest.required_imports[1].max_size = 4
ri._importRequiredSource = RomImporter._importRequiredSource
ri._importRequiredData = function(self) self._oversizedWasRead = true end
ri:_importRequiredSource("needs_source", "source", "oversized_required.bin")
check(not ri._oversizedWasRead, "oversized required file is rejected before import")
check(ri.requiredImportNotice.text:find("too large", 1, true) ~= nil,
  "oversized required file reports its size error in the modal")
ri.mods[1].manifest.required_imports[1].max_size = nil
love.filesystem.getInfo = savedGetInfo

ri.nativePicker = true
ri._importRequiredSource = function(self, modId, importId, source)
  self._requiredImported = { modId = modId, importId = importId, source = source }
  return true
end
love.filesystem.write("picked_required_import.bin", "source bytes")
ri:focus(true)
check(ri._requiredImported ~= nil, "focus consumes a required-file SAF pick")
eq(ri._requiredImported.modId, "needs_source", "focus routes to the pending mod")
eq(ri._requiredImported.importId, "source", "focus routes to the pending declaration")
check(love.filesystem.getInfo("picked_required_import.bin") == nil,
  "focus removes the staged required-file pick")


-- Current bridge completion: the large source already lives in final baseroms;
-- only the tiny path/digest/size marker crosses the launcher focus path.
local directPath = "mods/needs_source/baseroms/source.bin"
local directSavedGetInfo = love.filesystem.getInfo
love.filesystem.getInfo = function(name, kind)
  local info = directSavedGetInfo(name, kind)
  if info and name == directPath then
    info.size = 12
    info.modtime = 123456
  end
  return info
end
love.filesystem.createDirectory("mods/needs_source/baseroms")
love.filesystem.write(directPath, "source bytes")
ri.mods[1].manifest.required_imports[1].md5 = {
  "fe1eb7483479c3a4e44fd41ce6f6d6ad"
}
ri.pickerPendingKind = "required_import"
ri.pickerPendingModId = "needs_source"
ri.pickerPendingImportId = "source"
love.filesystem.write("pick_complete.flag",
  "v1\n" .. directPath .. "\nfe1eb7483479c3a4e44fd41ce6f6d6ad\n12\n")
ri:focus(true)
check(ri.modNotice ~= nil and ri.modNotice.ok == true,
  "focus accepts a direct native required-import completion")
check(love.filesystem.getInfo(directPath, "file") ~= nil,
  "direct required import remains in final baseroms")
check(love.filesystem.getInfo("pick_complete.flag") == nil,
  "direct completion marker is consumed")

-- A native digest that does not match the manifest is rejected and the direct
-- copy is removed, so an invalid source cannot masquerade as a validated one.
love.filesystem.write(directPath, "source bytes")
ri.pickerPendingKind = "required_import"
ri.pickerPendingModId = "needs_source"
ri.pickerPendingImportId = "source"
love.filesystem.write("pick_complete.flag",
  "v1\n" .. directPath .. "\n00000000000000000000000000000000\n12\n")
ri:focus(true)
check(ri.requiredImportNotice ~= nil,
  "bad direct digest reports a dependency validation error")
check(love.filesystem.getInfo(directPath, "file") == nil,
  "bad direct digest removes the rejected final copy")
love.filesystem.getInfo = directSavedGetInfo

-- Android releases with the updated launcher but the older native bridge do
-- not advertise required_import. They still support the established ROM SAF
-- picker, whose result must be quarantined to the pending dependency request.
love.system.pickFileKinds = function() return "rom,mod,sav" end
pickCalls = {}
ri = freshImporter({ red = true, blue = true })
ri.nativePicker = true
ri.mobileFileBridge = true
ri.mods = { {
  id = "needs_source",
  manifest = { id = "needs_source", name = "Needs Source", path = "mods/needs_source",
    required_imports = { { id = "source", name = "Source", file = "source.bin",
      format = "raw", md5 = { "00000000000000000000000000000000" } } } },
} }
ri:chooseRequiredImport("needs_source", "source")
eq(pickCalls[1], "rom", "legacy Android bridge falls back to its ROM SAF picker")
check(pickDestinations[1] == nil,
  "legacy ROM fallback receives no nested dependency destination")
check(ri.requiredImportLegacyRomPick,
  "legacy Android ROM picker result is marked as a required import")
ri._importRequiredSource = function(self, modId, importId, source)
  self._requiredImported = { modId = modId, importId = importId, source = source }
  return true
end
love.filesystem.write("picked_rom.gb", "source bytes")
ri:focus(true)
eq(ri._requiredImported.source, "picked_rom.gb",
  "legacy Android ROM staging name is routed to the required import")
eq(ri._requiredImported.modId, "needs_source",
  "legacy Android picker preserves the requested mod")
check(love.filesystem.getInfo("picked_rom.gb") == nil,
  "legacy Android dependency pick is removed after import")

-- A legacy bridge reports a failed copy using that same staging basename; it
-- must stay on the dependency page rather than becoming a game-ROM error.
ri:chooseRequiredImport("needs_source", "source")
love.filesystem.write("pick_error.flag", "picked_rom.gb")
ri:focus(true)
check(ri.modNotice ~= nil and ri.modNotice.ok == false,
  "legacy Android picker errors are shown as dependency import errors")
check(ri.pickerPendingKind == nil,
  "legacy Android picker error clears the pending dependency request")

love.system.getOS = saved.getOS
love.system.pickFile = saved.pickFile
love.system.pickFileKinds = saved.pickFileKinds
-- leftover cleanup if a failed assertion left files behind
love.filesystem.remove("usb_mod.zip")
love.filesystem.remove("picked_mod.zip")
love.filesystem.remove("picked_save.sav")
love.filesystem.remove("picked_required_import.bin")
love.filesystem.remove("picked_rom.gb")
love.filesystem.remove("pick_complete.flag")
love.filesystem.remove("pick_error.flag")
love.filesystem.remove("mods/needs_source/baseroms/source.bin")

S.finish()
