# Copyright 2026, Jiri Eischmann <jiri@eischmann.cz> and the meshy contributors
# SPDX-License-Identifier: GPL-3.0-or-later

"""SMAZ decompression for interoperability with meshcore-open clients.

Meshcore-open can compress short text messages using the SMAZ algorithm
(dictionary-based compression for short ASCII strings). Compressed messages
are sent with an 's:' prefix followed by base64-encoded SMAZ data.
"""

import base64

_VERBATIM_SINGLE = 254
_VERBATIM_RUN = 255

_CODEBOOK = [
    " ",
    "the",
    "e",
    "t",
    "a",
    "of",
    "o",
    "and",
    "i",
    "n",
    "s",
    "e ",
    "r",
    " th",
    " t",
    "in",
    "he",
    "th",
    "h",
    "he ",
    "to",
    "\r\n",
    "l",
    "s ",
    "d",
    " a",
    "an",
    "er",
    "c",
    " o",
    "d ",
    "on",
    " of",
    "re",
    "of ",
    "t ",
    ", ",
    "is",
    "u",
    "at",
    "   ",
    "n ",
    "or",
    "which",
    "f",
    "m",
    "as",
    "it",
    "that",
    "\n",
    "was",
    "en",
    "  ",
    " w",
    "es",
    " an",
    " i",
    "\r",
    "f ",
    "g",
    "p",
    "nd",
    " s",
    "nd ",
    "ed ",
    "w",
    "ed",
    "http://",
    "for",
    "te",
    "ing",
    "y ",
    "The",
    " c",
    "ti",
    "r ",
    "his",
    "st",
    " in",
    "ar",
    "nt",
    ",",
    " to",
    "y",
    "ng",
    " h",
    "with",
    "le",
    "al",
    "to ",
    "b",
    "ou",
    "be",
    "were",
    " b",
    "se",
    "o ",
    "ent",
    "ha",
    "ng ",
    "their",
    '"',
    "hi",
    "from",
    " f",
    "in ",
    "de",
    "ion",
    "me",
    "v",
    ".",
    "ve",
    "all",
    "re ",
    "ri",
    "ro",
    "is ",
    "co",
    "f t",
    "are",
    "ea",
    ". ",
    "her",
    " m",
    "er ",
    " p",
    "es ",
    "by",
    "they",
    "di",
    "ra",
    "ic",
    "not",
    "s, ",
    "d t",
    "at ",
    "ce",
    "la",
    "h ",
    "ne",
    "as ",
    "tio",
    "on ",
    "n t",
    "io",
    "we",
    " a ",
    "om",
    ", a",
    "s o",
    "ur",
    "li",
    "ll",
    "ch",
    "had",
    "this",
    "e t",
    "g ",
    "e\r\n",
    " wh",
    "ere",
    " co",
    "e o",
    "a ",
    "us",
    " d",
    "ss",
    "\n\r\n",
    "\r\n\r",
    '="',
    " be",
    " e",
    "s a",
    "ma",
    "one",
    "t t",
    "or ",
    "but",
    "el",
    "so",
    "l ",
    "e s",
    "s,",
    "no",
    "ter",
    " wa",
    "iv",
    "ho",
    "e a",
    " r",
    "hat",
    "s t",
    "ns",
    "ch ",
    "wh",
    "tr",
    "ut",
    "/",
    "have",
    "ly ",
    "ta",
    " ha",
    " on",
    "tha",
    "-",
    " l",
    "ati",
    "en ",
    "pe",
    " re",
    "there",
    "ass",
    "si",
    " fo",
    "wa",
    "ec",
    "our",
    "who",
    "its",
    "z",
    "fo",
    "rs",
    ">",
    "ot",
    "un",
    "<",
    "im",
    "th ",
    "nc",
    "ate",
    "><",
    "ver",
    "ad",
    " we",
    "ly",
    "ee",
    " n",
    "id",
    " cl",
    "ac",
    "il",
    "</",
    "rt",
    " wi",
    "div",
    "e, ",
    " it",
    "whi",
    " ma",
    "ge",
    "x",
    "e c",
    "men",
    ".com",
]

_CODEBOOK_BYTES = [entry.encode("ascii") for entry in _CODEBOOK]


def _decompress(data: bytes) -> bytes:
    out = bytearray()
    i = 0
    while i < len(data):
        code = data[i]
        if code == _VERBATIM_SINGLE:
            if i + 1 >= len(data):
                break
            out.append(data[i + 1])
            i += 2
        elif code == _VERBATIM_RUN:
            if i + 1 >= len(data):
                break
            length = data[i + 1] + 1
            end = i + 2 + length
            if end > len(data):
                break
            out.extend(data[i + 2 : end])
            i = end
        elif code < len(_CODEBOOK_BYTES):
            out.extend(_CODEBOOK_BYTES[code])
            i += 1
        else:
            break
    return bytes(out)


def try_decode_prefixed(text: str) -> str:
    """If text starts with 's:', decode base64 + SMAZ. Otherwise return as-is."""
    stripped = text.lstrip()
    if not stripped.startswith("s:") or len(stripped) <= 2:
        return text
    encoded = stripped[2:]
    try:
        compressed = base64.b64decode(encoded, validate=True)
    except Exception:
        try:
            normalized = encoded.replace("-", "+").replace("_", "/")
            pad = len(normalized) % 4
            if pad:
                normalized += "=" * (4 - pad)
            compressed = base64.b64decode(normalized)
        except Exception:
            return text
    try:
        return _decompress(compressed).decode("utf-8", errors="replace")
    except Exception:
        return text
