"""Postcode parser."""

from wojtek.uk_postcodes.exceptions import InvalidPostcodeError
from wojtek.uk_postcodes.models import UKPostcode
from wojtek.uk_postcodes.patterns import SPECIAL_POSTCODE_PATTERNS, STANDARD_POSTCODE_PATTERN


def parse_postcode(postcode: str, *, allow_special: bool = True) -> UKPostcode:
    """Parse a UK postcode into its structured components.

    Performs a strict full-match against the standard postcode pattern and,
    if that fails and allow_special is set to True, against a set of special-case patterns.
    On success, returns a UKPostcode instance populated with extracted parts and an
    is_special flag indicating whether a special-case pattern matched.
    The raw input is preserved exactly as provided (no normalization).

    Args:
        postcode: The postcode string to parse (for example, "SW1A 1AA").
            Note: this function does not trim leading/trailing whitespace.
        allow_special: If True, allow matching against special-case patterns.

    Returns:
        UKPostcode: An immutable value object containing:
            - raw: Original input.
            - outward: Outward code (outcode).
            - inward: Inward code (incode), if present.
            - area, district, sector, unit: Parsed components when available.
            - is_special: True if matched a special-case pattern.

    Raises:
        InvalidPostcodeError: If the input does not fully match any supported
            postcode pattern.

    Examples:
        >>> parse_postcode("SW1A 1AA").outward
        'SW1A'
        >>> parse_postcode("SW1A 1AA").inward
        '1AA'
        >>> parse_postcode("SW1A 1AA").is_special
        False
    """
    is_special = False
    match = STANDARD_POSTCODE_PATTERN.fullmatch(postcode)

    if not match and allow_special:
        for pattern in SPECIAL_POSTCODE_PATTERNS:
            match = pattern.fullmatch(postcode)
            if match:
                is_special = True
                break

    if not match:
        msg = f"Invalid postcode: {postcode}"
        raise InvalidPostcodeError(msg)

    return UKPostcode(
        raw=postcode,
        outward=match.group("outward"),
        inward=match.groupdict().get("inward"),
        area=match.groupdict().get("area"),
        district=match.groupdict().get("district"),
        sector=match.groupdict().get("sector"),
        unit=match.groupdict().get("unit"),
        is_special=is_special,
    )
