"""Character translation and TTS text cleaning for unspeakable characters.

This module provides character translation maps and functions to clean text
for text-to-speech (TTS) engines. It handles special Unicode characters,
punctuation, and context-sensitive replacements to improve speech synthesis
quality.

The module defines several translation maps with increasing levels of
punctuation verbosity:
- unspeakables: Basic character cleanup (spaces, hyphens)
- some: Basic punctuation (!, ?, @, etc.)
- most: Extended punctuation (includes quotes, backslash)
- all_punctuation: Complete punctuation (includes periods, commas)

Key features:
- Zero-width character removal
- Custom term pronunciations (e.g., 'json' -> 'JSon')
- Context-sensitive dash/hyphen handling
- Multi-character replacement support
- Regex-based substitution for complex patterns

Example:
    >>> from winston.unspeakables import substitute, most
    >>> text = "Here's code: x = y + 1"
    >>> substitute(text, most)
    'Here APOSTROPHE s code COLON x  EQUALS y  PLUS   1'

Typical usage:
    from winston.unspeakables import substitute, most
    clean_text = substitute(user_input, most)
    tts_engine.speak(clean_text)
"""

from __future__ import annotations
from typing import Callable
import re
import string

# Zero width non joiner
zwnj: str = '\u200c'  # &zwnj
"""Zero-width non-joiner Unicode character (U+200C)."""

# Custom translations for specific terms
custom: dict[str, str] = {
    'json': 'JSon',
}
"""Custom pronunciation translations for specific terms.

Maps lowercase terms to pronunciation-friendly versions.
Currently includes technical terms that need special pronunciation.
"""

# Basic unspeakable characters that should be cleaned
unspeakables: dict[str, str] = custom | {
    '\u2007': ' ',   # Replace Figure Space with a regular space
    '\u00a0': ' ',   # Replace non breaking space with a regular space
    '\u034f': ' ',   # Remove Combining Double Macron or replace with appropriate ASCII
    '\u00ad': ' ',   # Remove soft hyphens
    zwnj: ' ',       # Remove zero width non-joiners &zwnj
}
"""Basic unspeakable character replacements.

Includes Unicode whitespace variants, zero-width characters, and
soft hyphens that should be normalized or removed for TTS.
"""

# Some punctuation replacements
some: dict[str, str] = unspeakables | {
    '!': ' EXCLAIM ',
    '"': ' QUOTE ',
    '#': ' NUMBER ',
    '$': ' DOLLAR   ',
    '£': ' POUND ',
    '%': ' PERCENT   ',
    '&': ' AMPERSAND ',
    '(': ' LEFT PAREN  ',
    ')': ' RIGHT PAREN  ',
    '*': ' STAR ',
    '+': ' PLUS   ',
    # '-': ' DASH ', # this is handled by special logic with regex patterns
    '/': ' SLASH ',
    ';': ' SEMICOLON ',
    '<': ' LESS ',
    '=': ' EQUALS ',
    '>': ' GREATER ',
    '?': ' QUESTION   ',
    '@': ' AT ',
    '[': ' LEFT BRACKET ',
    ']': ' RIGHT BRACKET ',
    '^': ' CARET ',
    '_': ' UNDERLINE ',
    '{': ' LEFT BRACE ',
    '|': ' VERTICAL BAR ',
    '}': ' RIGHT BRACE ',
    '~': ' TILDE ',
    '…': ' ellipsis ',
}
"""Some punctuation replacements - basic set.

Includes common programming and markup punctuation. Does not include
dash/hyphen (handled by context-sensitive regex), apostrophes, or
sentence punctuation (periods, commas).
"""

# Most punctuation replacements
most: dict[str, str] = some | {
    "'": ' APOSTROPHE ',
    '`': ' GRAVE  ',
    '\\': ' BACKSLASH ',
}
"""Most punctuation replacements - extended set.

Adds quotes and backslash to the basic 'some' set. This is the
recommended default for general text-to-speech use.
"""

