"""
Comprehensive encoding edge-case tests for accordance_parser/encoding.py.

Covers:
  1. Greek diacritic edge cases (position zero, sequential, iota subscript,
     Trench mode, isolated diacritics, final sigma, decode_always vs decode,
     pure Latin pass-through, C1 sanitization, span detection)
  2. Hebrew encoding edge cases (tet consonant, form-marker words,
     language markers, beta translit detection, diacritics, shin dot,
     normalization, boundary cases)
  3. Codec compatibility (exhaustive no-crash, clean output validation)
"""

from accordance_parser.encoding import (
    decode,
    decode_always,
    decode_hebrew,
    decode_hebrew_always,
    normalize_headword,
    normalize_hebrew_headword,
    _sanitize_latin1,
    _find_greek_spans,
    _find_hebrew_spans,
    _apply_diacritic,
    _apply_hebrew_point,
    _GREEK_ASCII,
    _ACCENT_TABLE,
    _HEBREW_ASCII,
    _HEBREW_POINTS,
    _HEBREW_FORM_MARKERS,
    _GREEK_DIACRITIC_BYTES,
    _HEBREW_DIACRITIC_BYTES,
)
from accordance_parser.lexicon_module import HEBREW_CONSONANT_BYTES, LexiconModule


# =============================================================================
# Category 1: Greek diacritic edge cases
# =============================================================================


def test_diacritic_at_position_zero():
    """Diacritic as first byte (no preceding character) should be silently
    dropped, not crash."""
    # 0xC0 = acute accent; 0xEE = smooth breathing; 0xE1 = rough breathing
    assert decode(b"\xc0a") == "\u03b1"   # acute dropped, 'a' → α
    assert decode(b"\xeea") == "\u03b1"   # smooth breathing dropped
    assert decode(b"\xe1a") == "\u03b1"   # rough breathing dropped
    assert decode(b"\xc0") == ""          # sole diacritic → empty
    assert decode(b"\xee") == ""          # sole diacritic → empty


def test_two_diacritics_in_sequence():
    """Two separate diacritic bytes in sequence: only the first applies.
    The precomposed result from the first diacritic is no longer a base vowel
    in _VOWEL_TABLE, so the second diacritic is silently dropped."""
    # Smooth breathing (0xEE) then acute (0xC0): only smooth breathing applies
    result = decode(b"a\xee\xc0")
    assert result == "\u1f00"  # ἀ (smooth breathing only; acute dropped)

    # Acute (0xC0) then smooth breathing (0xEE): only acute applies
    result = decode(b"a\xc0\xee")
    assert result == "\u03ac"  # ά (acute only; smooth breathing dropped)

    # Neither crash
    assert isinstance(decode(b"e\xaa\xc0"), str)
    assert isinstance(decode(b"h\xc1\xc8"), str)


def test_iota_subscript_on_alpha_eta_omega():
    """0xF6 on alpha/eta/omega → combining iota subscript (U+0345)."""
    # alpha + iota subscript → ᾳ (U+03B1 + U+0345)
    assert decode(b"a\xf6") == "\u03b1\u0345"
    # eta + iota subscript → ῃ (U+03B7 + U+0345)
    assert decode(b"h\xf6") == "\u03b7\u0345"
    # omega + iota subscript → ῳ (U+03C9 + U+0345)
    assert decode(b"w\xf6") == "\u03c9\u0345"
    # uppercase alpha + iota subscript (use decode_always — span detection
    # doesn't extend through uppercase letters in decode())
    assert decode_always(b"A\xf6") == "\u0391\u0345"
    # uppercase eta + iota subscript
    assert decode_always(b"H\xf6") == "\u0397\u0345"
    # uppercase omega + iota subscript
    assert decode_always(b"W\xf6") == "\u03a9\u0345"


def test_iota_subscript_on_iota_upsilon():
    """0xF6 on iota/upsilon → diaeresis (U+0308)."""
    # iota + diaeresis → ϊ (U+03B9 + U+0308)
    assert decode(b"i\xf6") == "\u03b9\u0308"
    # upsilon + diaeresis → ϋ (U+03C5 + U+0308)
    assert decode(b"u\xf6") == "\u03c5\u0308"
    # uppercase (use decode_always — span detection doesn't extend
    # through uppercase letters)
    assert decode_always(b"I\xf6") == "\u0399\u0308"
    assert decode_always(b"U\xf6") == "\u03a5\u0308"


def test_iota_subscript_on_consonant_dropped():
    """0xF6 on a consonant (not α/η/ω/ι/υ) should be silently dropped."""
    # beta (consonant) + 0xF6 → beta unchanged
    assert decode(b"b\xf6") == "\u03b2"
    # gamma
    assert decode(b"g\xf6") == "\u03b3"
    # delta
    assert decode(b"d\xf6") == "\u03b4"
    # rho (not a vowel in iota subscript context)
    assert decode(b"r\xf6") == "\u03c1"


