#include <algorithm>
#include <array>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace {

constexpr int MODE_MIXED = 0;
constexpr int MODE_GENERAL = 1;
constexpr int MODE_MEDICAL = 2;
constexpr int LEVEL_LITE = 0;
constexpr int LEVEL_FULL = 1;
constexpr int LEVEL_ULTRA = 2;
constexpr int MASK_MIXED = 1 << 0;
constexpr int MASK_GENERAL = 1 << 1;
constexpr int MASK_MEDICAL = 1 << 2;
constexpr int MASK_ALL = MASK_MIXED | MASK_GENERAL | MASK_MEDICAL;

enum class TokType { Word, Space, Other, Protect };

using WordKey = uint64_t;

struct Token {
    TokType type;
    size_t start;
    size_t len;
    WordKey key;
};

struct Phrase {
    std::vector<WordKey> keys;
    std::string replacement;
    int modes;
    int min_level;
};

using PhraseMap = std::unordered_map<WordKey, std::vector<Phrase>>;

bool is_alpha(unsigned char c) {
    c = static_cast<unsigned char>(c | 32);
    return c >= 'a' && c <= 'z';
}

bool is_digit(unsigned char c) {
    return c >= '0' && c <= '9';
}

bool is_space(unsigned char c) {
    return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
}

char lower_char(unsigned char c) {
    if (c >= 'A' && c <= 'Z') return static_cast<char>(c + 32);
    return static_cast<char>(c);
}

std::string lower_ascii(const char* data, size_t len) {
    std::string out;
    out.reserve(len);
    for (size_t i = 0; i < len; ++i) out.push_back(lower_char(static_cast<unsigned char>(data[i])));
    return out;
}

std::string lower_ascii(const std::string& s) {
    return lower_ascii(s.data(), s.size());
}

WordKey word_key_lower(const char* data, size_t len) {
    WordKey h = 1469598103934665603ULL;
    h ^= static_cast<WordKey>(len);
    h *= 1099511628211ULL;
    for (size_t i = 0; i < len; ++i) {
        h ^= static_cast<unsigned char>(lower_char(static_cast<unsigned char>(data[i])));
        h *= 1099511628211ULL;
    }
    return h ? h : 1;
}

WordKey word_key_literal(const char* s) {
    return word_key_lower(s, std::strlen(s));
}

std::unordered_set<WordKey> make_key_set(std::initializer_list<const char*> words) {
    std::unordered_set<WordKey> out;
    out.reserve(words.size() * 2);
    for (const char* word : words) out.insert(word_key_literal(word));
    return out;
}

std::string_view token_view(const std::string& s, const Token& token) {
    return std::string_view(s.data() + token.start, token.len);
}

bool starts_with_ci(const std::string& s, size_t pos, const char* lit) {
    for (size_t i = 0; lit[i]; ++i) {
        if (pos + i >= s.size()) return false;
        if (lower_char(static_cast<unsigned char>(s[pos + i])) != lit[i]) return false;
    }
    return true;
}

bool contains_email_like(const std::string& s, size_t start, size_t end) {
    bool at = false;
    bool dot_after_at = false;
    for (size_t i = start; i < end; ++i) {
        if (s[i] == '@') at = true;
        else if (at && s[i] == '.') dot_after_at = true;
    }
    return at && dot_after_at;
}

std::vector<Token> tokenize(const std::string& s) {
    std::vector<Token> tokens;
    tokens.reserve(std::max<size_t>(16, s.size() / 5));
    size_t i = 0;
    while (i < s.size()) {
        unsigned char c = static_cast<unsigned char>(s[i]);

        if (is_space(c)) {
            size_t j = i + 1;
            while (j < s.size() && is_space(static_cast<unsigned char>(s[j]))) ++j;
            tokens.push_back({TokType::Space, i, j - i, 0});
            i = j;
            continue;
        }

        if (starts_with_ci(s, i, "http://") || starts_with_ci(s, i, "https://") || starts_with_ci(s, i, "www.")) {
            size_t j = i;
            while (j < s.size() && !is_space(static_cast<unsigned char>(s[j])) && s[j] != '<' && s[j] != '>' && s[j] != '(' && s[j] != ')') ++j;
            tokens.push_back({TokType::Protect, i, j - i, 0});
            i = j;
            continue;
        }

        if (s[i] == '`') {
            if (i + 2 < s.size() && s[i + 1] == '`' && s[i + 2] == '`') {
                size_t j = s.find("```", i + 3);
                if (j != std::string::npos) {
                    j += 3;
                    tokens.push_back({TokType::Protect, i, j - i, 0});
                    i = j;
                    continue;
                }
            } else {
                size_t j = s.find('`', i + 1);
                size_t newline = s.find('\n', i + 1);
                if (j != std::string::npos && (newline == std::string::npos || j < newline)) {
                    ++j;
                    tokens.push_back({TokType::Protect, i, j - i, 0});
                    i = j;
                    continue;
                }
            }
        }

        if (s[i] == '"') {
            size_t j = s.find('"', i + 1);
            size_t newline = s.find('\n', i + 1);
            if (j != std::string::npos && (newline == std::string::npos || j < newline) && j - i >= 4) {
                tokens.push_back({TokType::Protect, i, j - i + 1, 0});
                i = j + 1;
                continue;
            }
        }

        size_t segment_end = i;
        while (segment_end < s.size() && !is_space(static_cast<unsigned char>(s[segment_end]))) ++segment_end;
        if (contains_email_like(s, i, segment_end)) {
            tokens.push_back({TokType::Protect, i, segment_end - i, 0});
            i = segment_end;
            continue;
        }

        if (is_alpha(c)) {
            size_t j = i + 1;
            while (j < s.size()) {
                unsigned char d = static_cast<unsigned char>(s[j]);
                if (is_alpha(d) || (s[j] == '\'' && j + 1 < s.size() && is_alpha(static_cast<unsigned char>(s[j + 1])))) {
                    ++j;
                } else {
                    break;
                }
            }
            tokens.push_back({TokType::Word, i, j - i, word_key_lower(s.data() + i, j - i)});
            i = j;
            continue;
        }

        if (is_digit(c)) {
            size_t j = i + 1;
            while (j < s.size()) {
                unsigned char d = static_cast<unsigned char>(s[j]);
                if (is_digit(d) || is_alpha(d) || s[j] == '.' || s[j] == ',' || s[j] == ':' || s[j] == '/' || s[j] == '-' || s[j] == '%') ++j;
                else break;
            }
            tokens.push_back({TokType::Other, i, j - i, 0});
            i = j;
            continue;
        }

        tokens.push_back({TokType::Other, i, 1, 0});
        ++i;
    }
    return tokens;
}

