Changelog = {}

local CHANGELOG_URL = "https://odbls.amatorkabli.site/player/index.php"
local CACHE_TARGET = "changelog_cache.html"

local panel = nil
local textWidget = nil
local scrollBar = nil

local httpConn = nil
local gameConn = nil

local downloadedOnce = false
local hiddenForSession = false
local downloadRequestId = nil

local function logLine(msg)
  print("[client_changelog] " .. msg)
end

local function trim(s)
  if not s then return "" end
  s = s:gsub("^%s+", "")
  s = s:gsub("%s+$", "")
  return s
end

local function transliteratePL(s)
  if not s then return "" end
  s = s:gsub("ą", "a"):gsub("Ą", "A")
  s = s:gsub("ć", "c"):gsub("Ć", "C")
  s = s:gsub("ę", "e"):gsub("Ę", "E")
  s = s:gsub("ł", "l"):gsub("Ł", "L")
  s = s:gsub("ń", "n"):gsub("Ń", "N")
  s = s:gsub("ó", "o"):gsub("Ó", "O")
  s = s:gsub("ś", "s"):gsub("Ś", "S")
  s = s:gsub("ż", "z"):gsub("Ż", "Z")
  s = s:gsub("ź", "z"):gsub("Ź", "Z")
  return s
end

local function stripTags(html)
  if not html then return "" end
  html = html:gsub("<script[^>]*>.-</script>", " ")
  html = html:gsub("<style[^>]*>.-</style>", " ")
  html = html:gsub("<[^>]+>", " ")
  html = html:gsub("&nbsp;", " ")
  html = html:gsub("&amp;", "&")
  html = html:gsub("&lt;", "<")
  html = html:gsub("&gt;", ">")
  html = html:gsub("&#39;", "'")
  html = html:gsub("&quot;", "\"")
  html = html:gsub("[\r\n\t]+", " ")
  html = html:gsub(" +", " ")
  return trim(html)
end

local function tryGetClientVersionFromBackground()
  if _G.ODBLS_CLIENT_VERSION then
    return tostring(_G.ODBLS_CLIENT_VERSION)
  end

  local root = g_ui.getRootWidget()
  if not root then return nil end

  local w = root:recursiveGetChildById("clientVersionLabel")
  if not w then
    return nil
  end

  local txt = w:getText() or ""
  local ver = txt:match("ODBLS%s+Client%s+v([%w%._%-]+)")
  if ver and ver ~= "" then
    return ver
  end

  return nil
end

