```python
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP, localcontext


def _validated_cost_cents(row):
    if "price" in row:
        price = row["price"]
        if not isinstance(price, str):
            raise ValueError("price must be a finite nonnegative decimal string")

        stripped_price = price.strip()
        if not stripped_price:
            raise ValueError("price must be a finite nonnegative decimal string")

        try:
            with localcontext() as context:
                context.prec = max(50, len(stripped_price) + 10)
                value = Decimal(stripped_price)
                if not value.is_finite():
                    raise ValueError(
                        "price must be a finite nonnegative decimal string"
                    )
                if value < 0:
                    raise ValueError(
                        "price must be a finite nonnegative decimal string"
                    )
                cents = (value * 100).to_integral_value(rounding=ROUND_HALF_UP)
        except InvalidOperation:
            raise ValueError(
                "price must be a finite nonnegative decimal string"
            ) from None

        return int(cents)

    if "cost_cents" not in row:
        raise ValueError("cost_cents is required for legacy rows")

    cost_cents = row["cost_cents"]
    if isinstance(cost_cents, bool) or not isinstance(cost_cents, int):
        raise ValueError("cost_cents must be a nonnegative integer")
    if cost_cents < 0:
        raise ValueError("cost_cents must be a nonnegative integer")

    return cost_cents


def plan(rows, budget_cents, excluded_channels=()):
    if isinstance(budget_cents, bool) or not isinstance(budget_cents, int):
        raise ValueError("budget_cents must be a nonnegative integer")
    if budget_cents < 0:
        raise ValueError("budget_cents must be a nonnegative integer")

    normalized_excluded = {
        channel.strip().casefold() for channel in excluded_channels
    }

    seen_ids = set()
    candidates = []

    for row in rows:
        row_id = row["id"]
        if row_id in seen_ids:
            continue
        seen_ids.add(row_id)

        if row.get("archived", False):
            continue

        channel = row.get("channel", "")
        if channel.strip().casefold() in normalized_excluded:
            continue

        candidates.append(row)

    validated_candidates = [
        (row, _validated_cost_cents(row)) for row in candidates
    ]

    ordered_candidates = sorted(
        validated_candidates,
        key=lambda item: item[0].get("priority", 0),
        reverse=True,
    )

    selected_ids = []
    spent_cents = 0
    remaining_cents = budget_cents

    for row, cost_cents in ordered_candidates:
        if cost_cents <= remaining_cents:
            selected_ids.append(row["id"])
            spent_cents += cost_cents
            remaining_cents -= cost_cents

    return {
        "selected_ids": selected_ids,
        "spent_cents": spent_cents,
        "remaining_cents": remaining_cents,
    }
```