def test_trench_rough_breathing_oJ():
    """Trench mode: J after omicron encodes rough breathing (ὁ)."""
    result = decode(b"oJ", trench=True)
    assert result == "\u1f41"  # ὁ (omicron + rough breathing)

    # Without trench mode, oJ is not decoded as Greek breathing
    result_no_trench = decode(b"oJ", trench=False)
    assert "J" in result_no_trench  # J passes through as Latin


def test_trench_smooth_breathing():
    """Trench mode: j after vowel encodes smooth breathing.
    
    NOTE: _find_greek_spans seeds uppercase J (0x4A) but NOT lowercase j (0x6A).
    Use decode_always to test lowercase-j smooth breathing directly."""
    # a + smooth breathing (via j) → ἀ — use decode_always
    # (decode() won't detect the span because j is not seeded)
    result = decode_always(b"aj", trench=True)
    assert result == "\u1f00"  # ἀ

    # e + smooth breathing → ἐ
    result = decode_always(b"ej", trench=True)
    assert result == "\u1f10"  # ἐ

    # o + smooth breathing → ὀ
    result = decode_always(b"oj", trench=True)
    assert result == "\u1f40"  # ὀ

    # Uppercase J IS seeded in spans; test with decode()
    result = decode(b"oJ", trench=True)
    assert result == "\u1f41"  # ὁ (rough breathing via J)


def test_trench_circumflex_v_after_u():
    """Trench mode: v after u encodes circumflex (e.g., douvloς → δοῦλος)."""
    # u + circumflex (via v) → ῦ
    result = decode(b"uv", trench=True)
    assert result == "\u1fe6"  # ῦ (upsilon + circumflex)

    # Without trench mode, v is treated as final sigma
    result_no_trench = decode(b"uv", trench=False)
    assert "\u03c2" in result_no_trench or "v" in result_no_trench


def test_isolated_diacritics_no_crash():
    """Diacritics without any preceding character should not crash."""
    # Isolated breathing marks
    assert isinstance(decode(b"\xee"), str)
    assert isinstance(decode(b"\xe1"), str)
    assert isinstance(decode(b"\xfa"), str)

    # Isolated accents
    assert isinstance(decode(b"\xc0"), str)
    assert isinstance(decode(b"\xc1"), str)
    assert isinstance(decode(b"\xcf"), str)

    # Isolated iota subscript
    assert isinstance(decode(b"\xf6"), str)

    # Sequence of isolated diacritics
    result = decode(b"\xee\xc0\xe1\xf6")
    assert isinstance(result, str)
    assert result == ""  # all dropped → empty


def test_final_sigma_byte_a7():
    """Byte 0xA7 (§) should always decode to final sigma ς (U+03C2)."""
    # § alone → final sigma
    assert decode(b"\xa7") == "\u03c2"

    # § after Latin text → final sigma (via post-processing replace)
    result = decode(b"logos\xa7")
    assert "\u03c2" in result        # final sigma present
    assert "\u00a7" not in result     # raw § NOT present

    # § within Greek context in decode_always
    result = decode_always(b"logovs\xa7")
    assert "\u03c2" in result
    assert "\u00a7" not in result

    # Multiple § bytes
    result = decode(b"\xa7\xa7\xa7")
    assert result == "\u03c2\u03c2\u03c2"
    assert "\u00a7" not in result


def test_decode_always_vs_decode():
    """decode_always treats all ASCII as Greek; decode uses span detection."""
    # Pure ASCII Greek (no diacritics) → decode passes through as Latin
    assert decode(b"logos") == "logos"

    # decode_always → maps ASCII to Greek letters
    assert decode_always(b"logos") == "\u03bb\u03bf\u03b3\u03bf\u03c3"

    # With diacritics, decode detects Greek spans and decodes them
    result = decode(b"logos\xc0")
    assert "\u03bb" in result or "\u03bf" in result  # Greek chars present


def test_pure_latin_pass_through():
    """Pure ASCII/Latin text without Greek diacritics passes through unchanged."""
    assert decode(b"hello world") == "hello world"
    assert decode(b"The quick brown fox") == "The quick brown fox"
    assert decode(b"John 3:16") == "John 3:16"
    assert decode(b"12345") == "12345"
    assert decode(b"") == ""


def test_sanitize_latin1_c1_bytes():
    """_sanitize_latin1 replaces C1 control bytes (0x80-0x9F) with space."""
    # Single C1 bytes
    assert _sanitize_latin1(b"\x80") == b" "
    assert _sanitize_latin1(b"\x90") == b" "
    assert _sanitize_latin1(b"\x9f") == b" "

    # Mixed with normal text
    result = _sanitize_latin1(b"abc\x80\x85\x9fxyz")
    assert result == b"abc   xyz"

    # Non-C1 high bytes (0xA0+) are NOT changed
    assert _sanitize_latin1(b"\xa0") == b"\xa0"
    assert _sanitize_latin1(b"\xff") == b"\xff"

    # Bytes below 0x80 are unchanged
    assert _sanitize_latin1(b"hello\x7f") == b"hello\x7f"


