/**
 * Per-language Processor abstraction.
 *
 * A `LanguageProcessor` bundles the work needed for one language:
 * text normalization (`preProcess`) and grapheme-to-phoneme prediction
 * (`predict`). The registry below dispatches by BCP 47 language tag with
 * script-based fallback.
 *
 * The exported `languageRegistry` and free functions wrap a default global
 * registry to preserve the simple `phonemize("hello")` API. To create
 * isolated registrations (so multiple language sets can coexist without
 * stepping on each other) use `new LanguageRegistry()` directly or the
 * higher-level `createPhonemizer()` factory in `core.ts`.
 */
/**
 * A language-bound processor: text normalization plus grapheme-to-phoneme
 * prediction. Each implementation handles a single language's full
 * preProcess → predict pipeline; the registry dispatches per-segment.
 */
export interface LanguageProcessor {
    /**
     * Unique identifier for this processor
     */
    readonly id: string;
    /**
     * Human-readable name for this processor
     */
    readonly name: string;
    /**
     * Languages this processor can handle. Use BCP 47-style tags; the
     * registry treats `en` as a parent of `en-US`/`en-GB` etc., so a
     * processor that lists `["en"]` will be selected for `en-GB` requests
     * unless a more specific processor is also registered.
     */
    readonly supportedLanguages: string[];
    /**
     * Optional language-specific text normalization. Run before tokenization
     * on a slice of text identified as this processor's language; should
     * expand numbers, abbreviations, currency, dates, etc. into spoken form.
     *
     * Processors without language-specific normalization can leave this
     * undefined — the tokenizer treats the absence as a no-op.
     */
    preProcess?(text: string): string;
    /**
     * Optional per-token POS tag (mostly for homograph disambiguation).
     * The tokenizer routes each token to the matching processor's
     * `tagWord` (if any), then passes the returned `pos` into `predict`.
     * Context is the (optional) immediately neighboring tokens in original
     * surface form so taggers that need local cues — preceding determiner,
     * following particle, etc. — have access to them.
     *
     * Implementations that don't model POS should leave this undefined;
     * `predict` will then receive `undefined` for the `pos` argument.
     */
    tagWord?(word: string, context?: {
        prev?: string;
        next?: string;
    }): {
        pos: string;
        confidence: number;
    } | null;
    /**
     * Predict phonemes for a given word
     *
     * @param word - Word to convert to phonemes
     * @param language - Language code (optional, for disambiguation)
     * @param pos - Part of speech (optional, for homograph disambiguation)
     * @returns Phoneme string in IPA format, or null if cannot process
     */
    predict(word: string, language?: string, pos?: string): string | null;
    /**
     * Add a custom pronunciation for a word
     *
     * @param word - Word to add pronunciation for
     * @param pronunciation - IPA pronunciation string
     */
    addPronunciation(word: string, pronunciation: string): void;
}
/**
 * Return the primary language subtag — `en-GB` → `en`, `zh-Hant-TW` → `zh`.
 */
export declare function primaryLang(tag: string): string;
/**
 * Normalize a BCP 47 tag for comparison. The spec defines tag matching
 * as case-insensitive, so we lowercase before any equality check.
 */
export declare function normalizeTag(tag: string): string;
export declare class LanguageRegistry {
    private processors;
    /** Insertion order for stable "first registered wins" semantics. */
    private order;
    register(processor: LanguageProcessor): void;
    unregister(id: string): boolean;
    getProcessor(id: string): LanguageProcessor | undefined;
    getProcessorsForLanguage(language: string): LanguageProcessor[];
    getAllProcessors(): LanguageProcessor[];
    /**
     * Find the best processor for a given word and language. When a
     * language is provided, dialect-exact processors are tried first,
     * then parent-tag processors. With no language we fall back to the
     * first registered processor.
     */
    findBestProcessor(_word: string, language?: string): LanguageProcessor | null;
    predictPhonemes(word: string, language?: string, pos?: string): string | null;
    clear(): void;
    getSupportedLanguages(): string[];
}
export declare const languageRegistry: LanguageRegistry;
/**
 * Detect the language of the given text based on Unicode character ranges
 *
 * @param text - Text to detect language for
 * @returns Language code or null if not detected
 */
