-- Launcher touch-controls editor (#327): drag each on-screen button to a
-- new spot, resize them, toggle the overlay off entirely, Reset to
-- defaults, Done to persist into options.lua.  Opened from the game
-- panel's "Touch Controls" button; main.lua suspends the launcher the same
-- way it does for the save editor.
--
-- Everything edited here belongs to the orientation currently on screen
-- (#633): portrait and landscape keep separate positions and sizes, so a
-- player lays out each rotation once.  Rotate the device (or resize the
-- desktop window past square) and the editor switches buckets live.
--
-- Draws in full window LOVE units -- the same space TouchControls uses
-- after Renderer:endFrame -- so what you drag here is what you get in play.
--
-- Switch / gamepad: PadCursor draws the virtual pointer the launcher just
-- dropped (same soft-lock class as the save editor).  A clicks / drags, B
-- closes (Done), shoulders nudge button size.

local SaveData = require("src.core.SaveData")
local TouchControls = require("src.core.TouchControls")
local TouchSkin = require("src.core.TouchSkin")
local PadCursor = require("src.ui.PadCursor")
local GamepadMap = require("src.core.GamepadMap")

local Editor = {}

local PAL = {
  bgTop = { 8, 14, 36 },
  bgBot = { 4, 8, 22 },
  white = { 245, 248, 255 },
  label = { 160, 175, 210 },
  green = { 80, 220, 140 },
  red = { 240, 90, 110 },
  card = { 18, 28, 58 },
  stroke = { 120, 150, 220 },
}

local function col(c, a)
  love.graphics.setColor(c[1] / 255, c[2] / 255, c[3] / 255, a or 1)
end

local function inside(r, x, y)
  return r and x >= r.x and x <= r.x + r.w and y >= r.y and y <= r.y + r.h
end

local function roundRect(mode, x, y, w, h, r)
  love.graphics.rectangle(mode, x, y, w, h, r, r)
end

local function fitText(font, text, maxW)
  text = tostring(text)
  if maxW <= 0 or font:getWidth(text) <= maxW then return text end
  while #text > 1 and font:getWidth(text .. "...") > maxW do
    text = text:sub(1, #text - 1)
  end
  return text .. "..."
end

function Editor.load(opts)
  opts = opts or {}
  Editor.onClose = opts.onClose
  Editor.version = opts.version
  Editor.hostPoll = opts.hostPoll == true
  Editor.drag = nil
  Editor.rects = {}
  Editor._closed = false
  Editor._hostMouse = false
  Editor._hostTouches = nil
  Editor.fonts = {
    title = love.graphics.newFont(28),
    body = love.graphics.newFont(16),
    btn = love.graphics.newFont(18),
  }
  local optsTbl = SaveData.loadOptions()
  local applied = optsTbl
  local GameVersion = require("src.core.GameVersion")
  local gen2 = GameVersion.VERSIONS[opts.version]
    and GameVersion.generation(opts.version) == 2
  if gen2 then
    local gold = type(optsTbl.gold) == "table" and optsTbl.gold or {}
    applied = {
      touchControls = gold.touchControls,
      haptics = gold.haptics or optsTbl.haptics,
    }
  end
  TouchControls:init()
  TouchControls:ensureImages()
  TouchControls:applyOptions(applied)
  TouchControls:setPreview(true)
  Editor.enabled = TouchControls.enabled ~= false
  Editor.skins = TouchSkin.list()
  Editor.exportMsg = nil
  PadCursor.reset()
end

function Editor.skinIndex()
  for i, entry in ipairs(Editor.skins or {}) do
    if entry.id == TouchControls.skinId then return i + 1 end
  end
  return 1
end

function Editor.skinLabel()
  local entry = (Editor.skins or {})[Editor.skinIndex() - 1]
  if not entry then return "Built-in pad" end
  local skin = TouchSkin.active
  local pages = skin and #skin.pages or 1
  if pages > 1 then return entry.id .. " (" .. pages .. " pages)" end
  return entry.id
end

function Editor.cycleSkin(dir)
  local n = #(Editor.skins or {}) + 1
  local idx = ((Editor.skinIndex() - 1 + (dir or 1)) % n) + 1
  local entry = Editor.skins[idx - 1]
  Editor.exportMsg = nil
  TouchControls:selectSkin(entry and entry.id or nil)
  TouchControls:ensureImages()
end

function Editor.exportSkin()
  local skin = TouchSkin.active
  if not skin then return end
  local path, missing = TouchSkin.export(skin)
  if not path then
    Editor.exportMsg = "Export failed: " .. tostring(missing)
    return
  end
  Editor.exportMsg = "Exported to " .. path .. " in your save folder."
  if type(missing) == "table" and missing[1] then
    Editor.exportMsg = Editor.exportMsg
      .. " " .. #missing .. " image(s) were missing and left out."
  end
  Editor.skins = TouchSkin.list()
end

function Editor.unload()
  TouchControls:setPreview(false)
  TouchControls:reset()
  PadCursor.reset()
  Editor.drag = nil
  Editor.onClose = nil
  Editor.version = nil
  Editor.rects = {}
  Editor._hostMouse = false
  Editor._hostTouches = nil
end

local function persist()
  local opts = SaveData.loadOptions()
  local cfg = TouchControls:config()
  local block = {
    enabled = cfg.enabled,
    skin = cfg.skin,
    layouts = cfg.layouts,
  }
  local GameVersion = require("src.core.GameVersion")
  if GameVersion.VERSIONS[Editor.version]
      and GameVersion.generation(Editor.version) == 2 then
    opts.gold = type(opts.gold) == "table" and opts.gold or {}
    opts.gold.touchControls = block
  else
    opts.touchControls = block
  end
  SaveData.saveOptions(opts)
end

local function close()
  if Editor._closed then return end
  Editor._closed = true
  persist()
  local cb = Editor.onClose
  Editor.unload()
  if cb then cb() end
end

local function resetLayout()
  TouchControls:clearPositions()
end

local function toggleEnabled()
  Editor.enabled = not Editor.enabled
  TouchControls.enabled = Editor.enabled
  if not Editor.enabled then TouchControls:reset() end
end

function Editor.update(dt)
  PadCursor.update(dt or 0)
  if Editor.hostPoll then Editor.pollHostPointers() end
  if not Editor.drag then return end
  local x, y
  if Editor.drag.touchId == "pad" then
    x, y = PadCursor.pointer()
  elseif love.touch and love.touch.getPosition and Editor.drag.touchId then
    local ok, tx, ty = pcall(love.touch.getPosition, Editor.drag.touchId)
    if ok and tx then x, y = tx, ty end
  end
  if not x and love.mouse then
    x, y = love.mouse.getPosition()
  end
  if x then
    TouchControls:setControlCenter(
      Editor.drag.name,
      x - Editor.drag.offX,
      y - Editor.drag.offY)
  end
end

function Editor.draw()
  local SafeArea = require("src.core.SafeArea")
  local fullW, fullH = love.graphics.getDimensions()
  local ox, oy, ww, wh = SafeArea.rect()
  local s = math.max(0.75, math.min(1.4, wh / 768))
  -- resolve the orientation bucket before any chrome reads scale (#633)
  local bucket = TouchControls:currentBucket()
  Editor.rects = {}

  -- radial-ish navy field (two stacked fills; matches launcher atmosphere)
  col(PAL.bgBot)
  love.graphics.rectangle("fill", 0, 0, fullW, fullH)
  col(PAL.bgTop, 0.85)
  love.graphics.circle("fill", ox + ww * 0.5, oy + wh * 0.15, math.max(ww, wh) * 0.55)

  if TouchSkin.active then TouchControls:draw() end

  local pad = 18 * s
  local barH = 56 * s
  local btnH = 40 * s
  local btnW = 100 * s

  -- top bar (inside the safe area so it clears the notch / status bar)
  col(PAL.card, 0.92)
  love.graphics.rectangle("fill", 0, 0, fullW, oy + barH + pad)
  col(PAL.stroke, 0.35)
  love.graphics.setLineWidth(1)
  love.graphics.line(0, oy + barH + pad, fullW, oy + barH + pad)

  -- Done / Reset
  local done = { x = ox + ww - pad - btnW, y = oy + pad + (barH - btnH) / 2,
                 w = btnW, h = btnH }
  local reset = { x = done.x - 10 * s - btnW, y = done.y, w = btnW, h = btnH }
  Editor.rects.done, Editor.rects.reset = done, reset

  love.graphics.setFont(Editor.fonts.title)
  col(PAL.white)
  love.graphics.print(
    fitText(Editor.fonts.title, "Touch Controls", reset.x - 10 * s - (ox + pad)),
    ox + pad, oy + pad + 4 * s)

  local function chromeBtn(r, label, fill)
    col(fill, 0.9)
    roundRect("fill", r.x, r.y, r.w, r.h, 8 * s)
    col(PAL.white, 0.2)
    roundRect("line", r.x, r.y, r.w, r.h, 8 * s)
    love.graphics.setFont(Editor.fonts.btn)
    col(PAL.white)
    local tw = Editor.fonts.btn:getWidth(label)
    love.graphics.print(label, r.x + (r.w - tw) / 2,
                        r.y + (r.h - Editor.fonts.btn:getHeight()) / 2)
  end
  chromeBtn(reset, "Reset", { 60, 70, 110 })
  chromeBtn(done, "Done", PAL.green)

  -- enable toggle card
  local cardY = oy + barH + pad + 14 * s
  local cardH = 64 * s
  local cardX, cardW = ox + pad, ww - 2 * pad
  col(PAL.card, 0.88)
  roundRect("fill", cardX, cardY, cardW, cardH, 12 * s)
  col(PAL.stroke, 0.4)
  roundRect("line", cardX, cardY, cardW, cardH, 12 * s)

  love.graphics.setFont(Editor.fonts.body)
  col(PAL.label)
  love.graphics.print("On-screen controls", cardX + 16 * s,
                      cardY + 12 * s)
  love.graphics.setFont(Editor.fonts.btn)
  local on = Editor.enabled
  col(on and PAL.green or PAL.red)
  love.graphics.print(on and "ON" or "OFF", cardX + 16 * s,
                      cardY + 34 * s)

  local toggleW = 110 * s
  local toggle = {
    x = cardX + cardW - 16 * s - toggleW,
    y = cardY + (cardH - btnH) / 2,
    w = toggleW, h = btnH,
  }
  Editor.rects.toggle = toggle
  chromeBtn(toggle, on and "Disable" or "Enable",
            on and PAL.red or PAL.green)

  local skinY = cardY + cardH + 10 * s
  col(PAL.card, 0.88)
  roundRect("fill", cardX, skinY, cardW, cardH, 12 * s)
  col(PAL.stroke, 0.4)
  roundRect("line", cardX, skinY, cardW, cardH, 12 * s)

  local stepW = 52 * s
  local skinNext = { x = cardX + cardW - 16 * s - stepW,
                     y = skinY + (cardH - btnH) / 2, w = stepW, h = btnH }
  local skinPrev = { x = skinNext.x - 10 * s - stepW, y = skinNext.y,
                     w = stepW, h = btnH }
  Editor.rects.skinNext, Editor.rects.skinPrev = skinNext, skinPrev

  local leftmost = skinPrev.x
  if TouchSkin.active then
    local expW = 86 * s
    local export = { x = skinPrev.x - 10 * s - expW, y = skinPrev.y,
                     w = expW, h = btnH }
    Editor.rects.export = export
    leftmost = export.x
  else
    Editor.rects.export = nil
  end

  local skinTextW = leftmost - 10 * s - (cardX + 16 * s)
  love.graphics.setFont(Editor.fonts.body)
  col(PAL.label)
  love.graphics.print("Skin", cardX + 16 * s, skinY + 12 * s)
  love.graphics.setFont(Editor.fonts.btn)
  col(TouchControls.skinId and PAL.green or PAL.white)
  love.graphics.print(fitText(Editor.fonts.btn, Editor.skinLabel(), skinTextW),
                      cardX + 16 * s, skinY + 34 * s)

  chromeBtn(skinPrev, "<", { 60, 70, 110 })
  chromeBtn(skinNext, ">", { 60, 70, 110 })
  if Editor.rects.export then
    chromeBtn(Editor.rects.export, "Export", { 60, 70, 110 })
  end

  -- size card (#633): -/+ resize every control in the orientation on
  -- screen; the heading names it so it is plain the other one is untouched
  local sizeY = skinY + cardH + 10 * s
  col(PAL.card, 0.88)
  roundRect("fill", cardX, sizeY, cardW, cardH, 12 * s)
  col(PAL.stroke, 0.4)
  roundRect("line", cardX, sizeY, cardW, cardH, 12 * s)

  love.graphics.setFont(Editor.fonts.body)
  col(PAL.label)
  local orient = TouchControls.orientation == "landscape"
    and "Landscape" or "Portrait"
  love.graphics.print("Button size (" .. orient .. ")",
                      cardX + 16 * s, sizeY + 12 * s)
  love.graphics.setFont(Editor.fonts.btn)
  col(PAL.white)
  love.graphics.print(
    string.format("%d%%", math.floor((bucket.scale or 1) * 100 + 0.5)),
    cardX + 16 * s, sizeY + 34 * s)

  local plus = { x = cardX + cardW - 16 * s - stepW,
                 y = sizeY + (cardH - btnH) / 2, w = stepW, h = btnH }
  local minus = { x = plus.x - 10 * s - stepW, y = plus.y, w = stepW, h = btnH }
  Editor.rects.sizeUp, Editor.rects.sizeDown = plus, minus
  chromeBtn(minus, "-", { 60, 70, 110 })
  chromeBtn(plus, "+", { 60, 70, 110 })

  -- hint
  love.graphics.setFont(Editor.fonts.body)
  col(PAL.label, 0.9)
  local hint
  if Editor.exportMsg then
    hint = Editor.exportMsg
  elseif not on then
    hint = "Controls are hidden in-game. Enable them to show and edit the layout."
  elseif TouchSkin.active then
    hint = "This skin owns its own art, hitboxes and screen position, so size and dragging are off. Drop a RetroArch overlay folder or .zip into the skins folder of your save directory to add more."
  else
    hint = "Drag each button to reposition, -/+ to resize. Portrait and landscape are saved separately when you tap Done."
  end
  love.graphics.printf(hint, ox + pad, sizeY + cardH + 12 * s, ww - 2 * pad, "left")

  if not TouchSkin.active then TouchControls:draw() end

  -- highlight the control under drag
  if Editor.drag then
    local L = TouchControls:layout()
    local zone = L[Editor.drag.name]
    if zone then
      love.graphics.setLineWidth(3 * s)
      col(PAL.green, 0.85)
      love.graphics.circle("line", zone.cx, zone.cy, zone.w * 0.62)
    end
  end

  -- pad / Joy-Con virtual cursor (after chrome so it sits on top)
  PadCursor.draw()
end

local function beginDrag(id, x, y)
  -- chrome takes priority over controls
  if inside(Editor.rects.done, x, y) then close(); return end
  if inside(Editor.rects.reset, x, y) then resetLayout(); return end
  if inside(Editor.rects.toggle, x, y) then toggleEnabled(); return end
  if inside(Editor.rects.skinPrev, x, y) then Editor.cycleSkin(-1); return end
  if inside(Editor.rects.skinNext, x, y) then Editor.cycleSkin(1); return end
  if inside(Editor.rects.export, x, y) then Editor.exportSkin(); return end
  if inside(Editor.rects.sizeDown, x, y) then
    TouchControls:nudgeScale(-TouchControls.SCALE_STEP); return
  end
  if inside(Editor.rects.sizeUp, x, y) then
    TouchControls:nudgeScale(TouchControls.SCALE_STEP); return
  end

  if TouchSkin.active then return end
  local name = TouchControls:hitTest(x, y)
  if not name then return end
  local zone = TouchControls:layout()[name]
  Editor.drag = {
    name = name,
    touchId = id,
    offX = x - zone.cx,
    offY = y - zone.cy,
  }
end

local function moveDrag(id, x, y)
  local d = Editor.drag
  if not d then return end
  if d.touchId ~= nil and id ~= nil and d.touchId ~= id then return end
  TouchControls:setControlCenter(d.name, x - d.offX, y - d.offY)
end

local function endDrag(id)
  local d = Editor.drag
  if not d then return end
  if d.touchId ~= nil and id ~= nil and d.touchId ~= id then return end
  Editor.drag = nil
end

function Editor.pollHostPointers()
  local down = love.mouse and love.mouse.isDown and love.mouse.isDown(1)
  if down then
    local x, y = love.mouse.getPosition()
    if not Editor._hostMouse then
      Editor._hostMouse = true
      beginDrag("mouse", x, y)
      if Editor._closed then return end
    else
      moveDrag("mouse", x, y)
    end
  elseif Editor._hostMouse then
    Editor._hostMouse = false
    endDrag("mouse")
  end
  if not (love.touch and love.touch.getTouches and love.touch.getPosition) then
    return
  end
  Editor._hostTouches = Editor._hostTouches or {}
  local seen = {}
  for _, id in ipairs(love.touch.getTouches()) do
    seen[id] = true
    local ok, tx, ty = pcall(love.touch.getPosition, id)
    if ok and tx then
      if not Editor._hostTouches[id] then
        Editor._hostTouches[id] = true
        beginDrag(id, tx, ty)
        if Editor._closed then return end
      else
        moveDrag(id, tx, ty)
      end
    end
  end
  for id in pairs(Editor._hostTouches) do
    if not seen[id] then
      Editor._hostTouches[id] = nil
      endDrag(id)
    end
  end
end

function Editor.mousepressed(x, y, button)
  if button ~= 1 then return end
  -- Finger / mouse tap yields the Joy-Con pointer so the click lands where
  -- the event said (same NX soft-miss fix as the save editor).
  PadCursor.yieldToPointer()
  beginDrag("mouse", x, y)
end

function Editor.mousemoved(x, y)
  if love.mouse.isDown(1) then moveDrag("mouse", x, y) end
end

function Editor.mousereleased(x, y, button)
  if button ~= 1 then return end
  endDrag("mouse")
end

function Editor.touchpressed(id, x, y)
  PadCursor.yieldToPointer()
  beginDrag(id, x, y)
end

function Editor.touchmoved(id, x, y)
  moveDrag(id, x, y)
end

function Editor.touchreleased(id, x, y)
  endDrag(id)
end

local function handlePadAction(action)
  if not action then return end
  if action == "a" then
    local mx, my = PadCursor.pointer()
    beginDrag("pad", mx, my)
  elseif action == "b" then
    close()
  elseif action == "tab_prev" then
    TouchControls:nudgeScale(-TouchControls.SCALE_STEP)
  elseif action == "tab_next" then
    TouchControls:nudgeScale(TouchControls.SCALE_STEP)
  end
end

function Editor.gamepadpressed(joystick, button)
  handlePadAction(PadCursor.gamepadpressed(joystick, button))
end

function Editor.gamepadreleased(joystick, button)
  PadCursor.gamepadreleased(joystick, button)
  -- A release ends a pad drag (hold A + stick to reposition a control).
  local action = GamepadMap.mapGamepadButton(button)
  if action == "a" then endDrag("pad") end
end

function Editor.gamepadaxis(joystick, axis, value)
  PadCursor.gamepadaxis(joystick, axis, value)
end

function Editor.joystickpressed(joystick, button)
  handlePadAction(PadCursor.joystickpressed(joystick, button))
end

function Editor.joystickreleased(joystick, button)
  PadCursor.joystickreleased(joystick, button)
  if GamepadMap.ignoreRawForJoystick(joystick) then return end
  local padButton = GamepadMap.mapRawToGamepadButton(button)
  if padButton then
    local action = GamepadMap.mapGamepadButton(padButton)
    if action == "a" then endDrag("pad") end
  end
end

function Editor.joystickaxis(joystick, axis, value)
  PadCursor.joystickaxis(joystick, axis, value)
end

function Editor.joystickhat(joystick, hat, direction)
  PadCursor.joystickhat(joystick, hat, direction)
end

function Editor.keypressed(key)
  if key == "escape" or key == "return" or key == "space" then
    close()
  elseif key == "r" then
    resetLayout()
  elseif key == "[" then
    Editor.cycleSkin(-1)
  elseif key == "]" then
    Editor.cycleSkin(1)
  elseif key == "e" then
    Editor.exportSkin()
  -- desktop shortcuts for the size -/+ (POKEPORT_TOUCH=1 testing, #633)
  elseif key == "-" or key == "kp-" then
    TouchControls:nudgeScale(-TouchControls.SCALE_STEP)
  elseif key == "=" or key == "+" or key == "kp+" then
    TouchControls:nudgeScale(TouchControls.SCALE_STEP)
  end
end

function Editor.new(game)
  local state = { game = game, isOpaque = true }
  Editor.hostPoll = true
  Editor.load({
    version = require("src.core.GameVersion").get(),
    hostPoll = true,
    onClose = function()
      Editor.hostPoll = false
      if game.options then
        game.options.touchControls = TouchControls:config()
      end
      if game.stack and game.stack:top() == state then
        game.stack:pop()
      end
      if game.applyOptions then game:applyOptions() end
    end,
  })
  function state:wantsFillScale() return true end
  function state:drawsWidescreen() return true end
  function state:update(dt)
    Editor.update(dt)
    local input = self.game and self.game.input
    if input and input:wasPressed("b") then close() end
  end
  function state:draw() end
  function state:drawWidescreen(_w, _h)
    Editor.draw()
  end
  return state
end

return Editor