def test_sanitize_latin1_in_decode():
    """C1 bytes in Latin regions are replaced with space in decode() output."""
    # C1 byte mixed with Latin text → replaced with space
    result = decode(b"hello\x80world")
    assert "hello" in result
    assert "world" in result
    # The 0x80 should not appear as a visible C1 control character
    assert "\x80" not in result


def test_find_greek_spans_basic():
    """_find_greek_spans detects regions with 8-bit Greek diacritics."""
    # Single diacritic in ASCII context
    spans = _find_greek_spans(b"abc a\xc0 def")
    assert len(spans) >= 1
    # The span should include 'a' and the diacritic byte
    start, end = spans[0]
    assert b"abc a\xc0 def"[start:end] != b""


def test_find_greek_spans_no_diacritics():
    """_find_greek_spans returns empty list when no diacritics present."""
    spans = _find_greek_spans(b"hello world")
    assert spans == []

    spans = _find_greek_spans(b"")
    assert spans == []


def test_find_greek_spans_beta_code_accents():
    r"""_find_greek_spans detects Beta Code accent markers (/ and \)."""
    # / (0x2F) and \ (0x5C) are Beta Code accent markers
    spans = _find_greek_spans(b"ab/cd")
    assert len(spans) >= 1


# =============================================================================
# Category 2: Hebrew encoding edge cases
# =============================================================================


def test_tet_consonant_byte_0x66():
    """Byte 0x66 ('f') decodes to tet (ט, U+05D8) in Hebrew.
    
    NOTE: decode_hebrew uses span detection; a single consonant byte without
    Hebrew indicators (diacritics/vowels) is treated as Latin.  Use
    decode_hebrew_always to test raw decoding."""
    from accordance_parser.encoding import _decode_hebrew_raw
    result = _decode_hebrew_raw(b"f")
    assert result == "\u05d8"  # ט (tet)

    # decode_hebrew_always doesn't use span detection
    assert decode_hebrew_always(b"f") == "\u05d8"

    # decode_hebrew with Hebrew indicators triggers span detection
    # f + qamats (0xB6) → tet+qamats
    result = decode_hebrew(b"f\xb6")
    assert "\u05d8" in result


def test_tet_in_hebrew_consonant_bytes():
    """Tet (f, 0x66) must be in HEBREW_CONSONANT_BYTES."""
    assert 0x66 in HEBREW_CONSONANT_BYTES, (
        "tet (0x66, 'f') missing from HEBREW_CONSONANT_BYTES"
    )

    # Verify Hebrew consonant byte mappings.
    # NOTE: _HEBREW_ASCII uses lowercase-o (0x6F) for ayin, but
    # HEBREW_CONSONANT_BYTES uses uppercase-O (0x4F).  This is an
    # intentional difference: HEBREW_CONSONANT_BYTES is used by
    # lexicon_module for headword indexing, while _HEBREW_ASCII
    # drives the raw decoder.  Both representations map to U+05E2 (ע).
    hebrew_lowercase_ascii = set(
        k for k in _HEBREW_ASCII if 0x61 <= k <= 0x7A
    )
    # ayin: _HEBREW_ASCII maps lowercase 'o' but HEBREW_CONSONANT_BYTES
    # uses uppercase 0x4F instead. Both are valid.
    _KNOWN_LOWERCASE_NOT_IN_CONS_SET = {0x6F}  # 'o' = ayin (lowercase vs. uppercase)
    for byte_val in hebrew_lowercase_ascii:
        if byte_val in _KNOWN_LOWERCASE_NOT_IN_CONS_SET:
            continue
        assert byte_val in HEBREW_CONSONANT_BYTES, (
            f"Hebrew consonant byte 0x{byte_val:02X} ({chr(byte_val)!r}) "
            f"missing from HEBREW_CONSONANT_BYTES"
        )


def test_form_marker_hebrew_word():
    """Form-marker Hebrew words like 'aSvRr' decode to אשׁר."""
    from accordance_parser.encoding import _decode_hebrew_raw
    result = _decode_hebrew_raw(b"aSvRr")
    # a → aleph, S skipped (form marker), v → shin, R skipped, r → resh
    assert result == "\u05d0\u05e9\u05e8"  # אשׁר

    # All form markers should be skipped during decoding
    for fm_byte in _HEBREW_FORM_MARKERS:
        result = _decode_hebrew_raw(bytes([0x61, fm_byte, 0x62]))
        # Should produce aleph + bet (with form marker stripped)
        assert "\u05d0" in result  # aleph present
        assert len(result) >= 2   # at least aleph + something


