-- Immediate-mode in-launcher File Browser for handhelds and gamepads.
-- Allows navigating directories to pick ROM files (.gb/.gbc), import save files (.sav),
-- and select mod/skin archives (.zip) without requiring host desktop GUI pickers (zenity/kdialog).

local Theme = require("src.ui.kit.Theme")
local PAL = Theme.PAL
local Kit = nil
local function getKit()
  if not Kit then Kit = require("src.ui.kit.Kit") end
  return Kit
end

local FileBrowser = {
  active = false,
  title = "Select File",
  mode = "rom", -- "rom", "save", "mod", "all"
  currentDir = "/",
  entries = {},
  selectedIdx = 1,
  page = 1,
  perPage = 8,
  onSelect = nil,
  onCancel = nil,
  shortcutIdx = 1,
}

local SHORTCUTS = {
  { label = "SD Card", path = "/mnt/SDCARD" },
  { label = "GB ROMs", path = "/roms/gb" },
  { label = "GBC ROMs", path = "/roms/gbc" },
  { label = "Ports", path = "/roms/ports" },
  { label = "Game Dir", path = "." },
  { label = "Storage", path = "/storage" },
  { label = "Userdata", path = "/userdata/roms" },
  { label = "Root /", path = "/" },
}

local function findSdCardRoot()
  local candidates = {
    "/mnt/SDCARD",
    "/mnt/sdcard",
    "/mnt/mmc",
    "/sdcard",
    "/roms",
    "/storage/roms",
    "/userdata/roms",
    "/storage",
    "/userdata",
  }
  for _, path in ipairs(candidates) do
    local ok, h = pcall(io.popen, string.format('test -d "%s" && echo "yes"', path))
    if ok and h then
      local res = h:read("*a")
      h:close()
      if res and res:match("yes") then
        return path
      end
    end
  end
  return "/"
end

local function normalizePath(p)
  if not p or p == "" then return "/" end
  p = p:gsub("\\", "/")
  if p:sub(1, 1) ~= "/" and p:sub(1, 2) ~= "./" and p:sub(1, 3) ~= "../" then
    p = "/" .. p
  end
  p = p:gsub("/+", "/")
  return p
end

local function parentDir(p)
  p = normalizePath(p)
  if p == "/" or p == "." then return "/" end
  local parent = p:match("^(.*)/[^/]+/?$")
  if not parent or parent == "" then return "/" end
  return parent
end

local function isMatchingFilter(name, isDir, mode)
  if isDir then return true end
  local ext = name:match("%.([^.]+)$")
  if not ext then return (mode == "all") end
  ext = ext:lower()
  if mode == "rom" then
    return (ext == "gb" or ext == "gbc" or ext == "zip")
  elseif mode == "save" then
    return (ext == "sav")
  elseif mode == "mod" then
    return (ext == "zip")
  end
  return true
end

local function scanDirectory(dir, mode)
  dir = normalizePath(dir)
  local entries = {}

  local ok, handle = pcall(io.popen, string.format('ls -1ap "%s" 2>/dev/null', dir:gsub('"', '\\"')))
  if ok and handle then
    local output = handle:read("*a")
    handle:close()
    if output and #output > 0 then
      for line in output:gmatch("[^\r\n]+") do
        if line ~= "./" and line ~= "../" and line ~= "." and line ~= ".." then
          local isDir = (line:sub(-1) == "/")
          local cleanName = isDir and line:sub(1, -2) or line
          if isMatchingFilter(cleanName, isDir, mode) then
            table.insert(entries, {
              name = cleanName,
              isDir = isDir,
              path = (dir == "/" and "/" .. cleanName) or (dir .. "/" .. cleanName),
            })
          end
        end
      end
    end
  end

  if #entries == 0 and love and love.filesystem and love.filesystem.getDirectoryItems then
    local okFs, items = pcall(love.filesystem.getDirectoryItems, dir)
    if okFs and items then
      for _, name in ipairs(items) do
        local full = (dir == "/" and "/" .. name) or (dir .. "/" .. name)
        local info = love.filesystem.getInfo and love.filesystem.getInfo(full)
        local isDir = info and info.type == "directory"
        if isMatchingFilter(name, isDir, mode) then
          table.insert(entries, {
            name = name,
            isDir = isDir,
            path = full,
          })
        end
      end
    end
  end

  table.sort(entries, function(a, b)
    if a.isDir ~= b.isDir then
      return a.isDir
    end
    return a.name:lower() < b.name:lower()
  end)

  return entries
end

