-- GiveTakePartyMonItem (engine/pokemon/mon_menu.asm): the GIVE / TAKE menu the
-- party submenu's ITEM row opens, and the two routines behind it.
--
-- It is here because MAIL cannot be reached without it.  Attaching a letter to
-- a mon is GivePartyItem -> ComposeMailMessage and nothing else on the cart --
-- the PACK has no "use" for mail (ItemAttributes gives it ITEMMENU_NOUSE in
-- both menus), the MAILBOX only ever MOVES a letter that already exists, and
-- `givepokemail` is one scripted gift on Route 35.  So the compose keyboard's
-- only door is this one.
--
-- Drawn over the party list (`menu_coords 12, 12, SCREEN_WIDTH - 1,
-- SCREEN_HEIGHT - 1`), so this state is not opaque.
--
--   GIVE  .GiveItem: DepositSellPack, then
--         - a KEY_ITEM or an untossable item is ItemCantHeldText and the PACK
--           comes straight back (`jr .next` -> `.loop`)
--         - an empty-handed mon takes it (GiveItemToPokemon), and if the item
--           is MAIL the compose keyboard opens on top (GivePartyItem's
--           `farcall ItemIsMail / call ComposeMailMessage`)
--         - a mon already holding MAIL is refused with
--           _PokemonRemoveMailText, BEFORE the swap question is asked -- so
--           there is no way to knock a letter off a mon by accident
--         - anything else is the swap question
--   TAKE  TakePartyItem: the item goes back to the bag, or
--         _ItemStorageFullText when it will not fit
--
-- An EGG never gets here at all: GiveTakePartyMonItem's first two lines are
-- `cp EGG / jr z, .cancel`.

local Bag = require("src.inventory.Bag")
local Chrome = require("src.ui.gen2.Chrome")
local CommonText = require("src.core.gen2.CommonText")
local Mail = require("src.core.gen2.Mail")
local Screens = require("src.ui.Screens")

local HeldItemMenu = {}
HeldItemMenu.__index = HeldItemMenu
HeldItemMenu.isOpaque = false

-- GiveTakeItemMenuData: menu_coords 12, 12, 19, 17, STATICMENU_CURSOR and no
-- NO_TOP_SPACING, so GIVE is at (14,14) and TAKE two rows under it.
local MENU_X, MENU_Y, MENU_W, MENU_H = 12, 12, 8, 6
local MENU_LABEL_X, MENU_LABEL_Y = MENU_X + 2, MENU_Y + 2

local TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W, TEXT_BOX_H = 0, 12, 20, 6
local TEXT_X, TEXT_Y, TEXT_LINE = 1, 14, 2
local YESNO_X, YESNO_Y, YESNO_W, YESNO_H = 14, 7, 6, 5
local DOWN_ARROW = "\xe2\x96\xbc"
local ARROW_X, ARROW_Y = 18, 17

local ENTRIES = {
  { id = "give", label = "GIVE" },
  { id = "take", label = "TAKE" },
}

local function page(...) return { ... } end
local function pages(...) return { ... } end

-- data/text/common_2.asm.  The {STRBUF} markers are text_ram fields, spliced
-- here rather than being part of the string.
local TEXT = {
  hold = function(mon, item)
    return pages(page(("Made %s"):format(mon), ("hold %s."):format(item)))
  end,
  removeMail = pages(page("Please remove the", "MAIL first.")),
  notHolding = function(mon)
    return pages(page(("%s isn't"):format(mon), "holding anything."))
  end,
  storageFull = pages(page("Item storage space", "full.")),
  tookItem = function(item, mon)
    return pages(page(("Took %s"):format(item), ("from %s."):format(mon)))
  end,
  askSwap = function(mon, item)
    return pages(
      page(("%s is"):format(mon), "already holding"),
      page(("%s."):format(item), "Switch items?"))
  end,
  swapped = function(mon, old, new)
    return pages(
      page(("Took %s's"):format(mon), ("%s and"):format(old)),
      page("made it hold", ("%s."):format(new)))
  end,
  cantHold = pages(page("This item can't be", "held.")),
}

local LABELS = {
  hold = "_PokemonHoldItemText",
  removeMail = "_PokemonRemoveMailText",
  notHolding = "_PokemonNotHoldingText",
  storageFull = "_ItemStorageFullText",
  tookItem = "_PokemonTookItemText",
  askSwap = "_PokemonAskSwapItemText",
  swapped = "_PokemonSwapItemText",
  cantHold = "_ItemCantHeldText",
}

-- Each formatted entry's markers, in the order the ASM string names them.
local FILL = {
  hold = function(mon, item) return { mon, item } end,
  notHolding = function(mon) return { mon } end,
  tookItem = function(item, mon) return { item, mon } end,
  askSwap = function(mon, item) return { mon, item } end,
  swapped = function(mon, old, new) return { mon, old, new } end,
}

HeldItemMenu.TEXT = TEXT
HeldItemMenu.LABELS = LABELS
HeldItemMenu.ENTRIES = ENTRIES