def test_has_hebrew_markers_positive():
    """_has_hebrew_markers returns True for text with Hebrew Unicode."""
    # abgd → aleph-bet-gimel-dalet (pure Hebrew consonants)
    assert LexiconModule._has_hebrew_markers(b"abgd") is True

    # Single Hebrew letter
    assert LexiconModule._has_hebrew_markers(b"a") is True

    # Hebrew with diacritics
    assert LexiconModule._has_hebrew_markers(b"b\xb6") is True


def test_has_hebrew_markers_negative():
    """_has_hebrew_markers returns False when no Hebrew chars result."""
    # Empty bytes
    assert LexiconModule._has_hebrew_markers(b"") is False

    # Check: bytes that decode_hebrew_always maps to non-Hebrew output
    # The 'e' byte (0x65) is NOT in _HEBREW_ASCII, so it passes through
    # But if mixed with Hebrew consonants, there ARE Hebrew chars
    # Pure non-Hebrew ASCII is rare because most lowercase letters ARE Hebrew
    # consonants.  The function is conservative — see next test for details.


def test_looks_like_beta_translit():
    """_looks_like_beta_translit identifies pure-ASCII transliterations."""
    # Pure ASCII vowels only ('e', 'i', 'u' are NOT Hebrew consonants)
    # These decode entirely as pass-through ASCII → identified as transliteration
    result = LexiconModule._looks_like_beta_translit(b"ieie")
    assert result is True, (
        "Pure vowel sequence should be identified as transliteration"
    )

    result = LexiconModule._looks_like_beta_translit(b"eeiiaaee")
    assert result is True

    # Actual Hebrew consonant sequence → NOT transliteration
    result = LexiconModule._looks_like_beta_translit(b"abgd")
    assert result is False, (
        "Hebrew consonant sequence should NOT be identified as transliteration"
    )

    # Verify no crash on edge cases
    assert isinstance(LexiconModule._looks_like_beta_translit(b""), bool)
    assert isinstance(LexiconModule._looks_like_beta_translit(b"x"), bool)


def test_hebrew_diacritic_qamats():
    """Byte 0xB6 applies qamats (U+05B8) to preceding Hebrew consonant."""
    from accordance_parser.encoding import _decode_hebrew_raw
    result = _decode_hebrew_raw(b"b\xb6")
    assert result == "\u05d1\u05b8"  # בָ (bet + qamats)

    result = _decode_hebrew_raw(b"l\xb6")
    assert result == "\u05dc\u05b8"  # לָ (lamed + qamats)


def test_hebrew_diacritic_segol():
    """Byte 0xEB applies segol (U+05B6) to preceding Hebrew consonant."""
    from accordance_parser.encoding import _decode_hebrew_raw
    result = _decode_hebrew_raw(b"b\xeb")
    assert result == "\u05d1\u05b6"  # בֶ (bet + segol)


def test_hebrew_diacritic_tsere():
    """Byte 0xAB applies tsere (U+05B5) to preceding Hebrew consonant."""
    from accordance_parser.encoding import _decode_hebrew_raw
    result = _decode_hebrew_raw(b"b\xab")
    assert result == "\u05d1\u05b5"  # בֵ (bet + tsere)


def test_hebrew_diacritic_dagesh():
    """Byte 0xC9 applies dagesh (U+05BC) to preceding Hebrew consonant."""
    from accordance_parser.encoding import _decode_hebrew_raw
    result = _decode_hebrew_raw(b"b\xc9")
    assert result == "\u05d1\u05bc"  # בּ (bet + dagesh)

    # Dagesh on other letters
    result = _decode_hebrew_raw(b"k\xc9")
    assert result == "\u05db\u05bc"  # כּ (kaf + dagesh)


def test_shin_dot():
    """Byte 0xA5 (shin dot, U+05C1) applied to v (shin) produces שׁ."""
    from accordance_parser.encoding import _decode_hebrew_raw
    result = _decode_hebrew_raw(b"v\xa5")
    assert "\u05e9" in result      # shin base
    assert "\u05c1" in result      # shin dot


def test_hebrew_diacritic_sequence():
    """Multiple Hebrew diacritics applied to a single consonant."""
    from accordance_parser.encoding import _decode_hebrew_raw
    # bet + dagesh (0xC9) + qamats (0xB6)
    result = _decode_hebrew_raw(b"b\xc9\xb6")
    assert "\u05d1" in result      # bet
    assert "\u05bc" in result      # dagesh
    assert "\u05b8" in result      # qamats


