local PT = AhUI
if not PT or not PT.FarmTracker then return end

PT.FarmTracker.UI = PT.FarmTracker.UI or {}
local UI = PT.FarmTracker.UI
local FT = PT.FarmTracker
local L = PT.L or {}
local Fonts = PT.fonts or {}

local ROW_H = 30
local HEADER_H = 16
local WINDOW_W = 360
local COLLAPSED_W = WINDOW_W
local COLLAPSED_H = 32
local EXPANDED_W = WINDOW_W
local EXPANDED_H = 430
local CONTENT_W = WINDOW_W - 20
local LIST_W = WINDOW_W - 54

local function FormatCoins(value)
    return GetCoinTextureString(tonumber(value) or 0, 12)
end

local function FormatGoldOnly(value)
    local copper = tonumber(value) or 0
    local gold = math.floor(copper / 10000)
    return string.format("%d|TInterface\\MoneyFrame\\UI-GoldIcon:12:12:0:0|t", gold)
end

local function FormatDuration(seconds)
    seconds = math.max(0, tonumber(seconds) or 0)

    local h = math.floor(seconds / 3600)
    local m = math.floor((seconds % 3600) / 60)
    local s = math.floor(seconds % 60)

    if h > 0 then
        return string.format("%dч. %02dм.", h, m)
    elseif m > 0 then
        return string.format("%dм. %02dс.", m, s)
    else
        return string.format("%dс.", s)
    end
end

local function GetMoneyPerHour(totalCopper, duration)
    totalCopper = tonumber(totalCopper) or 0
    duration = tonumber(duration) or 0
    if duration <= 0 or totalCopper <= 0 then return 0 end
    return math.floor(totalCopper * 3600 / duration)
end

local function ResolveFontObject(size)
    size = tonumber(size) or 12

    if size >= 15 then
        return Fonts.title or Fonts.normal or Fonts.small or GameFontHighlight
    elseif size <= 11 then
        return Fonts.small or Fonts.normal or Fonts.title or GameFontNormal
    else
        return Fonts.normal or Fonts.small or Fonts.title or GameFontNormal
    end
end

local function SetFont(fs, size, r, g, b)
    if not fs then return end

    fs:SetFontObject(ResolveFontObject(size))
    fs:SetShadowOffset(1, -1)
    fs:SetShadowColor(0, 0, 0, 0.9)

    if r then
        fs:SetTextColor(r, g, b)
    end
end

local function ShortSourceName(source)
    if source == "AUCTIONATOR" then return "Auctionator" end
    if source == "TSM" then return "TSM" end
    if source == "VENDOR" then return "Vendor" end
    if source == "AUTO" then return "Auto" end
    return source or "-"
end

local function ShortObtainName(kind)
    if kind == "loot" then return L["FT_ObtainLoot"] or "Loot" end
    if kind == "crafting" then return L["FT_ObtainCrafting"] or "Crafting" end
    if kind == "mail" then return L["FT_ObtainMail"] or "Mail" end
    if kind == "money" then return L["FT_ObtainMoney"] or "Money" end
    return kind or "-"
end

