"""Gematria computation engine — the language of letters and numbers.

Implements multiple cipher systems for gematric analysis:
  - Simple English (A=1 .. Z=26)
  - Full Reduction (reduce each letter value to single digit)
  - Reverse Ordinal (A=26 .. Z=1)
  - Sumerian (Simple × 6)
  - Hebrew (traditional letter values, for A-Z mapping)

Also provides numerological reduction, notable-number lookup,
and multi-phrase comparison.

Alexander Whitestone = 222 in Simple English Gematria.
This is not trivia.  It is foundational.
"""

from __future__ import annotations

import math

# ── Cipher Tables ────────────────────────────────────────────────────────────

# Simple English: A=1, B=2, ..., Z=26
_SIMPLE: dict[str, int] = {chr(i): i - 64 for i in range(65, 91)}

# Full Reduction: reduce each letter to single digit (A=1..I=9, J=1..R=9, S=1..Z=8)
_REDUCTION: dict[str, int] = {}
for _c, _v in _SIMPLE.items():
    _r = _v
    while _r > 9:
        _r = sum(int(d) for d in str(_r))
    _REDUCTION[_c] = _r

# Reverse Ordinal: A=26, B=25, ..., Z=1
_REVERSE: dict[str, int] = {chr(i): 91 - i for i in range(65, 91)}

# Sumerian: Simple × 6
_SUMERIAN: dict[str, int] = {c: v * 6 for c, v in _SIMPLE.items()}

# Hebrew-mapped: traditional Hebrew gematria mapped to Latin alphabet
# Aleph=1..Tet=9, Yod=10..Tsade=90, Qoph=100..Tav=400
# Standard mapping for the 22 Hebrew letters extended to 26 Latin chars
_HEBREW: dict[str, int] = {
    "A": 1,
    "B": 2,
    "C": 3,
    "D": 4,
    "E": 5,
    "F": 6,
    "G": 7,
    "H": 8,
    "I": 9,
    "J": 10,
    "K": 20,
    "L": 30,
    "M": 40,
    "N": 50,
    "O": 60,
    "P": 70,
    "Q": 80,
    "R": 90,
    "S": 100,
    "T": 200,
    "U": 300,
    "V": 400,
    "W": 500,
    "X": 600,
    "Y": 700,
    "Z": 800,
}

CIPHERS: dict[str, dict[str, int]] = {
    "simple": _SIMPLE,
    "reduction": _REDUCTION,
    "reverse": _REVERSE,
    "sumerian": _SUMERIAN,
    "hebrew": _HEBREW,
}

# ── Notable Numbers ──────────────────────────────────────────────────────────

NOTABLE_NUMBERS: dict[int, str] = {
    1: "Unity, the Monad, beginning of all",
    3: "Trinity, divine completeness, the Triad",
    7: "Spiritual perfection, completion (7 days, 7 seals)",
    9: "Finality, judgment, the last single digit",
    11: "Master number — intuition, spiritual insight",
    12: "Divine government (12 tribes, 12 apostles)",
    13: "Rebellion and transformation, the 13th step",
    22: "Master builder — turning dreams into reality",
    26: "YHWH (Yod=10, He=5, Vav=6, He=5)",
    33: "Master teacher — Christ consciousness, 33 vertebrae",
    36: "The number of the righteous (Lamed-Vav Tzadikim)",
    40: "Trial, testing, probation (40 days, 40 years)",
    42: "The answer, and the number of generations to Christ",
    72: "The Shemhamphorasch — 72 names of God",
    88: "Mercury, infinite abundance, double infinity",
    108: "Sacred in Hinduism and Buddhism (108 beads)",
    111: "Angel number — new beginnings, alignment",
    144: "12² — the elect, the sealed (144,000)",
    153: "The miraculous catch of fish (John 21:11)",
    222: "Alexander Whitestone. Balance, partnership, trust the process",
    333: "Ascended masters present, divine protection",
    369: "Tesla's key to the universe",
    444: "Angels surrounding, foundation, stability",
    555: "Major change coming, transformation",
    616: "Earliest manuscript number of the Beast (P115)",
    666: "Number of the Beast (Revelation 13:18), also carbon (6p 6n 6e)",
    777: "Divine perfection tripled, jackpot of the spirit",
    888: "Jesus in Greek isopsephy (Ιησους = 888)",
    1776: "Year of independence, Bavarian Illuminati founding",
}