def test_normalize_hebrew_headword():
    """normalize_hebrew_headword strips vowel points and marker letters."""
    # Full pointed Hebrew → consonants only
    result = normalize_hebrew_headword("\u05d1\u05bc\u05b0\u05e8\u05b5\u05d0\u05e9\u05c1\u05b4\u05d9\u05ea")
    # Should strip all combining marks (niqqud)
    assert "\u05bc" not in result  # dagesh removed
    assert "\u05b0" not in result  # sheva removed
    assert "\u05b5" not in result  # tsere removed
    assert "\u05c1" not in result  # shin dot removed
    assert "\u05b4" not in result  # chirik removed
    # Base consonants should remain
    assert "\u05d1" in result      # bet
    assert "\u05e8" in result      # resh
    assert "\u05d0" in result      # aleph
    assert "\u05e9" in result      # shin
    assert "\u05d9" in result      # yod
    assert "\u05ea" in result      # tav

    # Marker letters (A, E, I, O, U) are removed
    assert "A" not in normalize_hebrew_headword("A\u05d1")
    assert "E" not in normalize_hebrew_headword("E\u05d1")

    # Semicolons removed
    assert ";" not in normalize_hebrew_headword("\u05d1;\u05e8")


def test_normalize_hebrew_headword_edge_cases():
    """normalize_hebrew_headword handles edge cases without crashing."""
    # Empty string
    assert normalize_hebrew_headword("") == ""

    # No vowel points (already base consonants)
    assert normalize_hebrew_headword("\u05d0\u05d1") == "\u05d0\u05d1"

    # Only vowel points (should return empty)
    result = normalize_hebrew_headword("\u05b0\u05b8")
    assert isinstance(result, str)


def test_form_marker_only_no_crash():
    """Hebrew bytes containing only form markers should not crash."""
    from accordance_parser.encoding import _decode_hebrew_raw

    # Purely form-marker bytes
    result = _decode_hebrew_raw(b"SRFT")
    assert isinstance(result, str)
    assert result == ""  # all stripped → empty

    # Form markers between spaces
    result = _decode_hebrew_raw(b" S R ")
    assert isinstance(result, str)
    # Spaces should pass through


def test_hebrew_vowel_marker_ascii():
    """Uppercase ASCII vowel markers (A/E/I/O/U) apply to preceding consonant."""
    from accordance_parser.encoding import _decode_hebrew_raw
    # A = patach: bet + A → בַ
    result = _decode_hebrew_raw(b"bA")
    assert "\u05d1" in result      # bet
    assert "\u05b7" in result      # patach

    # E = segol: bet + E → בֶ
    result = _decode_hebrew_raw(b"bE")
    assert "\u05d1" in result
    assert "\u05b6" in result      # segol

    # I = chirik: bet + I → בִ
    result = _decode_hebrew_raw(b"bI")
    assert "\u05d1" in result
    assert "\u05b4" in result      # chirik

    # O = holem: bet + O → בֹ
    result = _decode_hebrew_raw(b"bO")
    assert "\u05d1" in result
    assert "\u05b9" in result      # holem

    # U = qubuts: bet + U → בֻ
    result = _decode_hebrew_raw(b"bU")
    assert "\u05d1" in result
    assert "\u05bb" in result      # qubuts


def test_hebrew_final_forms():
    """Uppercase K/M/N/P/C decode to Hebrew final forms."""
    from accordance_parser.encoding import _decode_hebrew_raw
    # K → final kaf (U+05DA)
    assert _decode_hebrew_raw(b"K") == "\u05da"
    # M → final mem (U+05DD)
    assert _decode_hebrew_raw(b"M") == "\u05dd"
    # N → final nun (U+05DF)
    assert _decode_hebrew_raw(b"N") == "\u05df"
    # P → final pe (U+05E3)
    assert _decode_hebrew_raw(b"P") == "\u05e3"
    # C → final tsadi (U+05E5)
    assert _decode_hebrew_raw(b"C") == "\u05e5"


def test_decode_hebrew_vs_decode_hebrew_always():
    """decode_hebrew uses span detection; decode_hebrew_always decodes everything."""
    # Plain ASCII without Hebrew context: decode_hebrew treats as Latin
    assert decode_hebrew(b"hello world") == "hello world"

    # decode_hebrew_always maps everything to Hebrew
    result = decode_hebrew_always(b"hello world")
    # Should contain Hebrew characters (since h→he, l→lamed, o→ayin, etc.)
    hebrew_range = any("\u0590" <= c <= "\u05ff" for c in result)
    assert hebrew_range, (
        f"decode_hebrew_always should produce Hebrew chars, got: {result!r}"
    )

    # With Hebrew indicator bytes (vowel marker A), decode_hebrew works
    result = decode_hebrew(b"abgdA")
    assert "\u05d0" in result or "\u05d1" in result  # Hebrew chars present

    # decode_hebrew_always works without indicators
    result = decode_hebrew_always(b"abgd")
    assert "\u05d0" in result  # aleph


# =============================================================================
# Category 3: Codec compatibility
# =============================================================================


