"""Text normalization for Japanese emails.

Handles:
- Line ending normalization
- Japanese text normalization (neologdn + NFKC)
"""

import unicodedata
from dataclasses import dataclass

import neologdn

from yomail.exceptions import InvalidInputError


@dataclass(frozen=True, slots=True)
class NormalizedEmail:
    """Result of normalizing an email.

    Attributes:
        lines: List of normalized lines (without line endings).
        text: Full normalized text with newlines.
    """

    lines: tuple[str, ...]
    text: str


class Normalizer:
    """Normalizes Japanese email text for downstream processing.

    Applies the following transformations:
    1. Line ending normalization (CRLF/CR → LF)
    2. neologdn normalization (Japanese-specific)
    3. Unicode NFKC normalization
    """

    def normalize(self, text: str) -> NormalizedEmail:
        """Normalize email text.

        Args:
            text: Raw email text (UTF-8 string).

        Returns:
            NormalizedEmail with normalized lines and text.

        Raises:
            InvalidInputError: If text is empty after normalization.
        """
        # Normalize line endings: CRLF and CR to LF
        text = text.replace("\r\n", "\n").replace("\r", "\n")

        # Apply Japanese text normalization
        text = self._normalize_japanese(text)

        # Split into lines and strip whitespace from each
        lines = [line.strip() for line in text.split("\n")]

        # Remove leading blank lines
        while lines and not lines[0]:
            lines.pop(0)

        # Remove trailing blank lines
        while lines and not lines[-1]:
            lines.pop()

        # Check for empty result
        if not lines:
            raise InvalidInputError(message="Empty input after normalization")

        return NormalizedEmail(
            lines=tuple(lines),
            text="\n".join(lines),
        )

    # Dash-like characters for unification
    _DASH_CHARS = frozenset("-ー")

    # Zero-width characters to strip (invisible noise)
    _ZERO_WIDTH_CHARS = "\ufeff\u200b\u200c\u200d\u2060"

    # CHOONPUS from neologdn - these get collapsed (ーーー→ー)
    _CHOONPUS = frozenset("﹣－ｰ—―─━ー")

    # Delimiter line chars - lines with only these skip neologdn
    # Includes CHOONPUS plus decorative symbols used in email signatures
    _DELIMITER_CHARS = frozenset(
        "﹣－ｰ—―─━ー"  # CHOONPUS (dashes that collapse)
        "★☆◆◇■□●○▲△▼▽"  # decorative shapes
        "【】〔〕「」『』《》〈〉"  # brackets
        "="  # equals (often mixed with dashes)
    )

    def _is_delimiter_line(self, line: str) -> bool:
        """Check if line consists only of delimiter characters.

        Delimiter lines skip neologdn to prevent CHOONPU collapsing.
        Includes dashes, decorative shapes, and brackets.
        """
        # Strip zero-width chars first (they shouldn't affect detection)
        for ch in self._ZERO_WIDTH_CHARS:
            line = line.replace(ch, "")
        stripped = line.strip()
        if not stripped:
            return False
        return all(c in self._DELIMITER_CHARS for c in stripped)

    def _normalize_delimiter_line(self, line: str) -> str:
        """Normalize a delimiter line.

        Converts CHOONPU chars to '-', preserves other delimiter chars.
        Strips whitespace and zero-width characters.
        """
        # Strip zero-width chars first
        for ch in self._ZERO_WIDTH_CHARS:
            line = line.replace(ch, "")
        # Convert CHOONPUS to hyphen, keep other delimiter chars
        result = []
        for c in line:
            if c in self._CHOONPUS:
                result.append("-")
            elif c in self._DELIMITER_CHARS or c.isspace():
                result.append(c)
        return "".join(result).strip()

    def _normalize_japanese(self, text: str) -> str:
        """Apply Japanese-specific normalization.

        Uses neologdn followed by NFKC normalization.
        Lines containing only CHOONPU characters skip neologdn
        to prevent repeat collapsing (ーーー→ー).

        neologdn handles:
        - Full-width ASCII → half-width (Ａ→A, １→1)
        - Half-width katakana → full-width (ｶﾀｶﾅ→カタカナ)
        - Repeated prolonged sound marks (ーーー→ー)
        - Tilde/wave dash variants

        NFKC handles remaining Unicode compatibility decomposition.
        """
        # Process line by line to protect delimiter lines from CHOONPU collapsing
        lines = text.split("\n")
        normalized_lines = []

        for line in lines:
            if self._is_delimiter_line(line):
                # Skip neologdn, normalize delimiter chars directly
                normalized_lines.append(self._normalize_delimiter_line(line))
            else:
                # Full normalization
                normalized = neologdn.normalize(line)
                normalized = unicodedata.normalize("NFKC", normalized)
                # Strip zero-width characters
                for ch in self._ZERO_WIDTH_CHARS:
                    normalized = normalized.replace(ch, "")
                normalized_lines.append(normalized)

        text = "\n".join(normalized_lines)

        # Unify dashes in delimiter-only lines (for mixed dash lines that went through neologdn)
        text = self._unify_delimiter_lines(text)

        return text

    def _unify_delimiter_lines(self, text: str) -> str:
        """Unify dash characters in lines that contain only dashes.

        For lines consisting entirely of dash-like characters (- and ー),
        normalize all dashes to the majority character in that line.
        This preserves visual appearance while ensuring consistency.
        """
        lines = text.split("\n")
        result = []

        for line in lines:
            stripped = line.strip()
            if stripped and all(ch in self._DASH_CHARS for ch in stripped):
                # Line is all dashes - unify to majority
                count_hyphen = stripped.count("-")
                count_prolonged = stripped.count("ー")
                target = "-" if count_hyphen >= count_prolonged else "ー"
                # Preserve leading/trailing whitespace
                leading = line[: len(line) - len(line.lstrip())]
                trailing = line[len(line.rstrip()) :]
                unified = target * len(stripped)
                result.append(leading + unified + trailing)
            else:
                result.append(line)

        return "\n".join(result)
