# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-utxo-set-divergence-between-its-mempool-view-and-the-canonical-chain-during-a-reorg-without-trusting-a-single-peer-s-getra.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-utxo-set-divergence-between-its-mempool-view-and-the-canonical-chain-during-a-reorg-without-trusting-a-single-peer-s-getra.warrant.json
# Markdown SHA-256:   d8552ed5d858ac3ab0db1dab3f6c10a154b2dd9047bff3569cabd6b72fe3e8c0
# Code AST SHA-256:   36de375839e265fb922c10c1d72012bf2786146c32488378c36dcc1e0654813d
# Verified Classes:   MempoolChainDivergence
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class MempoolChainDivergence(RuntimeError):
    """Raised when the wallet's spendable set would double-count a UTXO
    that is both a confirmed chain output and an input to a mempool
    transaction with unconfirmed ancestors."""
    pass


def compute_spendable_set(chain_utxos, mempool_txs, wallet_script_hashes):
    """
    chain_utxos: dict[outpoint -> value] from the wallet's chain view
    mempool_txs: dict[txid -> {inputs: [outpoint], outputs: [(script, value)]}]
    """
    # Identify mempool transactions whose inputs intersect the wallet's
    # confirmed UTXO set AND whose ancestry includes an unconfirmed tx.
    spendable = dict(chain_utxos)
    spends_of_confirmed = {}

    for txid, tx in mempool_txs.items():
        for outpoint in tx["inputs"]:
            if outpoint in chain_utxos:
                spends_of_confirmed.setdefault(outpoint, []).append(txid)

    for outpoint, spending_txids in spends_of_confirmed.items():
        # If every spender is a direct mempool tx with all-confirmed
        # ancestors, the UTXO is spent; remove it and add the change.
        for txid in spending_txids:
            tx = mempool_txs[txid]
            if tx.get("has_unconfirmed_ancestor", False):
                raise MempoolChainDivergence(
                    f"UTXO {outpoint} is a confirmed chain output but is "
                    f"spent by mempool tx {txid} whose ancestry includes an "
                    f"unconfirmed transaction. Spendable set would "
                    f"double-count; rebuild mempool view against new tip "
                    f"before coin selection."
                )
        # Safe to mark spent and credit change.
        del spendable[outpoint]
        for script, value in mempool_txs[spending_txids[0]]["outputs"]:
            if script in wallet_script_hashes:
                spendable[(spending_txids[0], script)] = value

    return spendable


def select_coins(spendable, target_value):
    # Deterministic largest-first selection; raises if the set is
    # internally inconsistent (the divergence guard above should have
    # fired first, but we defend in depth).
    selected, total = [], 0
    for outpoint, value in sorted(spendable.items(), key=lambda kv: -kv[1]):
        selected.append(outpoint)
        total += value
        if total >= target_value:
            return selected, total
    raise MempoolChainDivergence(
        f"insufficient spendable value: {total} < {target_value}; "
        f"spendable set may be incomplete after reorg"
    )