local function extractedText(text)
  local out = setmetatable({}, { __index = TEXT })
  for key, label in pairs(LABELS) do
    local list = CommonText.of(text, label)
    if list then
      local fill = FILL[key]
      if fill then
        out[key] = function(...) return CommonText.fill(list, fill(...)) end
      else
        out[key] = list
      end
    end
  end
  return out
end

-- opts: save, slot, items (items.lua), text (text.lua), onClose()
function HeldItemMenu.new(game, opts)
  opts = opts or {}
  local self = setmetatable({}, HeldItemMenu)
  self.game = game
  self.save = opts.save or (game and game.save)
  self.slot = opts.slot or 1
  self.items = opts.items or (game and game.data and game.data.items)
  self.textData = opts.text or (game and game.world and game.world.text)
  self.TEXT = extractedText(self.textData)
  self.onClose = opts.onClose
  self.index = 1
  self.busy = false
  return self
end

function HeldItemMenu:mon()
  return self.save and self.save.party and self.save.party[self.slot]
end

function HeldItemMenu:monName()
  local mon = self:mon()
  if not mon then return "#MON" end
  return mon.nickname or mon.name or mon.species or "#MON"
end

function HeldItemMenu:itemName(id)
  local def = id and self.items and self.items[id]
  return (def and def.name) or id or "?"
end

function HeldItemMenu:close()
  if self.onClose then self.onClose() end
end

function HeldItemMenu:say(list, onDone)
  self.message = { pages = list or {}, page = 1, onDone = onDone }
end

function HeldItemMenu:ask(list, onYes, onNo)
  self.confirm = { pages = list or {}, page = 1, choice = 1,
    onYes = onYes, onNo = onNo }
end

-- ------------------------------------------------------------------- GIVE

-- .GiveItem's `cp KEY_ITEM_POCKET` and CheckTossableItem: a key item or an
-- item whose attributes forbid tossing cannot be held, and the PACK reopens
-- rather than the menu closing.
function HeldItemMenu:canHold(itemId)
  local def = itemId and self.items and self.items[itemId]
  if def and def.pocket == "KEY_ITEM" then return false end
  if def and def.canToss == false then return false end
  return true
end

function HeldItemMenu:openPack()
  local game = self.game
  if not (game and game.stack) then return self:close() end
  self.busy = true
  Screens.push(game, "Gen2PackMenu", {
    save = self.save,
    -- DepositSellPack: the PACK is a chooser here, so a field item must not
    -- run its effect on the way past.
    give = true,
    onChoose = function(itemId)
      game.stack:pop()
      self.busy = false
      self:giveItem(itemId)
    end,
    onClose = function()
      game.stack:pop()
      self.busy = false
      -- `.quit`: backing out of the PACK ends the whole GIVE.
      self:close()
    end,
  })
end

-- TryGiveItemToPartymon, in its own order.
function HeldItemMenu:giveItem(itemId)
  local mon = self:mon()
  if not (mon and itemId) then return self:close() end
  if not self:canHold(itemId) then
    return self:say(self.TEXT.cantHold, function() self:openPack() end)
  end
  local held = mon.item
  if held and Mail.isMail(held) then
    -- .please_remove_mail: a `ret`, so the whole GIVE ends here.
    return self:say(self.TEXT.removeMail, function() self:close() end)
  end
  if not held then
    Bag.remove(self.save, itemId, 1)
    mon.item = itemId
    local name = self:itemName(itemId)
    return self:say(self.TEXT.hold(self:monName(), name), function()
      self:composeIfMail(itemId)
    end)
  end
  -- .already_holding_item: the swap question, then ReceiveItemFromPokemon for
  -- the old one.  A bag that cannot take it back puts the old item straight
  -- back on the mon (.bag_full), so nothing is ever destroyed.
  self:ask(self.TEXT.askSwap(self:monName(), self:itemName(held)), function()
    Bag.remove(self.save, itemId, 1)
    if not Bag.add(self.save, held, 1, self.game and self.game.data) then
      Bag.add(self.save, itemId, 1, self.game and self.game.data)
      return self:say(self.TEXT.storageFull, function() self:close() end)
    end
    mon.item = itemId
    self:say(self.TEXT.swapped(self:monName(), self:itemName(held),
      self:itemName(itemId)), function() self:composeIfMail(itemId) end)
  end, function() self:close() end)
end