function FileBrowser.open(opts)
  opts = opts or {}
  FileBrowser.active = true
  FileBrowser.title = opts.title or "Select File"
  FileBrowser.mode = opts.mode or "rom"
  FileBrowser.onSelect = opts.onSelect
  FileBrowser.onCancel = opts.onCancel
  FileBrowser.selectedIdx = 1
  FileBrowser.page = 1

  local initPath = opts.initialPath or findSdCardRoot()
  FileBrowser.currentDir = initPath
  FileBrowser.entries = scanDirectory(FileBrowser.currentDir, FileBrowser.mode)
  if #FileBrowser.entries == 0 and initPath ~= "/" then
    FileBrowser.currentDir = "/"
    FileBrowser.entries = scanDirectory(FileBrowser.currentDir, FileBrowser.mode)
  end
end

function FileBrowser.close(path)
  if not FileBrowser.active then return end
  local onSelect = FileBrowser.onSelect
  local onCancel = FileBrowser.onCancel
  FileBrowser.active = false
  FileBrowser.onSelect = nil
  FileBrowser.onCancel = nil
  if path and onSelect then
    onSelect(path)
  elseif not path and onCancel then
    onCancel()
  end
end

function FileBrowser.setDirectory(dir)
  FileBrowser.currentDir = normalizePath(dir)
  FileBrowser.entries = scanDirectory(FileBrowser.currentDir, FileBrowser.mode)
  FileBrowser.selectedIdx = 1
  FileBrowser.page = 1
end

