-- ============================================================
-- Komo Hold'Em — Table window
-- Themed 900x560 window: window art, felt with gold rail + themed
-- inner line, card frames (face TGA + glyph FontStrings, purple
-- stroke / gold royals, K-logo backs), up to 8 seats, pot, board,
-- action bar (Fold / Check-Call / Bet slider+field+snaps), turn
-- highlight + countdown, winner banner.
-- ============================================================
local _, KH = ...
KH.Table = {}
local T  = KH.Table
local UI = KH.UI
local C  = KH.COLORS
local Cards = KH.Cards

local MEDIA = "Interface\\AddOns\\KomoHoldEm\\Media\\"
local FONT  = "Fonts\\FRIZQT__.TTF"
local WHITE8 = "Interface\\Buttons\\WHITE8x8"
local W, H = 900, 560


local THEME = {
    midnight = { win = "WinMidnight", felt = "FeltMidnight",
        ring = { 0.541, 0.361, 0.941 }, feltTint = { 0.75, 0.75, 0.85 } },
    alliance = { win = "WinAlliance", felt = "FeltAlliance",
        ring = { 0.184, 0.561, 0.910 }, feltTint = { 0.80, 0.82, 0.90 } },
    horde    = { win = "WinHorde",    felt = "FeltHorde",
        ring = { 0.847, 0.271, 0.227 }, feltTint = { 0.85, 0.78, 0.78 } },
}

local function setFont(fs, size, flags) fs:SetFont(FONT, size, flags or "") end

-- seat display slots (x, y offsets from felt center) by seat count
local SLOTS = {
    [2] = { {0,-215}, {0,205} },
    [3] = { {0,-215}, {-260,175}, {260,175} },
    [4] = { {0,-215}, {-330,0}, {0,205}, {330,0} },
    [5] = { {0,-215}, {-330,60}, {-170,195}, {170,195}, {330,60} },
    [6] = { {0,-215}, {-330,-60}, {-260,175}, {0,205}, {260,175}, {330,-60} },
    [7] = { {0,-215}, {-330,-60}, {-300,140}, {-120,205}, {120,205}, {300,140}, {330,-60} },
    [8] = { {0,-215}, {-330,-90}, {-330,90}, {-170,195}, {0,215}, {170,195}, {330,90}, {330,-90} },
}

-- ============================================================
-- card frames
-- ============================================================
local cardPool = {}

local function buildCard()
    local f = CreateFrame("Frame", nil, UIParent)

    f.face = f:CreateTexture(nil, "ARTWORK")
    f.face:SetAllPoints()

    -- suit symbols are textures: WoW fonts lack the unicode glyphs
    f.rank = f:CreateFontString(nil, "OVERLAY")
    f.rank:SetPoint("TOPLEFT", 4, -3)
    f.suitTL = f:CreateTexture(nil, "OVERLAY")
    f.suitTL:SetPoint("TOPLEFT", f.rank, "BOTTOMLEFT", 0, -1)

    f.rankBR = f:CreateFontString(nil, "OVERLAY")
    f.rankBR:SetPoint("BOTTOMRIGHT", -4, 3)
    f.suitBR = f:CreateTexture(nil, "OVERLAY")
    f.suitBR:SetPoint("BOTTOMRIGHT", f.rankBR, "TOPRIGHT", 0, 1)

    f.centerText = f:CreateFontString(nil, "OVERLAY")
    f.centerText:SetPoint("CENTER")
    f.centerTex = f:CreateTexture(nil, "OVERLAY")
    f.centerTex:SetPoint("CENTER")
    return f
end