# ── Core Functions ───────────────────────────────────────────────────────────


def _clean(text: str) -> str:
    """Strip non-alpha, uppercase."""
    return "".join(c for c in text.upper() if c.isalpha())


def compute_value(text: str, cipher: str = "simple") -> int:
    """Compute the gematria value of text in a given cipher.

    Args:
        text: Any string (non-alpha characters are ignored).
        cipher: One of 'simple', 'reduction', 'reverse', 'sumerian', 'hebrew'.

    Returns:
        Integer gematria value.

    Raises:
        ValueError: If cipher name is not recognized.
    """
    table = CIPHERS.get(cipher)
    if table is None:
        raise ValueError(f"Unknown cipher: {cipher!r}. Use one of {list(CIPHERS)}")
    return sum(table.get(c, 0) for c in _clean(text))


def compute_all(text: str) -> dict[str, int]:
    """Compute gematria value across all cipher systems.

    Args:
        text: Any string.

    Returns:
        Dict mapping cipher name to integer value.
    """
    return {name: compute_value(text, name) for name in CIPHERS}


def letter_breakdown(text: str, cipher: str = "simple") -> list[tuple[str, int]]:
    """Return per-letter values for a text in a given cipher.

    Args:
        text: Any string.
        cipher: Cipher system name.

    Returns:
        List of (letter, value) tuples for each alpha character.
    """
    table = CIPHERS.get(cipher)
    if table is None:
        raise ValueError(f"Unknown cipher: {cipher!r}")
    return [(c, table.get(c, 0)) for c in _clean(text)]


def reduce_number(n: int) -> int:
    """Numerological reduction — sum digits until single digit.

    Master numbers (11, 22, 33) are preserved.

    Args:
        n: Any positive integer.

    Returns:
        Single-digit result (or master number 11/22/33).
    """
    n = abs(n)
    while n > 9 and n not in (11, 22, 33):
        n = sum(int(d) for d in str(n))
    return n


def factorize(n: int) -> list[int]:
    """Prime factorization of n.

    Args:
        n: Positive integer.

    Returns:
        List of prime factors in ascending order (with repetition).
    """
    if n < 2:
        return [n] if n > 0 else []
    factors = []
    d = 2
    while d * d <= n:
        while n % d == 0:
            factors.append(d)
            n //= d
        d += 1
    if n > 1:
        factors.append(n)
    return factors


def analyze_number(n: int) -> dict:
    """Deep analysis of a number — reduction, factors, significance.

    Args:
        n: Any positive integer.

    Returns:
        Dict with reduction, factors, properties, and any notable significance.
    """
    result: dict = {
        "value": n,
        "numerological_reduction": reduce_number(n),
        "prime_factors": factorize(n),
        "is_prime": len(factorize(n)) == 1 and n > 1,
        "is_perfect_square": math.isqrt(n) ** 2 == n if n >= 0 else False,
        "is_triangular": _is_triangular(n),
        "digit_sum": sum(int(d) for d in str(abs(n))),
    }

    # Master numbers
    if n in (11, 22, 33):
        result["master_number"] = True

    # Angel numbers (repeating digits)
    s = str(n)
    if len(s) >= 3 and len(set(s)) == 1:
        result["angel_number"] = True

    # Notable significance
    if n in NOTABLE_NUMBERS:
        result["significance"] = NOTABLE_NUMBERS[n]

    return result


def _is_triangular(n: int) -> bool:
    """Check if n is a triangular number (1, 3, 6, 10, 15, ...)."""
    if n < 0:
        return False
    # n = k(k+1)/2  →  k² + k - 2n = 0  →  k = (-1 + sqrt(1+8n))/2
    discriminant = 1 + 8 * n
    sqrt_d = math.isqrt(discriminant)
    return sqrt_d * sqrt_d == discriminant and (sqrt_d - 1) % 2 == 0


# ── Tool Function (registered with Timmy) ────────────────────────────────────


