"""Postcode validation functions."""

from wojtek.uk_postcodes.patterns import SPECIAL_POSTCODE_PATTERNS, STANDARD_POSTCODE_PATTERN


def _is_special_postcode(postcode: str, *, require_full: bool) -> bool:
    """Determine whether the given postcode matches a recognized special-case format.

    Args:
        postcode: Postcode to evaluate.
        require_full: If False, any recognized special-case match is accepted.
            If True, the match must include an inward code component.

    Returns:
        bool: True if the postcode is a special-case postcode under the given rule, otherwise False.
    """
    for pattern in SPECIAL_POSTCODE_PATTERNS:
        if match := pattern.fullmatch(postcode):
            if not require_full:
                return True
            return bool(match.groupdict().get("inward"))

    return False


def _is_standard_postcode(postcode: str, *, require_full: bool) -> bool:
    """Determine whether the given postcode conforms to the standard format.

    Args:
        postcode: Postcode to evaluate.
        require_full: If False, any match to the standard pattern is accepted.
            If True, the postcode must include both sector and unit
            (i.e., the inward code) to be considered valid.

    Returns:
        bool: True if the postcode matches the standard pattern under the given rule, otherwise False.
    """
    match = STANDARD_POSTCODE_PATTERN.fullmatch(postcode)
    if not match:
        return False

    if not require_full:
        return True

    return bool(match.groupdict().get("sector") and match.groupdict().get("unit"))


def is_valid_postcode(postcode: str, *, allow_special: bool = True, require_full: bool = True) -> bool:
    """Validate a UK postcode string.

    This function validates standard postcodes and, optionally, special-case postcodes.

    Args:
        postcode: Postcode to validate.
        allow_special: If True, consider special-case postcodes when standard
            validation fails. If False, only standard postcodes are accepted.
        require_full: Controls acceptance of abbreviated forms. If False, any
            recognized match is accepted. If True, a complete postcode
            (including the inward code) is required.

    Returns:
        bool: True if the postcode is valid, according to the selected options, otherwise False.
    """
    if not isinstance(postcode, str):
        return False

    is_valid = _is_standard_postcode(postcode, require_full=require_full)
    if not is_valid and allow_special is False:
        return False

    if not is_valid and allow_special is True:
        return _is_special_postcode(postcode, require_full=require_full)

    return is_valid