local function acquireCard(parent, w, h)
    local f = table.remove(cardPool) or buildCard()
    f:SetParent(parent)
    f:SetSize(w, h)
    f:SetFrameLevel(parent:GetFrameLevel() + 2)
    local big = w >= 55
    setFont(f.rank,   big and 13 or 10, "")
    setFont(f.rankBR, big and 13 or 10, "")
    setFont(f.centerText, big and 24 or 16, "")
    local sSz = big and 10 or 8
    f.suitTL:SetSize(sSz, sSz)
    f.suitBR:SetSize(sSz, sSz)
    local cSz = big and 30 or 20
    f.centerTex:SetSize(cSz, cSz)
    f:Show()
    return f
end

local function releaseCard(f)
    f:Hide()
    f:ClearAllPoints()
    f:SetParent(UIParent)
    cardPool[#cardPool + 1] = f
end

local function paintCardFace(f, id)
    local theme = KH.db.settings.theme
    local rank, suit = Cards.Split(id)
    local royal = Cards.ROYAL[rank]

    -- rounded corners + stroke are baked into the texture
    f.face:SetTexture(MEDIA .. "Face" .. theme:gsub("^%l", string.upper) ..
        (royal and "Royal" or ""))
    f.face:SetTexCoord(0, 1, 0, 1)
    f.face:SetVertexColor(1, 1, 1)

    local suitTex = MEDIA .. "Suit" .. suit
    local rankC, suitC = Cards:CornerColors(id)
    f.rank:SetText(rank)     f.rank:SetTextColor(unpack(rankC))
    f.rankBR:SetText(rank)   f.rankBR:SetTextColor(unpack(rankC))
    f.suitTL:SetTexture(suitTex) f.suitTL:SetVertexColor(unpack(suitC)) f.suitTL:Show()
    f.suitBR:SetTexture(suitTex) f.suitBR:SetVertexColor(unpack(suitC)) f.suitBR:Show()

    -- center: K/Q/J letter in gold; A big suit in gold; 2-10 big suit in suit color
    if rank == "K" or rank == "Q" or rank == "J" then
        f.centerText:SetText(rank)
        f.centerText:SetTextColor(unpack(Cards.GOLD_COLOR))
        f.centerText:Show()
        f.centerTex:Hide()
    else
        local col = (rank == "A") and Cards.GOLD_COLOR or Cards.SUIT_COLOR[suit]
        f.centerTex:SetTexture(suitTex)
        f.centerTex:SetVertexColor(unpack(col))
        f.centerTex:Show()
        f.centerText:Hide()
    end
end

local function paintCardBack(f)
    f.face:SetTexture(MEDIA .. "CardBack")
    f.face:SetTexCoord(0, 1, 0, 1)
    f.face:SetVertexColor(1, 1, 1)
    f.rank:SetText("") f.rankBR:SetText("")
    f.suitTL:Hide() f.suitBR:Hide()
    f.centerText:SetText("") f.centerText:Hide()
    f.centerTex:Hide()
end

-- ============================================================
-- window
-- ============================================================
function T:Create()
    if self.frame then return self.frame end

    local f = UI.CreatePanel(UIParent, W, H)
    f:SetBackdropColor(0, 0, 0, 0)
    f:SetBackdropBorderColor(0, 0, 0, 0)
    f:SetFrameStrata("HIGH")
    f:SetPoint("CENTER", KH.db.window.tableX, KH.db.window.tableY)
    f:SetScale(KH.db.settings.scale)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:SetClampedToScreen(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", function()
        f:StopMovingOrSizing()
        local cx, cy = f:GetCenter()
        local px, py = UIParent:GetCenter()
        KH.db.window.tableX, KH.db.window.tableY = cx - px, cy - py
    end)
    f:Hide()
    self.frame = f

    self.winBG = f:CreateTexture(nil, "BACKGROUND", nil, 1)
    self.winBG:SetAllPoints()

    -- titlebar
    self.title = f:CreateFontString(nil, "OVERLAY")
    setFont(self.title, 13, "OUTLINE")
    self.title:SetPoint("TOPLEFT", 14, -13)
    self.title:SetTextColor(unpack(C.GOLD))

    self.handText = f:CreateFontString(nil, "OVERLAY")
    setFont(self.handText, 12, "")
    self.handText:SetPoint("LEFT", self.title, "RIGHT", 12, 0)
    self.handText:SetTextColor(unpack(C.TEXT_DIM))

    self.stakesText = f:CreateFontString(nil, "OVERLAY")
    setFont(self.stakesText, 11, "")
    self.stakesText:SetPoint("TOPRIGHT", -36, -16)

    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", -4, -4)
    close:SetScript("OnClick", function()
        if KH.Lobby.mine then
            KH:Print("You're the host — use /kh close to end the table for everyone.")
        else
            KH.Client:LeaveTable()
        end
    end)

    -- felt: gold rail + themed inner line
    local felt = CreateFrame("Frame", nil, f)
    felt:SetSize(W - 150, 350)
    felt:SetPoint("CENTER", 0, 22)
    self.felt = felt

    self.feltArt = felt:CreateTexture(nil, "BACKGROUND")
    self.feltArt:SetAllPoints()

    -- pot
    local pot = UI.CreatePill(felt, 150, 26)
    pot:SetPoint("CENTER", 0, 66)
    setFont(pot.text, 13, "")
    self.potPill = pot

    -- board card anchors
    self.boardAnchor = CreateFrame("Frame", nil, felt)
    self.boardAnchor:SetSize(320, 84)
    self.boardAnchor:SetPoint("CENTER", 0, 0)
    self.boardCards = {}

    -- winner banner
    local win = UI.CreatePill(felt, 320, 30)
    win:SetPoint("CENTER", 0, -62)
    win:SetBackdropColor(0, 0, 0, 0.8)
    setFont(win.text, 13, "")
    win:Hide()
    self.winnerPill = win

    -- seats
    self.seatFrames = {}
    for i = 1, 8 do
        local s = CreateFrame("Frame", nil, f, "BackdropTemplate")
        s:SetSize(150, 44)
        s:SetBackdrop(UI.BACKDROP)

        s.name = s:CreateFontString(nil, "OVERLAY")
        setFont(s.name, 12, "")
        s.name:SetPoint("TOP", 0, -6)

        s.stack = s:CreateFontString(nil, "OVERLAY")
        setFont(s.stack, 11, "")
        s.stack:SetPoint("TOP", 0, -22)
        s.stack:SetTextColor(unpack(C.GOLD))

        s.timer = CreateFrame("StatusBar", nil, s)
        s.timer:SetSize(140, 3)
        s.timer:SetPoint("BOTTOM", 0, 3)
        s.timer:SetStatusBarTexture(WHITE8)
        s.timer:SetStatusBarColor(0.965, 0.867, 0.522)
        local tbg = s.timer:CreateTexture(nil, "BACKGROUND")
        tbg:SetAllPoints()
        tbg:SetColorTexture(0, 0, 0, 0.5)
        s.timer:Hide()

        s.bet = UI.CreatePill(s, 74, 18)
        s.bet:SetPoint("BOTTOM", s, "TOP", 0, 4)
        setFont(s.bet.text, 10, "")
        s.bet:Hide()

        s.dealer = s:CreateFontString(nil, "OVERLAY")
        setFont(s.dealer, 11, "OUTLINE")
        s.dealer:SetPoint("RIGHT", -6, 8)
        s.dealer:SetText("|cffffffffD|r")
        s.dealer:Hide()

        s.cards = {}
        s:Hide()
        self.seatFrames[i] = s
    end

    -- my hole cards (bigger, above my seat)
    self.myCards = {}

    -- ---------------- action bar ----------------
    local bar = CreateFrame("Frame", nil, f, "BackdropTemplate")
    bar:SetPoint("BOTTOMLEFT", 1, 1)
    bar:SetPoint("BOTTOMRIGHT", -1, 1)
    bar:SetHeight(64)
    bar:SetBackdrop({ bgFile = WHITE8 })
    bar:SetBackdropColor(0.05, 0.055, 0.075, 0.92)
    self.bar = bar

    self.foldBtn = UI.CreateRedButton(bar, 90, 36, "Fold")
    self.foldBtn:SetPoint("LEFT", 14, 8)
    setFont(self.foldBtn.label, 13, "")
    self.foldBtn:SetScript("OnClick", function() T:Act("fold") end)

    self.callBtn = CreateFrame("Button", nil, bar, "BackdropTemplate")
    self.callBtn:SetSize(110, 36)
    self.callBtn:SetPoint("LEFT", self.foldBtn, "RIGHT", 8, 0)
    self.callBtn:SetBackdrop(UI.BACKDROP)
    self.callBtn:SetBackdropColor(0.075, 0.122, 0.090, 1)
    self.callBtn:SetBackdropBorderColor(0.180, 0.490, 0.298, 1)
    self.callBtn.label = self.callBtn:CreateFontString(nil, "OVERLAY")
    setFont(self.callBtn.label, 13, "")
    self.callBtn.label:SetPoint("CENTER")
    self.callBtn.label:SetText("Check")
    self.callBtn.label:SetTextColor(unpack(C.GREEN))
    self.callBtn:SetScript("OnClick", function()
        local v = KH.Client.view
        T:Act((v and (v.toCall or 0) > 0) and "call" or "check")
    end)

    -- bet slider
    local slider = CreateFrame("Slider", nil, bar, "BackdropTemplate")
    slider:SetOrientation("HORIZONTAL")
    slider:SetSize(220, 14)
    slider:SetPoint("LEFT", self.callBtn, "RIGHT", 16, 0)
    slider:SetBackdrop({ bgFile = WHITE8 })
    slider:SetBackdropColor(0, 0, 0, 0.55)
    slider:SetThumbTexture(WHITE8)
    local thumb = slider:GetThumbTexture()
    thumb:SetSize(10, 18)
    thumb:SetVertexColor(0.482, 0.361, 0.941)
    slider:SetMinMaxValues(1, 100)
    slider:SetValueStep(1)
    slider:SetObeyStepOnDrag(true)
    slider:SetScript("OnValueChanged", function(_, val)
        if T.suppressSlider then return end
        T.betBox:SetText(tostring(math.floor(val)))
    end)
    self.slider = slider

    self.betBox = UI.CreateEditBox(bar, 78, 30, true)
    self.betBox:SetPoint("LEFT", slider, "RIGHT", 12, 0)
    self.betBox:SetScript("OnTextChanged", function(box, user)
        if not user then return end
        local v = box:GetNumber()
        local mn, mx = slider:GetMinMaxValues()
        T.suppressSlider = true
        slider:SetValue(math.max(mn, math.min(v, mx)))
        T.suppressSlider = false
    end)

    -- snap buttons
    local snaps = { { "½ pot", 0.5 }, { "Pot", 1 }, { "All-in", 99 } }
    local prev
    for i, def in ipairs(snaps) do
        local b = UI.CreateBlueButton(bar, 56, 24, def[1])
        setFont(b.label, 10, "")
        if prev then b:SetPoint("LEFT", prev, "RIGHT", 5, 0)
        else b:SetPoint("LEFT", self.betBox, "RIGHT", 10, 0) end
        b:SetScript("OnClick", function() T:Snap(def[2]) end)
        prev = b
    end

    self.betBtn = UI.CreateGoldButton(bar, 90, 36, "Bet")
    self.betBtn:SetPoint("RIGHT", -14, 8)
    setFont(self.betBtn.label, 14, "")
    self.betBtn.label:SetTextColor(0.09, 0.075, 0.04)
    self.betBtn:SetScript("OnClick", function()
        T:Act("bet", T.betBox:GetNumber())
    end)

    self.status = bar:CreateFontString(nil, "OVERLAY")
    setFont(self.status, 12, "")
    self.status:SetPoint("BOTTOM", 0, 3)
    self.status:SetTextColor(unpack(C.TURQUOISE))

    -- ---------------- game over modal ----------------
    local dim = CreateFrame("Frame", nil, f)
    dim:SetAllPoints()
    dim:SetFrameLevel(f:GetFrameLevel() + 30)
    dim:EnableMouse(true)   -- swallow clicks under it
    local dimTex = dim:CreateTexture(nil, "BACKGROUND")
    dimTex:SetAllPoints()
    dimTex:SetColorTexture(0, 0, 0, 0.72)
    dim:Hide()
    self.gameOver = dim

    local goPanel = UI.CreatePanel(dim, 420, 190)
    goPanel:SetPoint("CENTER", 0, 20)
    goPanel:SetBackdropColor(0.055, 0.045, 0.02, 0.97)
    goPanel:SetBackdropBorderColor(0.722, 0.537, 0.165, 1)

    local goTitle = goPanel:CreateFontString(nil, "OVERLAY")
    setFont(goTitle, 22, "OUTLINE")
    goTitle:SetPoint("TOP", 0, -18)
    goTitle:SetText("GAME OVER")
    goTitle:SetTextColor(unpack(C.GOLD))

    local goReason = goPanel:CreateFontString(nil, "OVERLAY")
    setFont(goReason, 13, "")
    goReason:SetPoint("TOP", 0, -52)
    goReason:SetWidth(380)
    goReason:SetTextColor(1, 1, 1)
    self.goReason = goReason

    local goClose = UI.CreateGoldButton(goPanel, 320, 40, "Close Table & Settle Up")
    goClose:SetPoint("BOTTOM", 0, 44)
    setFont(goClose.label, 14, "")
    goClose.label:SetTextColor(0.09, 0.075, 0.04)
    goClose:SetScript("OnClick", function()
        KH.Lobby:CloseTable()
    end)
    self.goCloseBtn = goClose

    local goWait = goPanel:CreateFontString(nil, "OVERLAY")
    setFont(goWait, 12, "")
    goWait:SetPoint("BOTTOM", 0, 58)
    goWait:SetText("Waiting for the host to settle up...")
    goWait:SetTextColor(unpack(C.TURQUOISE))
    self.goWait = goWait

    local goLeave = UI.CreateRedButton(goPanel, 200, 30, "Leave Table")
    goLeave:SetPoint("BOTTOM", 0, 16)
    setFont(goLeave.label, 12, "")
    goLeave:SetScript("OnClick", function() KH.Client:LeaveTable() end)
    self.goLeaveBtn = goLeave

    -- ---------------- host end-of-hand controls ----------------
    local hc = UI.CreatePanel(f, 430, 58)
    hc:SetPoint("BOTTOM", self.felt, "BOTTOM", 0, 20)
    hc:SetFrameLevel(f:GetFrameLevel() + 20)
    hc:SetBackdropColor(0.03, 0.035, 0.05, 0.96)
    hc:SetBackdropBorderColor(0.722, 0.537, 0.165, 0.9)
    hc:Hide()
    self.hostControls = hc

    local nextBtn = UI.CreateGoldButton(hc, 200, 36, "Deal Next Hand")
    nextBtn:SetPoint("LEFT", 12, 0)
    setFont(nextBtn.label, 13, "")
    nextBtn.label:SetTextColor(0.09, 0.075, 0.04)
    nextBtn:SetScript("OnClick", function()
        hc:Hide()
        KH.Host:StartHand()
    end)

    local endBtn = UI.CreateRedButton(hc, 180, 36, "End Game")
    endBtn:SetPoint("RIGHT", -12, 0)
    setFont(endBtn.label, 13, "")
    endBtn:SetScript("OnClick", function()
        hc:Hide()
        KH.Lobby:CloseTable()
    end)

    -- countdown driver
    f:SetScript("OnUpdate", function() T:Tick() end)

    return f
end

function T:ShowGameOver(lastStanding, isHost)
    if not self.frame or not self.frame:IsShown() then self:Open() end
    if not self.frame then return end
    self.nextHandAt = nil
    self:SetActionBarEnabled(false)
    self.goReason:SetText("|cfffad990" .. (lastStanding or "Nobody") ..
        "|r |cffffffffhas all the chips - not enough players can continue.|r")
    self.goCloseBtn:SetShown(isHost)
    self.goWait:SetShown(not isHost)
    self.goLeaveBtn:SetShown(not isHost)
    self.gameOver:Show()
    KH.Sounds:Play("GOLD_NOTICE")
end

function T:ApplyTheme()
    if not self.frame then return end
    local th = THEME[KH.db.settings.theme] or THEME.midnight
    self.winBG:SetTexture(MEDIA .. th.win)
    self.feltArt:SetTexture(MEDIA .. th.felt)
end

-- ============================================================
-- open / close / render
-- ============================================================
function T:Open()
    local v = KH.Client.view
    if not v then return end
    local f = self:Create()
    self:ApplyTheme()

    self.title:SetText(v.config and v.config.name or "Komo Hold'Em")
    self.stakesText:SetText(v.config and (v.config.stakes == "gold"
        and "|cfffad990WoW Gold · " .. v.config.amount .. "g buy-in|r"
        or "|cff7db4f0Imitation Chips|r") or "")
    if KH.Launcher and KH.Launcher.frame then KH.Launcher.frame:Hide() end
    f:Show()
    self:RefreshSeats()
    self:RefreshBoard()
    self:SetActionBarEnabled(false)
end

function T:Close()
    if self.frame then
        if self.gameOver then self.gameOver:Hide() end
        if self.hostControls then self.hostControls:Hide() end
        self.frame:Hide()
    end
    self:ReleaseAllCards()
end

function T:ReleaseAllCards()
    for _, s in ipairs(self.seatFrames or {}) do
        for _, c in ipairs(s.cards) do releaseCard(c) end
        wipe(s.cards)
    end
    for _, c in ipairs(self.myCards or {}) do releaseCard(c) end
    if self.myCards then wipe(self.myCards) end
    for _, c in ipairs(self.boardCards or {}) do releaseCard(c) end
    if self.boardCards then wipe(self.boardCards) end
end

-- map table seat index -> display slot (I am always slot 1 / bottom)
function T:DisplayOrder()
    local v = KH.Client.view
    if not v or not v.count then return {} end
    local slots = SLOTS[math.max(2, math.min(v.count, 8))]
    local order = {}
    local my = v.mySeat or 1
    for ix = 1, v.count do
        local rel = ((ix - my) % v.count) + 1
        order[ix] = slots[rel]
    end
    return order
end

function T:RefreshSeats()
    if not self.frame or not self.frame:IsShown() then return end
    local v = KH.Client.view if not v then return end
    local order = self:DisplayOrder()

    for i, s in ipairs(self.seatFrames) do
        local p = v.seats and v.seats[i]
        local pos = order[i]
        if p and pos then
            s:ClearAllPoints()
            s:SetPoint("CENTER", self.felt, "CENTER", pos[1], pos[2])

            local isMe = (i == v.mySeat)
            local isActor = (v.actorIx == i)
            if isMe then
                s:SetBackdropColor(0.20, 0.14, 0.36, 0.96)
                s:SetBackdropBorderColor(0.482, 0.361, 0.941, 1)
            else
                s:SetBackdropColor(0.055, 0.063, 0.082, 0.94)
                s:SetBackdropBorderColor(0.416, 0.310, 0.753, 1)
            end
            if isActor then
                s:SetBackdropBorderColor(0.965, 0.867, 0.522, 1)
            end
            if p.folded then s:SetAlpha(0.45) else s:SetAlpha(1) end

            s.name:SetText(Ambiguate(p.name, "short") .. (isMe and " (You)" or ""))
            s.name:SetTextColor(1, 1, 1)
            s.stack:SetText(BreakUpLargeNumbers(p.stack or 0))

            if (p.bet or 0) > 0 then
                s.bet.text:SetText("|cffffffffBet|r |cfffad990" .. p.bet .. "|r")
                s.bet:Show()
            else s.bet:Hide() end

            s.dealer:SetShown(v.dealerIx == i)
            s.timer:SetShown(isActor)

            -- opponent cards: backs while live, reveals at showdown
            for _, c in ipairs(s.cards) do releaseCard(c) end
            wipe(s.cards)
            if not isMe and v.handNo and not p.folded then
                local reveal = v.reveals and v.reveals[i]
                for ci = 1, 2 do
                    local card = acquireCard(s, 34, 48)
                    card:SetPoint("BOTTOM", s, "TOP", (ci - 1.5) * 38, 22)
                    if reveal then paintCardFace(card, reveal[ci])
                    else paintCardBack(card) end
                    s.cards[ci] = card
                end
            end
            s:Show()
        else
            s:Hide()
        end
    end
    self.potPill.text:SetText("|cffffffffPot|r |cfffad990" .. BreakUpLargeNumbers(v.pot or 0) .. "|r")
    self:RefreshMyCards()
end

function T:RefreshMyCards()
    if not self.frame or not self.frame:IsShown() then return end
    local v = KH.Client.view if not v then return end
    for _, c in ipairs(self.myCards) do releaseCard(c) end
    wipe(self.myCards)
    if v.hole then
        local mySlot = self.seatFrames[v.mySeat or 1]
        for ci = 1, 2 do
            local card = acquireCard(self.frame, 62, 88)
            card:SetPoint("BOTTOM", mySlot, "TOP", (ci - 1.5) * 68, 8)
            paintCardFace(card, v.hole[ci])
            self.myCards[ci] = card
        end
    end
end

function T:RefreshBoard()
    if not self.frame or not self.frame:IsShown() then return end
    local v = KH.Client.view if not v then return end
    for _, c in ipairs(self.boardCards) do releaseCard(c) end
    wipe(self.boardCards)
    for i = 1, 5 do
        local id = v.board and v.board[i]
        local card = acquireCard(self.boardAnchor, 56, 80)
        card:SetPoint("LEFT", (i - 1) * 64, 0)
        if id and id ~= "" then paintCardFace(card, id)
        else
            card.face:SetTexture(MEDIA .. "CardSlot")
            card.face:SetTexCoord(0, 1, 0, 1)
            card.rank:SetText("") card.rankBR:SetText("")
            card.suitTL:Hide() card.suitBR:Hide()
            card.centerText:SetText("") card.centerText:Hide()
            card.centerTex:Hide()
        end
        self.boardCards[i] = card
    end
    self.potPill.text:SetText("|cffffffffPot|r |cfffad990" .. BreakUpLargeNumbers(v.pot or 0) .. "|r")
end

-- ============================================================
-- hand events
-- ============================================================
function T:OnHandStart()
    local v = KH.Client.view
    if self.gameOver then self.gameOver:Hide() end
    if self.hostControls then self.hostControls:Hide() end
    self.winnerPill:Hide()
    self.handText:SetText("Hand #" .. (v.handNo or "?"))
    self.status:SetText("Antes in — dealing...")
    self:RefreshBoard()
    self:RefreshSeats()
end

function T:OnActionRequest()
    local v = KH.Client.view if not v then return end
    self:RefreshSeats()
    local mine = v.actorIx == v.mySeat
    self:SetActionBarEnabled(mine)
    if mine then
        local toCall = v.toCall or 0
        self.callBtn.label:SetText(toCall > 0 and ("Call " .. toCall) or "Check")
        -- slider range: min raise-to .. all-in
        local me = v.seats[v.mySeat]
        local minTo = (v.toCall or 0) + (v.minRaise or 1) + (me.bet or 0)
        local maxTo = (me.stack or 0) + (me.bet or 0)
        minTo = math.min(minTo, maxTo)
        self.suppressSlider = true
        self.slider:SetMinMaxValues(math.max(1, minTo), math.max(1, maxTo))
        self.slider:SetValue(math.max(1, minTo))
        self.suppressSlider = false
        self.betBox:SetText(tostring(math.max(1, minTo)))
        self.status:SetText(toCall > 0
            and ("Your action — " .. toCall .. " to call.")
            or "Your action.")
    else
        local actor = v.seats[v.actorIx]
        self.status:SetText("Waiting on " ..
            (actor and Ambiguate(actor.name, "short") or "..."))
    end
end

function T:SetActionBarEnabled(on)
    local a = on and 1 or 0.35
    self.foldBtn:SetAlpha(a)  self.foldBtn:SetEnabled(on)
    self.callBtn:SetAlpha(a)  self.callBtn:SetEnabled(on)
    self.betBtn:SetAlpha(a)   self.betBtn:SetEnabled(on)
    self.slider:SetAlpha(a)
    self.betBox:SetAlpha(a)
    self.betBox:SetEnabled(on)
end

function T:Act(action, amount)
    KH.Client:Act(action, amount)
    self:SetActionBarEnabled(false)
end

function T:Snap(mult)
    local v = KH.Client.view if not v then return end
    local me = v.seats[v.mySeat] if not me then return end
    local maxTo = (me.stack or 0) + (me.bet or 0)
    local target
    if mult == 99 then target = maxTo
    else target = math.floor((v.pot or 0) * mult) end
    local mn, mx = self.slider:GetMinMaxValues()
    target = math.max(mn, math.min(target, mx))
    self.slider:SetValue(target)
    self.betBox:SetText(tostring(math.floor(target)))
end

function T:ShowWinner(ix, amount, label)
    local v = KH.Client.view
    local p = v and v.seats and v.seats[ix]
    local who = p and Ambiguate(p.name, "short") or "?"
    self.winnerPill.text:SetText("|cffffffff" .. who .. " wins|r |cfffad990" ..
        BreakUpLargeNumbers(amount) .. "|r |cffffffff—|r |cfff6dd85" .. (label or "") .. "|r")
    self.winnerPill:Show()
    self.status:SetText("")
end

function T:OnHandEnd(seconds)
    self:SetActionBarEnabled(false)
    if seconds and seconds > 0 then
        self.nextHandAt = GetTime() + seconds
        return
    end
    self.nextHandAt = nil
    if KH.Lobby.mine then
        if self.hostControls then self.hostControls:Show() end
        self.status:SetText("")
    else
        self.status:SetText("Waiting for the host to deal the next hand...")
    end
end

function T:Tick()
    local v = KH.Client.view if not v then return end
    -- next-hand countdown
    if self.nextHandAt then
        local left = self.nextHandAt - GetTime()
        if left > 0 then
            self.status:SetText("Next hand in " .. math.ceil(left) .. "...")
        else
            self.nextHandAt = nil
        end
    end
    -- turn countdown bar
    if v.actorIx and v.deadline then
        local s = self.seatFrames[v.actorIx]
        if s and s.timer:IsShown() then
            local total = v.config and v.config.timer or 30
            local left = math.max(0, v.deadline - GetTime())
            s.timer:SetMinMaxValues(0, total)
            s.timer:SetValue(left)
            if left < 6 and left > 0 and not self.warned then
                if v.actorIx == v.mySeat then KH.Sounds:Play("TIMER_WARN") end
                self.warned = true
            elseif left >= 6 then
                self.warned = false
            end
        end
    end
end