function FileBrowser.cycleShortcut()
  FileBrowser.shortcutIdx = (FileBrowser.shortcutIdx % #SHORTCUTS) + 1
  local target = SHORTCUTS[FileBrowser.shortcutIdx]
  FileBrowser.setDirectory(target.path)
end

function FileBrowser.gamepadpressed(button)
  if not FileBrowser.active then return false end

  local total = #FileBrowser.entries
  local perPage = FileBrowser.perPage
  local maxPage = math.max(1, math.ceil(total / perPage))

  if button == "dpup" then
    if FileBrowser.selectedIdx > 1 then
      FileBrowser.selectedIdx = FileBrowser.selectedIdx - 1
      FileBrowser.page = math.floor((FileBrowser.selectedIdx - 1) / perPage) + 1
    end
    return true
  elseif button == "dpdown" then
    if FileBrowser.selectedIdx < total then
      FileBrowser.selectedIdx = FileBrowser.selectedIdx + 1
      FileBrowser.page = math.floor((FileBrowser.selectedIdx - 1) / perPage) + 1
    end
    return true
  elseif button == "dpleft" or button == "leftshoulder" or button == "triggerleft" or button == "lefttrigger" or button == "l2" then
    if FileBrowser.page > 1 then
      FileBrowser.page = FileBrowser.page - 1
      FileBrowser.selectedIdx = (FileBrowser.page - 1) * perPage + 1
    end
    return true
  elseif button == "dpright" or button == "rightshoulder" or button == "triggerright" or button == "righttrigger" or button == "r2" then
    if FileBrowser.page < maxPage then
      FileBrowser.page = FileBrowser.page + 1
      FileBrowser.selectedIdx = math.min(total, (FileBrowser.page - 1) * perPage + 1)
    end
    return true
  elseif button == "a" or button == "start" then
    local entry = FileBrowser.entries[FileBrowser.selectedIdx]
    if entry then
      if entry.isDir then
        FileBrowser.setDirectory(entry.path)
      else
        FileBrowser.close(entry.path)
      end
    end
    return true
  elseif button == "b" then
    if FileBrowser.currentDir ~= "/" and FileBrowser.currentDir ~= "." then
      FileBrowser.setDirectory(parentDir(FileBrowser.currentDir))
    else
      FileBrowser.close(nil)
    end
    return true
  elseif button == "x" then
    FileBrowser.cycleShortcut()
    return true
  elseif button == "back" or button == "select" then
    FileBrowser.close(nil)
    return true
  end

  return true
end

function FileBrowser.keypressed(key)
  if not FileBrowser.active then return false end
  if key == "escape" then
    FileBrowser.close(nil)
    return true
  elseif key == "up" then
    return FileBrowser.gamepadpressed("dpup")
  elseif key == "down" then
    return FileBrowser.gamepadpressed("dpdown")
  elseif key == "left" then
    return FileBrowser.gamepadpressed("dpleft")
  elseif key == "right" then
    return FileBrowser.gamepadpressed("dpright")
  elseif key == "return" then
    return FileBrowser.gamepadpressed("a")
  elseif key == "backspace" then
    return FileBrowser.gamepadpressed("b")
  end
  return false
end

function FileBrowser.draw(m)
  if not FileBrowser.active then return end

  local Kit = getKit()
  local s = m and m.s or Kit.scale or 1
  local W = (m and m.W) or (love.graphics and love.graphics.getWidth()) or 640
  local H = (m and m.H) or (love.graphics and love.graphics.getHeight()) or 480

  Theme.fill(0, 0, W, H, PAL.bg, 0.94)
  Kit.blockClicks = true

  local pad = math.floor(14 * s)
  local modalW = math.floor(math.min(560 * s, W - 20 * s))
  local modalH = math.floor(math.min(420 * s, H - 20 * s))
  local px = math.floor((W - modalW) / 2)
  local py = math.floor((H - modalH) / 2)

  Kit.card(px, py, modalW, modalH, true)

  local cy = py + pad
  -- Header
  Kit.text("button", FileBrowser.title, px + pad, cy, PAL.heading)
  Kit.tag(px + modalW - pad - math.floor(60 * s), cy, math.floor(60 * s), math.floor(18 * s),
    FileBrowser.mode:upper(), PAL.blue)

  cy = cy + Kit.textHeight("button") + math.floor(6 * s)

  -- Breadcrumb / Current directory bar
  local pathBarH = math.floor(24 * s)
  Theme.fillRounded(px + pad, cy, modalW - 2 * pad, pathBarH, PAL.bg, 1)
  Theme.strokeRounded(px + pad, cy, modalW - 2 * pad, pathBarH, PAL.line, Theme.A.hairline, 1)
  local pathStr = Kit.ellipsizeLeft("small", FileBrowser.currentDir, modalW - 4 * pad)
  Kit.text("small", pathStr, px + pad + math.floor(6 * s), cy + math.floor(4 * s), PAL.heading)

  cy = cy + pathBarH + math.floor(8 * s)

  -- File / Directory List
  local total = #FileBrowser.entries
  local perPage = FileBrowser.perPage
  local startIdx = (FileBrowser.page - 1) * perPage + 1
  local endIdx = math.min(total, startIdx + perPage - 1)
  local rowH = math.floor(26 * s)
  local rowGap = math.floor(3 * s)

  if total == 0 then
    Theme.fillRounded(px + pad, cy, modalW - 2 * pad, rowH * 4, PAL.bg, 1)
    Kit.textCenter("small", "No files found in this folder.", px + pad, cy + rowH * 1.5, modalW - 2 * pad, PAL.muted)
    cy = cy + rowH * 4 + math.floor(8 * s)
  else
    for i = startIdx, endIdx do
      local entry = FileBrowser.entries[i]
      local isSel = (FileBrowser.selectedIdx == i)
      local rx = px + pad
      local rw = modalW - 2 * pad

      if Kit.press(rx, cy, rw, rowH) then
        FileBrowser.selectedIdx = i
        if entry.isDir then
          FileBrowser.setDirectory(entry.path)
        else
          FileBrowser.close(entry.path)
        end
      end

      if isSel then
        Theme.fillRounded(rx, cy, rw, rowH, PAL.ink, 1)
        local icon = entry.isDir and "[DIR] " or "[FILE] "
        Kit.text("small", icon .. entry.name, rx + math.floor(8 * s), cy + math.floor(4 * s), PAL.inverse)
      else
        Theme.fillRounded(rx, cy, rw, rowH, PAL.surface, 1)
        Theme.strokeRounded(rx, cy, rw, rowH, PAL.line, Theme.A.hairline, 1)
        local icon = entry.isDir and "[DIR] " or "[FILE] "
        local col = entry.isDir and PAL.blue or PAL.text
        Kit.text("small", icon .. entry.name, rx + math.floor(8 * s), cy + math.floor(4 * s), col)
      end

      cy = cy + rowH + rowGap
    end
  end

  -- Pager info & Controls guide
  local bottomY = py + modalH - pad - math.floor(18 * s)
  local pageInfo = string.format("Page %d of %d (%d items)", FileBrowser.page, math.max(1, math.ceil(total / perPage)), total)
  Kit.text("micro", pageInfo, px + pad, bottomY, PAL.muted)

  local guide = "A: Open/Select  B: Parent  X: Shortcut  Start: Confirm  Select: Cancel"
  Kit.textRight("micro", guide, px + modalW - pad, bottomY, PAL.muted)

  Kit.blockClicks = false
end

return FileBrowser