local function GetObtainText(entry)
    if not entry then
        return L["FT_ObtainLoot"] or "Loot"
    end

    local t = entry.obtainTypes
    if type(t) ~= "table" then
        return L["FT_ObtainLoot"] or "Loot"
    end

    local order = { "loot", "crafting", "mail", "money" }
    local found = {}

    for _, key in ipairs(order) do
        if t[key] then
            found[#found + 1] = ShortObtainName(key)
        end
    end

    if #found == 0 then
        return L["FT_ObtainLoot"] or "Loot"
    elseif #found == 1 then
        return found[1]
    elseif #found == 2 then
        return found[1] .. "/" .. found[2]
    else
        return L["FT_ObtainMixed"] or "Mixed"
    end
end

local function BuildItemMeta(entry)
    return string.format(
        "x%s  •  %s  •  %s",
        tostring(entry.qty or 0),
        GetObtainText(entry),
        ShortSourceName(entry.source)
    )
end

local function SetButtonGlow(button, enabled)
    if not button then return end

    if not button.glow then
        local glow = button:CreateTexture(nil, "OVERLAY")
        glow:SetPoint("TOPLEFT", -6, 6)
        glow:SetPoint("BOTTOMRIGHT", 6, -6)
        glow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
        glow:SetBlendMode("ADD")
        glow:SetAlpha(0)
        button.glow = glow
    end

    if enabled then
        button.glow:SetAlpha(0.8)
    else
        button.glow:SetAlpha(0)
    end
end

local function EnsureBackdrop(parent, alpha)
    if parent._ftBg then return end

    local bg = parent:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetTexture("Interface\\Buttons\\WHITE8x8")
    bg:SetVertexColor(0.03, 0.03, 0.04, alpha or 0.94)
    parent._ftBg = bg

    local t = parent:CreateTexture(nil, "BORDER")
    t:SetPoint("TOPLEFT")
    t:SetPoint("TOPRIGHT")
    t:SetHeight(1)
    t:SetTexture("Interface\\Buttons\\WHITE8x8")
    t:SetVertexColor(1, 1, 1, 0.35)

    local b = parent:CreateTexture(nil, "BORDER")
    b:SetPoint("BOTTOMLEFT")
    b:SetPoint("BOTTOMRIGHT")
    b:SetHeight(1)
    b:SetTexture("Interface\\Buttons\\WHITE8x8")
    b:SetVertexColor(1, 1, 1, 0.35)

    local l = parent:CreateTexture(nil, "BORDER")
    l:SetPoint("TOPLEFT")
    l:SetPoint("BOTTOMLEFT")
    l:SetWidth(1)
    l:SetTexture("Interface\\Buttons\\WHITE8x8")
    l:SetVertexColor(1, 1, 1, 0.35)

    local r = parent:CreateTexture(nil, "BORDER")
    r:SetPoint("TOPRIGHT")
    r:SetPoint("BOTTOMRIGHT")
    r:SetWidth(1)
    r:SetTexture("Interface\\Buttons\\WHITE8x8")
    r:SetVertexColor(1, 1, 1, 0.35)
end

local function CopyColorValue(src)
    if type(src) ~= "table" then return nil end
    return {
        r = src.r or 1,
        g = src.g or 1,
        b = src.b or 1,
        a = src.a or 1,
    }
end

local function ResolveCustomColor(cfg, key)
    if not cfg then return nil end

    local p = cfg.__preview and cfg.__preview[key]
    if p then
        return CopyColorValue(p)
    end

    local s = cfg[key]
    if s then
        return CopyColorValue(s)
    end

    return nil
end

local function GetFarmCustomization()
    local c = PT.config and PT.config.customization
    local cfg = c and c.farmTracker
    if not cfg then return nil end

    return {
        backgroundColor = ResolveCustomColor(cfg, "backgroundColor"),
        borderColor     = ResolveCustomColor(cfg, "borderColor"),
        --headerColor     = ResolveCustomColor(cfg, "headerColor"),
        dividerColor    = ResolveCustomColor(cfg, "dividerColor"),
        accentTextColor = ResolveCustomColor(cfg, "accentTextColor"),
        textColor       = ResolveCustomColor(cfg, "textColor"),
        mutedTextColor  = ResolveCustomColor(cfg, "mutedTextColor"),
    }
end

local function CreatePanel(parent, alpha)
    local f = CreateFrame("Frame", nil, parent)
    EnsureBackdrop(f, alpha)
    return f
end

local function CreateHeaderCell(parent, width, text)
    local fs = parent:CreateFontString(nil, "OVERLAY")
    fs:SetWidth(width)
    fs:SetJustifyH("LEFT")
    SetFont(fs, 11, 0.95, 0.82, 0.25)
    fs:SetText(text or "")
    return fs
end

local function CreateRowButton(parent)
    local b = CreateFrame("Button", nil, parent)
    b:SetHeight(ROW_H)

    b.bg = b:CreateTexture(nil, "BACKGROUND")
    b.bg:SetAllPoints()
    b.bg:SetTexture("Interface\\Buttons\\WHITE8x8")
    b.bg:SetVertexColor(1, 1, 1, 0)

    b.hl = b:CreateTexture(nil, "HIGHLIGHT")
    b.hl:SetAllPoints()
    b.hl:SetTexture("Interface\\Buttons\\WHITE8x8")
    b.hl:SetVertexColor(1, 1, 1, 0.05)

    return b
end

local function ClearRows(rows)
    if not rows then return end
    for _, row in ipairs(rows) do
        row:Hide()
        row:SetParent(nil)
    end
    wipe(rows)
end

local function ApplyWindowTopAnchor(frame)
    local db = FT.Data:GetDB()

    local point = db.window.point or "TOPLEFT"
    local relPoint = db.window.relPoint or "TOPLEFT"
    local x = db.window.x
    local y = db.window.y

    if x == nil or y == nil then
        point = "TOPLEFT"
        relPoint = "TOPLEFT"
        x = 0
        y = -190
    end

    frame:ClearAllPoints()
    frame:SetPoint(point, UIParent, relPoint, x, y)
end

local function SaveWindowTopAnchor(frame)
    local db = FT.Data:GetDB()
    local parent = UIParent

    local left = frame:GetLeft()
    local top = frame:GetTop()
    if not left or not top then return end

    local parentLeft = parent:GetLeft() or 0
    local parentTop = parent:GetTop() or 0

    db.window.point = "TOPLEFT"
    db.window.relPoint = "TOPLEFT"
    db.window.x = left - parentLeft
    db.window.y = top - parentTop

    ApplyWindowTopAnchor(frame)
end

local function MakeMovable(frame)
    frame:SetMovable(true)
end

local function UpdateWindowGeometry(f)
    local cdb = FT.Data:GetCharDB()
    local db = FT.Data:GetDB()
    local collapsed = cdb.ui.collapsed == true

    f.content:SetShown(not collapsed)
    f.btnReset:SetShown(not collapsed)
    f.btnStart:SetShown(not collapsed)
    f.btnPause:SetShown(not collapsed)
    f.btnStop:SetShown(not collapsed)
    f.tabs.session:SetShown(not collapsed)
    f.tabs.history:SetShown(not collapsed)
    f.headerDivider:SetShown(not collapsed)

    ApplyWindowTopAnchor(f)
    f:SetWidth(WINDOW_W)

    if collapsed then
        f:SetHeight(COLLAPSED_H)
    else
        local h = tonumber(db.window.height) or EXPANDED_H
        if h > 520 or h < 300 then
            h = EXPANDED_H
        end
        f:SetHeight(h)
    end
end

local function GetSortedSessionItems(session)
    local items = {}
    if not session or not session.items then return items end

    for _, entry in pairs(session.items) do
        items[#items + 1] = entry
    end

    table.sort(items, function(a, b)
        return (a.totalPrice or 0) > (b.totalPrice or 0)
    end)

    return items
end

local function UpdateLiveTicker()
    if not UI.frame then return end

    if UI._ticker then
        UI._ticker:Cancel()
        UI._ticker = nil
    end

    if not UI.frame:IsShown() then return end
    if FT:GetSessionState() ~= "active" and FT:GetSessionState() ~= "paused" then return end

    UI._ticker = C_Timer.NewTicker(1, function()
        if not UI.frame or not UI.frame:IsShown() then return end

        local tab = FT.Data:GetCharDB().ui.activeTab or "session"
        if tab == "session" then
            UI:RefreshSession()
            if UI.ApplyCustomization then
                UI:ApplyCustomization()
            end
        end
    end)
end

local function CreateMainFrame()
    if UI.frame then return UI.frame end

    local cdb = FT.Data:GetCharDB()
    local db = FT.Data:GetDB()

    db.window.width = WINDOW_W
    if not db.window.height or db.window.height > 520 then
        db.window.height = EXPANDED_H
    end
    db.window.collapsedWidth = WINDOW_W

    if cdb.ui.collapsed == nil then cdb.ui.collapsed = true end

    local f = CreateFrame("Frame", "AhUIFarmTrackerFrame", UIParent)
    f:SetClampedToScreen(true)
    f:SetFrameStrata("HIGH")
    EnsureBackdrop(f, 0.96)

    if db.window.point then
        f:SetPoint(db.window.point, UIParent, db.window.relPoint or db.window.point, db.window.x or 0, db.window.y or 0)
    else
        f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 0, -190)
    end

    MakeMovable(f)
    UI.frame = f

    local header = CreateFrame("Button", nil, f)
    header:SetPoint("TOPLEFT", 0, 0)
    header:SetPoint("TOPRIGHT", 0, 0)
    header:SetHeight(COLLAPSED_H)
    header:RegisterForClicks("LeftButtonUp")
    f.header = header

    header:RegisterForDrag("LeftButton")
    header:SetScript("OnDragStart", function()
        if PT.config and PT.config.lockWindows then return end
        f:StartMoving()
    end)
    header:SetScript("OnDragStop", function()
        f:StopMovingOrSizing()
        SaveWindowTopAnchor(f)
    end)

    f.headerDivider = header:CreateTexture(nil, "ARTWORK")
    f.headerDivider:SetHeight(1)
    f.headerDivider:SetPoint("BOTTOMLEFT", 8, 0)
    f.headerDivider:SetPoint("BOTTOMRIGHT", -8, 0)
    f.headerDivider:SetTexture("Interface\\Buttons\\WHITE8x8")
    f.headerDivider:SetVertexColor(1, 1, 1, 0.3)

    f.headerTimer = header:CreateFontString(nil, "OVERLAY")
    f.headerTimer:SetPoint("LEFT", header, "LEFT", 10, 0)
    f.headerTimer:SetWidth(68)
    f.headerTimer:SetJustifyH("LEFT")
    f.headerTimer:SetWordWrap(false)
    SetFont(f.headerTimer, 14, 1, 0.84, 0.20)
    f.headerTimer:SetText("0с.")

    f.sep1 = header:CreateTexture(nil, "ARTWORK")
    f.sep1:SetSize(1, 16)
    f.sep1:SetTexture("Interface\\Buttons\\WHITE8x8")
    f.sep1:SetVertexColor(1, 1, 1, 0.35)
    f.sep1:SetPoint("LEFT", header, "LEFT", 82, 0)

local function HeaderIconButton(anchor)
    local b = CreateFrame("Button", nil, header)
    b:SetSize(20, 20)
    b:SetPoint("RIGHT", anchor, "LEFT", -4, 0)

    b.tex = b:CreateTexture(nil, "ARTWORK")
    b.tex:SetAllPoints()
    b.tex:SetAtlas("CreditsScreen-Assets-Buttons-Play")

    b.hl = b:CreateTexture(nil, "HIGHLIGHT")
    b.hl:SetAllPoints()
    b.hl:SetTexture("Interface\\Buttons\\WHITE8x8")
    b.hl:SetVertexColor(1, 1, 1, 0.10)

    return b
end

f.headerStop = HeaderIconButton(header)
f.headerStop:SetPoint("RIGHT", -6, 0)
f.headerStop.tex:SetAtlas("common-icon-redx")
f.headerStop:SetScript("OnClick", function()
    FT:StopSession()
    UI:Refresh()
end)

f.headerPause = HeaderIconButton(f.headerStop)
f.headerPause.tex:SetAtlas("CreditsScreen-Assets-Buttons-Pause")
f.headerPause:SetScript("OnClick", function()
    if FT:IsSessionPaused() then
        FT:ResumeSession()
    else
        FT:PauseSession()
    end
    UI:Refresh()
end)

f.headerStart = HeaderIconButton(f.headerPause)
f.headerStart.tex:SetAtlas("CreditsScreen-Assets-Buttons-Play")
f.headerStart:SetScript("OnClick", function()
    if FT:IsSessionPaused() then
        FT:ResumeSession()
    else
        FT:StartSession()
    end
    UI:Refresh()
end)

f.headerTotal = header:CreateFontString(nil, "OVERLAY")
f.headerTotal:SetPoint("LEFT", header, "LEFT", 95, 0)
f.headerTotal:SetWidth(60)
f.headerTotal:SetJustifyH("LEFT")
f.headerTotal:SetWordWrap(false)
SetFont(f.headerTotal, 13, 1, 1, 1)
f.headerTotal:SetText(FormatGoldOnly(0))

f.sep2 = header:CreateTexture(nil, "ARTWORK")
f.sep2:SetSize(1, 16)
f.sep2:SetTexture("Interface\\Buttons\\WHITE8x8")
f.sep2:SetVertexColor(1, 1, 1, 0.35)
f.sep2:SetPoint("LEFT", header, "LEFT", 165, 0)

f.headerGPH = header:CreateFontString(nil, "OVERLAY")
f.headerGPH:SetPoint("LEFT", header, "LEFT", 175, 0)
f.headerGPH:SetWidth(110)
f.headerGPH:SetJustifyH("LEFT")
f.headerGPH:SetWordWrap(false)
SetFont(f.headerGPH, 12, 0.88, 0.92, 1)
f.headerGPH:SetText((L["FT_GoldPerHour"] or "GPH") .. ": " .. FormatGoldOnly(0))

    header:SetScript("OnMouseUp", function(_, button)
        if button == "LeftButton"
            and not MouseIsOver(f.headerStart)
            and not MouseIsOver(f.headerPause)
            and not MouseIsOver(f.headerStop)
        then
            cdb.ui.collapsed = not cdb.ui.collapsed
            UpdateWindowGeometry(f)
            UI:Refresh()
        end
    end)

    f.content = CreateFrame("Frame", nil, f)
    f.content:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -42)
    f.content:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -10, 10)

    f.tabs = {}

    local tabSession = CreateFrame("Button", nil, f.content, "UIPanelButtonTemplate")
    tabSession:SetSize(72, 20)
    tabSession:SetPoint("TOPLEFT", 0, 0)
    tabSession:SetText(L["FT_TabSession"] or "Session")
    tabSession:SetScript("OnClick", function()
        UI:ShowTab("session")
    end)
    f.tabs.session = tabSession

    local tabHistory = CreateFrame("Button", nil, f.content, "UIPanelButtonTemplate")
    tabHistory:SetSize(72, 20)
    tabHistory:SetPoint("LEFT", tabSession, "RIGHT", 4, 0)
    tabHistory:SetText(L["FT_TabHistory"] or "History")
    tabHistory:SetScript("OnClick", function()
        UI:ShowTab("history")
    end)
    f.tabs.history = tabHistory

    f.status = f.content:CreateFontString(nil, "OVERLAY")
    f.status:SetPoint("LEFT", tabHistory, "RIGHT", 8, 0)
    f.status:SetPoint("RIGHT", 0, 0)
    f.status:SetJustifyH("RIGHT")
    SetFont(f.status, 11, 0.55, 0.85, 1)
    f.status:SetText(L["FT_Inactive"] or "Inactive")

    f.btnReset = CreateFrame("Button", nil, f.content, "UIPanelButtonTemplate")
    f.btnReset:SetSize(74, 20)
    f.btnReset:SetPoint("TOPLEFT", 0, -24)
    f.btnReset:SetText(L["FT_Reset"] or "Reset")
    f.btnReset:SetScript("OnClick", function()
        FT:ResetSession()
    end)

    f.btnStop = CreateFrame("Button", nil, f.content, "UIPanelButtonTemplate")
    f.btnStop:SetSize(74, 20)
    f.btnStop:SetPoint("LEFT", f.btnReset, "RIGHT", 4, 0)
    f.btnStop:SetText(L["FT_Stop"] or "Stop")
    f.btnStop:SetScript("OnClick", function()
        FT:StopSession()
        UI:Refresh()
    end)

    f.btnPause = CreateFrame("Button", nil, f.content, "UIPanelButtonTemplate")
    f.btnPause:SetSize(74, 20)
    f.btnPause:SetPoint("LEFT", f.btnStop, "RIGHT", 4, 0)
    f.btnPause:SetText(L["FT_Pause"] or "Pause")
    f.btnPause:SetScript("OnClick", function()
        if FT:IsSessionPaused() then
            FT:ResumeSession()
        else
            FT:PauseSession()
        end
    end)

    f.btnStart = CreateFrame("Button", nil, f.content, "UIPanelButtonTemplate")
    f.btnStart:SetSize(74, 20)
    f.btnStart:SetPoint("LEFT", f.btnPause, "RIGHT", 4, 0)
    f.btnStart:SetText(L["FT_Start"] or "Start")
    f.btnStart:SetScript("OnClick", function()
        FT:StartSession()
    end)

    local sessionPanel = CreateFrame("Frame", nil, f.content)
    sessionPanel:SetPoint("TOPLEFT", 0, -50)
    sessionPanel:SetPoint("BOTTOMRIGHT")
    f.sessionPanel = sessionPanel

    local historyPanel = CreateFrame("Frame", nil, f.content)
    historyPanel:SetPoint("TOPLEFT", 0, -50)
    historyPanel:SetPoint("BOTTOMRIGHT")
    historyPanel:Hide()
    f.historyPanel = historyPanel

    local detailsPanel = CreateFrame("Frame", nil, f.content)
    detailsPanel:SetPoint("TOPLEFT", 0, -50)
    detailsPanel:SetPoint("BOTTOMRIGHT")
    detailsPanel:Hide()
    f.detailsPanel = detailsPanel

    local summary = CreatePanel(sessionPanel, 0.92)
    summary:SetPoint("TOPLEFT")
    summary:SetPoint("TOPRIGHT")
    summary:SetHeight(115)
    sessionPanel.summary = summary

    local function AddSummaryLine(_, x, y, labelText, w)
        local label = summary:CreateFontString(nil, "OVERLAY")
        label:SetPoint("TOPLEFT", x, y)
        label:SetWidth(w or 144)
        label:SetJustifyH("LEFT")
        label:SetWordWrap(false)
        SetFont(label, 11, 0.70, 0.76, 0.88)
        label:SetText(labelText)

        local value = summary:CreateFontString(nil, "OVERLAY")
        value:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 0, -1)
        value:SetWidth(w or 144)
        value:SetJustifyH("LEFT")
        value:SetWordWrap(false)
        SetFont(value, 12, 1, 1, 1)
        value:SetText("-")

        return label, value
    end

    summary.zoneLabel,     summary.zoneValue     = AddSummaryLine(summary, 12,  -10, L["FT_Zone"] or "Zone", 148)
    summary.goldLabel,     summary.goldValue     = AddSummaryLine(summary, 176, -10, L["FT_GoldLooted"] or "Gold looted", 148)
    summary.durationLabel, summary.durationValue = AddSummaryLine(summary, 12,  -45, L["FT_Duration"] or "Duration", 148)
    summary.totalLabel,    summary.totalValue    = AddSummaryLine(summary, 176, -45, L["FT_TotalValue"] or "Total value", 148)

    summary.topLabel = summary:CreateFontString(nil, "OVERLAY")
    summary.topLabel:SetPoint("TOPLEFT", 12, -80)
    summary.topLabel:SetPoint("RIGHT", -12, 0)
    summary.topLabel:SetJustifyH("LEFT")
    summary.topLabel:SetWordWrap(false)
    SetFont(summary.topLabel, 11, 0.70, 0.76, 0.88)
    summary.topLabel:SetText(L["FT_GoldPerHour"] or "GPH")

    summary.topValue = summary:CreateFontString(nil, "OVERLAY")
    summary.topValue:SetPoint("TOPLEFT", summary.topLabel, "BOTTOMLEFT", 0, -1)
    summary.topValue:SetPoint("RIGHT", -12, 0)
    summary.topValue:SetJustifyH("LEFT")
    summary.topValue:SetWordWrap(false)
    SetFont(summary.topValue, 12, 1, 1, 1)
    summary.topValue:SetText(FormatCoins(0))

    local listFrame = CreatePanel(sessionPanel, 0.92)
    listFrame:SetPoint("TOPLEFT", summary, "BOTTOMLEFT", 0, -8)
    listFrame:SetPoint("BOTTOMRIGHT")
    sessionPanel.listFrame = listFrame

    local header2 = CreateFrame("Frame", nil, listFrame)
    header2:SetPoint("TOPLEFT", 8, -8)
    header2:SetPoint("TOPRIGHT", -18, -8)
    header2:SetHeight(HEADER_H)
    listFrame.header = header2

    local h1 = CreateHeaderCell(header2, 180, L["FT_Item"] or "Item")
    h1:SetPoint("LEFT", 0, 0)

    local h2 = CreateHeaderCell(header2, 90, L["FT_TotalPrice"] or "Total")
    h2:SetPoint("RIGHT", -4, 0)
    h2:SetJustifyH("RIGHT")

    local scroll = CreateFrame("ScrollFrame", nil, listFrame, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", header2, "BOTTOMLEFT", 0, -4)
    scroll:SetPoint("BOTTOMRIGHT", -26, 8)
    listFrame.scroll = scroll

    local child = CreateFrame("Frame", nil, scroll)
    child:SetSize(LIST_W, 1)
    scroll:SetScrollChild(child)
    listFrame.child = child
    listFrame.rows = {}

    local controls = CreateFrame("Frame", nil, historyPanel)
    controls:SetPoint("TOPLEFT")
    controls:SetPoint("TOPRIGHT")
    controls:SetHeight(24)
    historyPanel.controls = controls

    controls.filterLabel = controls:CreateFontString(nil, "OVERLAY")
    controls.filterLabel:SetPoint("LEFT", 0, 0)
    SetFont(controls.filterLabel, 12, 0.88, 0.88, 0.88)
    controls.filterLabel:SetText(L["FT_Filter"] or "Filter")

    controls.filterBtn = CreateFrame("Button", nil, controls, "UIPanelButtonTemplate")
    controls.filterBtn:SetSize(170, 22)
    controls.filterBtn:SetPoint("LEFT", controls.filterLabel, "RIGHT", 8, 0)
    controls.filterBtn:SetScript("OnClick", function()
        local cdb2 = FT.Data:GetCharDB()
        cdb2.ui.historyFilter = ((cdb2.ui.historyFilter or "current") == "current") and "all" or "current"
        UI:Refresh()
    end)

    local deleteAll = CreateFrame("Button", nil, controls, "UIPanelButtonTemplate")
    deleteAll:SetSize(110, 22)
    deleteAll:SetPoint("RIGHT", 0, 0)
    deleteAll:SetText(L["FT_DeleteAll"] or "Delete all")
    deleteAll:SetScript("OnClick", function()
        FT:DeleteAllHistory()
    end)

    local histFrame = CreatePanel(historyPanel, 0.92)
    histFrame:SetPoint("TOPLEFT", controls, "BOTTOMLEFT", 0, -6)
    histFrame:SetPoint("BOTTOMRIGHT")
    historyPanel.listFrame = histFrame

    local histHeader = CreateFrame("Frame", nil, histFrame)
    histHeader:SetPoint("TOPLEFT", 8, -8)
    histHeader:SetPoint("TOPRIGHT", -18, -8)
    histHeader:SetHeight(HEADER_H)

    local hh1 = CreateHeaderCell(histHeader, 180, L["FT_Session"] or "Session")
    hh1:SetPoint("LEFT", 0, 0)

    local hh2 = CreateHeaderCell(histHeader, 90, L["FT_TotalValue"] or "Total")
    hh2:SetPoint("RIGHT", -4, 0)
    hh2:SetJustifyH("RIGHT")

    local histScroll = CreateFrame("ScrollFrame", nil, histFrame, "UIPanelScrollFrameTemplate")
    histScroll:SetPoint("TOPLEFT", histHeader, "BOTTOMLEFT", 0, -4)
    histScroll:SetPoint("BOTTOMRIGHT", -26, 8)
    historyPanel.listFrame.scroll = histScroll

    local histChild = CreateFrame("Frame", nil, histScroll)
    histChild:SetSize(LIST_W, 1)
    histScroll:SetScrollChild(histChild)
    historyPanel.listFrame.child = histChild
    historyPanel.listFrame.rows = {}

    detailsPanel.back = CreateFrame("Button", nil, detailsPanel, "UIPanelButtonTemplate")
    detailsPanel.back:SetSize(100, 22)
    detailsPanel.back:SetPoint("TOPLEFT", 0, 0)
    detailsPanel.back:SetText(L["FT_Back"] or "Back")
    detailsPanel.back:SetScript("OnClick", function()
        UI.selectedHistoryID = nil
        UI:ShowTab("history")
    end)

    detailsPanel.delete = CreateFrame("Button", nil, detailsPanel, "UIPanelButtonTemplate")
    detailsPanel.delete:SetSize(100, 22)
    detailsPanel.delete:SetPoint("LEFT", detailsPanel.back, "RIGHT", 6, 0)
    detailsPanel.delete:SetText(L["FT_Delete"] or "Delete")
    detailsPanel.delete:SetScript("OnClick", function()
        if UI.selectedHistoryID then
            FT:DeleteHistorySession(UI.selectedHistoryID)
            UI.selectedHistoryID = nil
            UI:ShowTab("history")
        end
    end)

    detailsPanel.title = detailsPanel:CreateFontString(nil, "OVERLAY")
    detailsPanel.title:SetPoint("TOPLEFT", detailsPanel.back, "BOTTOMLEFT", 0, -10)
    detailsPanel.title:SetPoint("RIGHT", -10, 0)
    detailsPanel.title:SetJustifyH("LEFT")
    SetFont(detailsPanel.title, 15, 1, 1, 1)
    detailsPanel.title:SetText("")

    detailsPanel.summary = CreatePanel(detailsPanel, 0.92)
    detailsPanel.summary:SetPoint("TOPLEFT", detailsPanel.title, "BOTTOMLEFT", 0, -8)
    detailsPanel.summary:SetPoint("TOPRIGHT", 0, -40)
    detailsPanel.summary:SetHeight(72)

    detailsPanel.summaryText = detailsPanel.summary:CreateFontString(nil, "OVERLAY")
    detailsPanel.summaryText:SetPoint("TOPLEFT", 12, -10)
    detailsPanel.summaryText:SetPoint("BOTTOMRIGHT", -12, 10)
    detailsPanel.summaryText:SetJustifyH("LEFT")
    detailsPanel.summaryText:SetJustifyV("TOP")
    SetFont(detailsPanel.summaryText, 11, 1, 1, 1)
    detailsPanel.summaryText:SetText("")

    detailsPanel.listFrame = CreatePanel(detailsPanel, 0.92)
    detailsPanel.listFrame:SetPoint("TOPLEFT", detailsPanel.summary, "BOTTOMLEFT", 0, -8)
    detailsPanel.listFrame:SetPoint("BOTTOMRIGHT")

    local dHeader = CreateFrame("Frame", nil, detailsPanel.listFrame)
    dHeader:SetPoint("TOPLEFT", 8, -8)
    dHeader:SetPoint("TOPRIGHT", -18, -8)
    dHeader:SetHeight(HEADER_H)

    local dh1 = CreateHeaderCell(dHeader, 180, L["FT_Item"] or "Item")
    dh1:SetPoint("LEFT", 0, 0)

    local dh2 = CreateHeaderCell(dHeader, 90, L["FT_TotalPrice"] or "Total")
    dh2:SetPoint("RIGHT", -4, 0)
    dh2:SetJustifyH("RIGHT")

    local dScroll = CreateFrame("ScrollFrame", nil, detailsPanel.listFrame, "UIPanelScrollFrameTemplate")
    dScroll:SetPoint("TOPLEFT", dHeader, "BOTTOMLEFT", 0, -4)
    dScroll:SetPoint("BOTTOMRIGHT", -26, 8)
    detailsPanel.listFrame.scroll = dScroll

    local dChild = CreateFrame("Frame", nil, dScroll)
    dChild:SetSize(LIST_W, 1)
    dScroll:SetScrollChild(dChild)
    detailsPanel.listFrame.child = dChild
    detailsPanel.listFrame.rows = {}

    if UI.ApplyCustomization then
        UI:ApplyCustomization()
    end

    UpdateWindowGeometry(f)
    f:Hide()
    return f
end

function UI:CreateItemRows(parent, rows, data)
    ClearRows(rows)

    local width = LIST_W
    parent:SetWidth(width)

    for i, entry in ipairs(data) do
        local row = CreateRowButton(parent)
        row:SetPoint("TOPLEFT", 0, -((i - 1) * ROW_H))
        row:SetSize(width, ROW_H)

        row.icon = row:CreateTexture(nil, "ARTWORK")
        row.icon:SetSize(22, 22)
        row.icon:SetPoint("LEFT", 4, 0)
        row.icon:SetTexture(entry.icon or 134400)

        row.total = row:CreateFontString(nil, "OVERLAY")
        row.total:SetPoint("RIGHT", -6, 5)
        row.total:SetWidth(90)
        row.total:SetJustifyH("RIGHT")
        row.total:SetWordWrap(false)
        SetFont(row.total, 11, 1, 1, 1)
        row.total:SetText(FormatCoins(entry.totalPrice or 0))

        row.name = row:CreateFontString(nil, "OVERLAY")
        row.name:SetPoint("TOPLEFT", row.icon, "TOPRIGHT", 6, 1)
        row.name:SetPoint("RIGHT", row.total, "LEFT", -6, 0)
        row.name:SetJustifyH("LEFT")
        row.name:SetWordWrap(false)
        SetFont(row.name, 11, 1, 1, 1)
        row.name:SetText(entry.itemLink or entry.itemName or ("item:" .. tostring(entry.itemID or "?")))

        row.meta = row:CreateFontString(nil, "OVERLAY")
        row.meta:SetPoint("TOPLEFT", row.name, "BOTTOMLEFT", 0, -1)
        row.meta:SetPoint("RIGHT", row.total, "LEFT", -6, 0)
        row.meta:SetJustifyH("LEFT")
        row.meta:SetWordWrap(false)
        SetFont(row.meta, 10, 0.72, 0.80, 0.90)
        row.meta:SetText(BuildItemMeta(entry))

        rows[#rows + 1] = row
    end

    parent:SetHeight(math.max(1, #data * ROW_H))
end

function UI:CreateHistoryRows(parent, rows, data)
    ClearRows(rows)

    local width = LIST_W
    parent:SetWidth(width)

    for i, entry in ipairs(data) do
        local row = CreateRowButton(parent)
        row:SetPoint("TOPLEFT", 0, -((i - 1) * ROW_H))
        row:SetSize(width, ROW_H)
        row.sessionID = entry.id

        row:SetScript("OnClick", function()
            UI.selectedHistoryID = row.sessionID
            UI:ShowTab("details")
        end)

        row.total = row:CreateFontString(nil, "OVERLAY")
        row.total:SetPoint("RIGHT", -6, 5)
        row.total:SetWidth(90)
        row.total:SetJustifyH("RIGHT")
        row.total:SetWordWrap(false)
        SetFont(row.total, 11, 1, 1, 1)
        row.total:SetText(FormatCoins(entry.totalValue or 0))

        row.title = row:CreateFontString(nil, "OVERLAY")
        row.title:SetPoint("TOPLEFT", 6, -1)
        row.title:SetPoint("RIGHT", row.total, "LEFT", -6, 0)
        row.title:SetJustifyH("LEFT")
        row.title:SetWordWrap(false)
        SetFont(row.title, 11, 1, 1, 1)
        row.title:SetText(entry.zone or UNKNOWN)

        row.meta = row:CreateFontString(nil, "OVERLAY")
        row.meta:SetPoint("BOTTOMLEFT", 6, 2)
        row.meta:SetPoint("RIGHT", row.total, "LEFT", -6, 0)
        row.meta:SetJustifyH("LEFT")
        row.meta:SetWordWrap(false)
        SetFont(row.meta, 10, 0.72, 0.80, 0.90)
        row.meta:SetText(string.format(
            "%s  •  %s  •  %s",
            date("%Y-%m-%d", entry.startTime or time()),
            entry.charName or UNKNOWNOBJECT,
            FormatDuration(tonumber(entry.duration) or 0)
        ))

        rows[#rows + 1] = row
    end

    parent:SetHeight(math.max(1, #data * ROW_H))
end

function UI:ShowTab(tab)
    local f = self.frame or self:Create()
    if not f then return end

    local cdb = FT.Data:GetCharDB()
    cdb.ui.activeTab = tab

    f.sessionPanel:Hide()
    f.historyPanel:Hide()
    f.detailsPanel:Hide()

    if tab == "history" then
        f.historyPanel:Show()
    elseif tab == "details" then
        f.detailsPanel:Show()
    else
        f.sessionPanel:Show()
        tab = "session"
    end

    self:Refresh()
end

function UI:RefreshSession()
    local f = self.frame
    local s = FT:GetActiveSession()

    if not s then
        f.status:SetText(L["FT_Inactive"] or "Inactive")
        f.sessionPanel.summary.zoneValue:SetText("-")
        f.sessionPanel.summary.durationValue:SetText("0s")
        f.sessionPanel.summary.goldValue:SetText(FormatCoins(0))
        f.sessionPanel.summary.totalValue:SetText(FormatCoins(0))
        f.sessionPanel.summary.topValue:SetText(FormatCoins(0))
        self:CreateItemRows(f.sessionPanel.listFrame.child, f.sessionPanel.listFrame.rows, {})
        f.headerTimer:SetText("0с")
        f.headerTotal:SetText(FormatGoldOnly(0))
        f.headerGPH:SetText((L["FT_GoldPerHour"] or "GPH") .. ": " .. FormatGoldOnly(0))

        SetButtonGlow(f.headerStart, false)
        SetButtonGlow(f.headerPause, false)
        return
    end

    local state = FT:GetSessionState()
    if state == "paused" then
        f.status:SetText(L["FT_Paused"] or "Paused")
        f.btnPause:SetText(L["FT_Resume"] or "Resume")
    else
        f.status:SetText(L["FT_Active"] or "Active")
        f.btnPause:SetText(L["FT_Pause"] or "Pause")
    end

    local duration = FT.Data:GetElapsedSeconds(s)

    f.sessionPanel.summary.zoneValue:SetText(s.zone or "-")
    f.sessionPanel.summary.durationValue:SetText(FormatDuration(duration))
    f.sessionPanel.summary.goldValue:SetText(FormatCoins(s.goldLooted or 0))
    f.sessionPanel.summary.totalValue:SetText(FormatCoins(s.totalValue or 0))
    f.sessionPanel.summary.topValue:SetText(
        FormatCoins(GetMoneyPerHour(s.totalValue or 0, duration))
    )

    self:CreateItemRows(f.sessionPanel.listFrame.child, f.sessionPanel.listFrame.rows, GetSortedSessionItems(s))

    f.headerTimer:SetText(FormatDuration(duration))
    f.headerTotal:SetText(FormatGoldOnly(s.totalValue or 0))
    f.headerGPH:SetText((L["FT_GoldPerHour"] or "GPH") .. ": " .. FormatGoldOnly(GetMoneyPerHour(s.totalValue or 0, duration)))
    UpdateLiveTicker()

    local state = FT:GetSessionState()

    if state == "active" then
        SetButtonGlow(f.headerStart, true)
        SetButtonGlow(f.headerPause, false)
    elseif state == "paused" then
        SetButtonGlow(f.headerStart, false)
        SetButtonGlow(f.headerPause, true)
    else
        SetButtonGlow(f.headerStart, false)
        SetButtonGlow(f.headerPause, false)
    end
end

function UI:RefreshHistory()
    local f = self.frame
    local cdb = FT.Data:GetCharDB()
    local filter = cdb.ui.historyFilter or "current"

    f.historyPanel.controls.filterBtn:SetText(
        filter == "current"
            and (L["FT_FilterCurrent"] or "Current character")
            or (L["FT_FilterAll"] or "All characters")
    )

    self:CreateHistoryRows(f.historyPanel.listFrame.child, f.historyPanel.listFrame.rows, FT:GetHistory(filter))
end

function UI:RefreshDetails()
    local f = self.frame
    local s = FT:GetHistorySessionByID(self.selectedHistoryID)

    if not s then
        f.detailsPanel.title:SetText(L["FT_NoSessionSelected"] or "No session selected")
        f.detailsPanel.summaryText:SetText("")
        self:CreateItemRows(f.detailsPanel.listFrame.child, f.detailsPanel.listFrame.rows, {})
        return
    end

    f.detailsPanel.title:SetText(s.title or FT:BuildSessionTitle(s))
    f.detailsPanel.summaryText:SetText(string.format(
        "%s: %s\n%s: %s\n%s: %s\n%s: %s\n%s: %s\n%s: %s",
        L["FT_Character"] or "Character", s.charName or "-",
        L["FT_Zone"] or "Zone", s.zone or "-",
        L["FT_Duration"] or "Duration", FormatDuration(tonumber(s.duration) or 0),
        L["FT_GoldLooted"] or "Gold looted", FormatCoins(s.goldLooted or 0),
        L["FT_ItemValue"] or "Item value", FormatCoins(s.itemValue or 0),
        L["FT_TotalValue"] or "Total value", FormatCoins(s.totalValue or 0)
    ))

    self:CreateItemRows(f.detailsPanel.listFrame.child, f.detailsPanel.listFrame.rows, GetSortedSessionItems(s))
end

function UI:Refresh()
    local f = self.frame or self:Create()
    if not f then return end

    UpdateWindowGeometry(f)

    local tab = FT.Data:GetCharDB().ui.activeTab or "session"
    f.sessionPanel:SetShown(tab == "session")
    f.historyPanel:SetShown(tab == "history")
    f.detailsPanel:SetShown(tab == "details")

    if tab == "history" then
        self:RefreshHistory()
    elseif tab == "details" then
        self:RefreshDetails()
    else
        self:RefreshSession()
    end
    if self.ApplyCustomization then
        self:ApplyCustomization()
    end
end

function UI:Show()
    if not FT:IsEnabled() then return end

    local f = self.frame or self:Create()
    if not f then return end

    f:Show()
    self:Refresh()
    UpdateLiveTicker()
end

function UI:Hide(force)
    if UI._ticker then
        UI._ticker:Cancel()
        UI._ticker = nil
    end

    if self.frame and (force or not FT:IsEnabled()) then
        self.frame:Hide()
    end
end

function UI:ToggleVisibility()
    if not FT:IsEnabled() then return end

    local f = self.frame or self:Create()
    if not f then return end

    if f:IsShown() then
        self:Hide(true)
    else
        local cdb = FT.Data:GetCharDB()
        cdb.ui.collapsed = true
        f:Show()
        self:Refresh()
        UpdateLiveTicker()
    end
end

function UI:Toggle()
    if not FT:IsEnabled() then return end

    local f = self.frame or self:Create()
    if not f then return end

    local cdb = FT.Data:GetCharDB()

    if not f:IsShown() then
        cdb.ui.collapsed = true
        f:Show()
    else
        cdb.ui.collapsed = not cdb.ui.collapsed
    end

    self:Refresh()
    UpdateLiveTicker()
end

function UI:ApplyCustomization()
    local f = self.frame
    if not f then return end

    local cfg = GetFarmCustomization()
    if not cfg then return end

    local bg      = cfg.backgroundColor
    local border  = cfg.borderColor
    local header  = cfg.headerColor
    local divider = cfg.dividerColor
    local accent  = cfg.accentTextColor
    local text    = cfg.textColor
    local muted   = cfg.mutedTextColor

    local function ApplyFrameBg(frame)
        if frame and frame._ftBg and bg then
            frame._ftBg:SetVertexColor(bg.r, bg.g, bg.b, bg.a)
        end
    end

    local function ApplyFrameBorder(frame)
        if not frame or not border then return end
        local regions = { frame:GetRegions() }
        for _, region in ipairs(regions) do
            if region and region.GetObjectType and region:GetObjectType() == "Texture" and region ~= frame._ftBg then
                local w = region:GetWidth()
                local h = region:GetHeight()
                if (w and w <= 2) or (h and h <= 2) then
                    region:SetVertexColor(border.r, border.g, border.b, border.a)
                end
            end
        end
    end

    ApplyFrameBg(f)
    ApplyFrameBorder(f)

    ApplyFrameBg(f.sessionPanel and f.sessionPanel.summary)
    ApplyFrameBorder(f.sessionPanel and f.sessionPanel.summary)
    ApplyFrameBg(f.sessionPanel and f.sessionPanel.listFrame)
    ApplyFrameBorder(f.sessionPanel and f.sessionPanel.listFrame)

    ApplyFrameBg(f.historyPanel and f.historyPanel.listFrame)
    ApplyFrameBorder(f.historyPanel and f.historyPanel.listFrame)

    ApplyFrameBg(f.detailsPanel and f.detailsPanel.summary)
    ApplyFrameBorder(f.detailsPanel and f.detailsPanel.summary)
    ApplyFrameBg(f.detailsPanel and f.detailsPanel.listFrame)
    ApplyFrameBorder(f.detailsPanel and f.detailsPanel.listFrame)

    if f.headerDivider and divider then
        f.headerDivider:SetVertexColor(divider.r, divider.g, divider.b, divider.a)
    end

    if f.sep1 and divider then
        f.sep1:SetVertexColor(divider.r, divider.g, divider.b, divider.a)
    end

    if f.sep2 and divider then
        f.sep2:SetVertexColor(divider.r, divider.g, divider.b, divider.a)
    end

    if accent then
        if f.headerTimer then f.headerTimer:SetTextColor(accent.r, accent.g, accent.b, accent.a) end
    end

    if text then
        if f.headerTotal then f.headerTotal:SetTextColor(text.r, text.g, text.b, text.a) end
        if f.headerGPH then f.headerGPH:SetTextColor(text.r, text.g, text.b, text.a) end
        if f.sessionPanel and f.sessionPanel.summary then
            if f.sessionPanel.summary.zoneValue then f.sessionPanel.summary.zoneValue:SetTextColor(text.r, text.g, text.b, text.a) end
            if f.sessionPanel.summary.goldValue then f.sessionPanel.summary.goldValue:SetTextColor(text.r, text.g, text.b, text.a) end
            if f.sessionPanel.summary.durationValue then f.sessionPanel.summary.durationValue:SetTextColor(text.r, text.g, text.b, text.a) end
            if f.sessionPanel.summary.totalValue then f.sessionPanel.summary.totalValue:SetTextColor(text.r, text.g, text.b, text.a) end
            if f.sessionPanel.summary.topValue then f.sessionPanel.summary.topValue:SetTextColor(text.r, text.g, text.b, text.a) end
        end
    end

    if muted then
        if f.status then
            f.status:SetTextColor(muted.r, muted.g, muted.b, muted.a)
        end

        if f.historyPanel and f.historyPanel.controls and f.historyPanel.controls.filterLabel then
            f.historyPanel.controls.filterLabel:SetTextColor(muted.r, muted.g, muted.b, muted.a)
        end

        if f.sessionPanel and f.sessionPanel.summary then
            local s = f.sessionPanel.summary

            if s.zoneLabel then s.zoneLabel:SetTextColor(muted.r, muted.g, muted.b, muted.a) end
            if s.goldLabel then s.goldLabel:SetTextColor(muted.r, muted.g, muted.b, muted.a) end
            if s.durationLabel then s.durationLabel:SetTextColor(muted.r, muted.g, muted.b, muted.a) end
            if s.totalLabel then s.totalLabel:SetTextColor(muted.r, muted.g, muted.b, muted.a) end
            if s.topLabel then s.topLabel:SetTextColor(muted.r, muted.g, muted.b, muted.a) end
        end
    end

    local function ApplyRows(rows)
        if not rows then return end
        for _, row in ipairs(rows) do
            if text and row.name then row.name:SetTextColor(text.r, text.g, text.b, text.a) end
            if text and row.total then row.total:SetTextColor(text.r, text.g, text.b, text.a) end
            if muted and row.meta then row.meta:SetTextColor(muted.r, muted.g, muted.b, muted.a) end
            if text and row.title then row.title:SetTextColor(text.r, text.g, text.b, text.a) end
        end
    end

    ApplyRows(f.sessionPanel and f.sessionPanel.listFrame and f.sessionPanel.listFrame.rows)
    ApplyRows(f.historyPanel and f.historyPanel.listFrame and f.historyPanel.listFrame.rows)
    ApplyRows(f.detailsPanel and f.detailsPanel.listFrame and f.detailsPanel.listFrame.rows)
end

function UI:Create()
    return CreateMainFrame()
end