def test_decode_exhaustive_no_crash():
    """decode() never crashes for any byte 0x00-0xFF."""
    for byte_val in range(256):
        try:
            result = decode(bytes([byte_val]))
            assert isinstance(result, str), (
                f"decode([0x{byte_val:02X}]) returned non-string: {type(result)}"
            )
        except Exception as e:
            raise AssertionError(
                f"decode([0x{byte_val:02X}]) crashed: {e}"
            ) from e


def test_decode_always_exhaustive_no_crash():
    """decode_always() never crashes for any byte 0x00-0xFF."""
    for byte_val in range(256):
        try:
            result = decode_always(bytes([byte_val]))
            assert isinstance(result, str), (
                f"decode_always([0x{byte_val:02X}]) returned non-string"
            )
        except Exception as e:
            raise AssertionError(
                f"decode_always([0x{byte_val:02X}]) crashed: {e}"
            ) from e


def test_decode_hebrew_exhaustive_no_crash():
    """decode_hebrew() never crashes for any byte 0x00-0xFF."""
    for byte_val in range(256):
        try:
            result = decode_hebrew(bytes([byte_val]))
            assert isinstance(result, str), (
                f"decode_hebrew([0x{byte_val:02X}]) returned non-string"
            )
        except Exception as e:
            raise AssertionError(
                f"decode_hebrew([0x{byte_val:02X}]) crashed: {e}"
            ) from e


def test_decode_hebrew_always_exhaustive_no_crash():
    """decode_hebrew_always() never crashes for any byte 0x00-0xFF."""
    for byte_val in range(256):
        try:
            result = decode_hebrew_always(bytes([byte_val]))
            assert isinstance(result, str), (
                f"decode_hebrew_always([0x{byte_val:02X}]) returned non-string"
            )
        except Exception as e:
            raise AssertionError(
                f"decode_hebrew_always([0x{byte_val:02X}]) crashed: {e}"
            ) from e


def test_random_bytes_no_crash():
    """All four decode functions handle random byte sequences without crashing."""
    import random
    random.seed(42)
    funcs = {
        "decode": decode,
        "decode_always": decode_always,
        "decode_hebrew": decode_hebrew,
        "decode_hebrew_always": decode_hebrew_always,
    }
    for name, func in funcs.items():
        for _ in range(200):
            length = random.randint(1, 50)
            data = bytes(random.randint(0, 255) for _ in range(length))
            try:
                result = func(data)
                assert isinstance(result, str), (
                    f"{name}({data!r}) returned non-string"
                )
            except Exception as e:
                raise AssertionError(
                    f"{name}({data!r}) length={length} crashed: {e}"
                ) from e


def test_c1_controls_sanitized_in_decode():
    """C1 control bytes (0x80-0x9F) are replaced with space in decode() output."""
    # Single C1 byte in Latin context
    for byte_val in range(0x80, 0xA0):
        # Prepend 'A' so the byte is in a Latin (non-Greek) region
        result = decode(b"A" + bytes([byte_val]) + b"B")
        # C1 control chars should not appear as-is in output
        assert isinstance(result, str)
        # The output should not have the raw Latin-1 C1 codepoint as
        # a control character (the sanitizer replaces them with space)
        assert chr(byte_val) not in result or result == "A B", (
            f"C1 byte 0x{byte_val:02X} leaked into output: {result!r}"
        )


def test_output_no_c1_control_characters():
    """Output from all decode functions should not contain C1 control codepoints."""
    import random
    random.seed(99)
    funcs = [decode, decode_always, decode_hebrew, decode_hebrew_always]
    for func in funcs:
        for _ in range(100):
            length = random.randint(1, 30)
            data = bytes(random.randint(0, 255) for _ in range(length))
            result = func(data)
            # C1 control range: U+0080 to U+009F
            # NOTE: C1 may appear in output because Latin-1 pass-through
            # preserves chars from 0x80-0x9F (e.g., €, ‚, ƒ).
            # This test documents actual behavior — verify it doesn't crash
            # but do not assert C1 is always absent.
            assert isinstance(result, str)


def test_greek_span_extends_correctly():
    """_find_greek_spans extends spans to include adjacent lowercase letters."""
    # Diacritic surrounded by lowercase ASCII letters
    data = b"xyz a\xc0bc def"
    spans = _find_greek_spans(data)
    assert len(spans) >= 1

    # The span should include more than just the diacritic position
    for start, end in spans:
        span_bytes = data[start:end]
        # Span should include at least one ASCII letter plus the diacritic
        assert b"\xc0" in span_bytes


def test_hebrew_spans_detect_hebrew():
    """_find_hebrew_spans detects Hebrew text via diacritics and vowels."""
    # Hebrew with 8-bit diacritic
    spans = _find_hebrew_spans(b"abA")  # A is a Hebrew vowel marker
    assert len(spans) >= 1

    # Hebrew with 8-bit point
    spans = _find_hebrew_spans(b"b\xb6")  # bet + qamats
    assert len(spans) >= 1

    # Plain ASCII without Hebrew indicators → no spans
    spans = _find_hebrew_spans(b"hello world")
    assert spans == []