export declare function detectLanguage(text: string): string | null;
/**
 * Whole-text analysis: identifies the dominant language by character share
 * and resolves the CJK Han ambiguity (`zh` vs `ja`).
 *
 * Han routing heuristic — only **hiragana cluster count** drives the
 * zh/ja decision:
 *
 *   `hanIsJa` is true when the text has **two or more** separate
 *   hiragana clusters — i.e. multiple grammatical particles or verb
 *   inflections (は・を・が・で・に・ます・です・ている・…) interleaved
 *   with non-hiragana chars, which is the structural signature of
 *   Japanese prose.
 *
 *   Why hiragana cluster *count* rather than length or katakana:
 *
 *   - Taiwan-Chinese text routinely embeds Japanese *loanwords* —
 *     usually katakana (ラーメン, コーヒー, ドラマ) but sometimes
 *     hiragana for food (うどん, おでん, やきとり). These appear as
 *     one isolated kana cluster surrounded by Han, so length-based
 *     rules misfire ("max contiguous hira ≥ 2" would flip `我超愛うどん`
 *     to ja). Counting *separate* clusters skips this class entirely:
 *     a single loanword contributes one cluster.
 *
 *   - Decorative single hiragana — overwhelmingly `の`, which academic
 *     surveys (Karen S. Chung, *Some Returned Loans*) treat as
 *     functionally identical to 之/的 in Taiwan Mandarin — also contribute
 *     one cluster, so they stay below the threshold.
 *
 *   - Real Japanese sentences almost always contain two or more
 *     separate hiragana clusters (subject-marker は + verb ending ます,
 *     or any combination of particles). Even short ones like 田中さんは
 *     trip the rule via `さん` + `は`.
 *
 *   Trade-offs:
 *     - Single-particle Japanese fragments (`今日は`, `頑張って`) and
 *       single-word Japanese (`私の本`) won't trip the flag — accepted
 *       as out-of-context ambiguity that needs `options.language: "ja"`
 *       to disambiguate.
 *     - Pure-katakana brand names (`スターバックス`) and kana-only
 *       words (`ねこ`, `こんにちは`) don't need hanIsJa anyway: they
 *       carry no Han to mis-dispatch, and the character-share count
 *       below still puts `ja` on top so neutral runs route correctly.
 *
 * Primary language is the bucket with the most characters after han→ja
 * reassignment, or undefined when the text has no non-neutral chars at all.
 */
export interface TextAnalysis {
    /** Dominant language by character share; undefined for pure-neutral text. */
    primary?: string;
    /** When true, Han chars in this text should be routed as Japanese. */
    hanIsJa: boolean;
}
export declare function analyzeText(text: string): TextAnalysis;
export interface ScriptRun {
    text: string;
    /** Detected language code; empty string when the run contains only neutrals. */
    lang: string;
}
/**
 * Split text into script-based runs. Neutral characters (digits, whitespace,
 * punctuation) attach to the surrounding non-neutral run instead of starting
 * a new one, so e.g. `"我有 3 本书"` stays one Chinese run rather than
 * fragmenting into `[zh, en-digit, zh]`.
 *
 * Pass `hanIsJa: true` (typically from a prior `analyzeText` call) to route
 * Han chars through the Japanese path instead of the default Chinese path —
 * essential for Japanese prose, which mixes kanji with kana.
 */
export declare function splitByScript(text: string, options?: {
    hanIsJa?: boolean;
}): ScriptRun[];
/**
 * Run each script-run through its language processor's `preProcess`.
 *
 * Resolution order for each run's effective language:
 * 1. The run's own detected script (zh/ja/ko/…)
 * 2. The caller-supplied `defaultLang` (e.g. `options.language`)
 * 3. The document's primary language inferred by `analyzeText`
 *
 * Step 3 ensures that pure-neutral inputs like `"123"` get the right
 * expansion when the surrounding context (or another part of the same
 * call) is non-English.
 */
export declare function preProcessByScript(text: string, registry: LanguageRegistry, defaultLang?: string): string;
/**
 * Register a language processor on the default global registry.
 *
 * For multi-instance setups, prefer `createPhonemizer()` from `./core`.
 */
export declare function useProcessor(processor: LanguageProcessor): void;
export declare function findProcessor(word: string, language?: string): LanguageProcessor | null;
export declare function predictPhonemes(word: string, language?: string, pos?: string): string | null;
export declare function getRegisteredProcessorIds(): string[];
export declare function getProcessorsForLanguage(language: string): LanguageProcessor[];
export declare function getSupportedLanguages(): string[];