int mode_mask(int mode) {
    if (mode == MODE_GENERAL) return MASK_GENERAL;
    if (mode == MODE_MEDICAL) return MASK_MEDICAL;
    return MASK_MIXED;
}

void add_phrase(PhraseMap& m, std::initializer_list<const char*> words, const char* repl, int modes = MASK_ALL, int min_level = LEVEL_LITE) {
    Phrase phrase;
    phrase.replacement = repl;
    phrase.modes = modes;
    phrase.min_level = min_level;
    phrase.keys.reserve(words.size());
    for (const char* word : words) phrase.keys.push_back(word_key_literal(word));
    if (!phrase.keys.empty()) m[phrase.keys.front()].push_back(std::move(phrase));
}

const PhraseMap& phrase_map() {
    static PhraseMap map = [] {
        PhraseMap m;

        add_phrase(m, {"it", "is", "important", "to", "note", "that"}, "");
        add_phrase(m, {"it", "is", "important", "to", "remember", "that"}, "");
        add_phrase(m, {"it", "is", "important", "to", "understand", "that"}, "");
        add_phrase(m, {"important", "to", "note", "that"}, "");
        add_phrase(m, {"important", "to", "remember", "that"}, "");
        add_phrase(m, {"important", "to", "understand", "that"}, "");
        add_phrase(m, {"please", "note", "that"}, "");
        add_phrase(m, {"it", "should", "be", "noted", "that"}, "");
        add_phrase(m, {"it", "is", "worth", "noting", "that"}, "");
        add_phrase(m, {"as", "a", "matter", "of", "fact"}, "");
        add_phrase(m, {"in", "order", "to"}, "to");
        add_phrase(m, {"so", "as", "to"}, "to");
        add_phrase(m, {"for", "the", "purpose", "of"}, "for");
        add_phrase(m, {"due", "to", "the", "fact", "that"}, "because");
        add_phrase(m, {"in", "light", "of", "the", "fact", "that"}, "because");
        add_phrase(m, {"despite", "the", "fact", "that"}, "although");
        add_phrase(m, {"the", "fact", "that"}, "that");
        add_phrase(m, {"at", "this", "point", "in", "time"}, "now");
        add_phrase(m, {"at", "the", "present", "time"}, "now");
        add_phrase(m, {"in", "the", "near", "future"}, "soon");
        add_phrase(m, {"in", "the", "event", "that"}, "if");
        add_phrase(m, {"with", "regard", "to"}, "re");
        add_phrase(m, {"in", "regard", "to"}, "re");
        add_phrase(m, {"regarding", "the", "matter", "of"}, "re");
        add_phrase(m, {"with", "respect", "to"}, "re");
        add_phrase(m, {"in", "relation", "to"}, "re");
        add_phrase(m, {"in", "terms", "of"}, "re");
        add_phrase(m, {"when", "it", "comes", "to"}, "re");
        add_phrase(m, {"as", "a", "result", "of"}, "because of");
        add_phrase(m, {"a", "number", "of"}, "many");
        add_phrase(m, {"a", "lot", "of"}, "many");
        add_phrase(m, {"there", "are", "a", "lot", "of"}, "many");
        add_phrase(m, {"a", "variety", "of"}, "many");
        add_phrase(m, {"a", "wide", "range", "of"}, "many");
        add_phrase(m, {"wide", "range", "of"}, "many");
        add_phrase(m, {"large", "number", "of"}, "many");
        add_phrase(m, {"significant", "number", "of"}, "many");
        add_phrase(m, {"the", "majority", "of"}, "most");
        add_phrase(m, {"prior", "to"}, "before");
        add_phrase(m, {"subsequent", "to"}, "after");
        add_phrase(m, {"greater", "than", "or", "equal", "to"}, ">=");
        add_phrase(m, {"less", "than", "or", "equal", "to"}, "<=");
        add_phrase(m, {"greater", "than"}, ">");
        add_phrase(m, {"less", "than"}, "<");
        add_phrase(m, {"is", "able", "to"}, "can");
        add_phrase(m, {"are", "able", "to"}, "can");
        add_phrase(m, {"was", "able", "to"}, "could");
        add_phrase(m, {"were", "able", "to"}, "could");
        add_phrase(m, {"will", "be", "able", "to"}, "can");
        add_phrase(m, {"has", "the", "ability", "to"}, "can");
        add_phrase(m, {"have", "the", "ability", "to"}, "can");
        add_phrase(m, {"is", "unable", "to"}, "cannot");
        add_phrase(m, {"are", "unable", "to"}, "cannot");
        add_phrase(m, {"was", "unable", "to"}, "could not");
        add_phrase(m, {"were", "unable", "to"}, "could not");
        add_phrase(m, {"make", "sure", "to"}, "");
        add_phrase(m, {"be", "sure", "to"}, "");
        add_phrase(m, {"i", "would", "recommend"}, "Recommend");
        add_phrase(m, {"i", "recommend"}, "Recommend");
        add_phrase(m, {"we", "recommend"}, "Recommend");
        add_phrase(m, {"you", "should", "consider"}, "Consider");
        add_phrase(m, {"you", "may", "want", "to", "consider"}, "Consider");
        add_phrase(m, {"it", "may", "be", "helpful", "to"}, "Consider");
        add_phrase(m, {"there", "is", "a", "need", "to"}, "need to");
        add_phrase(m, {"kind", "of"}, "");
        add_phrase(m, {"sort", "of"}, "");
        add_phrase(m, {"results", "in"}, "causes");
        add_phrase(m, {"leads", "to"}, "causes");
        add_phrase(m, {"brings", "about"}, "causes");
        add_phrase(m, {"increases", "the", "risk", "of"}, "up risk of");
        add_phrase(m, {"decreases", "the", "risk", "of"}, "down risk of");
        add_phrase(m, {"increase", "in"}, "up");
        add_phrase(m, {"decrease", "in"}, "down");
        add_phrase(m, {"improvement", "in"}, "better");
        add_phrase(m, {"worsening", "of"}, "worse");
        add_phrase(m, {"as", "well", "as"}, "+");
        add_phrase(m, {"in", "addition", "to"}, "+");
        add_phrase(m, {"in", "addition"}, "also");
        add_phrase(m, {"at", "the", "same", "time"}, "meanwhile");
        add_phrase(m, {"on", "the", "other", "hand"}, "but");
        add_phrase(m, {"in", "other", "words"}, "i.e.");
        add_phrase(m, {"for", "this", "reason"}, "therefore");
        add_phrase(m, {"it", "is", "possible", "that"}, "maybe");
        add_phrase(m, {"it", "is", "likely", "that"}, "likely");
        add_phrase(m, {"at", "the", "end", "of", "the", "day"}, "ultimately");
        add_phrase(m, {"take", "into", "account"}, "consider");
        add_phrase(m, {"takes", "into", "account"}, "considers");
        add_phrase(m, {"taken", "into", "account"}, "considered");
        add_phrase(m, {"make", "use", "of"}, "use");
        add_phrase(m, {"makes", "use", "of"}, "uses");
        add_phrase(m, {"made", "use", "of"}, "used");
        add_phrase(m, {"can", "be", "used", "to"}, "can");
        add_phrase(m, {"with", "the", "exception", "of"}, "except");
        add_phrase(m, {"as", "soon", "as", "possible"}, "ASAP");
        add_phrase(m, {"more", "and", "more"}, "more");
        add_phrase(m, {"over", "and", "over"}, "repeatedly");

        add_phrase(m, {"this", "means", "that"}, "means");
        add_phrase(m, {"what", "this", "means", "is"}, "means");
        add_phrase(m, {"it", "appears", "that"}, "appears");
        add_phrase(m, {"it", "seems", "that"}, "seems");
        add_phrase(m, {"there", "is"}, "");
        add_phrase(m, {"there", "are"}, "");
        add_phrase(m, {"there", "was"}, "");
        add_phrase(m, {"there", "were"}, "");
        add_phrase(m, {"i", "think"}, "", MASK_GENERAL);
        add_phrase(m, {"i", "believe"}, "", MASK_GENERAL);
        add_phrase(m, {"in", "my", "opinion"}, "", MASK_GENERAL);
        add_phrase(m, {"from", "my", "perspective"}, "", MASK_GENERAL);

        add_phrase(m, {"zero"}, "0");
        add_phrase(m, {"one"}, "1");
        add_phrase(m, {"two"}, "2");
        add_phrase(m, {"three"}, "3");
        add_phrase(m, {"four"}, "4");
        add_phrase(m, {"five"}, "5");
        add_phrase(m, {"six"}, "6");
        add_phrase(m, {"seven"}, "7");
        add_phrase(m, {"eight"}, "8");
        add_phrase(m, {"nine"}, "9");
        add_phrase(m, {"ten"}, "10");
        add_phrase(m, {"eleven"}, "11");
        add_phrase(m, {"twelve"}, "12");

        int clin = MASK_MIXED | MASK_MEDICAL;
        add_phrase(m, {"the", "patient", "is", "a"}, "Pt:", clin);
        add_phrase(m, {"patient", "is", "a"}, "Pt:", clin);
        add_phrase(m, {"the", "patient"}, "pt", clin);
        add_phrase(m, {"patient"}, "pt", clin);
        add_phrase(m, {"provider"}, "clinician", clin);
        add_phrase(m, {"physician"}, "MD", clin);
        add_phrase(m, {"nurse", "practitioner"}, "NP", clin);
        add_phrase(m, {"physician", "assistant"}, "PA", clin);
        add_phrase(m, {"chief", "complaint"}, "CC", clin);
        add_phrase(m, {"history", "of", "present", "illness"}, "HPI", clin);
        add_phrase(m, {"past", "medical", "history"}, "PMH", clin);
        add_phrase(m, {"past", "surgical", "history"}, "PSH", clin);
        add_phrase(m, {"family", "history"}, "FHx", clin);
        add_phrase(m, {"social", "history"}, "SHx", clin);
        add_phrase(m, {"review", "of", "systems"}, "ROS", clin);
        add_phrase(m, {"physical", "examination"}, "exam", clin);
        add_phrase(m, {"physical", "exam"}, "exam", clin);
        add_phrase(m, {"assessment", "and", "plan"}, "A/P", clin);
        add_phrase(m, {"differential", "diagnosis"}, "DDx", clin);
        add_phrase(m, {"medical", "history"}, "hx", clin);
        add_phrase(m, {"history", "of"}, "hx", clin);
        add_phrase(m, {"status", "post"}, "s/p", clin);
        add_phrase(m, {"rule", "out"}, "r/o", clin);
        add_phrase(m, {"secondary", "to"}, "2/2", clin);
        add_phrase(m, {"versus"}, "vs", clin);
        add_phrase(m, {"with"}, "w/", clin);
        add_phrase(m, {"without"}, "w/o", clin);
        add_phrase(m, {"follow", "up"}, "f/u", clin);
        add_phrase(m, {"as", "needed"}, "PRN", clin);
        add_phrase(m, {"nothing", "by", "mouth"}, "NPO", clin);
        add_phrase(m, {"by", "mouth"}, "PO", clin);
        add_phrase(m, {"intravenous"}, "IV", clin);
        add_phrase(m, {"intramuscular"}, "IM", clin);
        add_phrase(m, {"subcutaneous"}, "SQ", clin);
        add_phrase(m, {"every", "day"}, "daily", clin);
        add_phrase(m, {"once", "daily"}, "daily", clin);
        add_phrase(m, {"twice", "daily"}, "BID", clin);
        add_phrase(m, {"three", "times", "daily"}, "TID", clin);
        add_phrase(m, {"four", "times", "daily"}, "QID", clin);
        add_phrase(m, {"every", "other", "day"}, "qod", clin);
        add_phrase(m, {"every", "morning"}, "qAM", clin);
        add_phrase(m, {"every", "evening"}, "qPM", clin);
        add_phrase(m, {"at", "bedtime"}, "qHS", clin);
        add_phrase(m, {"complains", "of"}, "reports", clin);
        add_phrase(m, {"complaining", "of"}, "reports", clin);
        add_phrase(m, {"reports", "that"}, "reports", clin);
        add_phrase(m, {"states", "that"}, "states", clin);
        add_phrase(m, {"denies", "any"}, "denies", clin);
        add_phrase(m, {"denied", "any"}, "denied", clin);
        add_phrase(m, {"negative", "for"}, "no", clin);
        add_phrase(m, {"positive", "for"}, "+", clin);
        add_phrase(m, {"not", "associated", "with"}, "not w/", clin);
        add_phrase(m, {"associated", "with"}, "w/", clin);
        add_phrase(m, {"consistent", "with"}, "fits", clin);
        add_phrase(m, {"concerning", "for"}, "c/f", clin);
        add_phrase(m, {"concern", "for"}, "c/f", clin);
        add_phrase(m, {"suspicious", "for"}, "c/f", clin);
        add_phrase(m, {"may", "represent"}, "could be", clin);
        add_phrase(m, {"shortness", "of", "breath"}, "SOB", clin);
        add_phrase(m, {"dyspnea", "on", "exertion"}, "DOE", clin);
        add_phrase(m, {"chest", "pain"}, "CP", clin);
        add_phrase(m, {"abdominal", "pain"}, "abd pain", clin);
        add_phrase(m, {"lower", "abdominal", "pain"}, "lower abd pain", clin);
        add_phrase(m, {"upper", "abdominal", "pain"}, "upper abd pain", clin);
        add_phrase(m, {"nausea", "and", "vomiting"}, "N/V", clin);
        add_phrase(m, {"headache"}, "HA", clin);
        add_phrase(m, {"loss", "of", "consciousness"}, "LOC", clin);
        add_phrase(m, {"altered", "mental", "status"}, "AMS", clin);
        add_phrase(m, {"fever", "and", "chills"}, "F/C", clin);
        add_phrase(m, {"fevers", "and", "chills"}, "F/C", clin);
        add_phrase(m, {"urinary", "tract", "infection"}, "UTI", clin);
        add_phrase(m, {"upper", "respiratory", "infection"}, "URI", clin);
        add_phrase(m, {"lower", "extremity"}, "LE", clin);
        add_phrase(m, {"upper", "extremity"}, "UE", clin);
        add_phrase(m, {"right", "lower", "quadrant"}, "RLQ", clin);
        add_phrase(m, {"left", "lower", "quadrant"}, "LLQ", clin);
        add_phrase(m, {"right", "upper", "quadrant"}, "RUQ", clin);
        add_phrase(m, {"left", "upper", "quadrant"}, "LUQ", clin);
        add_phrase(m, {"blood", "pressure"}, "BP", clin);
        add_phrase(m, {"heart", "rate"}, "HR", clin);
        add_phrase(m, {"respiratory", "rate"}, "RR", clin);
        add_phrase(m, {"oxygen", "saturation"}, "SpO2", clin);
        add_phrase(m, {"temperature"}, "temp", clin);
        add_phrase(m, {"white", "blood", "cell", "count"}, "WBC", clin);
        add_phrase(m, {"red", "blood", "cell", "count"}, "RBC", clin);
        add_phrase(m, {"hemoglobin"}, "Hgb", clin);
        add_phrase(m, {"hematocrit"}, "Hct", clin);
        add_phrase(m, {"platelet", "count"}, "Plt", clin);
        add_phrase(m, {"sodium"}, "Na", clin);
        add_phrase(m, {"potassium"}, "K", clin);
        add_phrase(m, {"chloride"}, "Cl", clin);
        add_phrase(m, {"carbon", "dioxide"}, "CO2", clin);
        add_phrase(m, {"blood", "urea", "nitrogen"}, "BUN", clin);
        add_phrase(m, {"creatinine"}, "Cr", clin);
        add_phrase(m, {"liver", "function", "tests"}, "LFTs", clin);
        add_phrase(m, {"computed", "tomography", "angiography"}, "CTA", clin);
        add_phrase(m, {"computed", "tomography"}, "CT", clin);
        add_phrase(m, {"magnetic", "resonance", "imaging"}, "MRI", clin);
        add_phrase(m, {"ultrasound"}, "US", clin);
        add_phrase(m, {"electrocardiogram"}, "ECG", clin);
        add_phrase(m, {"electrocardiography"}, "ECG", clin);
        add_phrase(m, {"myocardial", "infarction"}, "MI", clin);
        add_phrase(m, {"heart", "failure"}, "HF", clin);
        add_phrase(m, {"congestive", "heart", "failure"}, "CHF", clin);
        add_phrase(m, {"coronary", "artery", "disease"}, "CAD", clin);
        add_phrase(m, {"chronic", "obstructive", "pulmonary", "disease"}, "COPD", clin);
        add_phrase(m, {"diabetes", "mellitus", "type", "2"}, "T2DM", clin);
        add_phrase(m, {"type", "2", "diabetes", "mellitus"}, "T2DM", clin);
        add_phrase(m, {"diabetes", "mellitus", "type", "1"}, "T1DM", clin);
        add_phrase(m, {"type", "1", "diabetes", "mellitus"}, "T1DM", clin);
        add_phrase(m, {"hypertension"}, "HTN", clin);
        add_phrase(m, {"hyperlipidemia"}, "HLD", clin);
        add_phrase(m, {"chronic", "kidney", "disease"}, "CKD", clin);
        add_phrase(m, {"acquired", "immunodeficiency", "syndrome"}, "AIDS", clin);
        add_phrase(m, {"human", "immunodeficiency", "virus"}, "HIV", clin);
        add_phrase(m, {"seek", "emergency", "care"}, "go ER", clin);
        add_phrase(m, {"go", "to", "the", "emergency", "department"}, "go ED", clin);
        add_phrase(m, {"go", "to", "the", "emergency", "room"}, "go ER", clin);
        add_phrase(m, {"call", "emergency", "medical", "services"}, "call EMS", clin);
        add_phrase(m, {"return", "precautions"}, "return precautions", clin);
        add_phrase(m, {"if", "symptoms", "worsen"}, "if worse", clin);
        add_phrase(m, {"emergency", "department"}, "ED", clin);
        add_phrase(m, {"emergency", "room"}, "ER", clin);
        add_phrase(m, {"intensive", "care", "unit"}, "ICU", clin);
        add_phrase(m, {"primary", "care", "physician"}, "PCP", clin);
        add_phrase(m, {"primary", "care", "provider"}, "PCP", clin);
        add_phrase(m, {"history", "and", "physical"}, "H&P", clin);
        add_phrase(m, {"presented", "to"}, "p/w", clin);
        add_phrase(m, {"presents", "to"}, "p/w", clin);
        add_phrase(m, {"presenting", "with"}, "p/w", clin);
        add_phrase(m, {"baseline", "mental", "status"}, "baseline MS", clin);
        add_phrase(m, {"difficulty", "breathing"}, "dyspnea", clin);
        add_phrase(m, {"difficulty", "swallowing"}, "dysphagia", clin);
        add_phrase(m, {"pain", "with", "urination"}, "dysuria", clin);
        add_phrase(m, {"blood", "in", "urine"}, "hematuria", clin);
        add_phrase(m, {"blood", "in", "stool"}, "hematochezia", clin);
        add_phrase(m, {"black", "tarry", "stools"}, "melena", clin);
        add_phrase(m, {"vomiting", "blood"}, "hematemesis", clin);
        add_phrase(m, {"coughing", "up", "blood"}, "hemoptysis", clin);
        add_phrase(m, {"normal", "saline"}, "NS", clin);
        add_phrase(m, {"complete", "blood", "count"}, "CBC", clin);
        add_phrase(m, {"basic", "metabolic", "panel"}, "BMP", clin);
        add_phrase(m, {"comprehensive", "metabolic", "panel"}, "CMP", clin);
        add_phrase(m, {"pulmonary", "embolism"}, "PE", clin);
        add_phrase(m, {"deep", "vein", "thrombosis"}, "DVT", clin);
        add_phrase(m, {"cerebrovascular", "accident"}, "CVA", clin);
        add_phrase(m, {"transient", "ischemic", "attack"}, "TIA", clin);
        add_phrase(m, {"atrial", "fibrillation"}, "AFib", clin);
        add_phrase(m, {"acute", "kidney", "injury"}, "AKI", clin);
        add_phrase(m, {"end", "stage", "renal", "disease"}, "ESRD", clin);

        add_phrase(m, {"milligrams"}, "mg", clin);
        add_phrase(m, {"milligram"}, "mg", clin);
        add_phrase(m, {"micrograms"}, "mcg", clin);
        add_phrase(m, {"microgram"}, "mcg", clin);
        add_phrase(m, {"grams"}, "g", clin);
        add_phrase(m, {"gram"}, "g", clin);
        add_phrase(m, {"milliliters"}, "mL", clin);
        add_phrase(m, {"milliliter"}, "mL", clin);
        add_phrase(m, {"liters"}, "L", clin);
        add_phrase(m, {"liter"}, "L", clin);
        add_phrase(m, {"millimeters", "of", "mercury"}, "mmHg", clin);
        add_phrase(m, {"beats", "per", "minute"}, "bpm", clin);
        add_phrase(m, {"breaths", "per", "minute"}, "rpm", clin);
        add_phrase(m, {"degrees", "fahrenheit"}, "\xC2\xB0" "F", clin);
        add_phrase(m, {"degrees", "celsius"}, "\xC2\xB0" "C", clin);
        add_phrase(m, {"per", "os"}, "PO", clin);

        add_phrase(m, {"because", "of"}, "2/2", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"because"}, "bc", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"and"}, "&", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"or"}, "/", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"approximately"}, "~", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"around"}, "~", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"including"}, "incl", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"between"}, "btwn", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"within"}, "w/in", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"through"}, "thru", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"before"}, "pre", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"after"}, "post", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"during"}, "during", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"number"}, "#", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"first"}, "1st", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"second"}, "2nd", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"third"}, "3rd", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"people"}, "ppl", MASK_GENERAL, LEVEL_FULL);
        add_phrase(m, {"development"}, "dev", MASK_GENERAL, LEVEL_FULL);
        add_phrase(m, {"process"}, "proc", MASK_GENERAL, LEVEL_FULL);
        add_phrase(m, {"system"}, "sys", MASK_GENERAL, LEVEL_FULL);
        add_phrase(m, {"systems"}, "sys", MASK_GENERAL, LEVEL_FULL);
        add_phrase(m, {"available"}, "avail", MASK_GENERAL, LEVEL_FULL);
        add_phrase(m, {"different"}, "diff", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"important"}, "key", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"significant"}, "sig", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"associated"}, "assoc", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"observed"}, "seen", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"using"}, "via", MASK_ALL, LEVEL_FULL);
        add_phrase(m, {"figure"}, "Fig", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"figures"}, "Figs", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"table"}, "Tbl", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"tables"}, "Tbls", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"analysis"}, "anal", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"studies"}, "studies", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"protein"}, "prot", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"proteins"}, "prots", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"patients"}, "pts", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"control"}, "ctrl", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"controls"}, "ctrls", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"treatment"}, "tx", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"disease"}, "dz", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"diseases"}, "dz", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"expression"}, "expr", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"activity"}, "act", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"experiment"}, "expt", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"experiments"}, "expts", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"et", "al"}, "", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"based", "on"}, "from", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"compared", "with"}, "vs", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"compared", "to"}, "vs", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"as", "compared", "with"}, "vs", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"in", "contrast", "to"}, "vs", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"results"}, "res", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"result"}, "res", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"sequence"}, "seq", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"sequences"}, "seqs", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"specific"}, "spec", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"binding"}, "bind", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"group"}, "grp", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"groups"}, "grps", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"method"}, "meth", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"methods"}, "meth", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"level"}, "lvl", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"levels"}, "lvls", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"effect"}, "fx", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"effects"}, "fx", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"model"}, "mdl", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"models"}, "mdls", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"increased"}, "up", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"decreased"}, "down", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"higher"}, "high", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"lower"}, "low", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"similar"}, "sim", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);
        add_phrase(m, {"however"}, "but", MASK_MIXED | MASK_MEDICAL, LEVEL_FULL);

        add_phrase(m, {"because", "of"}, "2/2", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"because"}, "->", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"therefore"}, "so", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"however"}, "but", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"without"}, "w/o", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"with"}, "w/", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"and"}, "+", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"or"}, "/", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"to", "evaluate", "for"}, "eval r/o", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"evaluate", "for"}, "eval", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"configuration"}, "config", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"database"}, "DB", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"authentication"}, "auth", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"authorization"}, "authz", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"request"}, "req", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"response"}, "res", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"application"}, "app", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"information"}, "info", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"documentation"}, "docs", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"implementation"}, "impl", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"need", "to"}, "need", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"has", "to"}, "must", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"have", "to"}, "must", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"going", "to"}, "will", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"for", "example"}, "e.g.", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"for", "instance"}, "e.g.", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"such", "as"}, "e.g.", MASK_ALL, LEVEL_ULTRA);
        add_phrase(m, {"cell"}, "cl", MASK_MIXED | MASK_MEDICAL, LEVEL_ULTRA);
        add_phrase(m, {"cells"}, "cls", MASK_MIXED | MASK_MEDICAL, LEVEL_ULTRA);
        add_phrase(m, {"study"}, "stdy", MASK_MIXED | MASK_MEDICAL, LEVEL_ULTRA);
        add_phrase(m, {"studies"}, "stdys", MASK_MIXED | MASK_MEDICAL, LEVEL_ULTRA);
        add_phrase(m, {"data"}, "dat", MASK_MIXED | MASK_MEDICAL, LEVEL_ULTRA);
        add_phrase(m, {"gene"}, "gn", MASK_MIXED | MASK_MEDICAL, LEVEL_ULTRA);
        add_phrase(m, {"genes"}, "gns", MASK_MIXED | MASK_MEDICAL, LEVEL_ULTRA);
        add_phrase(m, {"shown"}, "seen", MASK_MIXED | MASK_MEDICAL, LEVEL_ULTRA);

        for (auto& kv : m) {
            auto& v = kv.second;
            std::sort(v.begin(), v.end(), [](const Phrase& a, const Phrase& b) {
                if (a.keys.size() != b.keys.size()) return a.keys.size() > b.keys.size();
                return a.min_level > b.min_level;
            });
        }
        return m;
    }();
    return map;
}

