package com.jsjdesigns.poker.game

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

/**
 * A [PlayerAgent] driven by the UI rather than by code.
 *
 * When it is this player's turn, [act] publishes the decision on offer and then
 * suspends until [submit] delivers a choice. Cancelling the coroutine running the
 * hand — a screen closing, a game being abandoned — completes the pending wait
 * exceptionally rather than leaving it parked forever.
 */
class HumanAgent : PlayerAgent {

    private val lock = Mutex()
    private var pending: CompletableDeferred<Action>? = null

    /** The decision currently awaiting input, or null when it is not our turn. */
    var awaiting: DecisionContext? = null
        private set

    override suspend fun act(ctx: DecisionContext): Action {
        val deferred = CompletableDeferred<Action>()
        lock.withLock {
            check(pending == null) { "already awaiting a decision for this agent" }
            pending = deferred
            awaiting = ctx
        }
        return try {
            deferred.await()
        } finally {
            // Runs on normal completion AND on cancellation, so the agent is never
            // left believing it is still waiting.
            lock.withLock {
                pending = null
                awaiting = null
            }
        }
    }

    /**
     * Supplies the player's choice. Returns false when nothing was waiting, which
     * makes a double-tap or a stale click harmless rather than a crash.
     */
    suspend fun submit(action: Action): Boolean = lock.withLock {
        val deferred = pending ?: return@withLock false
        deferred.complete(action)
    }

    /** Abandons the pending decision, releasing [act] with a cancellation. */
    suspend fun cancel(cause: Throwable = IllegalStateException("hand abandoned")) {
        lock.withLock { pending?.completeExceptionally(cause) }
    }

    val isAwaitingInput: Boolean get() = awaiting != null
}