# All punctuation replacements
all_punctuation: dict[str, str] = most | {
    '.': ' DOT ',
    ',': ' COMMA ',
    ':': ' COLON ',
    '-': ' MINUS ',
    '¬': ' OPTIONAL HYPHEN ',
}
"""All punctuation replacements - complete set.

Includes sentence punctuation (periods, commas, colons) which are
normally left alone by TTS engines. Use this for maximum verbosity
when reading code or technical content character-by-character.
"""

# Helper function to check if string is all punctuation
is_punctuation: Callable[[str], bool] = lambda s: all(char in string.punctuation for char in s)
"""Check if string consists entirely of punctuation characters.

Args:
    s: String to check.

Returns:
    True if all characters are punctuation, False otherwise.

Example:
    >>> is_punctuation("!!!")
    True
    >>> is_punctuation("hello!")
    False
"""


def substitute(input_text: str, translation_map: dict[str, str] = most) -> str:
    """Translate unsupported characters in text for TTS engines.

    Performs two-pass translation:
    1. First pass: Apply translation_map using regex substitution
    2. Second pass: Context-sensitive dash/hyphen replacement

    The dash/hyphen logic distinguishes between:
    - Dashes (surrounded by spaces/quotes/line boundaries) -> 'dash'
    - Hyphens (between words, no spaces) -> space (for compound words)

    Args:
        input_text: The original text to be processed.
        translation_map: Dictionary mapping characters to replacement strings.
            Can use multi-character replacements. Defaults to 'most'.

    Returns:
        The translated text suitable for the TTS engine.

    Example:
        >>> substitute("x = y + 1", most)
        'x  EQUALS y  PLUS   1'
        >>> substitute("Hello-world", most)
        'Hello world'  # hyphen becomes space
        >>> substitute("Hello - world", most)
        'Hello dash world'  # dash surrounded by spaces

    Note:
        Dash character '-' is handled specially by regex patterns, not by
        translation_map. This allows context-sensitive replacement.
    """
    # Create a regex pattern to match all keys in the translation map
    pattern = re.compile("|".join(map(re.escape, translation_map.keys())))
    # Replace each match using the translation map
    text = pattern.sub(lambda match: translation_map[match.group(0)], input_text)

    # Second pass: Apply context-sensitive replacements for '-'
    # Dash pattern: Replace '-' with 'dash' when surrounded by spaces, quotes, or boundaries
    dash_pattern = re.compile(r'(?<=\s)-|-(?=\s)|(?<=^)-|-(?=$)|(?<=[\'"])-|-(?=[\'"])')  # Include ' and " in the context
    # Hyphen pattern: Replace remaining '-' (between words) with space
    hyphen_pattern = re.compile(r'(?<!\s)-|-(?!\s)')  # For zero-width space cases
    text = dash_pattern.sub('dash', text)  # Replace '-' with 'dash' in specified contexts
    text = hyphen_pattern.sub(' ', text)  # Replace remaining '-' with space

    return text


def clean_unspeakables(
    input_text: str,
    translation_map: dict[str, str] | None = None
) -> str:
    """Clean unspeakable characters using basic character replacement.

    Uses Python's str.translate() for fast single-character replacement.
    This is faster than substitute() but only handles single-character
    mappings and doesn't support context-sensitive rules.

    Args:
        input_text: The original text to be processed.
        translation_map: Dictionary mapping single characters to single
            character replacements. If None, uses single-char mappings
            from 'unspeakables' dict. Defaults to None.

    Returns:
        The cleaned text suitable for the TTS engine.

    Note:
        This function is simpler and faster than substitute() but
        less flexible. Use substitute() for multi-character replacements
        or context-sensitive rules.

    Example:
        >>> clean_unspeakables("Hello\\u00a0World")  # non-breaking space
        'Hello World'
    """
    # Create a translation table for basic character replacement
    if translation_map is None:
        # Use only single-character mappings from unspeakables
        translation_map = {k: v for k, v in unspeakables.items() if len(k) == 1 and len(v) == 1}
    translation_table = str.maketrans(translation_map)
    # Translate the text
    return input_text.translate(translation_table)


# Example usage
if __name__ == "__main__":
    # Test the functions
    test_text = "Here's an example with special chars"
    print("Original:", test_text)
    print("Substituted:", substitute(test_text))
    print("Cleaned:", clean_unspeakables(test_text))