-- Screen-by-screen end credits (engine/movie/credits.asm HallOfFamePC +
-- Credits).  After the Hall of Fame induction fades out, the screen sits
-- blank for 100 frames, then the black letterbox bars appear
-- (FillFourRowsWithBlack: rows 0-3 and 14-17), Music_Credits starts and
-- the first screen follows 128 frames later.  Each CreditsOrder screen
-- places its lines at hlcoord 9,6 plus the per-line signed column offset
-- (rows 6, 8, 10, ...) and runs its terminator:
--   CRED_TEXT_FADE_MON  fade in (4 BGP steps x 5 frames), hold 90, mon wipe
--   CRED_TEXT_MON       text appears at once, hold 110, mon wipe
--   CRED_TEXT_FADE      fade in, hold 120, next screen replaces the text
--   CRED_TEXT           text appears at once, hold 140
-- The mon wipe is DisplayCreditsMon: three CreditsCopyTileMapToVRAM copies
-- (9 frames of Delay3, text still up), then the middle band scrolls left 8px
-- per frame for 27 frames (ScrollCreditsMonLeft x7 then x20) while the next
-- CreditsMons entry crosses right-to-left as a black silhouette
-- (BGP %11111100), leaving the band blank; BGP is left at %11000000, which
-- is why every post-wipe screen is a FADE variant.  CRED_COPYRIGHT
-- composes the Nintendo / Creatures inc. / GAME FREAK inc. block on its
-- screen (LoadCopyrightTiles: rows 7/9/11 from column 2).  CRED_THE_END
-- waits 16 frames on the blank band, shows the interleaved THE END
-- letters at tile (4,8), and runs one more FadeInCredits (a no-op: the
-- letters are color 3, so they are black from the start).
--
-- Then the caller's onTheEnd fires -- the point where
-- HallOfFameResetEventsAndSaveScript (scripts/HallOfFame.asm) sets
-- wLastBlackoutMap := PALLET_TOWN and runs SaveGameData -- the screen
-- holds 600 more frames (the script's 5 x 120 DelayFrames) and finally
-- waits for A/B (WaitForTextScrollButtonPress: no visible arrow here and
-- no press SFX) before popping and calling onDone (the script's
-- `jp Init`).  If field.credits hasn't been extracted the roll degrades
-- to just THE END.

local Font = require("src.render.Font")
local GameVersion = require("src.core.GameVersion")
local Music = require("src.core.Music")
local Strings = require("src.core.Strings")

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

-- FadeInCredits: HoFGBPalettes steps the text color index through GB
-- shades 0 (white) -> 1 -> 2 -> 3 (black), 5 frames per step.  The font
-- glyphs are black-on-transparent, so drawing them at these alphas over
-- the white band reproduces the 255 -> 170 -> 85 -> 0 gray ramp.
local FADE_STEPS = { 0, 1 / 3, 2 / 3, 1 }
local FADE_STEP_FRAMES = 5
local FADE_FRAMES = FADE_STEP_FRAMES * #FADE_STEPS -- 20

-- DelayFrames after each screen's text is up (Credits .next1/.next2)
local HOLD_FADE_MON = 90
local HOLD_MON = 110
local HOLD_FADE = 120
local HOLD_TEXT = 140

local WIPE_FRAMES = 27 -- ScrollCreditsMonLeft: 7 + 20 calls, 8px/frame
-- DisplayCreditsMon runs three CreditsCopyTileMapToVRAM calls (vBGMap0+$c,
-- vBGMap0, vBGMap1) before the first scroll, and each one ends in `jp Delay3`
-- (home/palettes.asm), so the credits text sits still for 9 more frames on
-- every mon screen.  Dropping them ran the 15 mon screens 135 frames short and
-- brought THE END up 2.2s early against a credits theme whose length is fixed
-- by the ROM program (Music_Credits is 5880 frames and does not loop), which
-- is what made the song look like it overran the roll (#703).
local MON_PREP_FRAMES = 9

-- LoadCopyrightTiles (engine/movie/title.asm CopyrightTextString): tile
-- sequences into the extracted title/copyright.png strip (tiles $60-$72:
-- Red/Blue (c)'95.'96.'98, Yellow (c)1995-1999 + NineTile) + Nintendo +
-- Creatures inc.; the GAME FREAK inc. row is title/gamefreak_inc.png
-- (GameFreakLogoGraphics, tiles $73-$7B), with the intro's composed
-- gamefreak_text.png as a fallback for pre-regeneration data.
local COPY_PREFIX_RB = { 0, 1, 2, 1, 3, 1, 4 }            -- (c)'95.'96.'98
local COPY_PREFIX_YELLOW = { 0, 1, 2, 3, 1, 2 }           -- (c)1995-199
local COPY_NINTENDO = { 5, 6, 7, 8, 9, 10 }               -- Nintendo
local COPY_CREATURES = { 11, 12, 13, 14, 15, 16, 17, 18 } -- Creatures inc.

local function tryImage(path)
  if not path then return nil end
  local ok, img = pcall(love.graphics.newImage, path)
  return ok and img or nil
end

-- DisplayCreditsMon shows the mon as a black silhouette: BGP %11111100
-- maps colors 1-3 to black and keeps color 0 white.  The extracted
-- front-sprite PNGs keep GB color 0 as white/transparent pixels, so
-- paint every opaque non-white pixel black.  Without love.image
-- (headless stub) fall back to a black tint (second return value), which
-- also blackens interior color-0 pixels.
local function silhouette(path)
  if not path then return nil end
  if love.image and love.image.newImageData then
    local ok, imgData = pcall(love.image.newImageData, path)
    if ok and imgData then
      imgData:mapPixel(function(_, _, r, g, b, a)
        if a > 0 and r + g + b < 2.9 then return 0, 0, 0, 1 end
        return r, g, b, a
      end)
      local ok2, img = pcall(love.graphics.newImage, imgData)
      if ok2 and img then return img, false end
    end
  end
  local ok, img = pcall(love.graphics.newImage, path)
  if ok and img then return img, true end
  return nil
end

function Credits.new(game, onDone, onTheEnd)
  local self = setmetatable({}, Credits)
  self.game = game
  self.onDone = onDone
  self.onTheEnd = onTheEnd
  local credits = game.data.field and game.data.field.credits or {}
  self.screens = credits.screens or {}
  self.theEnd = credits.theEnd
  self.music = credits.music or "Music_Credits"
  self.index = 0
  self.screen = nil
  self.phase = "white"
  self.timer = 100 -- HallOfFamePC: ClearScreen + 100 DelayFrames
  self.shade = 0

  -- assets (all optional; missing ones fall back to Font glyphs)
  self.endImg = tryImage(self.theEnd and self.theEnd.path)
  self.endQuads = {}
  if self.endImg then
    local iw, ih = self.endImg:getDimensions()
    for l = 0, 4 do -- 8x16 letter columns T,H,E,N,D
      self.endQuads[l] = love.graphics.newQuad(l * 8, 0, 8, 16, iw, ih)
    end
  end
  local title = game.data.field and game.data.field.title
  self.copyImg = tryImage(title and title.copyright and title.copyright.path)
  self.copyQuads = {}
  if self.copyImg then
    local iw, ih = self.copyImg:getDimensions()
    for t = 0, 18 do
      self.copyQuads[t] = love.graphics.newQuad(t * 8, 0, 8, 8, iw, ih)
    end
  end
  local intro = game.data.field and game.data.field.intro
  self.gfImg = tryImage(title and title.gamefreakInc
                        and title.gamefreakInc.path)
             or tryImage(intro and intro.gamefreakText
                         and intro.gamefreakText.path)
  self.yellowCopy = GameVersion.isYellow()
    or (title and title.layout == "yellow_pikachu")
  self.copyPrefix = self.yellowCopy and COPY_PREFIX_YELLOW or COPY_PREFIX_RB
  self.nineImg = self.yellowCopy and tryImage(
    title and title.nine and title.nine.path
      or "assets/generated/title/nine.png") or nil
  return self
end

function Credits:enter()
  -- AnimateHallOfFame ended on HoFFadeOutScreenAndMusic: silence over the
  -- blank lead-in; MUSIC_CREDITS starts when the bars appear
  pcall(Music.stop)
end

function Credits:monSprite(species)
  local path = require("src.pokemon.Sprites").path(
    self.game.data, species, "front", { kind = "credits" })
  return silhouette(path)
end

-- advance to the next CreditsOrder screen (Credits .nextCreditsScreen);
-- past the last one, CRED_THE_END takes over
function Credits:nextScreen()
  self.index = self.index + 1
  local screen = self.screens[self.index]
  self.screen = screen
  if not screen then
    self.phase = "end_blank" -- .showTheEnd: ld c, 16 on the blank band
    self.timer = 16
    return
  end
  if screen.fade then
    self.phase = "fade"
    self.timer = FADE_FRAMES
    self.shade = 0
  else
    -- no fade: BGP was left black by the previous screen's fade
    self.phase = "hold"
    self.shade = 1
    self.timer = screen.mon and HOLD_MON or HOLD_TEXT
  end
end

function Credits:update(dt)
  if self.phase == "end_wait" then
    -- WaitForTextScrollButtonPress: A or B ends the credits; the HoF
    -- script then soft-resets (`jp Init`).  No SFX on this press.
    local input = self.game.input
    if input:wasPressed("a") or input:wasPressed("b") then
      self.game.stack:pop()
      if self.onDone then self.onDone() end
    end
    return
  end
  self.timer = self.timer - 1
  if self.timer > 0 then
    if self.phase == "fade" then
      local step = math.floor((FADE_FRAMES - self.timer) / FADE_STEP_FRAMES)
      self.shade = FADE_STEPS[math.min(#FADE_STEPS, step + 1)]
    end
    return
  end
  if self.phase == "white" then
    -- bars on, stop-music SFX + PlayMusic MUSIC_CREDITS, then 128 frames
    self.phase = "intro"
    self.timer = 128
    local data = self.game.data
    if data.audio and data.audio.songs and data.audio.songs[self.music] then
      pcall(Music.play, data, self.music)
    end
  elseif self.phase == "intro" then
    self:nextScreen()
  elseif self.phase == "fade" then
    self.shade = 1
    self.phase = "hold"
    self.timer = self.screen.mon and HOLD_FADE_MON or HOLD_FADE
  elseif self.phase == "hold" then
    if self.screen.mon then
      -- the text stays up through DisplayCreditsMon's VRAM copies; the
      -- silhouette only starts moving once ScrollCreditsMonLeft does
      self.phase = "mon_prep"
      self.timer = MON_PREP_FRAMES
    else
      self:nextScreen()
    end
  elseif self.phase == "mon_prep" then
    self.phase = "wipe"
    self.timer = WIPE_FRAMES
    self.monImg, self.monTint = self:monSprite(self.screen.mon)
  elseif self.phase == "wipe" then
    self.monImg = nil
    self:nextScreen()
  elseif self.phase == "end_blank" then
    -- THE END letters are color 3: visible from the first fade palette
    self.phase = "end_fade"
    self.timer = FADE_FRAMES
  elseif self.phase == "end_fade" then
    -- Credits returns to HallOfFameResetEventsAndSaveScript here: the
    -- save (plus post-game home relocate, #103) happens now, then
    -- 5 x 120 DelayFrames before the button wait
    if self.onTheEnd then self.onTheEnd() end
    self.phase = "end_hold"
    self.timer = 600
  elseif self.phase == "end_hold" then
    self.phase = "end_wait"
  end
end

-- one credits screen: lines at rows 6/8/10... with the extractor's
-- absolute column (9 + signed offset), plus the copyright block
function Credits:drawPage(screen, xoff, shade)
  if not screen then return end
  love.graphics.setColor(0, 0, 0, shade)
  for i, line in ipairs(screen.lines or {}) do
    Font.draw(line.text, xoff + (line.column or 0) * 8, 48 + (i - 1) * 16)
  end
  love.graphics.setColor(1, 1, 1, 1)
  if screen.copyright then self:drawCopyright(xoff) end
end

function Credits:drawCopyPrefix(x, y)
  local img = self.copyImg
  for _, t in ipairs(self.copyPrefix) do
    love.graphics.draw(img, self.copyQuads[t], x, y)
    x = x + 8
  end
  if self.nineImg then
    love.graphics.draw(self.nineImg, x, y)
  end
end

function Credits:drawCopyright(xoff)
  local img = self.copyImg
  if img then
    -- the copyright tiles are loaded fresh (not color-shifted like the
    -- font), so they are color 3: always solid, no fade
    local function row(seq, x, y)
      for _, t in ipairs(seq) do
        love.graphics.draw(img, self.copyQuads[t], x, y)
        x = x + 8
      end
      return x
    end
    self:drawCopyPrefix(xoff + 16, 56)
    row(COPY_NINTENDO, xoff + 80, 56)
    self:drawCopyPrefix(xoff + 16, 72)
    row(COPY_CREATURES, xoff + 80, 72)
    self:drawCopyPrefix(xoff + 16, 88)
    if self.gfImg then
      love.graphics.draw(self.gfImg, xoff + 80, 88)
    else
      love.graphics.setColor(0, 0, 0, 1)
      Font.draw(Strings("GAME FREAK"), xoff + 80, 88)
      love.graphics.setColor(1, 1, 1, 1)
    end
  else
    love.graphics.setColor(0, 0, 0, 1)
    Font.draw(Strings("Nintendo"), xoff + 80, 56)
    Font.draw(Strings("Creatures inc."), xoff + 80, 72)
    Font.draw(Strings("GAME FREAK inc."), xoff + 16, 88)
    love.graphics.setColor(1, 1, 1, 1)
  end
end

-- the mon silhouette crossing during the wipe; x is the left edge of its
-- 7x7 box (bottom-centered inside it, like the GB pic buffer padding)
function Credits:drawMon(x)
  local img = self.monImg
  if not img then return end
  local w, h = img:getDimensions()
  if self.monTint then love.graphics.setColor(0, 0, 0, 1) end
  love.graphics.draw(img, x + math.floor((56 - w) / 2), 48 + (56 - h))
  love.graphics.setColor(1, 1, 1, 1)
end

-- TheEndTextString: 12 tile columns from (4,8), each an 8x16 letter
-- column of the interleaved the_end gfx (pattern indexes T,H,E,N,D)
function Credits:drawTheEnd()
  local te = self.theEnd
  if self.endImg and te and te.pattern then
    love.graphics.setColor(1, 1, 1, 1)
    for i, letter in ipairs(te.pattern) do
      if letter >= 0 then
        love.graphics.draw(self.endImg, self.endQuads[letter],
                           32 + (i - 1) * 8, 64)
      end
    end
  else
    love.graphics.setColor(0, 0, 0, 1)
    Font.draw((te and te.display) or Strings("T H E  E N D"), 32, 64)
    love.graphics.setColor(1, 1, 1, 1)
  end
end

function Credits:draw()
  love.graphics.setColor(1, 1, 1, 1)
  love.graphics.rectangle("fill", 0, 0, 160, 144)
  if self.phase == "white" then return end
  -- FillFourRowsWithBlack: rows 0-3 and 14-17 stay solid black
  love.graphics.setColor(0, 0, 0, 1)
  love.graphics.rectangle("fill", 0, 0, 160, 32)
  love.graphics.rectangle("fill", 0, 112, 160, 32)
  love.graphics.setColor(1, 1, 1, 1)
  if self.phase == "fade" or self.phase == "hold"
      or self.phase == "mon_prep" then
    self:drawPage(self.screen, 0, self.shade)
  elseif self.phase == "wipe" then
    -- ScrollCreditsMonLeft: the middle band scrolls left 8px/frame while
    -- the silhouette enters from the right edge one screen behind it
    local s = (WIPE_FRAMES - self.timer) * 8
    self:drawPage(self.screen, -s, 1)
    self:drawMon(160 - s)
  elseif self.phase == "end_fade" or self.phase == "end_hold"
      or self.phase == "end_wait" then
    self:drawTheEnd()
  end
  love.graphics.setColor(1, 1, 1, 1)
end

return Credits
