-- Events panel: flags, defeated trainers, taken items, and per-map object
-- visibility toggles.  All four sections read/write through Ops so a flip is
-- always dirty + narrated.
--
-- Sub-tabs are pills; the filter is a real Kit.textfield (the old panel
-- edge-detected love.keyboard state every frame because Kit had no input
-- widget, which swallowed every keystroke the rest of the app wanted); and
-- the rows are a two-column grid so twenty fit per page instead of ten.

local Theme = require("Theme")
local Ops = require("Ops")
local Gen = require("Gen")
local PAL = Theme.PAL

local M = {}

local SUB_TABS = {
  { id = "flags",    label = "Flags" },
  { id = "trainers", label = "Trainers" },
  { id = "items",    label = "Items taken" },
  { id = "toggles",  label = "Object toggles" },
}

local HINTS = {
  flags    = "Story flags scraped from data/scripts and the trainer headers, plus any MOD_ flags a loaded mod defines.",
  trainers = "Keys look like MAP_obj_N (save.defeatedTrainers): checked means that trainer stays beaten.",
  items    = "Keys look like MAP_obj_N (save.itemsTaken): checked means that ground item is gone.",
  toggles  = "Per-map object visibility overrides (save.objectToggles), grouped by map.",
}

local function sortedKeys(t)
  local keys = {}
  for k in pairs(t) do keys[#keys + 1] = k end
  table.sort(keys)
  return keys
end

local function contains(haystack, needle)
  if needle == "" then return true end
  return haystack:lower():find(needle:lower(), 1, true) ~= nil
end

-- Each sub-tab reduces to the same shape: a list of rows, where a row knows
-- how to read its checked state, render a label, and write a flip back.
local function buildRows(S)
  local tab = S.eventsTab
  local filter = S.eventFilter or ""
  local rows = {}
  if tab == "flags" then
    for _, name in ipairs(S.events or {}) do
      if contains(name, filter) then
        rows[#rows + 1] = {
          label = name,
          checked = Gen.getFlag(S.save, name),
          set = function(on) Ops.setFlag(S, name, on) end,
        }
      end
    end
  elseif tab == "trainers" or tab == "items" then
    local key = (tab == "trainers") and "defeatedTrainers" or "itemsTaken"
    S.save[key] = S.save[key] or {}
    local t = S.save[key]
    for _, k in ipairs(sortedKeys(t)) do
      if contains(k, filter) then
        rows[#rows + 1] = {
          label = k,
          checked = t[k] == true,
          set = function(on) Ops.setKey(S, key, k, on) end,
        }
      end
    end
  else
    S.save.objectToggles = S.save.objectToggles or {}
    local toggles = S.save.objectToggles
    for _, mapId in ipairs(sortedKeys(toggles)) do
      local mapRows = {}
      for _, name in ipairs(sortedKeys(toggles[mapId])) do
        if contains(name, filter) or contains(mapId, filter) then
          mapRows[#mapRows + 1] = {
            label = name,
            checked = toggles[mapId][name] == true,
            set = function(on) Ops.setToggle(S, mapId, name, on) end,
          }
        end
      end
      if #mapRows > 0 then
        rows[#rows + 1] = { header = true, label = "[" .. mapId .. "]" }
        for _, r in ipairs(mapRows) do rows[#rows + 1] = r end
      end
    end
  end
  return rows
end

function M.draw(S, Kit, x, y, w, h)
  local s = Kit.scale
  local pad = 20 * s
  S.eventsTab = S.eventsTab or "flags"
  S.eventFilter = S.eventFilter or ""

  Kit.card(x, y, w, h)
  local cx = x + pad
  local inner = w - 2 * pad

  -- ------------------------------------------------------------ sub-tabs
  -- The pills flow left to right and WRAP when the card is too narrow to
  -- hold all four on one line (#715): a fixed row used to run the last pill
  -- past the card edge.
  local pillH = 32 * s
  local px, py = cx, y + pad
  local pills = SUB_TABS
  if Gen.of(S.save) == 2 then
    pills = { SUB_TABS[1] }
    if S.eventsTab ~= "flags" then S.eventsTab = "flags" end
  end
  for _, t in ipairs(pills) do
    local pw = Kit.textWidth("small", t.label) + 32 * s
    if px > cx and px + pw > cx + inner then
      px = cx
      py = py + pillH + 8 * s
    end
    local active = (S.eventsTab == t.id)
    Theme.col(PAL.rowBg, 0.6)
    love.graphics.rectangle("fill", px, py, pw, pillH, pillH / 2, pillH / 2)
    Theme.stroke(px, py, pw, pillH, pillH / 2,
      active and PAL.blue or PAL.cardBorder, active and 0.8 or 0.24,
      active and 1.5 * s or 1)
    Kit.textCenter("small", t.label, px, py + (pillH - Kit.textHeight("small")) / 2,
      pw, active and PAL.heading or PAL.muted)
    if Kit.press(px, py, pw, pillH) then
      S.eventsTab = t.id
      S.eventsOffset = 0
      Ops.disarm(S)
      Ops.say(S, HINTS[t.id])
    end
    px = px + pw + 10 * s
  end

  -- The filter shares the last pill row when there is room for at least a
  -- usable field beside the pills; on a narrow window it wraps onto its own
  -- row instead of painting over the last pill (#715).
  local clearW = 74 * s
  local filterY = py
  local availF = cx + inner - clearW - 10 * s - px - 10 * s
  if availF < 120 * s then
    filterY = py + pillH + 8 * s
    availF = inner - clearW - 10 * s
  end
  local fieldW = math.min(280 * s, math.max(120 * s, availF))
  local fieldX = cx + inner - clearW - 10 * s - fieldW
  S.eventFilter = Kit.textfield("event-filter", fieldX, filterY, fieldW, pillH,
    S.eventFilter, "filter keys...")
  if Kit.button(cx + inner - clearW, filterY, clearW, pillH, "Clear",
      { kind = "accent", font = "small", radius = 8 * s,
        enabled = S.eventFilter ~= "" }) then
    S.eventFilter = ""
    Kit.blur()
    Ops.say(S, "Filter cleared")
  end

  local hintY = filterY + pillH + 10 * s
  Kit.text("small", Kit.ellipsize("small", HINTS[S.eventsTab] or "", inner),
    cx, hintY, PAL.caption)

  -- ---------------------------------------------------------- row grid
  local rows = buildRows(S)
  local pagerH = 30 * s
  local pagerY = y + h - pad - pagerH
  local gridTop = hintY + Kit.textHeight("small") + 14 * s
  local rowH = 34 * s
  local rowGap = 8 * s
  local colGap = 20 * s
  -- two columns need ~460 logical px before the checkbox labels read; a
  -- phone gets one full-width column instead of two crushed ones (#715)
  local cols = (inner >= 460 * s) and 2 or 1
  local colW = (inner - colGap * (cols - 1)) / cols
  local gridH = pagerY - 12 * s - gridTop
  local perCol = math.max(1, math.floor(gridH / (rowH + rowGap)))
  local perPage = perCol * cols

  S.eventsOffset = Ops.clamp(S.eventsOffset or 0, 0, math.max(0, #rows - perPage))
  -- wheel / touch drag move whole grid rows, same contract as the pager (#715)
  S.eventsOffset = Kit.scroll(cx, gridTop, inner, gridH, S.eventsOffset,
    #rows, perPage, cols)

  if #rows == 0 then
    Kit.emptyBox(cx, gridTop, inner, math.min(gridH, 80 * s),
      S.eventFilter ~= "" and "No key matches that filter."
        or "Nothing recorded here yet.")
  end
  -- clip the grid body: on a window too short for even one row the partial
  -- row clips (and its hit test is fenced) instead of covering the pager (#715)
  Kit.pushClip(cx, gridTop, inner, gridH)
  for i = 1, math.min(perPage, #rows - S.eventsOffset) do
    local row = rows[S.eventsOffset + i]
    local ci = (i - 1) % cols
    local ri = math.floor((i - 1) / cols)
    local rx = cx + ci * (colW + colGap)
    local ry = gridTop + ri * (rowH + rowGap)
    if row.header then
      -- a map heading inside the toggles list: not a checkbox, so it must
      -- not look clickable
      Kit.text("mono", Kit.ellipsize("mono", row.label, colW),
        rx + 4 * s, ry + (rowH - Kit.textHeight("mono")) / 2, PAL.caption)
    else
      local newChecked, changed = Kit.checkbox(rx, ry, colW, rowH,
        row.checked, row.label)
      if changed then row.set(newChecked) end
    end
  end
  Kit.popClip()

  Kit.scrollbar(cx, gridTop, inner, gridH, S.eventsOffset, #rows, perPage)

  -- "Clear all" only makes sense for the two key tables the editor owns
  -- wholesale; flags and object toggles are cleared one row at a time.  Its
  -- width is reserved BEFORE the pager draws, so the pager's counter yields
  -- to the button instead of running underneath it (#715).
  local clearKey = (S.eventsTab == "trainers" and "defeatedTrainers")
    or (S.eventsTab == "items" and "itemsTaken") or nil
  local clearBw = 0
  if clearKey then
    local label = (S.eventsTab == "trainers") and "Clear all trainers"
      or "Clear all items taken"
    clearBw = Kit.textWidth("small", label) + 32 * s
    if Kit.button(cx + inner - clearBw, pagerY, clearBw, pagerH,
        Ops.armLabel(S, "clear-" .. clearKey, label),
        { kind = "danger", font = "small", radius = 8 * s }) then
      Ops.clearTable(S, clearKey, label:gsub("^Clear all ", ""))
    end
    clearBw = clearBw + 10 * s
  end
  S.eventsOffset = Kit.pager(cx, pagerY, inner - clearBw, S.eventsOffset,
    #rows, perPage)
end

return M
