--[[
  TimeLogger — records PLAYER_LOGIN / PLAYER_LOGOUT; exports raw events and derived sessions (CSV/JSON).
  Commands: /timelogger, /tlog
]]

local ADDON_NAME = ...

local db

local pendingPruneDays

local function EnsureDB()
  if type(TimeLoggerDB) ~= "table" then
    TimeLoggerDB = {}
  end
  if type(TimeLoggerDB.events) ~= "table" then
    TimeLoggerDB.events = {}
  end
  db = TimeLoggerDB
end

local function UtcIso()
  return date("!%Y-%m-%dT%H:%M:%SZ")
end

local function Record(kind)
  EnsureDB()
  local char = UnitName("player") or ""
  local realm = GetRealmName() or ""
  db.events[#db.events + 1] = {
    unix = time(),
    utc = UtcIso(),
    event = kind,
    character = char,
    realm = realm,
  }
end

local function CharKey(e)
  return (e.realm or "") .. "|" .. (e.character or "")
end

--- Derive sessions per character (realm|name): login → logout, or no_logout / open.
local function BuildSessions()
  EnsureDB()
  local sorted = {}
  for i = 1, #db.events do
    sorted[#sorted + 1] = db.events[i]
  end
  table.sort(sorted, function(a, b)
    return (a.unix or 0) < (b.unix or 0)
  end)

  local pending = {}
  local sessions = {}
  local session_id = 0

  for _, e in ipairs(sorted) do
    local k = CharKey(e)
    if e.event == "login" then
      if pending[k] then
        local p = pending[k]
        session_id = session_id + 1
        sessions[#sessions + 1] = {
          session_id = session_id,
          start_unix = p.start_unix,
          start_utc = p.start_utc,
          end_unix = e.unix,
          end_utc = e.utc,
          duration_sec = e.unix - p.start_unix,
          character = p.character,
          realm = p.realm,
          status = "no_logout",
        }
      end
      pending[k] = {
        start_unix = e.unix,
        start_utc = e.utc,
        character = e.character,
        realm = e.realm,
      }
    elseif e.event == "logout" then
      local p = pending[k]
      if p then
        session_id = session_id + 1
        sessions[#sessions + 1] = {
          session_id = session_id,
          start_unix = p.start_unix,
          start_utc = p.start_utc,
          end_unix = e.unix,
          end_utc = e.utc,
          duration_sec = e.unix - p.start_unix,
          character = p.character,
          realm = p.realm,
          status = "closed",
        }
        pending[k] = nil
      end
    end
  end

  for _, p in pairs(pending) do
    session_id = session_id + 1
    sessions[#sessions + 1] = {
      session_id = session_id,
      start_unix = p.start_unix,
      start_utc = p.start_utc,
      end_unix = nil,
      end_utc = nil,
      duration_sec = nil,
      character = p.character,
      realm = p.realm,
      status = "open",
    }
  end

  return sessions
end

local function CsvEscape(s)
  s = tostring(s or "")
  if s:find('["\r\n,]') then
    s = '"' .. s:gsub('"', '""') .. '"'
  end
  return s
end

local function BuildCSV()
  local lines = { "unix,utc_iso,event,character,realm" }
  for i = 1, #db.events do
    local e = db.events[i]
    lines[#lines + 1] = string.format(
      "%d,%s,%s,%s,%s",
      e.unix or 0,
      CsvEscape(e.utc),
      CsvEscape(e.event),
      CsvEscape(e.character),
      CsvEscape(e.realm)
    )
  end
  return table.concat(lines, "\n")
end

local function JsonEscape(s)
  s = tostring(s or "")
  return s:gsub("\\", "\\\\"):gsub('"', '\\"'):gsub("\r", "\\r"):gsub("\n", "\\n")
end

local function BuildJSON()
  local parts = { "[" }
  local n = #db.events
  for i = 1, n do
    local e = db.events[i]
    local chunk = string.format(
      '{"unix":%d,"utc":"%s","event":"%s","character":"%s","realm":"%s"}',
      e.unix or 0,
      JsonEscape(e.utc),
      JsonEscape(e.event),
      JsonEscape(e.character),
      JsonEscape(e.realm)
    )
    if i < n then
      chunk = chunk .. ","
    end
    parts[#parts + 1] = chunk
  end
  parts[#parts + 1] = "]"
  return table.concat(parts, "\n")
end

local function BuildSessionsCSV()
  local sessions = BuildSessions()
  local lines = { "session_id,start_unix,start_utc,end_unix,end_utc,duration_sec,character,realm,status" }
  for i = 1, #sessions do
    local s = sessions[i]
    lines[#lines + 1] = string.format(
      "%d,%s,%s,%s,%s,%s,%s,%s,%s",
      s.session_id,
      s.start_unix or 0,
      CsvEscape(s.start_utc),
      s.end_unix ~= nil and tostring(s.end_unix) or "",
      CsvEscape(s.end_utc or ""),
      s.duration_sec ~= nil and tostring(s.duration_sec) or "",
      CsvEscape(s.character),
      CsvEscape(s.realm),
      CsvEscape(s.status)
    )
  end
  return table.concat(lines, "\n")
end

local function BuildSessionsJSON()
  local sessions = BuildSessions()
  local parts = { "[" }
  local n = #sessions
  for i = 1, n do
    local s = sessions[i]
    local chunk
    if s.end_unix == nil then
      chunk = string.format(
        '{"session_id":%d,"start_unix":%d,"start_utc":"%s","end_unix":null,"end_utc":null,"duration_sec":null,"character":"%s","realm":"%s","status":"%s"}',
        s.session_id,
        s.start_unix or 0,
        JsonEscape(s.start_utc),
        JsonEscape(s.character),
        JsonEscape(s.realm),
        JsonEscape(s.status)
      )
    else
      chunk = string.format(
        '{"session_id":%d,"start_unix":%d,"start_utc":"%s","end_unix":%d,"end_utc":"%s","duration_sec":%d,"character":"%s","realm":"%s","status":"%s"}',
        s.session_id,
        s.start_unix or 0,
        JsonEscape(s.start_utc),
        s.end_unix,
        JsonEscape(s.end_utc or ""),
        s.duration_sec or 0,
        JsonEscape(s.character),
        JsonEscape(s.realm),
        JsonEscape(s.status)
      )
    end
    if i < n then
      chunk = chunk .. ","
    end
    parts[#parts + 1] = chunk
  end
  parts[#parts + 1] = "]"
  return table.concat(parts, "\n")
end

local function CopyEventsSnapshot()
  local t = {}
  for i = 1, #db.events do
    local e = db.events[i]
    t[i] = CopyTable and CopyTable(e) or {
      unix = e.unix,
      utc = e.utc,
      event = e.event,
      character = e.character,
      realm = e.realm,
    }
  end
  return t
end

local function DoPrune(days)
  EnsureDB()
  local cutoff = time() - (days * 86400)
  db.events_backup = CopyEventsSnapshot()
  db.events_backup_time = time()
  local kept = {}
  for i = 1, #db.events do
    local e = db.events[i]
    if (e.unix or 0) >= cutoff then
      kept[#kept + 1] = e
    end
  end
  db.events = kept
  print(
    string.format(
      "|cff00ff00TimeLogger:|r Pruned events older than %d days. Kept %d rows; full pre-prune snapshot in events_backup (%d rows).",
      days,
      #db.events,
      #db.events_backup
    )
  )
end

-- Export UI -----------------------------------------------------------------

local exportFrame
local exportEdit
local exportMode = "events_csv"

local function ResizeExportEdit()
  if not exportEdit or not exportFrame then
    return
  end
  local scroll = exportFrame.scroll
  local w = math.max(scroll:GetWidth() - 24, 1)
  exportEdit:SetWidth(w)
  local insetL, insetR, insetT, insetB = exportEdit:GetTextInsets()
  insetL = insetL or 0
  insetR = insetR or 0
  insetT = insetT or 0
  insetB = insetB or 0
  local contentW = math.max(w - insetL - insetR, 1)
  local fs = exportFrame.measureFS
  local font, size, flags = exportEdit:GetFont()
  if font then
    fs:SetFont(font, size, flags)
  end
  fs:SetWidth(contentW)
  if fs.SetWordWrap then
    fs:SetWordWrap(true)
  end
  fs:SetText(exportEdit:GetText() or "")
  local textH = fs:GetStringHeight() or 0
  local h = math.max(textH + insetT + insetB + 4, scroll:GetHeight())
  exportEdit:SetHeight(h)
end

local function GetExportText()
  EnsureDB()
  if exportMode == "events_json" then
    return BuildJSON()
  elseif exportMode == "sessions_csv" then
    return BuildSessionsCSV()
  elseif exportMode == "sessions_json" then
    return BuildSessionsJSON()
  end
  return BuildCSV()
end

local function RefreshExportText()
  if not exportEdit then
    return
  end
  exportEdit:SetText(GetExportText())
  ResizeExportEdit()
end

local function CreateExportUI()
  local f = CreateFrame("Frame", "TimeLoggerExportFrame", UIParent, "BackdropTemplate")
  f:SetSize(540, 480)
  f:SetPoint("CENTER")
  f:SetFrameStrata("DIALOG")
  f:SetMovable(true)
  f:EnableMouse(true)
  f:RegisterForDrag("LeftButton")
  f:SetScript("OnDragStart", f.StartMoving)
  f:SetScript("OnDragStop", f.StopMovingOrSizing)
  f:SetBackdrop({
    bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
    edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
    tile = true,
    tileSize = 32,
    edgeSize = 32,
    insets = { left = 8, right = 8, top = 10, bottom = 8 },
  })

  local title = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  title:SetPoint("TOP", 0, -14)
  title:SetText("TimeLogger — events & sessions (copy below)")

  local closeBtn = CreateFrame("Button", nil, f, "UIPanelCloseButton")
  closeBtn:SetPoint("TOPRIGHT", -4, -4)
  closeBtn:SetScript("OnClick", function()
    f:Hide()
  end)

  local evCsv = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
  evCsv:SetSize(88, 22)
  evCsv:SetPoint("TOPLEFT", 16, -38)
  evCsv:SetText("Events CSV")
  evCsv:SetScript("OnClick", function()
    exportMode = "events_csv"
    RefreshExportText()
  end)

  local evJson = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
  evJson:SetSize(88, 22)
  evJson:SetPoint("LEFT", evCsv, "RIGHT", 6, 0)
  evJson:SetText("Events JSON")
  evJson:SetScript("OnClick", function()
    exportMode = "events_json"
    RefreshExportText()
  end)

  local copyBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
  copyBtn:SetSize(120, 22)
  copyBtn:SetPoint("TOPRIGHT", -32, -38)
  copyBtn:SetText("Copy to clipboard")
  copyBtn:SetScript("OnClick", function()
    local text = exportEdit:GetText() or ""
    if C_ChatInfo and C_ChatInfo.CopyStringToClipboard then
      C_ChatInfo.CopyStringToClipboard(text)
      print("|cff00ff00TimeLogger:|r Copied " .. #text .. " characters to clipboard.")
    else
      exportEdit:SetFocus()
      exportEdit:HighlightText()
      print("|cffff9900TimeLogger:|r Select all (Ctrl+A) and copy (Ctrl+C).")
    end
  end)

  local sessCsv = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
  sessCsv:SetSize(108, 22)
  sessCsv:SetPoint("TOPLEFT", 16, -66)
  sessCsv:SetText("Sessions CSV")
  sessCsv:SetScript("OnClick", function()
    exportMode = "sessions_csv"
    RefreshExportText()
  end)

  local sessJson = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
  sessJson:SetSize(108, 22)
  sessJson:SetPoint("LEFT", sessCsv, "RIGHT", 6, 0)
  sessJson:SetText("Sessions JSON")
  sessJson:SetScript("OnClick", function()
    exportMode = "sessions_json"
    RefreshExportText()
  end)

  local pruneLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
  pruneLabel:SetPoint("BOTTOMLEFT", 20, 38)
  pruneLabel:SetText("Prune events older than")

  local pruneDays = CreateFrame("EditBox", nil, f, "InputBoxTemplate")
  pruneDays:SetSize(50, 20)
  pruneDays:SetPoint("LEFT", pruneLabel, "RIGHT", 8, 0)
  pruneDays:SetAutoFocus(false)
  pruneDays:SetText("30")
  if pruneDays.SetNumeric then
    pruneDays:SetNumeric(true)
  end
  f.pruneDaysEdit = pruneDays

  local daysSuffix = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
  daysSuffix:SetPoint("LEFT", pruneDays, "RIGHT", 6, 0)
  daysSuffix:SetText("days")

  local pruneBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
  pruneBtn:SetSize(72, 22)
  pruneBtn:SetPoint("LEFT", daysSuffix, "RIGHT", 12, 0)
  pruneBtn:SetText("Prune")
  pruneBtn:SetScript("OnClick", function()
    local d = tonumber(pruneDays:GetText())
    if not d or d < 1 then
      print("|cffff9900TimeLogger:|r Enter a number of days (1 or more).")
      return
    end
    pendingPruneDays = math.floor(d)
    StaticPopup_Show("TIMELOGGER_PRUNE_CONFIRM", tostring(pendingPruneDays))
  end)

  local scroll = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
  scroll:SetPoint("TOPLEFT", 16, -94)
  scroll:SetPoint("BOTTOMRIGHT", -32, 68)
  f.scroll = scroll

  local measureFS = f:CreateFontString(nil, "ARTWORK", "ChatFontNormal")
  measureFS:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 10000, 10000)
  measureFS:Hide()
  f.measureFS = measureFS

  local edit = CreateFrame("EditBox", nil, scroll)
  edit:SetMultiLine(true)
  edit:SetAutoFocus(false)
  edit:SetFontObject(ChatFontNormal)
  edit:SetWidth(460)
  edit:SetTextInsets(8, 8, 8, 8)
  edit:SetScript("OnEscapePressed", function()
    f:Hide()
  end)
  edit:SetScript("OnTextChanged", function(self, userInput)
    if userInput then
      return
    end
    ResizeExportEdit()
  end)
  scroll:SetScrollChild(edit)

  exportFrame = f
  exportEdit = edit
end

local function ShowExport()
  EnsureDB()
  if not exportFrame then
    CreateExportUI()
  end
  RefreshExportText()
  exportFrame:Show()
  exportFrame:Raise()
  C_Timer.After(0, function()
    if exportFrame and exportFrame:IsShown() then
      ResizeExportEdit()
    end
  end)
end

StaticPopupDialogs["TIMELOGGER_PRUNE_CONFIRM"] = {
  text = "Prune events older than %s days?|nYour full current event list will be copied to events_backup first.",
  button1 = YES,
  button2 = NO,
  OnAccept = function()
    if pendingPruneDays then
      DoPrune(pendingPruneDays)
      pendingPruneDays = nil
      if exportFrame and exportFrame:IsShown() then
        RefreshExportText()
      end
    end
  end,
  OnCancel = function()
    pendingPruneDays = nil
  end,
  timeout = 0,
  whileDead = true,
  hideOnEscape = true,
}

-- Events --------------------------------------------------------------------

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("PLAYER_LOGOUT")

eventFrame:SetScript("OnEvent", function(_, event, arg1)
  if event == "ADDON_LOADED" and arg1 == ADDON_NAME then
    EnsureDB()
  elseif event == "PLAYER_LOGIN" then
    Record("login")
  elseif event == "PLAYER_LOGOUT" then
    Record("logout")
  end
end)

SLASH_TIMELOGGER1 = "/timelogger"
SLASH_TIMELOGGER2 = "/tlog"
SlashCmdList["TIMELOGGER"] = function()
  ShowExport()
end