const PhraseMap& phrase_map_for_level(int level) {
    static const std::array<PhraseMap, 3> maps = [] {
        std::array<PhraseMap, 3> out;
        const PhraseMap& all = phrase_map();
        for (int lvl = LEVEL_LITE; lvl <= LEVEL_ULTRA; ++lvl) {
            for (const auto& kv : all) {
                std::vector<Phrase> phrases;
                for (const Phrase& phrase : kv.second) {
                    if (phrase.min_level <= lvl) phrases.push_back(phrase);
                }
                if (!phrases.empty()) {
                    std::sort(phrases.begin(), phrases.end(), [](const Phrase& a, const Phrase& b) {
                        if (a.keys.size() != b.keys.size()) return a.keys.size() > b.keys.size();
                        return a.min_level > b.min_level;
                    });
                    out[lvl][kv.first] = std::move(phrases);
                }
            }
        }
        return out;
    }();
    return maps[level];
}

bool token_breaks_sentence(const std::string& input, const Token& t) {
    if (t.type == TokType::Protect) return true;
    std::string_view raw = token_view(input, t);
    if (t.type == TokType::Space) return raw.find('\n') != std::string_view::npos || raw.find('\r') != std::string_view::npos;
    if (t.type != TokType::Other) return false;
    if (raw.find('\x1e') != std::string_view::npos) return true;
    return raw.find('.') != std::string_view::npos || raw.find('!') != std::string_view::npos ||
           raw.find('?') != std::string_view::npos || raw.find(';') != std::string_view::npos ||
           raw.find(':') != std::string_view::npos;
}