-- GivePartyItem's tail: `ld d, a / farcall ItemIsMail / call
-- ComposeMailMessage`.  The keyboard has no cancel -- the item is already on
-- the mon by the time it opens -- so an empty message is a blank letter, not
-- a refusal.
function HeldItemMenu:composeIfMail(itemId)
  if not Mail.isMail(itemId) then return self:close() end
  local game = self.game
  if not (game and game.stack) then return self:close() end
  self.busy = true
  Screens.push(game, "Gen2MailCompose", {
    onDone = function(message)
      game.stack:pop()
      self.busy = false
      Mail.compose(self.save, self.slot, message, self:mon(), itemId)
      self:close()
    end,
  })
end

-- ------------------------------------------------------------------- TAKE

function HeldItemMenu:takeItem()
  local mon = self:mon()
  if not mon then return self:close() end
  local held = mon.item
  if not held then
    return self:say(self.TEXT.notHolding(self:monName()),
      function() self:close() end)
  end
  if not Bag.add(self.save, held, 1, self.game and self.game.data) then
    return self:say(self.TEXT.storageFull, function() self:close() end)
  end
  mon.item = nil
  -- Taking a mail item back leaves no letter behind it: the struct has nowhere
  -- to live once the stationery is gone.  The MAIL row's own TAKE is the path
  -- that asks about the PC first (src/ui/gen2/MailMenu.lua); this one is only
  -- reachable for a mon that is NOT holding mail, because the submenu shows
  -- MAIL instead of ITEM in that case.
  Mail.clear(self.save, self.slot)
  self:say(self.TEXT.tookItem(self:itemName(held), self:monName()),
    function() self:close() end)
end

function HeldItemMenu:choose()
  local entry = ENTRIES[self.index]
  if not entry then return end
  if entry.id == "give" then return self:openPack() end
  self:takeItem()
end

-- ------------------------------------------------------------------ update

function HeldItemMenu:updateMessage(input)
  if not (input:wasPressed("a") or input:wasPressed("b")) then return end
  local message = self.message
  if message.page < #message.pages then
    message.page = message.page + 1
    return
  end
  self.message = nil
  if message.onDone then message.onDone() end
end

function HeldItemMenu:updateConfirm(input)
  local confirm = self.confirm
  if confirm.page < #confirm.pages then
    if input:wasPressed("a") or input:wasPressed("b") then
      confirm.page = confirm.page + 1
    end
    return
  end
  if input:wasPressed("up") or input:wasPressed("down") then
    confirm.choice = confirm.choice == 1 and 2 or 1
    return
  end
  if input:wasPressed("b") then
    self.confirm = nil
    if confirm.onNo then confirm.onNo() end
    return
  end
  if input:wasPressed("a") then
    local yes = confirm.choice == 1
    self.confirm = nil
    if yes then
      if confirm.onYes then confirm.onYes() end
    elseif confirm.onNo then
      confirm.onNo()
    end
  end
end

function HeldItemMenu:update(_dt)
  -- The PACK or the keyboard is on top of the stack; it owns input.
  if self.busy then return end
  local input = self.game and self.game.input
  if not input then return end
  if self.message then return self:updateMessage(input) end
  if self.confirm then return self:updateConfirm(input) end

  local total = #ENTRIES
  if input:wasPressed("up") then
    self.index = self.index > 1 and self.index - 1 or total
  elseif input:wasPressed("down") then
    self.index = self.index < total and self.index + 1 or 1
  elseif input:wasPressed("a") then
    self:choose()
  elseif input:wasPressed("b") then
    self:close()
  end
end

-- -------------------------------------------------------------------- draw

function HeldItemMenu:drawTextBox(lines)
  Chrome.box(TEXT_BOX_X, TEXT_BOX_Y, TEXT_BOX_W, TEXT_BOX_H)
  for i, line in ipairs(lines or {}) do
    Chrome.print(line, TEXT_X, TEXT_Y + (i - 1) * TEXT_LINE)
  end
end

function HeldItemMenu:drawYesNo(choice)
  Chrome.box(YESNO_X, YESNO_Y, YESNO_W, YESNO_H)
  Chrome.print("YES", YESNO_X + 2, YESNO_Y + 1)
  Chrome.print("NO", YESNO_X + 2, YESNO_Y + 3)
  Chrome.cursor(YESNO_X + 1, YESNO_Y + (choice == 1 and 1 or 3))
end

function HeldItemMenu:drawPanel()
  if self.message then
    self:drawTextBox(self.message.pages[self.message.page])
    if self.message.page < #self.message.pages then
      Chrome.print(DOWN_ARROW, ARROW_X, ARROW_Y)
    end
    love.graphics.setColor(1, 1, 1, 1)
    return
  end
  if self.confirm then
    self:drawTextBox(self.confirm.pages[self.confirm.page])
    if self.confirm.page >= #self.confirm.pages then
      self:drawYesNo(self.confirm.choice)
    else
      Chrome.print(DOWN_ARROW, ARROW_X, ARROW_Y)
    end
    love.graphics.setColor(1, 1, 1, 1)
    return
  end
  Chrome.box(MENU_X, MENU_Y, MENU_W, MENU_H)
  for row, entry in ipairs(ENTRIES) do
    local ty = MENU_LABEL_Y + (row - 1) * 2
    if row == self.index then Chrome.cursor(MENU_LABEL_X - 1, ty) end
    Chrome.print(entry.label, MENU_LABEL_X, ty)
  end
  love.graphics.setColor(1, 1, 1, 1)
end

function HeldItemMenu:draw()
  self:drawPanel()
end

return HeldItemMenu