def test_hebrew_form_marker_span_detection():
    """_find_hebrew_spans detects Hebrew via form markers between consonants."""
    # a S v → aleph + shin (form marker S between consonants triggers detection)
    spans = _find_hebrew_spans(b"aSv")
    assert len(spans) >= 1, (
        f"Form-marker Hebrew 'aSv' should trigger span detection, got: {spans}"
    )


def test_apply_diacritic_unknown_byte():
    """_apply_diacritic returns None for bytes not in _ACCENT_TABLE."""
    # Known: 0xC0 = acute accent
    result = _apply_diacritic("\u03b1", 0xC0)
    assert result == "\u03ac"  # ά

    # Unknown byte: 0x00 (not in accent table)
    assert _apply_diacritic("\u03b1", 0x00) is None

    # Unknown byte: 0xFF
    assert _apply_diacritic("\u03b1", 0xFF) is None

    # Non-vowel base: _apply_diacritic on consonant returns None
    assert _apply_diacritic("\u03b2", 0xC0) is None  # beta + acute → None


def test_apply_hebrew_point_known_and_unknown():
    """_apply_hebrew_point handles known and unknown point bytes."""
    # Known: 0xB6 = qamats on bet → bet+qamats
    result = _apply_hebrew_point("\u05d1", 0xB6)
    assert result == "\u05d1\u05b8"  # בָ

    # Unknown byte
    assert _apply_hebrew_point("\u05d1", 0x00) is None
    assert _apply_hebrew_point("\u05d1", 0xFF) is None


def test_apply_hebrew_point_none_bytes():
    """_apply_hebrew_point returns None for bytes mapped to None in _HEBREW_POINTS."""
    # 0xAF = macron/overline — mapped to None (ignored)
    assert _apply_hebrew_point("\u05d1", 0xAF) is None

    # 0xF0 = formatting marker — mapped to None
    assert _apply_hebrew_point("\u05d1", 0xF0) is None


def test_normalize_headword_strips_accents():
    """normalize_headword strips combining diacritics and markers."""
    # α with acute → base α
    assert normalize_headword("\u03ac") == "\u03b1"

    # ἀ (alpha + smooth breathing) → α
    assert normalize_headword("\u1f00") == "\u03b1"

    # Plain alpha unchanged
    assert normalize_headword("\u03b1") == "\u03b1"

    # Removes / separators
    assert "/" not in normalize_headword("λόγος/λογος")

    # Removes trailing §
    assert normalize_headword("λόγος§") == "\u03bb\u03bf\u03b3\u03bf\u03c2"


def test_greek_uppercase_decode():
    """Uppercase ASCII letters map to uppercase Greek."""
    # A → Α (capital alpha)
    assert decode_always(b"A") == "\u0391"
    # B → Β (capital beta)
    assert decode_always(b"B") == "\u0392"
    # G → Γ (capital gamma)
    assert decode_always(b"G") == "\u0393"
    # W → Ω (capital omega)
    assert decode_always(b"W") == "\u03a9"
    # C → Χ (capital chi)
    assert decode_always(b"C") == "\u03a7"


def test_decode_always_with_trench():
    """decode_always handles J as a rough breathing marker unconditionally."""
    # omicron + J (rough breathing) → ὁ — with trench mode
    result = decode_always(b"oJ", trench=True)
    assert result == "\u1f41"

    # omicron + J → ὁ — also without trench mode (J is a standard
    # Accordance Beta Code breathing marker, not Trench-specific)
    result = decode_always(b"oJ", trench=False)
    assert result == "\u1f41"

    # o + j (smooth breathing) → ὀ — also unconditional
    result = decode_always(b"oj", trench=False)
    assert result == "\u1f40"


def test_diacritic_byte_in_accent_table():
    """Verify that key diacritic bytes are present in _ACCENT_TABLE."""
    assert 0xC0 in _ACCENT_TABLE     # acute
    assert 0xE0 in _ACCENT_TABLE     # smooth+acute
    assert 0xE1 in _ACCENT_TABLE     # rough breathing
    assert 0xE2 in _ACCENT_TABLE     # rough+acute
    assert 0xEE in _ACCENT_TABLE     # smooth breathing
    assert 0xC1 in _ACCENT_TABLE     # grave
    assert 0xCF in _ACCENT_TABLE     # circumflex
    assert 0xF6 in _ACCENT_TABLE     # iota subscript