WordKey previous_word(const std::string& input, const std::vector<Token>& tokens, size_t idx) {
    for (size_t i = idx; i-- > 0;) {
        if (tokens[i].type == TokType::Word) return tokens[i].key;
        if (token_breaks_sentence(input, tokens[i])) break;
    }
    return 0;
}

WordKey next_word(const std::string& input, const std::vector<Token>& tokens, size_t idx) {
    for (size_t i = idx + 1; i < tokens.size(); ++i) {
        if (tokens[i].type == TokType::Word) return tokens[i].key;
        if (token_breaks_sentence(input, tokens[i])) break;
    }
    return 0;
}

const Phrase* match_phrase(const std::vector<Token>& tokens, size_t idx, int mode, int level, size_t& end_idx) {
    if (tokens[idx].type != TokType::Word) return nullptr;
    const auto& m = phrase_map_for_level(level);
    auto it = m.find(tokens[idx].key);
    if (it == m.end()) return nullptr;
    int mask = mode_mask(mode);
    for (const Phrase& p : it->second) {
        if ((p.modes & mask) == 0) continue;
        size_t j = idx;
        bool ok = true;
        for (size_t k = 0; k < p.keys.size(); ++k) {
            if (k > 0) {
                while (j < tokens.size() && tokens[j].type == TokType::Space) ++j;
            }
            if (j >= tokens.size() || tokens[j].type != TokType::Word || tokens[j].key != p.keys[k]) {
                ok = false;
                break;
            }
            ++j;
        }
        if (ok) {
            end_idx = j;
            return &p;
        }
    }
    return nullptr;
}