local function parseChangelog(html)
  if not html or #html < 10 then
    return false, "Empty response", nil
  end

  local version = html:match("Current%s+version:%s*<b>(.-)</b>")
  if version then version = trim(stripTags(version)) end

  local stripped = stripTags(html)
  local releaseId = stripped:match("Release%s+([%w%._%-]+)")
  local releaseText = nil
  if releaseId then
    releaseText = "Release " .. releaseId
  end

  local ulBlock = nil
  local releasePos = nil
  if releaseId then
    releasePos = html:find("Release%s+" .. releaseId)
  else
    releasePos = html:find("Release")
  end

  if releasePos then
    local ulStart = html:find("<ul", releasePos)
    if ulStart then
      local ulEnd = html:find("</ul>", ulStart)
      if ulEnd then
        ulBlock = html:sub(ulStart, ulEnd + 4)
      end
    end
  end

  local items = {}
  if ulBlock then
    for li in ulBlock:gmatch("<li[^>]*>(.-)</li>") do
      local t = trim(stripTags(li))
      if t ~= "" then
        table.insert(items, t)
      end
    end
  end

  if (not version or version == "") and (not releaseText) and (#items == 0) then
    return false, "No expected fields found", nil
  end

  local out = ""
  if version and version ~= "" then
    out = out .. "Current version: " .. version .. "\n"
  end
  if releaseText then
    out = out .. releaseText .. "\n"
  end
  if #items > 0 then
    out = out .. "\n"
    for i = 1, #items do
      out = out .. "- " .. items[i] .. "\n"
    end
  else
    out = out .. "\n(no entries found)\n"
  end

  out = transliteratePL(out)
  return true, out, version
end

local function setPanelText(txt)
  if not textWidget then return end
  textWidget:setText(transliteratePL(txt or ""))
end

local function bindScroll()
  if not textWidget or not scrollBar then
    return
  end

  -- Some forks expose setVerticalScrollBar on TextEdit / UIWidget, some only on UIScrollArea.
  if textWidget.setVerticalScrollBar then
    local ok, err = pcall(function()
      textWidget:setVerticalScrollBar(scrollBar)
    end)
    if ok then
      logLine("ScrollBar bound via changelogText:setVerticalScrollBar().")
    else
      logLine("WARNING: setVerticalScrollBar failed: " .. tostring(err))
    end
  else
    logLine("WARNING: changelogText has no setVerticalScrollBar()")
  end
end

local function ensureUI()
  if _G.__odbls_changelog_hidden_for_session then
    hiddenForSession = true
    return false
  end

  if panel and not panel:isDestroyed() then
    return true
  end

  local root = g_ui.getRootWidget()
  if not root then
    return false
  end

  local enterGame = root:getChildById("enterGame")
  if not enterGame then
    return false
  end

  panel = g_ui.loadUI("changelog", root)
  if not panel then
    logLine("ERROR: failed to load changelog.otui (panel is nil)")
    return false
  end

  textWidget = panel:getChildById("changelogText")
  if not textWidget then
    logLine("ERROR: changelogText widget not found")
    return false
  end

  scrollBar = panel:getChildById("changelogScrollBar")
  if not scrollBar then
    logLine("WARNING: changelogScrollBar widget not found")
  end

  bindScroll()

  if hiddenForSession then
    panel:hide()
  else
    panel:show()
  end

  setPanelText("Loading changelog...")
  return true
end

local function vfsNormalizePath(p)
  p = tostring(p or "")
  p = p:gsub("^/+", "")
  p = p:gsub("\\", "/")
  return p
end

local function vfsReadDownloaded(pathFromEvent)
  local rel = vfsNormalizePath(pathFromEvent)
  if rel == "" then
    rel = CACHE_TARGET
  end
  local vfsPath = "/downloads/" .. rel

  local exists = false
  if g_resources and g_resources.fileExists then
    exists = g_resources.fileExists(vfsPath)
  end

  logLine(string.format("Try read: vfsPath=%s exists=%s", vfsPath, tostring(exists)))

  if not exists then
    return nil, "not_found:" .. vfsPath
  end

  local ok, dataOrErr = pcall(function()
    return g_resources.readFileContents(vfsPath)
  end)
  if not ok then
    return nil, "read_exception:" .. tostring(dataOrErr)
  end

  return dataOrErr, nil
end

local function startDownloadOnce()
  if downloadedOnce then
    logLine("Download already started earlier in this client session, skipping.")
    return
  end
  downloadedOnce = true

  if _G.__odbls_changelog_hidden_for_session then
    logLine("Hidden for session -> skip download.")
    return
  end

  logLine("Starting download: url=" .. CHANGELOG_URL)
  logLine("Cache target passed to download=" .. CACHE_TARGET)

  if g_resources and g_resources.getWorkDir then
    logLine("g_resources.getWorkDir()=" .. tostring(g_resources.getWorkDir()))
  end
  if g_resources and g_resources.getWriteDir then
    logLine("g_resources.getWriteDir()=" .. tostring(g_resources.getWriteDir()))
  end

  if not g_http or not g_http.download then
    logLine("ERROR: g_http.download not available")
    setPanelText("Failed to download changelog (g_http.download missing).")
    return
  end

  downloadRequestId = g_http.download(CHANGELOG_URL, CACHE_TARGET)
  logLine("g_http.download requestId=" .. tostring(downloadRequestId))
end

local function loadAndShowFromDownloads(pathFromEvent, attempt, maxAttempts, delayMs)
  local html, err = vfsReadDownloaded(pathFromEvent)
  if (not html or #html == 0) then
    if attempt < maxAttempts then
      logLine("Downloaded file not ready yet: attempt " .. attempt .. "/" .. maxAttempts .. " err=" .. tostring(err))
      scheduleEvent(function()
        loadAndShowFromDownloads(pathFromEvent, attempt + 1, maxAttempts, delayMs)
      end, delayMs)
      return
    end

    logLine("Read FAILED after retries, err=" .. tostring(err))
    setPanelText("Failed to download changelog (read failed).")
    return
  end

  logLine("Read OK, bytes=" .. tostring(#html))

  local ok, parsedOrErr, pageVersion = parseChangelog(html)
  if ok then
    logLine("Parse OK.")

    local clientVer = tryGetClientVersionFromBackground()
    if clientVer then
      logLine("Client version detected=" .. tostring(clientVer))
    else
      logLine("Client version not detected (clientVersionLabel not found or pattern mismatch)")
    end

    if clientVer and pageVersion and tostring(clientVer) ~= tostring(pageVersion) then
      local header = "UPDATE REQUIRED: client=" .. tostring(clientVer) .. " latest=" .. tostring(pageVersion) .. "\n\n"
      parsedOrErr = transliteratePL(header) .. parsedOrErr
      logLine("Update check: DIFFERENT")
    else
      if clientVer and pageVersion then
        logLine("Update check: OK")
      else
        logLine("Update check: skipped (missing client or page version)")
      end
    end

    setPanelText(parsedOrErr)
    if panel and not panel:isDestroyed() and (not hiddenForSession) then
      panel:show()
    end
  else
    logLine("Parse FAILED: " .. tostring(parsedOrErr))

    local preview = stripTags(html)
    preview = transliteratePL(preview)
    local N = 400
    if #preview > N then preview = preview:sub(1, N) .. "..." end

    setPanelText("Failed to parse changelog.\n\nPreview:\n" .. preview)
    if panel and not panel:isDestroyed() and (not hiddenForSession) then
      panel:show()
    end
  end
end

function Changelog.onGameStart()
  hiddenForSession = true
  _G.__odbls_changelog_hidden_for_session = true

  if panel and not panel:isDestroyed() then
    panel:destroy()
  end

  panel = nil
  textWidget = nil
  scrollBar = nil

  logLine("Game started -> changelog hidden for this session.")
end

function Changelog.onDownload(...)
  local args = { ... }
  logLine("onDownload called, argc=" .. tostring(#args))

  for i = 1, #args do
    local v = args[i]
    local t = type(v)
    local s = tostring(v)
    if #s > 160 then s = s:sub(1, 160) .. "..." end
    logLine("  arg[" .. i .. "] type=" .. t .. " val=" .. s)
  end

  local rid = args[1]
  if type(rid) == "number" and downloadRequestId and rid ~= downloadRequestId then
    logLine("Ignoring onDownload for other requestId=" .. tostring(rid))
    return
  end

  local pathFromEvent = args[4]
  logLine("Download completed -> path=" .. tostring(pathFromEvent))

  loadAndShowFromDownloads(pathFromEvent, 1, 10, 100)
end

function Changelog.init()
  logLine("init()")

  if _G.__odbls_changelog_hidden_for_session then
    hiddenForSession = true
    logLine("Hidden for session -> init does nothing.")
    return
  end

  ensureUI()

  if g_http then
    httpConn = connect(g_http, { onDownload = Changelog.onDownload })
    logLine("Connected g_http.onDownload")
  else
    logLine("WARNING: g_http not available at init")
  end

  if g_game then
    gameConn = connect(g_game, { onGameStart = Changelog.onGameStart })
    logLine("Connected g_game.onGameStart")
  end

  scheduleEvent(function()
    ensureUI()
    startDownloadOnce()
  end, 200)
end

function Changelog.terminate()
  logLine("terminate()")

  if httpConn then
    disconnect(g_http, httpConn)
    httpConn = nil
  end

  if gameConn then
    disconnect(g_game, gameConn)
    gameConn = nil
  end

  if panel and not panel:isDestroyed() then
    panel:destroy()
  end
  panel = nil
  textWidget = nil
  scrollBar = nil
end
