"""String utilities — slug generation, normalization, truncation.

No Django or app-layer imports — pure Python only.
"""
from __future__ import annotations

import re
import unicodedata

__all__ = [
    "slugify",
    "normalize_fa",
    "normalize_ar",
    "truncate",
    "to_snake_case",
    "to_camel_case",
    "strip_html",
    "initials",
]

# ---------------------------------------------------------------------------
# Character normalization tables
# ---------------------------------------------------------------------------

# Arabic → Persian equivalents (Alef variants, Kaf, Ya)
_AR_TO_FA: dict[str, str] = {
    "\u0643": "\u06a9",  # Arabic Kaf → Persian Kaf
    "\u064a": "\u06cc",  # Arabic Ya → Persian Ya
    "\u0649": "\u06cc",  # Alef Maksura → Persian Ya
    "\u0622": "\u0627",  # Alef with Madda → Alef
    "\u0623": "\u0627",  # Alef with Hamza above → Alef
    "\u0625": "\u0627",  # Alef with Hamza below → Alef
    "\u0671": "\u0627",  # Alef Wasla → Alef
    "\u0624": "\u0648",  # Waw with Hamza → Waw
    "\u06c0": "\u0647",  # Heh with Ye above → Heh
    "\u06be": "\u0647",  # Heh Doachashmee → Heh
}

_TRANSLATION_TABLE = str.maketrans(_AR_TO_FA)

# Arabic-Indic digits → ASCII
_AR_INDIC = str.maketrans("٠١٢٣٤٥٦٧٨٩", "0123456789")
# Extended Arabic-Indic (Farsi digits)
_FA_DIGITS = str.maketrans("۰۱۲۳۴۵۶۷۸۹", "0123456789")


def normalize_fa(text: str) -> str:
    """Normalize Persian text: fix Arabic variants, remove diacritics.

    - Replaces Arabic Kaf/Ya/Alef variants with standard Persian forms
    - Strips Arabic diacritics (harakat/tashkeel)
    - Normalizes Arabic-Indic digits to ASCII
    """
    if not text:
        return text
    # Translate character variants
    text = text.translate(_TRANSLATION_TABLE)
    # Convert Arabic-Indic and Farsi digits to ASCII
    text = text.translate(_AR_INDIC).translate(_FA_DIGITS)
    # Strip diacritics (U+064B–U+065F, U+0670)
    text = re.sub(r"[\u064b-\u065f\u0670]", "", text)
    return text


def normalize_ar(text: str) -> str:
    """Normalize Arabic text: standardize Alef variants, strip diacritics.

    Similar to :func:`normalize_fa` but keeps Arabic Kaf/Ya.
    """
    if not text:
        return text
    # Only Alef variants
    ar_alef = {
        "\u0622": "\u0627",
        "\u0623": "\u0627",
        "\u0625": "\u0627",
        "\u0671": "\u0627",
        "\u0624": "\u0648",
    }
    text = text.translate(str.maketrans(ar_alef))
    text = text.translate(_AR_INDIC)
    text = re.sub(r"[\u064b-\u065f\u0670]", "", text)
    return text


# ---------------------------------------------------------------------------
# Slug generation
# ---------------------------------------------------------------------------

def slugify(text: str, separator: str = "-", allow_unicode: bool = False) -> str:
    """Generate a URL-safe slug from *text*.

    Handles Latin, Persian, and Arabic input.
    When *allow_unicode* is ``True``, non-ASCII alphanumerics are preserved
    (useful for RTL slugs).  Otherwise only ASCII alphanumerics are kept.

    Examples::

        slugify("Hello World")           → "hello-world"
        slugify("سلام دنیا", allow_unicode=True)  → "سلام-دنیا"
    """
    text = normalize_fa(text).strip()
    if allow_unicode:
        text = unicodedata.normalize("NFKC", text)
        text = re.sub(r"[^\w\s-]", "", text, flags=re.UNICODE)
    else:
        text = unicodedata.normalize("NFKD", text)
        text = text.encode("ascii", "ignore").decode("ascii")
        text = re.sub(r"[^\w\s-]", "", text)
    text = re.sub(r"[\s_-]+", separator, text)
    text = text.strip(separator)
    return text.lower()


# ---------------------------------------------------------------------------
# Truncation
# ---------------------------------------------------------------------------

def truncate(text: str, max_len: int, suffix: str = "…") -> str:
    """Truncate *text* to *max_len* characters, appending *suffix* if cut.

    Truncates at a word boundary when possible.
    """
    if not text or len(text) <= max_len:
        return text
    cutoff = max_len - len(suffix)
    if cutoff <= 0:
        return suffix[:max_len]
    # Try to cut at a word boundary
    boundary = text.rfind(" ", 0, cutoff)
    cut = boundary if boundary > 0 else cutoff
    return text[:cut] + suffix


# ---------------------------------------------------------------------------
# Case conversion
# ---------------------------------------------------------------------------

def to_snake_case(text: str) -> str:
    """Convert ``CamelCase`` or ``mixedCase`` to ``snake_case``."""
    text = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", text)
    text = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", text)
    return text.lower()


def to_camel_case(text: str, upper_first: bool = False) -> str:
    """Convert ``snake_case`` to ``camelCase`` or ``PascalCase``."""
    parts = text.split("_")
    if not parts:
        return text
    if upper_first:
        return "".join(p.capitalize() for p in parts)
    return parts[0] + "".join(p.capitalize() for p in parts[1:])


# ---------------------------------------------------------------------------
# Misc
# ---------------------------------------------------------------------------

_HTML_TAG_RE = re.compile(r"<[^>]+>")


def strip_html(text: str) -> str:
    """Remove HTML tags from *text*."""
    return _HTML_TAG_RE.sub("", text)


def initials(full_name: str, *, max_chars: int = 2) -> str:
    """Return uppercase initials for a full name.

    Examples::

        initials("Ali Mohammadi")   → "AM"
        initials("علی محمدی")       → "عم"
    """
    parts = full_name.strip().split()
    chars = [p[0] for p in parts if p]
    return "".join(chars[:max_chars]).upper()