const std::unordered_set<WordKey>& general_lite_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "a", "an", "the", "actually", "basically", "really", "very", "simply", "just", "quite", "rather", "somewhat", "please",
        "that", "this", "these", "those", "it", "there", "also", "some"
    });
    return s;
}

const std::unordered_set<WordKey>& medical_lite_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "a", "an", "the", "actually", "basically", "really", "very", "simply", "just", "quite", "rather", "somewhat", "please",
        "of", "that"
    });
    return s;
}

const std::unordered_set<WordKey>& mixed_lite_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "a", "an", "the", "actually", "basically", "really", "very", "simply", "just", "quite", "rather", "somewhat", "please",
        "that", "this", "these", "those", "it", "there", "also"
    });
    return s;
}

const std::unordered_set<WordKey>& general_full_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "a", "an", "the", "actually", "basically", "really", "very", "simply", "just", "quite", "rather", "somewhat", "please",
        "that", "clearly", "obviously", "perhaps", "maybe", "generally", "usually",
        "on", "by", "from", "as", "at", "this", "these", "those", "it", "its", "there", "also", "some",
        "you", "your", "we", "our", "they", "their", "them", "he", "his", "i"
    });
    return s;
}

const std::unordered_set<WordKey>& general_ultra_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "a", "an", "the", "actually", "basically", "really", "very", "simply", "just", "quite", "rather", "somewhat", "please",
        "that", "clearly", "obviously", "perhaps", "maybe", "generally", "usually",
        "of", "to", "in", "on", "by", "from", "as", "at", "this", "these", "those", "it", "its", "there", "also", "some",
        "you", "your", "we", "our", "they", "their", "them", "he", "his", "i",
        "which", "all", "each", "both", "other", "such", "more", "only", "most", "then", "into"
    });
    return s;
}

