-- Pokédex CONTENTS screen: engine/menus/pokedex.asm:157-348
-- (Yellow: pokeyellow engine/menus/pokedex.asm:263-336).

local Font = require("src.render.Font")
local Strings = require("src.core.Strings")
local Theme = require("src.ui.Theme")

local PokedexMenu = {}
PokedexMenu.__index = PokedexMenu
PokedexMenu.isOpaque = true

-- engine/menus/pokedex.asm:226 (`ld d, 7`), :21 (top menu item Y 3, X 0)
local ROWS = 7
local FIRST_ROW_Y = 24
local NUM_X, BALL_X, NAME_X, CURSOR_X = 8, 24, 32, 0

-- engine/menus/pokedex.asm:163-199
-- (Yellow: pokeyellow engine/menus/pokedex.asm:266-306)
local RED_ROWS = { seen = 2, own = 5, rule = 8, items = 10 }
local YELLOW_ROWS = { seen = 1, own = 4, rule = 6, items = 8 }

-- engine/menus/pokedex.asm:350 (DrawPokedexVerticalLine): column 14, $71
-- toggling to $70 down each nine-tile run, both runs restarting on $71
local function dividerCodes()
  local codes = { [0] = 0x71 }
  for _, top in ipairs({ 1, 9 }) do
    local code = 0x71
    for i = 0, 8 do
      codes[top + i] = code
      code = code == 0x71 and 0x70 or 0x71
    end
  end
  return codes
end
local DIVIDER = dividerCodes()

-- SGB: PalPacket_Pokedex, whole screen
function PokedexMenu:sgbPalettes(game)
  return require("src.render.PaletteFX").wholeNamed(game.data, "BROWNMON")
end