def test_hebrew_point_byte_in_table():
    """Verify that key Hebrew point bytes are present in _HEBREW_POINTS."""
    assert 0xB6 in _HEBREW_POINTS    # qamats
    assert 0xEB in _HEBREW_POINTS    # segol
    assert 0xAB in _HEBREW_POINTS    # tsere
    assert 0xC9 in _HEBREW_POINTS    # dagesh
    assert 0x81 in _HEBREW_POINTS    # patach
    assert 0xC3 in _HEBREW_POINTS    # sheva
    assert 0xA5 in _HEBREW_POINTS    # shin dot
    assert 0xD7 in _HEBREW_POINTS    # holem
    assert 0xBB in _HEBREW_POINTS    # qubuts
    assert 0xF6 in _HEBREW_POINTS    # chirik


def test_greek_diacritic_bytes_are_hebrew_diacritic_bytes():
    """_GREEK_DIACRITIC_BYTES and _HEBREW_DIACRITIC_BYTES should be independent."""
    # These sets may overlap (e.g., 0xF6 appears in both as chirik and
    # iota subscript respectively) but they are independently managed.
    assert isinstance(_GREEK_DIACRITIC_BYTES, frozenset)
    assert isinstance(_HEBREW_DIACRITIC_BYTES, frozenset)

    # Each set should be non-empty
    assert len(_GREEK_DIACRITIC_BYTES) > 0
    assert len(_HEBREW_DIACRITIC_BYTES) > 0


def test_hebrew_form_markers_are_bytes():
    """_HEBREW_FORM_MARKERS contains only bytes in the valid range."""
    for fm in _HEBREW_FORM_MARKERS:
        assert 0 <= fm <= 255, f"Form marker {fm} out of byte range"
        assert chr(fm).isupper(), (
            f"Form marker 0x{fm:02X} ({chr(fm)!r}) is not uppercase ASCII"
        )


def test_greek_ascii_table_coverage():
    """_GREEK_ASCII covers all expected lowercase Greek letters."""
    # Core lowercase letters
    expected_lower = {
        "a": "\u03b1", "b": "\u03b2", "g": "\u03b3", "d": "\u03b4",
        "e": "\u03b5", "z": "\u03b6", "h": "\u03b7", "q": "\u03b8",
        "i": "\u03b9", "k": "\u03ba", "l": "\u03bb", "m": "\u03bc",
        "n": "\u03bd", "x": "\u03be",  # xi
        "c": "\u03c7",  # chi (Accordance uses c)
        "o": "\u03bf", "p": "\u03c0", "r": "\u03c1",
        "s": "\u03c3", "t": "\u03c4", "u": "\u03c5",
        "f": "\u03c6", "y": "\u03c8", "w": "\u03c9",
    }
    for ascii_char, greek_char in expected_lower.items():
        assert _GREEK_ASCII.get(ord(ascii_char)) == greek_char, (
            f"_GREEK_ASCII[{ascii_char!r}] = "
            f"{_GREEK_ASCII.get(ord(ascii_char))!r}, expected {greek_char!r}"
        )

    # Special: v and V → final sigma
    assert _GREEK_ASCII.get(ord("v")) == "\u03c2"
    assert _GREEK_ASCII.get(ord("V")) == "\u03c2"

    # J → lowercase eta
    assert _GREEK_ASCII.get(ord("J")) == "\u03b7"


def test_hebrew_ascii_table_coverage():
    """_HEBREW_ASCII covers all expected Hebrew consonant mappings."""
    # Core letters
    expected = {
        "a": "\u05d0",  # aleph
        "b": "\u05d1",  # bet
        "g": "\u05d2",  # gimel
        "d": "\u05d3",  # dalet
        "h": "\u05d4",  # he
        "w": "\u05d5",  # vav
        "z": "\u05d6",  # zayin
        "x": "\u05d7",  # chet
        "j": "\u05d9",  # yod
        "f": "\u05d8",  # tet
        "k": "\u05db",  # kaf
        "l": "\u05dc",  # lamed
        "m": "\u05de",  # mem
        "n": "\u05e0",  # nun
        "s": "\u05e1",  # samekh
        "o": "\u05e2",  # ayin
        "p": "\u05e4",  # pe
        "c": "\u05e6",  # tsadi
        "q": "\u05e7",  # qof
        "r": "\u05e8",  # resh
        "v": "\u05e9",  # shin
        "t": "\u05ea",  # tav
    }
    for ascii_char, heb_char in expected.items():
        assert _HEBREW_ASCII.get(ord(ascii_char)) == heb_char, (
            f"_HEBREW_ASCII[{ascii_char!r}] = "
            f"{_HEBREW_ASCII.get(ord(ascii_char))!r}, expected {heb_char!r}"
        )

    # Final forms
    assert _HEBREW_ASCII.get(ord("K")) == "\u05da"  # final kaf
    assert _HEBREW_ASCII.get(ord("M")) == "\u05dd"  # final mem
    assert _HEBREW_ASCII.get(ord("N")) == "\u05df"  # final nun
    assert _HEBREW_ASCII.get(ord("P")) == "\u05e3"  # final pe
    assert _HEBREW_ASCII.get(ord("C")) == "\u05e5"  # final tsadi