const std::unordered_set<WordKey>& medical_full_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "a", "an", "the", "actually", "basically", "really", "very", "simply", "just", "quite", "rather", "somewhat", "please",
        "that", "generally",
        "of", "this", "these", "those", "it", "its", "there", "also", "some",
        "we", "our", "which", "all", "each", "both", "other", "such", "more", "only", "most", "then", "into", "i"
    });
    return s;
}

const std::unordered_set<WordKey>& medical_ultra_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "a", "an", "the", "actually", "basically", "really", "very", "simply", "just", "quite", "rather", "somewhat", "please",
        "that", "generally",
        "of", "to", "in", "on", "by", "from", "as", "at", "this", "these", "those", "it", "its", "there", "also", "some",
        "we", "our", "for", "which", "all", "each", "both", "other", "such", "more", "only", "most", "then", "into", "i"
    });
    return s;
}

const std::unordered_set<WordKey>& mixed_full_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "a", "an", "the", "actually", "basically", "really", "very", "simply", "just", "quite", "rather", "somewhat", "please",
        "that", "generally", "this", "these", "those", "it", "its", "there", "also", "some", "we", "our"
    });
    return s;
}

const std::unordered_set<WordKey>& aux_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({"is", "are", "was", "were", "be", "being", "been", "has", "have", "had"});
    return s;
}