function PokedexMenu.new(game, opts)
  opts = opts or {}
  local self = setmetatable({}, PokedexMenu)
  self.game = game
  self.onCancel = opts.onCancel -- B returns to the start menu when opened from it
  self.index = 1
  self.scroll = 0
  self.rowsAt = require("src.core.GameVersion").isYellow()
    and YELLOW_ROWS or RED_ROWS
  local dex = game.save.pokedex or { seen = {}, owned = {} }
  local byDex = {}
  for species, def in pairs(game.data.pokemon) do
    if def.dex then byDex[def.dex] = def end
  end
  -- dex bound and number width come from constants; the fallbacks keep a
  -- cache imported before those keys existed on the Kanto numbering
  local constants = game.data.constants or {}
  local numFmt = ("%%0%dd"):format(constants.dexDigits or 3)
  local dexSize = constants.dexSize or 151
  -- engine/menus/pokedex.asm:200-216: the list stops at wDexMaxSeenMon, the
  -- highest number the player has seen, not at the end of the roster
  local maxSeen = 0
  local seen, owned = 0, 0
  for n = 1, dexSize do
    local def = byDex[n]
    if def then
      if dex.owned[def.id] then
        owned = owned + 1
        seen = seen + 1
        maxSeen = n
      elseif dex.seen[def.id] then
        seen = seen + 1
        maxSeen = n
      end
    end
  end
  local items = {}
  for n = 1, math.min(dexSize, maxSeen) do
    local def = byDex[n]
    if def then
      local known = dex.owned[def.id] or dex.seen[def.id]
      -- engine/menus/pokedex.asm:265 (.dashedLine) for anything unseen
      local name = known and def.name or "----------"
      items[#items + 1] = {
        num = numFmt:format(n),
        name = name,
        label = (numFmt .. " %s"):format(n, name),
        -- owned entries carry the pokéball marker like the original
        -- list; seen-only entries are just the name
        ball = dex.owned[def.id] or nil,
        value = known and def.id or nil,
      }
    end
  end
  self.items = items
  -- SEEN / OWN, `lb bc, 1, 3` at hlcoord 16,3 and 16,6: engine/menus/
  -- pokedex.asm HandlePokedexListMenu (#639)
  self.seenCount, self.ownedCount = seen, owned
  self.footer = Strings("SEEN %3d  OWN %3d", seen, owned)
  return self
end

-- HandleMenuInput_ replays SFX_PRESS_AB for A and B (home/window.asm)
local function beep(self)
  if not (self.game and self.game.data) then return end
  require("src.core.Sound").play(self.game.data, "Press_AB")
end

function PokedexMenu:rows()
  return math.min(ROWS, #self.items)
end

-- engine/menus/pokedex.asm:290-313
function PokedexMenu:syncScroll()
  local rows = self:rows()
  if rows == 0 then
    self.index, self.scroll = 1, 0
    return
  end
  self.index = math.max(1, math.min(#self.items, self.index))
  if self.index - self.scroll > rows then self.scroll = self.index - rows end
  if self.index - self.scroll < 1 then self.scroll = self.index - 1 end
end

-- engine/menus/pokedex.asm:314-342: Left and Right move wListScrollOffset
-- by seven and never touch wCurrentMenuItem, so the cursor keeps its row
function PokedexMenu:pageScroll(dir)
  local n = #self.items
  if n < ROWS then return end
  local row = self.index - self.scroll
  local scroll = self.scroll + dir * ROWS
  scroll = math.max(0, math.min(n - ROWS, scroll))
  self.scroll = scroll
  self.index = math.min(n, scroll + row)
end

function PokedexMenu:close()
  local top = self.game.stack:top()
  if top == self then self.game.stack:pop() end
end

function PokedexMenu:update(dt)
  -- back on top of the stack, so the side menu's hollow '▷' is gone
  -- (engine/menus/pokedex.asm:61, PlaceUnfilledArrowMenuCursor)
  self.hollowIndex = nil
  local input = self.game.input
  if #self.items == 0 then
    if input:wasPressed("a") or input:wasPressed("b") then
      beep(self)
      self.game.stack:pop()
      if self.onCancel then self.onCancel() end
    end
    return
  end
  if input:wasPressed("up") then
    self.index = self.index - 1
  elseif input:wasPressed("down") then
    self.index = self.index + 1
  elseif input:wasPressed("left") then
    self:pageScroll(-1)
  elseif input:wasPressed("right") then
    self:pageScroll(1)
  elseif input:wasPressed("b") then
    beep(self)
    self.game.stack:pop()
    if self.onCancel then self.onCancel() end
    return
  elseif input:wasPressed("a") then
    beep(self)
    self.onChoose(self.items[self.index], self)
    return
  end
  self:syncScroll()
end

-- engine/menus/pokedex.asm:371-375, :81-85
function PokedexMenu:sideItems()
  local labels = { Strings("DATA"), Strings("CRY"), Strings("AREA") }
  if require("src.core.GameVersion").isYellow() then
    labels[#labels + 1] = Strings("PRNT")
  end
  labels[#labels + 1] = Strings("QUIT")
  return labels
end

local function sideRowY(menu, row)
  return (menu.ty + menu.th - 2 - (#menu.items - row) * 2) * 8
end

local function drawSideMenu(self)
  love.graphics.setColor(0, 0, 0, 1)
  for row = 1, #self.items do
    Font.draw(self.items[row].label, (self.tx + 2) * 8, sideRowY(self, row))
  end
  Font.drawCode(Theme.cursor, (self.tx + 1) * 8, sideRowY(self, self.index))
  love.graphics.setColor(1, 1, 1, 1)
end

-- engine/menus/pokedex.asm HandlePokedexSideMenu, ShowPokedexMenu
-- .exitPokedex (#571)
local function chooseEntry(item, dexList)
  local game = dexList.game
  -- engine/menus/pokedex.asm:77-80: an unseen row hands back b=2 at once
  if not item.value then return end
  local Menu = require("src.ui.Menu")
  local Screens = require("src.ui.Screens")
  local entries = {
    { label = Strings("DATA"), onSelect = function()
        Screens.push(game, "DexEntryMenu", item.value)
      end },
    { label = Strings("CRY"), keepOpen = true, onSelect = function()
        require("src.core.Sound").playCry(game.data, item.value)
      end },
    { label = Strings("AREA"), onSelect = function()
        Screens.push(game, "TownMap", { nestSpecies = item.value })
      end },
  }
  -- pokeyellow engine/menus/pokedex.asm PokedexMenuItemsText,
  -- PrintPokedexEntry
  if require("src.core.GameVersion").isYellow() then
    entries[#entries + 1] = { label = Strings("PRNT"), onSelect = function()
      local DexEntryMenu = require("src.ui.DexEntryMenu")
      local Printer = require("src.core.Printer")
      local TextBox = require("src.render.TextBox")
      local def = game.data.pokemon[item.value]
      local path = require("src.pokemon.Sprites").path(
        game.data, item.value, "front", { kind = "dex" })
      local ok, sprite = false, nil
      if path then ok, sprite = pcall(love.graphics.newImage, path) end
      local saved, err = Printer.save("dex_" .. item.value, 160, 144,
        function()
          DexEntryMenu.render(game, def, ok and sprite or nil, false)
        end)
      game.stack:push(TextBox.new(game, saved
        and Strings("Printed %s's\ndata!\fSaved as\n%s\vin the save\nfolder.",
                    def.name, saved)
        or Strings("Printer error!\n%s", tostring(err))))
    end }
  end
  entries[#entries + 1] = { label = Strings("QUIT"), onSelect = function()
    -- engine/menus/pokedex.asm .exitPokedex
    dexList:close()
    if dexList.onCancel then dexList.onCancel() end
  end }
  local ty = dexList.rowsAt.items - 2
  local side = Menu.new(game, entries, { tx = 14, ty = ty, tw = 6 })
  -- the block is already on screen, so the side menu is the cursor alone:
  -- text at column 16, cursor at 15 (engine/menus/pokedex.asm:82-86)
  side.tx, side.tw, side.ty, side.th = 14, 6, ty, #entries * 2 + 2
  side.draw = drawSideMenu
  dexList.hollowIndex = dexList.index
  game.stack:push(side)
end
PokedexMenu.onChoose = chooseEntry

-- engine/menus/pokedex.asm:256 writes tile $72 in the column left of the
-- name; the port paints the same marker rather than carry a one-tile sheet
local function drawBall(x, y)
  love.graphics.circle("fill", x + 4, y + 4, 3.5)
  love.graphics.setColor(1, 1, 1, 1)
  love.graphics.rectangle("fill", x + 0.5, y + 3.5, 7, 1)
  love.graphics.circle("fill", x + 4, y + 4, 1.2)
  love.graphics.setColor(0, 0, 0, 1)
end

function PokedexMenu:draw()
  love.graphics.setColor(1, 1, 1, 1)
  love.graphics.rectangle("fill", 0, 0, 160, 144)
  local at = self.rowsAt
  local DexEntryMenu = require("src.ui.DexEntryMenu")
  -- engine/menus/pokedex.asm:159-176: the divider column and the rule that
  -- fences SEEN / OWN off from the side-menu block
  for ty = 0, 17 do
    DexEntryMenu.tile(self.game, DIVIDER[ty], 14, ty)
  end
  love.graphics.setColor(0, 0, 0, 1)
  Font.draw(("─"):rep(5), 120, at.rule * 8)
  Font.draw(Strings("CONTENTS"), 8, 8)
  Font.draw(Strings("SEEN"), 128, at.seen * 8)
  Font.draw(Strings("OWN"), 128, at.own * 8)
  -- PrintNumber's fixed three-digit field ends on column 19
  local function count(n, ty)
    local text = tostring(n)
    Font.draw(text, 152 - Font.width(text), ty * 8)
  end
  count(self.seenCount, at.seen + 1)
  count(self.ownedCount, at.own + 1)
  local labels = self:sideItems()
  for i, label in ipairs(labels) do
    Font.draw(label, 128, (at.items + (i - 1) * 2) * 8)
  end
  for row = 1, self:rows() do
    local i = self.scroll + row
    local item = self.items[i]
    if not item then break end
    local y = FIRST_ROW_Y + (row - 1) * 16
    -- the number sits one row above its name (engine/menus/pokedex.asm:242-246)
    Font.draw(item.num, NUM_X, y - 8)
    if item.ball then drawBall(BALL_X, y) end
    Font.draw(item.name, NAME_X, y)
    if i == self.index then
      Font.drawCode(self.hollowIndex == i
                    and Theme.cursorHollow or Theme.cursor, CURSOR_X, y)
    end
  end
  love.graphics.setColor(1, 1, 1, 1)
end

return PokedexMenu
