from __future__ import annotations

import re
from collections.abc import Mapping


def sensitive_key_tokens(key: object) -> set[str]:
    value = str(key).strip()
    value = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", value)
    return {item for item in re.split(r"[^A-Za-z0-9]+", value.lower()) if item}


def is_sensitive_key(key: object) -> bool:
    tokens = sensitive_key_tokens(key)
    if not tokens:
        return False
    if tokens & {"authorization", "cookie", "credential", "credentials", "password", "passwd", "secret", "token"}:
        return True
    return bool({"api", "key"} <= tokens or {"access", "key"} <= tokens or {"private", "key"} <= tokens)


def redact_secret_values(value: object, *, placeholder: str = "<redacted>") -> object:
    if isinstance(value, Mapping):
        result: dict[str, object] = {}
        for key, item in value.items():
            if is_sensitive_key(key):
                result[str(key)] = placeholder
            else:
                result[str(key)] = redact_secret_values(item, placeholder=placeholder)
        return result
    if isinstance(value, list):
        return [redact_secret_values(item, placeholder=placeholder) for item in value]
    if isinstance(value, tuple):
        return tuple(redact_secret_values(item, placeholder=placeholder) for item in value)
    return value


def contains_plain_secret(value: object) -> bool:
    if isinstance(value, Mapping):
        for key, item in value.items():
            normalized_key = str(key).strip().casefold().replace("-", "_")
            if normalized_key in {"credential_ref", "secret_ref", "secret_reference"}:
                continue
            if is_sensitive_key(key) and item not in (None, "", {"secret_ref": ""}):  # nosec B105 - empty redaction sentinels.
                return True
            if contains_plain_secret(item):
                return True
    elif isinstance(value, (list, tuple)):
        return any(contains_plain_secret(item) for item in value)
    return False