const std::unordered_set<WordKey>& numeric_guard_drop_words() {
    static const std::unordered_set<WordKey> s = make_key_set({"of", "to", "in", "on", "by", "from", "as", "at", "for"});
    return s;
}

const std::unordered_set<WordKey>& dangerous_words() {
    static const std::unordered_set<WordKey> s = make_key_set({
        "no", "not", "never", "none", "without", "denies", "denied", "cannot", "can't", "unable", "avoid",
        "possible", "possibly", "probable", "probably", "likely", "unlikely", "suspected", "concern", "concerning",
        "may", "might", "could", "worse", "worsening", "severe", "acute", "new", "allergy", "allergic", "pregnant", "pregnancy"
    });
    return s;
}

bool contains(const std::unordered_set<WordKey>& s, WordKey item) {
    return s.find(item) != s.end();
}

bool token_has_digit(const std::string& input, const Token& token) {
    std::string_view raw = token_view(input, token);
    for (unsigned char c : raw) {
        if (is_digit(c)) return true;
    }
    return false;
}

bool adjacent_digit_token(const std::string& input, const std::vector<Token>& tokens, size_t idx) {
    for (size_t i = idx; i-- > 0;) {
        if (tokens[i].type == TokType::Space) continue;
        if (token_has_digit(input, tokens[i])) return true;
        break;
    }
    for (size_t i = idx + 1; i < tokens.size(); ++i) {
        if (tokens[i].type == TokType::Space) continue;
        if (token_has_digit(input, tokens[i])) return true;
        break;
    }
    return false;
}

bool should_drop_word(const std::string& input, const std::vector<Token>& tokens, size_t idx, int mode, int level) {
    WordKey word = tokens[idx].key;
    if (contains(dangerous_words(), word)) return false;
    if (contains(numeric_guard_drop_words(), word) && adjacent_digit_token(input, tokens, idx)) return false;

    if (level == LEVEL_LITE) {
        if (mode == MODE_GENERAL) return contains(general_lite_drop_words(), word);
        if (mode == MODE_MEDICAL) return contains(medical_lite_drop_words(), word);
        return contains(mixed_lite_drop_words(), word);
    }

    if (mode == MODE_GENERAL) {
        if (contains(level == LEVEL_ULTRA ? general_ultra_drop_words() : general_full_drop_words(), word)) return true;
    } else if (mode == MODE_MEDICAL) {
        if (contains(level == LEVEL_ULTRA ? medical_ultra_drop_words() : medical_full_drop_words(), word)) return true;
    } else {
        if (contains(mixed_full_drop_words(), word)) return true;
    }

    if (contains(aux_drop_words(), word)) {
        WordKey prev = previous_word(input, tokens, idx);
        WordKey next = next_word(input, tokens, idx);
        static const WordKey not_k = word_key_literal("not");
        static const WordKey no_k = word_key_literal("no");
        static const WordKey never_k = word_key_literal("never");
        static const WordKey what_k = word_key_literal("what");
        static const WordKey where_k = word_key_literal("where");
        static const WordKey when_k = word_key_literal("when");
        static const WordKey why_k = word_key_literal("why");
        static const WordKey how_k = word_key_literal("how");
        static const WordKey without_k = word_key_literal("without");
        static const WordKey unable_k = word_key_literal("unable");
        if (prev == not_k || prev == no_k || prev == never_k || prev == what_k || prev == where_k || prev == when_k || prev == why_k || prev == how_k) return false;
        if (next == not_k || next == no_k || next == never_k || next == without_k || next == unable_k) return false;
        return true;
    }

    return false;
}

std::string pronoun_replacement(WordKey lower, int mode) {
    if (mode == MODE_GENERAL) return {};
    static const std::unordered_set<WordKey> pronouns = make_key_set({
        "he", "she", "they", "him", "her", "them", "his", "hers", "their", "theirs"
    });
    if (pronouns.find(lower) != pronouns.end()) return "pt";
    return {};
}

std::string match_case(std::string_view original, const std::string& replacement) {
    bool upper = true;
    bool any_alpha = false;
    for (unsigned char c : original) {
        if (is_alpha(c)) {
            any_alpha = true;
            if (!(c >= 'A' && c <= 'Z')) upper = false;
        }
    }
    if (any_alpha && upper) {
        std::string out = replacement;
        for (char& c : out) if (c >= 'a' && c <= 'z') c = static_cast<char>(c - 32);
        return out;
    }
    if (!original.empty() && original[0] >= 'A' && original[0] <= 'Z' && !replacement.empty()) {
        std::string out = replacement;
        if (out[0] >= 'a' && out[0] <= 'z') out[0] = static_cast<char>(out[0] - 32);
        return out;
    }
    return replacement;
}

struct Builder {
    std::string out;
    int pending_space = 0; // 0 none, 1 space, 2 newline

    explicit Builder(size_t reserve_size) { out.reserve(reserve_size); }

    static bool is_close(char c) { return c == ',' || c == '.' || c == ';' || c == ':' || c == '!' || c == '?' || c == ')' || c == ']' || c == '}'; }
    static bool is_open(char c) { return c == '(' || c == '[' || c == '{'; }

    void trim_trailing_space() {
        while (!out.empty() && (out.back() == ' ' || out.back() == '\t')) out.pop_back();
    }

