-- ============================================================
-- Komo Hold'Em — GameClient
-- Every player (host included, via self-echoed broadcasts) runs
-- this mirror. It holds the client's view of the table and drives
-- the Table window + lobby views. Settle nets are computed locally
-- from final stacks so every client reaches identical results.
-- ============================================================
local _, KH = ...
KH.Client = {}
local Client = KH.Client

local function MSG() return KH.Comms.MSG end

-- my current table view (host or joiner)
-- { id, host, config = {name,stakes,amount,seats,timer},
--   mySeat, seats = { ix -> {name, ready, stack, bet, folded} },
--   count, rev, handNo, board, pot, street, actorIx, hole }
Client.view = nil
Client.pendingRoster = {}

function Client:Reset()
    self.view = nil
    self.pendingRoster = {}
end

function Client:InTable()
    return self.view ~= nil
end

local function ensureView(id, host)
    if not Client.view then
        Client.view = { id = id, host = host, seats = {}, board = {}, pot = 0 }
    end
    return Client.view
end

function Client:Init()
    local Comms = KH.Comms

    -- ---------------- lobby / roster ----------------
    Comms:RegisterHandler(MSG().JOIN_ACCEPT, function(sender, _, f)
        local id, seatIx, name, stakes, amount, seats, timer = unpack(f)
        local v = ensureView(id, sender)
        v.mySeat = tonumber(seatIx)
        v.config = {
            name = name, stakes = stakes, amount = tonumber(amount),
            seats = tonumber(seats), timer = tonumber(timer),
        }
        KH.Sounds:Play("PLAYER_JOIN")
        if v.config.stakes == "gold" then KH.Sounds:Play("GOLD_NOTICE") end
        if KH.Launcher then KH.Launcher:ShowJoinLobby() end
    end)

    Comms:RegisterHandler("JD", function(sender, _, f)
        KH:Print("|cfff08a80Couldn't join:|r table is " .. (f[2] or "full") .. ".")
    end)

    Comms:RegisterHandler(MSG().LOBBY_STATE, function(sender, _, f)
        local id, rev, ix, name, ready, stack = unpack(f)
        local v = Client.view
        local mineHosted = KH.Lobby.mine and KH.Lobby.mine.id == id
        if not v and not mineHosted then return end
        if mineHosted then
            v = ensureView(id, KH.playerFull)
            v.config = v.config or KH.Lobby.mine.config
        end
        if v.id ~= id then return end
        rev = tonumber(rev)
        Client.pendingRoster[rev] = Client.pendingRoster[rev] or {}
        Client.pendingRoster[rev][tonumber(ix)] = {
            name = name, ready = ready == "1",
            stack = tonumber(stack) or 0, bet = 0, folded = false,
        }
    end)

    Comms:RegisterHandler("LE", function(sender, _, f)
        local id, rev, count = unpack(f)
        local v = Client.view
        if not v or v.id ~= id then return end
        rev, count = tonumber(rev), tonumber(count)
        local roster = Client.pendingRoster[rev]
        if not roster then return end
        local have = 0
        for _ in pairs(roster) do have = have + 1 end
        if have < count then return end
        v.seats, v.count, v.rev = roster, count, rev
        Client.pendingRoster = {}
        -- find my seat
        for ix, p in pairs(v.seats) do
            if KH:IsMe(p.name) then v.mySeat = ix end
        end
        if KH.Launcher then KH.Launcher:RefreshLobby() end
        if KH.Table and KH.Table.frame and KH.Table.frame:IsShown() then
            KH.Table:RefreshSeats()
        end
    end)

    Comms:RegisterHandler(MSG().TABLE_CLOSE, function(sender, _, f)
        local v = Client.view
        if v and v.id == f[1] and not KH:IsMe(sender) then
            KH:Print("The host closed the table.")
            Client:FinishTable()
        end
    end)

    -- ---------------- game ----------------
    Comms:RegisterHandler(MSG().GAME_START, function(sender, _, f)
        local v = Client.view
        if not v or v.id ~= f[1] then return end
        v.started = true
        KH.Table:Open()
    end)

    Comms:RegisterHandler("HS", function(sender, _, f)
        local v = Client.view if not v then return end
        v.handNo   = tonumber(f[2])
        v.dealerIx = tonumber(f[3])
        v.ante     = tonumber(f[4])
        v.pot      = tonumber(f[5]) or 0
        v.board    = {}
        v.street   = "PREFLOP"
        -- whispers can outrun the party broadcast: only clear hole
        -- cards that belong to an older hand
        if v.holeHand ~= v.handNo then v.hole = nil end
        v.actorIx  = nil
        v.winners  = {}
        v.reveals  = {}
        for _, p in pairs(v.seats) do p.bet = 0 p.folded = false end
        KH.Sounds:Play("CARD_DEAL")
        KH.Table:OnHandStart()
    end)

    Comms:RegisterHandler(MSG().HOLE_CARDS, function(sender, _, f)
        Client:OnHoleCards(tonumber(f[1]), f[2], f[3])
    end)

    Comms:RegisterHandler("ST", function(sender, _, f)
        local v = Client.view if not v then return end
        local handNo, ix, stack, bet, folded, pot = unpack(f)
        local p = v.seats[tonumber(ix)]
        if p then
            p.stack  = tonumber(stack) or p.stack
            p.bet    = tonumber(bet) or 0
            p.folded = folded == "1"
        end
        v.pot = tonumber(pot) or v.pot
        KH.Table:RefreshSeats()
    end)

    Comms:RegisterHandler(MSG().ACTION_REQUEST, function(sender, _, f)
        local v = Client.view if not v then return end
        local handNo, ix, toCall, minRaise, timer = unpack(f)
        v.actorIx  = tonumber(ix)
        v.toCall   = tonumber(toCall) or 0
        v.minRaise = tonumber(minRaise) or v.ante or 1
        v.deadline = GetTime() + (tonumber(timer) or 30)
        if v.actorIx == v.mySeat then KH.Sounds:Play("YOUR_TURN") end
        KH.Table:OnActionRequest()
    end)

    Comms:RegisterHandler("BD", function(sender, _, f)
        local v = Client.view if not v then return end
        v.street = f[2]
        v.board  = { strsplit(";", f[3] or "") }
        v.actorIx = nil
        KH.Sounds:Play("CARD_DEAL")
        KH.Table:RefreshBoard()
    end)

    Comms:RegisterHandler(MSG().SHOWDOWN, function(sender, _, f)
        local v = Client.view if not v then return end
        local handNo, ix, c1, c2, label = unpack(f)
        v.reveals = v.reveals or {}
        v.reveals[tonumber(ix)] = { c1, c2, label }
        KH.Sounds:Play("CARD_FLIP")
        KH.Table:RefreshSeats()
    end)

    Comms:RegisterHandler("WN", function(sender, _, f)
        local v = Client.view if not v then return end
        local handNo, ix, amount, label = unpack(f)
        v.winners = v.winners or {}
        v.winners[#v.winners + 1] = { ix = tonumber(ix), amt = tonumber(amount), label = label }
        KH.Table:ShowWinner(tonumber(ix), tonumber(amount), label)
    end)

    Comms:RegisterHandler("HE", function(sender, _, f)
        local v = Client.view if not v then return end
        v.actorIx = nil
        KH.Table:OnHandEnd(tonumber(f[2]) or 6)
    end)

    Comms:RegisterHandler("GO", function(sender, _, f)
        local v = Client.view
        if not v or v.id ~= f[1] then return end
        v.actorIx = nil
        KH.Table:ShowGameOver(f[2], KH.Lobby.mine ~= nil)
    end)

    Comms:RegisterHandler(MSG().SETTLE, function(sender, _, f)
        local v = Client.view
        if not v or v.id ~= f[1] then return end
        Client:Settle(tonumber(f[2]) or 0)
    end)
end

function Client:OnHoleCards(handNo, c1, c2)
    local v = self.view if not v then return end
    v.hole = { c1, c2 }
    v.holeHand = handNo
    KH.Table:RefreshMyCards()
end

-- ------------------------------------------------------------
-- my actions -> host (or direct if I am host)
-- ------------------------------------------------------------
function Client:Act(action, amount)
    local v = self.view if not v then return end
    if v.demo then
        KH:Print("Demo table — " .. action .. (amount and amount > 0 and (" " .. amount) or "") ..
            " noted. |cfffad990/kh demo|r advances the hand.")
        return
    end
    if KH.Lobby.mine then
        KH.Host:LocalAction(action, amount or 0)
    else
        KH.Comms:Whisper(v.host, MSG().ACTION, v.handNo, action, amount or 0)
    end
end

function Client:SetReady(ready)
    local v = self.view if not v then return end
    if KH.Lobby.mine then return end
    KH.Comms:Whisper(v.host, MSG().READY, v.id, ready and 1 or 0)
    KH.Sounds:Play(ready and "READY_CLICK" or "UNREADY")
end

function Client:LeaveTable()
    local v = self.view if not v then return end
    if v.demo then self:FinishTable() return end
    if not KH.Lobby.mine then
        KH.Comms:Whisper(v.host, "LV", v.id)
    end
    self:FinishTable()
end

-- ------------------------------------------------------------
-- settle: identical deterministic computation on every client
-- ------------------------------------------------------------
function Client:Settle(hands)
    local v = self.view if not v then return end
    local results, transfers = {}, {}
    local isGold = v.config and v.config.stakes == "gold"

    local winners, losers = {}, {}
    for ix, p in pairs(v.seats) do
        local net = (p.stack or 0) - (v.config and v.config.amount or 0)
        results[p.name] = net
        if net > 0 then winners[#winners + 1] = { name = p.name, amt = net }
        elseif net < 0 then losers[#losers + 1] = { name = p.name, amt = -net } end
    end
    table.sort(winners, function(a, b) return a.amt > b.amt or (a.amt == b.amt and a.name < b.name) end)
    table.sort(losers,  function(a, b) return a.amt > b.amt or (a.amt == b.amt and a.name < b.name) end)

    if isGold then
        local wi = 1
        for _, l in ipairs(losers) do
            local owe = l.amt
            while owe > 0 and wi <= #winners do
                local w = winners[wi]
                local pay = math.min(owe, w.amt)
                transfers[#transfers + 1] = { from = l.name, to = w.name, amt = pay, paid = false }
                owe = owe - pay
                w.amt = w.amt - pay
                if w.amt == 0 then wi = wi + 1 end
            end
        end
    end

    KH.Ledger:RecordTable(
        { id = v.id, config = v.config, handNo = hands },
        results, transfers)

    KH.Table:Close()
    if KH.Launcher then
        KH.Launcher:ShowSettle(v, results, transfers, hands)
    end
    self.view = nil
end

function Client:FinishTable()
    KH.Table:Close()
    self.view = nil
    if KH.Launcher and KH.Launcher.frame and KH.Launcher.frame:IsShown() then
        KH.Launcher:ShowHome()
    end
end


-- ============================================================
-- Demo mode — /kh demo
-- Solo UI test: fake 3-seat table with the royal flush hand from
-- the mockups. Each /kh demo advances: flop -> your turn -> river
-- -> showdown -> next hand. No comms, no host, pure rendering.
-- ============================================================
local DEMO_BOARD = { "QS", "JS", "4D", "7H", "10S" }

function Client:Demo()
    local v = self.view
    if v and not v.demo then
        KH:Print("You're at a real table — finish or leave it first.")
        return
    end

    if not v then
        v = {
            id = "demo", host = KH.playerFull, demo = true,
            config = { name = "Demo Table", stakes = "chips",
                       amount = 2500, seats = 6, timer = 30 },
            mySeat = 1, count = 3, rev = 1,
            seats = {
                [1] = { name = KH.playerFull, ready = true, stack = 2450, bet = 25 },
                [2] = { name = "Thrallmart",  ready = true, stack = 2450, bet = 25 },
                [3] = { name = "Peon",        ready = true, stack = 2450, bet = 25 },
            },
            handNo = 1, dealerIx = 1, ante = 25, pot = 75,
            board = {}, street = "PREFLOP",
            hole = { "AS", "KS" }, holeHand = 1,
            reveals = {}, winners = {},
        }
        self.view = v
        v.demoStage = 0
        KH.Table:Open()
        KH.Table:OnHandStart()
        self.view.hole = { "AS", "KS" }
        KH.Table:RefreshMyCards()
        KH:Print("Demo table open. |cfffad990/kh demo|r again to deal the flop.")
        return
    end

    v.demoStage = (v.demoStage or 0) + 1
    local stage = v.demoStage

    if stage == 1 then
        v.street = "FLOP"
        v.board = { DEMO_BOARD[1], DEMO_BOARD[2], DEMO_BOARD[3] }
        v.pot = 375
        v.actorIx, v.toCall, v.minRaise = 2, 0, 50
        v.deadline = GetTime() + v.config.timer
        KH.Sounds:Play("CARD_DEAL")
        KH.Table:RefreshBoard()
        KH.Table:OnActionRequest()
    elseif stage == 2 then
        v.street = "TURN"
        v.board[4] = DEMO_BOARD[4]
        v.pot = 825
        for _, p in pairs(v.seats) do p.bet = 150 end
        v.actorIx, v.toCall, v.minRaise = 1, 150, 150
        v.deadline = GetTime() + v.config.timer
        KH.Sounds:Play("CARD_DEAL")
        KH.Table:RefreshBoard()
        KH.Table:OnActionRequest()
    elseif stage == 3 then
        v.street = "RIVER"
        v.board[5] = DEMO_BOARD[5]
        v.pot = 1240
        for _, p in pairs(v.seats) do p.bet = 0 end
        v.actorIx, v.toCall, v.minRaise = 3, 0, 50
        v.deadline = GetTime() + v.config.timer
        KH.Sounds:Play("CARD_DEAL")
        KH.Table:RefreshBoard()
        KH.Table:OnActionRequest()
    elseif stage == 4 then
        v.actorIx = nil
        v.reveals = { [2] = { "QH", "QD", "Three of a Kind" },
                      [3] = { "9C", "9D", "Two Pair" } }
        v.seats[1].stack = 2450 + 1240
        v.pot = 0
        KH.Sounds:Play("CARD_FLIP")
        KH.Table:RefreshSeats()
        KH.Table:ShowWinner(1, 1240, "Royal Flush")
        KH.Sounds:Play("POT_WIN")
        KH.Table:OnHandEnd(999)
        KH.Table.nextHandAt = nil
        KH.Table.status:SetText("Showdown — /kh demo for the next hand")
    else
        v.demoStage = 0
        v.handNo = v.handNo + 1
        v.board = {}
        v.street = "PREFLOP"
        v.pot = 75
        v.reveals = {}
        for _, p in pairs(v.seats) do p.bet = 25 p.folded = false end
        v.hole = { "AS", "KS" }
        v.holeHand = v.handNo
        KH.Table:OnHandStart()
        KH.Table:RefreshMyCards()
    end
end
