-- ============================================================
-- Komo Hold'Em — GameHost
-- Host-side orchestration: seats joiners, runs hands, applies
-- actions, enforces turn timers, computes side pots + winners,
-- and drives every broadcast. Only ever active on the host.
-- ============================================================
local _, KH = ...
KH.Host = {}
local Host  = KH.Host
local Game  = KH.Game
local Cards = KH.Cards

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

-- ------------------------------------------------------------
-- roster
-- ------------------------------------------------------------
function Host:BroadcastRoster()
    local t = KH.Lobby.mine
    if not t then return end
    t.rosterRev = (t.rosterRev or 0) + 1
    for ix, name in ipairs(t.seatOrder) do
        local p = t.players[name]
        KH.Comms:Broadcast(MSG().LOBBY_STATE,
            t.id, t.rosterRev, ix, name, p.ready and 1 or 0, p.stack)
    end
    KH.Comms:Broadcast("LE", t.id, t.rosterRev, #t.seatOrder)
    KH.Lobby:Announce()
end

function Host:SeatPlayer(name)
    local t = KH.Lobby.mine
    if not t or t.state ~= Game.STATE.LOBBY then return false, "in_progress" end
    if t.players[name] then return true end
    if #t.seatOrder >= t.config.seats then return false, "full" end
    t.seatOrder[#t.seatOrder + 1] = name
    t.players[name] = {
        seat = #t.seatOrder, stack = t.config.amount,
        ready = false, connected = true, folded = false, out = false,
    }
    KH.Sounds:Play("PLAYER_JOIN")
    return true
end

function Host:RemovePlayer(name)
    local t = KH.Lobby.mine
    if not t or not t.players[name] then return end
    if t.state == Game.STATE.LOBBY then
        t.players[name] = nil
        for i, n in ipairs(t.seatOrder) do
            if n == name then table.remove(t.seatOrder, i) break end
        end
        for i, n in ipairs(t.seatOrder) do t.players[n].seat = i end
    else
        -- mid-game: fold them out permanently
        local p = t.players[name]
        p.folded, p.out, p.connected = true, true, false
        self:AdvanceIfActorLeft(name)
    end
    self:BroadcastRoster()
    if KH.Launcher then KH.Launcher:RefreshLobby() end
end

-- ------------------------------------------------------------
-- message handlers (host side)
-- ------------------------------------------------------------
function Host:Init()
    local Comms = KH.Comms

    Comms:RegisterHandler(MSG().JOIN_REQUEST, function(sender)
        local t = KH.Lobby.mine
        if not t then return end
        local ok, why = Host:SeatPlayer(sender)
        if ok then
            local cfg = t.config
            Comms:Whisper(sender, MSG().JOIN_ACCEPT,
                t.id, t.players[sender].seat, cfg.name, cfg.stakes,
                cfg.amount, cfg.seats, cfg.timer)
            Host:BroadcastRoster()
            if KH.Launcher then KH.Launcher:RefreshLobby() end
        else
            Comms:Whisper(sender, "JD", t.id, why or "full") -- join denied
        end
    end)

    Comms:RegisterHandler(MSG().READY, function(sender, _, f)
        local t = KH.Lobby.mine
        if not t or not t.players[sender] then return end
        t.players[sender].ready = (f[2] == "1")
        KH.Sounds:Play(f[2] == "1" and "READY_CLICK" or "UNREADY")
        Host:BroadcastRoster()
        if KH.Launcher then KH.Launcher:RefreshLobby() end
    end)

    Comms:RegisterHandler("LV", function(sender, _, f) -- leave
        Host:RemovePlayer(sender)
    end)

    Comms:RegisterHandler(MSG().ACTION, function(sender, _, f)
        Host:OnAction(sender, f[2], tonumber(f[3]) or 0, tonumber(f[1]))
    end)
end

-- ------------------------------------------------------------
-- game flow
-- ------------------------------------------------------------
function Host:StartGame()
    local t = KH.Lobby.mine
    if not t then return end
    local can = Game:CanStart(t)
    if not can then return end
    t.state = "STARTED"
    KH.Comms:Broadcast(MSG().GAME_START, t.id)
    KH.Sounds:Play("GAME_START")
    C_Timer.After(1.0, function() Host:StartHand() end)
end

local function activePlayers(t)
    local list = {}
    for _, name in ipairs(t.seatOrder) do
        local p = t.players[name]
        if not p.out and p.stack > 0 then list[#list + 1] = name end
    end
    return list
end

function Host:StartHand()
    local t = KH.Lobby.mine
    if not t then return end
    local active = activePlayers(t)
    if #active < 2 then
        -- big in-your-face stop: everyone sees the overlay, host closes
        local last = active[1] and Ambiguate(active[1], "short") or "Nobody"
        KH.Comms:Broadcast("GO", t.id, last)
        return
    end

    t.handNo = t.handNo + 1
    repeat t.dealerIx = (t.dealerIx % #t.seatOrder) + 1
    until not t.players[t.seatOrder[t.dealerIx]].out and t.players[t.seatOrder[t.dealerIx]].stack > 0

    t.deck  = Cards:Shuffle(Cards:NewDeck())
    t.board = {}
    t.pot   = 0
    t.street = "PREFLOP"
    t.currentBet = 0
    t.lastRaise  = t.ante

    for _, name in ipairs(t.seatOrder) do
        local p = t.players[name]
        p.bet, p.roundTotal, p.acted = 0, 0, false
        p.folded = p.out or p.stack <= 0
        p.handTotal = 0
        p.hole = nil
    end

    -- antes
    for _, name in ipairs(active) do
        local p = t.players[name]
        local ante = math.min(t.ante, p.stack)
        p.stack = p.stack - ante
        p.handTotal = ante
        t.pot = t.pot + ante
    end

    KH.Comms:Broadcast("HS", t.id, t.handNo, t.dealerIx, t.ante, t.pot)
    for ix, name in ipairs(t.seatOrder) do
        local p = t.players[name]
        KH.Comms:Broadcast("ST", t.handNo, ix, p.stack, 0, p.folded and 1 or 0, t.pot)
    end

    -- hole cards: whisper others, loopback self
    for _, name in ipairs(active) do
        local p = t.players[name]
        p.hole = { Cards:Draw(t.deck), Cards:Draw(t.deck) }
        if name == KH.playerFull then
            KH.Client:OnHoleCards(t.handNo, p.hole[1], p.hole[2])
        else
            KH.Comms:SendHoleCards(name, t.handNo, p.hole[1], p.hole[2])
        end
    end

    self:BeginRound(true)
end

local function nextActor(t, fromIx)
    local n = #t.seatOrder
    for step = 1, n do
        local ix = ((fromIx - 1 + step) % n) + 1
        local p = t.players[t.seatOrder[ix]]
        if not p.folded and p.stack > 0 and not (p.acted and p.bet == t.currentBet) then
            return ix
        end
    end
    return nil
end

local function unfolded(t)
    local c, last = 0, nil
    for ix, name in ipairs(t.seatOrder) do
        if not t.players[name].folded then c = c + 1 last = ix end
    end
    return c, last
end

function Host:BeginRound(isPreflop)
    local t = KH.Lobby.mine
    t.currentBet = 0
    t.lastRaise  = t.ante
    for _, name in ipairs(t.seatOrder) do
        local p = t.players[name]
        p.bet, p.acted = 0, false
    end
    self:RequestAction(nextActor(t, t.dealerIx))
end

function Host:RequestAction(ix)
    local t = KH.Lobby.mine
    if not ix then self:EndRound() return end

    -- everyone else all-in / only one can act with nothing to call -> run out
    local canAct = 0
    for _, name in ipairs(t.seatOrder) do
        local p = t.players[name]
        if not p.folded and p.stack > 0 then canAct = canAct + 1 end
    end
    if canAct <= 1 and t.currentBet <= (t.players[t.seatOrder[ix]].bet or 0) then
        self:EndRound() return
    end

    t.actorIx = ix
    local p = t.players[t.seatOrder[ix]]
    local toCall = t.currentBet - p.bet
    KH.Comms:Broadcast(MSG().ACTION_REQUEST,
        t.handNo, ix, toCall, math.max(t.lastRaise, t.ante), t.config.timer)

    if self.turnTimer then self.turnTimer:Cancel() end
    local thisHand, thisIx = t.handNo, ix
    self.turnTimer = C_Timer.NewTimer(t.config.timer + 2, function()
        local tt = KH.Lobby.mine
        if tt and tt.handNo == thisHand and tt.actorIx == thisIx then
            local pp = tt.players[tt.seatOrder[thisIx]]
            local auto = (tt.currentBet - pp.bet) <= 0 and "check" or "fold"
            KH:Print(Ambiguate(tt.seatOrder[thisIx], "short") .. " timed out — auto-" .. auto .. ".")
            Host:Apply(thisIx, auto, 0)
        end
    end)
end

function Host:OnAction(sender, action, amount, handNo)
    local t = KH.Lobby.mine
    if not t or handNo ~= t.handNo then return end
    local ix = t.actorIx
    if not ix or t.seatOrder[ix] ~= sender then return end -- not your turn
    self:Apply(ix, action, amount)
end

-- local (host player) action entry, called by Table UI
function Host:LocalAction(action, amount)
    local t = KH.Lobby.mine
    if not t or not t.actorIx then return end
    if t.seatOrder[t.actorIx] ~= KH.playerFull then return end
    self:Apply(t.actorIx, action, amount)
end

function Host:Apply(ix, action, amount)
    local t = KH.Lobby.mine
    local name = t.seatOrder[ix]
    local p = t.players[name]
    if self.turnTimer then self.turnTimer:Cancel() self.turnTimer = nil end

    if action == "fold" then
        p.folded = true
        KH.Sounds:Play("CARD_FOLD")
    elseif action == "check" and t.currentBet <= p.bet then
        KH.Sounds:Play("CHIP_CALL")
    elseif action == "call" then
        local need = math.min(t.currentBet - p.bet, p.stack)
        p.stack = p.stack - need
        p.bet = p.bet + need
        p.handTotal = p.handTotal + need
        t.pot = t.pot + need
        KH.Sounds:Play("CHIP_CALL")
    elseif action == "bet" then
        -- amount = raise-to total for this round
        local target = math.max(amount, t.currentBet + math.max(t.lastRaise, t.ante))
        local need = math.min(target - p.bet, p.stack)
        if need <= 0 then action = "check" else
            if p.bet + need > t.currentBet then
                t.lastRaise = (p.bet + need) - t.currentBet
                t.currentBet = p.bet + need
                -- re-open action
                for _, n2 in ipairs(t.seatOrder) do
                    local q = t.players[n2]
                    if not q.folded then q.acted = false end
                end
            end
            p.stack = p.stack - need
            p.bet = p.bet + need
            p.handTotal = p.handTotal + need
            t.pot = t.pot + need
            KH.Sounds:Play("CHIP_RAISE")
        end
    else
        -- illegal -> treat as check/fold
        if t.currentBet <= p.bet then KH.Sounds:Play("CHIP_CALL")
        else p.folded = true end
    end
    p.acted = true

    KH.Comms:Broadcast("ST", t.handNo, ix, p.stack, p.bet, p.folded and 1 or 0, t.pot)

    local count, lastIx = unfolded(t)
    if count == 1 then
        self:AwardFoldout(lastIx)
        return
    end

    local nxt = nextActor(t, ix)
    if nxt then self:RequestAction(nxt) else self:EndRound() end
end

function Host:AdvanceIfActorLeft(name)
    local t = KH.Lobby.mine
    if t and t.actorIx and t.seatOrder[t.actorIx] == name then
        self:Apply(t.actorIx, "fold", 0)
    end
end

function Host:EndRound()
    local t = KH.Lobby.mine
    if t.street == "PREFLOP" then
        Cards:Draw(t.deck)
        for i = 1, 3 do t.board[#t.board + 1] = Cards:Draw(t.deck) end
        t.street = "FLOP"
    elseif t.street == "FLOP" then
        Cards:Draw(t.deck)
        t.board[#t.board + 1] = Cards:Draw(t.deck)
        t.street = "TURN"
    elseif t.street == "TURN" then
        Cards:Draw(t.deck)
        t.board[#t.board + 1] = Cards:Draw(t.deck)
        t.street = "RIVER"
    elseif t.street == "RIVER" then
        self:Showdown()
        return
    end
    KH.Comms:Broadcast("BD", t.handNo, t.street, table.concat(t.board, ";"))
    self:BeginRound(false)
end

function Host:AwardFoldout(winnerIx)
    local t = KH.Lobby.mine
    local name = t.seatOrder[winnerIx]
    local won = t.pot
    t.players[name].stack = t.players[name].stack + won
    t.pot = 0
    KH.Comms:Broadcast("WN", t.handNo, winnerIx, won, "takes it")
    KH.Comms:Broadcast("ST", t.handNo, winnerIx, t.players[name].stack, 0, 0, 0)
    self:ScheduleNextHand()
end

-- side pots: layered by contribution caps
function Host:Showdown()
    local t = KH.Lobby.mine
    KH.Sounds:Play("CARD_FLIP")

    -- reveal + evaluate
    local hands = {}
    for ix, name in ipairs(t.seatOrder) do
        local p = t.players[name]
        if not p.folded and p.hole then
            local cat, tie, label = KH.Game:Evaluate(p.hole, t.board)
            hands[ix] = { cat = cat, tie = tie, label = label }
            KH.Comms:Broadcast(MSG().SHOWDOWN,
                t.handNo, ix, p.hole[1], p.hole[2], label)
        end
    end

    -- build side pots from handTotal layers
    local layers = {}
    for ix, name in ipairs(t.seatOrder) do
        local c = t.players[name].handTotal or 0
        if c > 0 then layers[#layers + 1] = c end
    end
    table.sort(layers)
    local caps, seen = {}, {}
    for _, c in ipairs(layers) do
        if not seen[c] then seen[c] = true caps[#caps + 1] = c end
    end

    local prev = 0
    for _, cap in ipairs(caps) do
        local potAmt, eligible = 0, {}
        for ix, name in ipairs(t.seatOrder) do
            local p = t.players[name]
            local contrib = math.min(p.handTotal or 0, cap) - prev
            if contrib > 0 then potAmt = potAmt + contrib end
            if not p.folded and (p.handTotal or 0) >= cap and hands[ix] then
                eligible[#eligible + 1] = ix
            end
        end
        prev = cap
        if potAmt > 0 and #eligible > 0 then
            -- best hand among eligible
            local best = { eligible[1] }
            for i = 2, #eligible do
                local a, b = hands[eligible[i]], hands[best[1]]
                local cmp = KH.Game:Compare(a.cat, a.tie, b.cat, b.tie)
                if cmp > 0 then best = { eligible[i] }
                elseif cmp == 0 then best[#best + 1] = eligible[i] end
            end
            local share = math.floor(potAmt / #best)
            local rem = potAmt - share * #best
            for i, ix in ipairs(best) do
                local name = t.seatOrder[ix]
                local win = share + (i == 1 and rem or 0)
                t.players[name].stack = t.players[name].stack + win
                KH.Comms:Broadcast("WN", t.handNo, ix, win, hands[ix].label)
            end
        end
    end

    t.pot = 0
    for ix, name in ipairs(t.seatOrder) do
        local p = t.players[name]
        KH.Comms:Broadcast("ST", t.handNo, ix, p.stack, 0, p.folded and 1 or 0, 0)
    end
    KH.Sounds:Play("POT_WIN")
    self:ScheduleNextHand()
end

function Host:ScheduleNextHand()
    -- no auto-deal: the host decides between next hand and ending
    local t = KH.Lobby.mine
    KH.Comms:Broadcast("HE", t.handNo, 0)
end