    void note_space(std::string_view raw) {
        if (raw.find('\n') != std::string_view::npos || raw.find('\r') != std::string_view::npos) pending_space = 2;
        else if (pending_space == 0) pending_space = 1;
    }

    void emit_pending() {
        if (pending_space == 0 || out.empty()) {
            pending_space = 0;
            return;
        }
        char last = out.back();
        if (last == ' ' || last == '\n' || last == '/' || is_open(last)) {
            pending_space = 0;
            return;
        }
        out.push_back(pending_space == 2 ? '\n' : ' ');
        pending_space = 0;
    }

    void append_wordlike(std::string_view s) {
        if (s.empty()) return;
        for (size_t i = 0; i < s.size(); ++i) {
            char c = s[i];
            if (is_space(static_cast<unsigned char>(c))) {
                if (c == '\n' || c == '\r') pending_space = 2;
                else if (pending_space == 0) pending_space = 1;
            } else if (is_close(c)) {
                trim_trailing_space();
                out.push_back(c);
                pending_space = 0;
            } else if (c == '/') {
                trim_trailing_space();
                out.push_back(c);
                pending_space = 0;
            } else if (c == '+' || c == '&') {
                trim_trailing_space();
                out.push_back(c);
                pending_space = 0;
            } else if (is_open(c)) {
                emit_pending();
                out.push_back(c);
                pending_space = 0;
            } else {
                emit_pending();
                out.push_back(c);
            }
        }
    }

    void append_protect(std::string_view s) {
        emit_pending();
        out.append(s.data(), s.size());
        pending_space = 0;
    }

    void finish() {
        while (!out.empty() && (out.back() == ' ' || out.back() == '\t' || out.back() == '\n' || out.back() == '\r')) out.pop_back();
        size_t start = 0;
        while (start < out.size() && (out[start] == ' ' || out[start] == '\t' || out[start] == '\n' || out[start] == '\r')) ++start;
        if (start) out.erase(0, start);
    }
};

std::string convert(const char* data, size_t len, int mode, int level, int keep_case, int bullets) {
    (void)keep_case;
    (void)bullets;
    std::string input(data, len);
    bool only_space = true;
    for (unsigned char c : input) {
        if (!is_space(c)) {
            only_space = false;
            break;
        }
    }
    if (only_space) return {};

    std::vector<Token> tokens = tokenize(input);
    Builder b(input.size());
    for (size_t i = 0; i < tokens.size(); ++i) {
        const Token& tok = tokens[i];
        if (tok.type == TokType::Space) {
            b.note_space(token_view(input, tok));
            continue;
        }
        if (tok.type == TokType::Protect) {
            b.append_protect(token_view(input, tok));
            continue;
        }
        if (tok.type == TokType::Other) {
            b.append_wordlike(token_view(input, tok));
            continue;
        }

        size_t end_idx = i + 1;
        if (const Phrase* p = match_phrase(tokens, i, mode, level, end_idx)) {
            b.append_wordlike(p->replacement);
            i = end_idx - 1;
            continue;
        }

        if (should_drop_word(input, tokens, i, mode, level)) continue;

        std::string repl = pronoun_replacement(tok.key, mode);
        if (!repl.empty()) b.append_wordlike(match_case(token_view(input, tok), repl));
        else b.append_wordlike(token_view(input, tok));
    }
    b.finish();
    return b.out;
}

} // namespace

extern "C" const char* caveman_native_version() {
    return "1";
}

char* copy_to_c_buffer(const std::string& out, size_t* out_len) {
    char* buf = static_cast<char*>(std::malloc(out.size() + 1));
    if (!buf) return nullptr;
    if (!out.empty()) std::memcpy(buf, out.data(), out.size());
    buf[out.size()] = '\0';
    if (out_len) *out_len = out.size();
    return buf;
}

extern "C" char* caveman_native_convert(const char* data, size_t len, int mode, int level, int keep_case, int bullets, size_t* out_len) {
    try {
        std::string out = convert(data ? data : "", data ? len : 0, mode, level, keep_case, bullets);
        return copy_to_c_buffer(out, out_len);
    } catch (...) {
        return nullptr;
    }
}

extern "C" int caveman_native_convert_many(
    const char** data,
    const size_t* lens,
    size_t count,
    int mode,
    int level,
    int keep_case,
    int bullets,
    char*** out_data,
    size_t** out_lens
) {
    if (!out_data || !out_lens) return 0;
    *out_data = nullptr;
    *out_lens = nullptr;
    try {
        char** outputs = static_cast<char**>(std::calloc(count, sizeof(char*)));
        size_t* lengths = static_cast<size_t*>(std::calloc(count, sizeof(size_t)));
        if (!outputs || !lengths) {
            std::free(outputs);
            std::free(lengths);
            return 0;
        }
        for (size_t i = 0; i < count; ++i) {
            std::string out = convert(data[i] ? data[i] : "", data[i] ? lens[i] : 0, mode, level, keep_case, bullets);
            outputs[i] = copy_to_c_buffer(out, &lengths[i]);
            if (!outputs[i]) {
                for (size_t j = 0; j < i; ++j) std::free(outputs[j]);
                std::free(outputs);
                std::free(lengths);
                return 0;
            }
        }
        *out_data = outputs;
        *out_lens = lengths;
        return 1;
    } catch (...) {
        return 0;
    }
}

extern "C" char* caveman_native_convert_many_joined(
    const char** data,
    const size_t* lens,
    size_t count,
    int mode,
    int level,
    int keep_case,
    int bullets,
    const char* separator,
    size_t separator_len,
    size_t* out_len
) {
    try {
        size_t reserve = separator_len * (count ? count - 1 : 0);
        for (size_t i = 0; i < count; ++i) reserve += data[i] ? lens[i] : 0;
        std::string joined;
        joined.reserve(reserve);
        for (size_t i = 0; i < count; ++i) {
            if (i != 0 && separator && separator_len) joined.append(separator, separator_len);
            std::string out = convert(data[i] ? data[i] : "", data[i] ? lens[i] : 0, mode, level, keep_case, bullets);
            joined.append(out);
        }
        return copy_to_c_buffer(joined, out_len);
    } catch (...) {
        return nullptr;
    }
}

extern "C" void caveman_native_free(char* ptr) {
    std::free(ptr);
}

extern "C" void caveman_native_free_many(char** data, size_t* lens, size_t count) {
    (void)lens;
    if (data) {
        for (size_t i = 0; i < count; ++i) std::free(data[i]);
    }
    std::free(data);
    std::free(lens);
}