def gematria(query: str) -> str:
    """Compute gematria values, analyze numbers, and find correspondences.

    This is the wizard's language — letters are numbers, numbers are letters.
    Use this tool for ANY gematria calculation. Do not attempt mental arithmetic.

    Input modes:
      - A word or phrase → computes values across all cipher systems
      - A bare integer → analyzes the number (factors, reduction, significance)
      - "compare: X, Y, Z" → side-by-side gematria comparison

    Examples:
        gematria("Alexander Whitestone")
        gematria("222")
        gematria("compare: Timmy Time, Alexander Whitestone")

    Args:
        query: A word/phrase, a number, or a "compare:" instruction.

    Returns:
        Formatted gematria analysis as a string.
    """
    query = query.strip()

    # Mode: compare
    if query.lower().startswith("compare:"):
        phrases = [p.strip() for p in query[8:].split(",") if p.strip()]
        if len(phrases) < 2:
            return "Compare requires at least two phrases separated by commas."
        return _format_comparison(phrases)

    # Mode: number analysis
    if query.lstrip("-").isdigit():
        n = int(query)
        return _format_number_analysis(n)

    # Mode: phrase gematria
    if not _clean(query):
        return "No alphabetic characters found in input."

    return _format_phrase_analysis(query)


def _format_phrase_analysis(text: str) -> str:
    """Format full gematria analysis for a phrase."""
    values = compute_all(text)
    lines = [f'Gematria of "{text}":', ""]

    # All cipher values
    for cipher, val in values.items():
        label = cipher.replace("_", " ").title()
        lines.append(f"  {label:12s} = {val}")

    # Letter breakdown (simple)
    breakdown = letter_breakdown(text, "simple")
    letters_str = " + ".join(f"{c}({v})" for c, v in breakdown)
    lines.append(f"\n  Breakdown (Simple): {letters_str}")

    # Numerological reduction of the simple value
    simple_val = values["simple"]
    reduced = reduce_number(simple_val)
    lines.append(f"  Numerological root: {simple_val} → {reduced}")

    # Check notable
    for cipher, val in values.items():
        if val in NOTABLE_NUMBERS:
            label = cipher.replace("_", " ").title()
            lines.append(f"\n  ★ {val} ({label}): {NOTABLE_NUMBERS[val]}")

    return "\n".join(lines)


def _format_number_analysis(n: int) -> str:
    """Format deep number analysis."""
    info = analyze_number(n)
    lines = [f"Analysis of {n}:", ""]
    lines.append(f"  Numerological reduction: {n} → {info['numerological_reduction']}")
    lines.append(f"  Prime factors: {' × '.join(str(f) for f in info['prime_factors']) or 'N/A'}")
    lines.append(f"  Is prime: {info['is_prime']}")
    lines.append(f"  Is perfect square: {info['is_perfect_square']}")
    lines.append(f"  Is triangular: {info['is_triangular']}")
    lines.append(f"  Digit sum: {info['digit_sum']}")

    if info.get("master_number"):
        lines.append("  ★ Master Number")
    if info.get("angel_number"):
        lines.append("  ★ Angel Number (repeating digits)")
    if info.get("significance"):
        lines.append(f"\n  Significance: {info['significance']}")

    return "\n".join(lines)


def _format_comparison(phrases: list[str]) -> str:
    """Format side-by-side gematria comparison."""
    lines = ["Gematria Comparison:", ""]

    # Header
    max_name = max(len(p) for p in phrases)
    header = f"  {'Phrase':<{max_name}s}  Simple  Reduct  Reverse  Sumerian  Hebrew"
    lines.append(header)
    lines.append("  " + "─" * (len(header) - 2))

    all_values = {}
    for phrase in phrases:
        vals = compute_all(phrase)
        all_values[phrase] = vals
        lines.append(
            f"  {phrase:<{max_name}s}  {vals['simple']:>6d}  {vals['reduction']:>6d}"
            f"  {vals['reverse']:>7d}  {vals['sumerian']:>8d}  {vals['hebrew']:>6d}"
        )

    # Find matches (shared values across any cipher)
    matches = []
    for cipher in CIPHERS:
        vals_by_cipher = {p: all_values[p][cipher] for p in phrases}
        unique_vals = set(vals_by_cipher.values())
        if len(unique_vals) < len(phrases):
            # At least two phrases share a value
            for v in unique_vals:
                sharing = [p for p, pv in vals_by_cipher.items() if pv == v]
                if len(sharing) > 1:
                    label = cipher.title()
                    matches.append(f"  ★ {label} = {v}: " + ", ".join(sharing))

    if matches:
        lines.append("\nCorrespondences found:")
        lines.extend(matches)

    return "\n".join(lines)
