"""Models, types, and enums for the package."""

from dataclasses import asdict, dataclass
from enum import Enum
from typing import TypedDict, cast


class FormatStyle(Enum):
    """Output formatting styles for UKPostcode.format().

    Members:
      STANDARD: Include a space between the outward and inward parts
        (e.g., 'SW1A 1AA').
      NO_SPACES: Remove the space between outward and inward parts
        (e.g., 'SW1A1AA').
    """

    STANDARD = "standard"
    NO_SPACES = "no-spaces"


class PostcodeDict(TypedDict):
    """Typed dictionary representing the serialized shape of a UKPostcode.

    Keys:
      raw: Original, unnormalized postcode input string.
      outward: Outward code (area + district), e.g., 'SW1A'.
      inward: Inward code (sector + unit), e.g., '1AA'. Can be None.
      area: Area component (letters), e.g., 'SW'. Can be None.
      district: District component (digits and possibly a letter), e.g., '1A'. Can be None.
      sector: Sector component (first digit of the inward part), e.g., '1'. Can be None.
      unit: Unit component (last two letters of the inward part), e.g., 'AA'. Can be None.
      is_special: True if the postcode is a known special/non-geographic form.
    """

    raw: str
    outward: str
    inward: str | None
    area: str | None
    district: str | None
    sector: str | None
    unit: str | None
    is_special: bool


@dataclass(frozen=True, slots=True)
class UKPostcode:
    """Immutable value object representing a parsed UK postcode.

    Attributes:
      raw: Original input string from which the postcode was parsed.
      outward: Outward code (area + district), e.g., 'SW1A'.
      inward: Inward code (sector + unit), e.g., '1AA'. Can be None.
      area: Area component (letters), e.g., 'SW'. Can be None.
      district: District component (digits and possibly a letter), e.g., '1A'. Can be None.
      sector: Sector component (first digit of the inward part), e.g., '1'. Can be None.
      unit: Unit component (last two letters of the inward part), e.g., 'AA'. Can be None.
      is_special: True if the postcode is a known special/non-geographic form.

    Note:
      This class does not perform validation or normalization. It is designed to be
      populated by upstream parsing logic and provides convenience methods for
      formatting and serialization.
    """

    raw: str
    outward: str
    inward: str | None = None
    area: str | None = None
    district: str | None = None
    sector: str | None = None
    unit: str | None = None
    is_special: bool = False

    def format(self, style: FormatStyle = FormatStyle.STANDARD) -> str:
        """Return the postcode as a string in the requested style.

        Args:
          style: Formatting style to use. Defaults to FormatStyle.STANDARD.

        Returns:
          The formatted postcode string.

        Raises:
          ValueError: If an unsupported formatting style is provided.
        """
        match style:
            case FormatStyle.STANDARD:
                return f"{self.outward} {self.inward}"
            case FormatStyle.NO_SPACES:
                return f"{self.outward}{self.inward}"
            case _:
                # For runtime safety
                msg = f"Invalid format style: {style}"
                raise ValueError(msg)

    def to_dict(self) -> PostcodeDict:
        """Return a JSON-serializable mapping of this postcode.

        Returns:
          A PostcodeDict containing the same fields as this dataclass.
        """
        return cast("PostcodeDict", asdict(self))
