import 'dart:convert';
import 'dart:typed_data';

class Smaz {
  static const int _verbatimSingle = 254;
  static const int _verbatimRun = 255;

  static const List<String> _rcb = [
    " ",
    "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",
  ];

  static final List<Uint8List> _rcbBytes = _rcb
      .map((s) => Uint8List.fromList(ascii.encode(s)))
      .toList(growable: false);
  static final int _maxEntryLen = _rcbBytes.fold(0, (maxLen, entry) {
    return entry.length > maxLen ? entry.length : maxLen;
  });

  static String encodeIfSmaller(String text) {
    if (text.isEmpty || text.startsWith('s:')) return text;
    final originalBytes = Uint8List.fromList(utf8.encode(text));
    final compressed = compressBytes(originalBytes);
    final encoded = base64Encode(compressed);
    final candidate = 's:$encoded';
    if (utf8.encode(candidate).length < originalBytes.length) {
      return candidate;
    }
    return text;
  }

  static String? tryDecodePrefixed(String text) {
    final trimmedLeft = text.trimLeft();
    if (!trimmedLeft.startsWith('s:') || trimmedLeft.length <= 2) return null;
    final encoded = trimmedLeft.substring(2);
    try {
      final compressed = _decodeBase64Flexible(encoded);
      final decompressed = decompressBytes(compressed);
      return utf8.decode(decompressed, allowMalformed: true);
    } catch (_) {
      return null;
    }
  }

  static Uint8List compressBytes(Uint8List input) {
    final out = BytesBuilder(copy: false);
    final verbatim = <int>[];
    int index = 0;

    void flushVerbatim() {
      if (verbatim.isEmpty) return;
      if (verbatim.length == 1) {
        out.addByte(_verbatimSingle);
        out.addByte(verbatim[0]);
      } else {
        out.addByte(_verbatimRun);
        out.addByte(verbatim.length - 1);
        out.add(verbatim);
      }
      verbatim.clear();
    }

    while (index < input.length) {
      int bestLen = 0;
      int bestCode = -1;
      final remaining = input.length - index;
      final maxLen = remaining < _maxEntryLen ? remaining : _maxEntryLen;

      for (int code = 0; code < _rcbBytes.length; code++) {
        final entry = _rcbBytes[code];
        final entryLen = entry.length;
        if (entryLen == 0 || entryLen > maxLen || entryLen <= bestLen) {
          continue;
        }
        if (_matches(input, index, entry)) {
          bestLen = entryLen;
          bestCode = code;
          if (bestLen == maxLen) {
            break;
          }
        }
      }

      if (bestCode >= 0) {
        flushVerbatim();
        out.addByte(bestCode);
        index += bestLen;
        continue;
      }

      verbatim.add(input[index]);
      index++;
      if (verbatim.length == 256) {
        flushVerbatim();
      }
    }

    flushVerbatim();
    return out.toBytes();
  }

  static Uint8List decompressBytes(Uint8List input) {
    final out = BytesBuilder(copy: false);
    int index = 0;

    while (index < input.length) {
      final code = input[index];
      if (code == _verbatimSingle) {
        if (index + 1 >= input.length) {
          throw const FormatException(
            'Invalid SMAZ stream: truncated verbatim byte.',
          );
        }
        out.addByte(input[index + 1]);
        index += 2;
      } else if (code == _verbatimRun) {
        if (index + 1 >= input.length) {
          throw const FormatException(
            'Invalid SMAZ stream: truncated verbatim length.',
          );
        }
        final len = input[index + 1] + 1;
        final end = index + 2 + len;
        if (end > input.length) {
          throw const FormatException(
            'Invalid SMAZ stream: truncated verbatim run.',
          );
        }
        out.add(input.sublist(index + 2, end));
        index = end;
      } else {
        if (code >= _rcbBytes.length) {
          throw const FormatException(
            'Invalid SMAZ stream: code out of range.',
          );
        }
        out.add(_rcbBytes[code]);
        index += 1;
      }
    }

    return out.toBytes();
  }

  static bool _matches(Uint8List input, int offset, Uint8List entry) {
    final len = entry.length;
    if (offset + len > input.length) return false;
    for (int i = 0; i < len; i++) {
      if (input[offset + i] != entry[i]) return false;
    }
    return true;
  }

  static Uint8List _decodeBase64Flexible(String encoded) {
    final trimmed = encoded.trim();
    try {
      return base64Decode(trimmed);
    } catch (_) {
      // Try base64url with missing padding.
      var normalized = trimmed.replaceAll('-', '+').replaceAll('_', '/');
      final pad = normalized.length % 4;
      if (pad != 0) {
        normalized = normalized.padRight(normalized.length + (4 - pad), '=');
      }
      return base64Decode(normalized);
    }
  }
}
