//! NER backend implementations.
//!
//! Each backend implements the `Model` trait for consistent usage.
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────┐
//! │ Layer 3: ML Backends (feature-gated)                │
//! │                                                     │
//! │  Zero-Shot NER (any entity type):                   │
//! │   - GLiNER: Bi-encoder span classification          │
//! │   - NuNER: Token classification (arbitrary length)  │
//! │                                                     │
//! │  Complex Structures (nested/discontinuous):         │
//! │   - W2NER: Word-word relation grids                 │
//! │                                                     │
//! │  Traditional (fixed types):                         │
//! │   - BertNEROnnx: Sequence labeling                  │
//! ├─────────────────────────────────────────────────────┤
//! │ Layer 2: HeuristicNER (zero deps)                   │
//! │   Person/Org/Location via heuristics                │
//! ├─────────────────────────────────────────────────────┤
//! │ Layer 1: RegexNER (zero deps)                     │
//! │   Date/Time/Money/Email/URL/Phone                   │
//! └─────────────────────────────────────────────────────┘
//! ```
//!
//! # Backend Comparison
//!
//! | Backend | Feature | Zero-Shot | Nested | Notes |
//! |---------|---------|-----------|--------|-------|
//! | `StackedNER` | - | No | No | Composable with any backend |
//! | `RegexNER` | - | No | No | Structured entities only |
//! | `HeuristicNER` | - | No | No | Simple heuristic baseline |
//! | `GLiNER` | `onnx` | Yes | No | Span-based |
//! | `NuNER` | `onnx` | Yes | No | Token-based |
//! | `W2NER` | `onnx` | No | Yes | Grid-based |
//! | `BertNEROnnx` | `onnx` | No | No | Traditional fixed-label NER |
//!
//! # When to Use What
//!
//! - **Default choice**: `NERExtractor::best_available()` (picks the best available backend at
//!   runtime, based on enabled features)
//! - **Zero deps**: `StackedNER::default()` - no ML, good baseline
//! - **Hybrid approach**: `StackedNER` with ML backends - combine ML accuracy with pattern speed
//! - **Custom types**: `GLiNER` or `NuNER` - zero-shot, any entity type
//! - **Nested entities**: `W2NER` - handles overlapping spans
//! - **Structured data**: `RegexNER` - dates, emails, money
//!
//! # Backend Combination Design Space
//!
//! Two approaches for combining multiple backends:
//!
//! | Combiner | Execution | Conflict Resolution | Best For |
//! |----------|-----------|---------------------|----------|
//! | [`StackedNER`] | Sequential (cascade) | Priority/LongestSpan/HighestConf | Production, latency |
//! | [`EnsembleNER`] | Parallel (all) | Weighted voting + agreement | Maximum accuracy |
//!
//! **StackedNER** runs backends in layer order. Earlier layers claim spans first.
//! Good for: fast execution, structured patterns + ML fill-in.
//!
//! **EnsembleNER** runs ALL backends, groups overlapping spans into conflict clusters,
//! and resolves via weighted voting with type-conditioned weights and agreement bonuses.
//! Good for: maximum accuracy when latency allows.
//!
//! Both accept any `Model` implementation - they're fully composable with ML backends.
//!
//! # Quick Start
//!
//! Zero-dependency default (Pattern + Heuristic):
//!
//! ```rust
//! use anno::{Model, StackedNER};
//!
//! let ner = StackedNER::default();
//! let entities = ner.extract_entities("Dr. Smith charges $100/hr", None).unwrap();
//! ```
//!
//! Custom stack with pattern + heuristic:
//!
//! ```rust
//! use anno::{Model, RegexNER, HeuristicNER, StackedNER};
//! use anno::backends::stacked::ConflictStrategy;
//!
//! let ner = StackedNER::builder()
//!     .layer(RegexNER::new())
//!     .layer(HeuristicNER::new())
//!     .strategy(ConflictStrategy::LongestSpan)
//!     .build();
//! ```
//!
//! **StackedNER is fully composable** - you can combine ML backends with pattern/heuristic layers:
//!
//! ```rust,no_run
//! #[cfg(feature = "onnx")]
//! {
//! use anno::{Model, StackedNER, GLiNEROnnx, RegexNER, HeuristicNER};
//! use anno::backends::stacked::ConflictStrategy;
//!
//! // ML-first: ML runs first, then patterns fill gaps
//! let ner = StackedNER::with_ml_first(
//!     Box::new(GLiNEROnnx::new("onnx-community/gliner_small-v2.1").unwrap())
//! );
//!
//! // ML-fallback: patterns/heuristics first, ML as fallback
//! let ner = StackedNER::with_ml_fallback(
//!     Box::new(GLiNEROnnx::new("onnx-community/gliner_small-v2.1").unwrap())
//! );
//!
//! // Custom stack: any combination of backends
//! let ner = StackedNER::builder()
//!     .layer(RegexNER::new())           // High-precision structured entities
//!     .layer_boxed(Box::new(GLiNEROnnx::new("onnx-community/gliner_small-v2.1").unwrap()))  // ML layer
//!     .layer(HeuristicNER::new())       // Quick named entities
//!     .strategy(ConflictStrategy::HighestConf)  // Resolve conflicts by confidence
//!     .build();
//! }
//! ```

/// Macros for generating feature-gated backend stubs.
#[macro_use]
pub(crate) mod macros;

/// Shared HuggingFace model loading and ONNX session construction utilities.
#[cfg(any(feature = "onnx", feature = "candle"))]
pub(crate) mod hf_loader;

/// Coreference resolution backends (trait, neural, heuristic).
pub mod coref;

// Always available (zero deps beyond std)
/// BiLSTM + CRF NER - neural baseline from 2015-2018.
///
/// Bidirectional LSTM with Conditional Random Field output layer.
/// The dominant neural NER architecture before BERT/transformers.
pub mod bilstm_crf;
pub mod catalog;
pub mod crf;
pub mod encoder;
pub mod event_extractor;
pub mod extractor;
pub mod heuristic;
pub mod inference;
/// Label prompt normalization for zero-shot NER systems.
pub mod label_prompt;
pub mod lexicon;
pub mod nuner;
pub(crate) mod ort_compat;
pub mod regex;
/// Language-aware routing for automatic backend selection.
pub(crate) mod router;
/// Shared span-tensor utilities for span-based NER backends (GLiNER/NuNER family).
#[allow(dead_code)]
pub(crate) mod span_utils;
pub mod stacked;
pub mod tplinker;
pub mod w2ner;

/// Ensemble NER - weighted voting across multiple backends.
///
/// Unlike `StackedNER` (priority-based layers), `EnsembleNER` collects
/// candidates from ALL backends and resolves conflicts via weighted voting
/// with agreement bonuses.
pub mod ensemble;

/// Hidden Markov Model NER - classical statistical approach.
///
/// Implements HMM-based sequence labeling, the dominant approach from the 1990s
/// before CRFs. Useful as a baseline and for understanding NER history.
pub mod hmm;

/// Middleware pipeline for preprocessing and postprocessing.
///
/// Provides a chain-of-responsibility pattern for transforming text before
/// entity extraction and filtering/enriching entities afterward.
pub mod middleware;

/// Streaming NER for incremental entity extraction.
///
/// Iterator-based API for processing large documents in chunks,
/// with backpressure support and boundary handling.
pub mod streaming;

/// Chunking helpers for long text.
///
/// Always provides a lightweight rule-based chunker (paragraph boundaries + size limits + overlap).
/// With the `semantic-chunking` feature enabled, adds a sentence-similarity chunker (no embeddings).
pub mod semantic_chunking;

/// Map a backend name (stable ID used in stacked/ensemble compositions) to an
/// [`ExtractionMethod`](anno_core::ExtractionMethod).
///
/// Shared by `StackedNER` and `EnsembleNER` so the mapping stays consistent.
pub(crate) fn method_for_backend_name(name: &str) -> anno_core::ExtractionMethod {
    match name {
        // Stable IDs used by built-in compositions.
        "regex" => anno_core::ExtractionMethod::Pattern,
        "heuristic" => anno_core::ExtractionMethod::Heuristic,
        // Legacy backend id (deprecated, but still used in tests/compositions).
        "rule" => anno_core::ExtractionMethod::Heuristic,
        // Everything else: treat as neural by default.
        _ => anno_core::ExtractionMethod::Neural,
    }
}

// =============================================================================
// Tests for shared utilities
// =============================================================================

#[cfg(test)]
mod tests {
    use super::method_for_backend_name;
    use anno_core::ExtractionMethod;

    // -------------------------------------------------------------------------
    // method_for_backend_name: exact stable IDs
    // -------------------------------------------------------------------------

    #[test]
    fn regex_maps_to_pattern() {
        assert_eq!(
            method_for_backend_name("regex"),
            ExtractionMethod::Pattern,
            "\"regex\" must map to Pattern"
        );
    }

    #[test]
    fn heuristic_maps_to_heuristic() {
        assert_eq!(
            method_for_backend_name("heuristic"),
            ExtractionMethod::Heuristic,
            "\"heuristic\" must map to Heuristic"
        );
    }

    #[test]
    fn rule_legacy_maps_to_heuristic() {
        // "rule" is the deprecated legacy ID for the old RuleBasedNER.
        // It must map to Heuristic so provenance stays consistent with
        // compositions that still use the old ID.
        assert_eq!(
            method_for_backend_name("rule"),
            ExtractionMethod::Heuristic,
            "\"rule\" (deprecated) must map to Heuristic for backwards compatibility"
        );
    }

    // -------------------------------------------------------------------------
    // method_for_backend_name: wildcard / unknown IDs -> Neural
    // -------------------------------------------------------------------------

    #[test]
    fn unknown_ids_map_to_neural() {
        // Any backend name that is not one of the three stable IDs must fall
        // back to Neural.  This covers ML backends (gliner, bert, nuner, candle)
        // as well as completely arbitrary strings.
        let neural_names = [
            "gliner",
            "gliner-candle",
            "GLiNER-ONNX",
            "bert-ner-onnx",
            "bert-onnx",
            "nuner",
            "NuNER",
            "w2ner",
            "llm",
            "custom-backend",
            "",
            "REGEX",     // case-sensitive: must NOT match "regex"
            "Heuristic", // case-sensitive: must NOT match "heuristic"
            "Rule",      // case-sensitive: must NOT match "rule"
        ];
        for name in neural_names {
            assert_eq!(
                method_for_backend_name(name),
                ExtractionMethod::Neural,
                "\"{}\" should map to Neural (unknown/wildcard ID)",
                name
            );
        }
    }

    #[test]
    fn ensemble_nested_id_maps_to_neural() {
        // An EnsembleNER's transparent name looks like "ensemble(regex|heuristic)".
        // The composite string is not one of the stable keys, so it must map to
        // Neural — the caller is responsible for extracting inner provenance.
        let name = "ensemble(regex|heuristic)";
        assert_eq!(
            method_for_backend_name(name),
            ExtractionMethod::Neural,
            "composite ensemble ID '{}' should map to Neural",
            name
        );
    }

    #[test]
    fn stacked_id_maps_to_neural() {
        let name = "stacked(regex|heuristic)";
        assert_eq!(
            method_for_backend_name(name),
            ExtractionMethod::Neural,
            "composite stacked ID '{}' should map to Neural",
            name
        );
    }

    // -------------------------------------------------------------------------
    // Stability: the three exact matches remain stable under whitespace
    // -------------------------------------------------------------------------

    #[test]
    fn match_is_exact_no_trimming() {
        // Whitespace-padded variants are NOT trimmed; they should fall through
        // to Neural, confirming the match is exact byte-equality.
        for padded in &[" regex", "regex ", " heuristic", "heuristic\n", "\trule"] {
            assert_eq!(
                method_for_backend_name(padded),
                ExtractionMethod::Neural,
                "whitespace-padded name '{}' should not match a stable ID",
                padded
            );
        }
    }
}

// Burn ML framework (training + inference)
#[cfg(feature = "burn")]
pub mod burn;

// Advanced backends
#[cfg(feature = "onnx")]
pub mod albert;
#[cfg(feature = "onnx")]
pub mod deberta_v3;
pub mod gliner_poly;
/// GLiREL: Zero-shot relation extraction via ONNX.
///
/// Uses a DeBERTa-v3 encoder with relation scoring head from the GLiREL family.
/// Export models with `scripts/export_glirel_onnx.py`.
pub mod glirel;
pub mod universal_ner;

// LLM client abstraction (config, providers, mock)
pub mod llm_client;

// LLM-based NER prompting (CodeNER-style)
pub(crate) mod llm_prompt;

// Demonstration selection for few-shot NER (CMAS-inspired)
#[allow(dead_code)]
pub(crate) mod demonstration;

// GLiNER via ONNX (uses same feature as other ONNX models)

// ONNX implementations
#[cfg(feature = "onnx")]
pub mod gliner_onnx;

#[cfg(feature = "onnx")]
pub mod onnx;

// Pure Rust via Candle
#[cfg(feature = "candle")]
pub mod candle;

#[cfg(feature = "candle")]
pub mod encoder_candle;

#[cfg(feature = "candle")]
pub mod gliner_candle;

#[cfg(feature = "candle")]
pub mod gliner_pipeline;

// GLiNER2 multi-task extraction (ONNX or Candle)
#[cfg(any(feature = "onnx", feature = "candle"))]
pub mod gliner2;

// Production infrastructure
#[cfg(feature = "production")]
pub mod async_adapter;

#[cfg(all(feature = "production", feature = "onnx"))]
pub mod session_pool;

// Model warmup for cold-start mitigation
pub mod warmup;

// Re-exports (always available)
pub use bilstm_crf::BiLstmCrfNER;
pub use crf::CrfNER;
pub use ensemble::EnsembleNER;
pub use event_extractor::{Event, EventExtractor, RuleBasedEventExtractor};
pub use extractor::{BackendType, NERExtractor};
pub use heuristic::HeuristicNER;
pub use lexicon::LexiconNER;
pub use nuner::NuNER;
pub use regex::RegexNER;
pub use router::AutoNER;
pub use stacked::{ConflictStrategy, StackedNER};

/// Backwards compatibility alias for `HeuristicNER`.
///
/// The name "StatisticalNER" was misleading since it doesn't use
/// statistical/probabilistic methods like CRF or HMM - it uses
/// capitalization and context heuristics.
#[deprecated(
    since = "0.3.0",
    note = "Use HeuristicNER instead - StatisticalNER was misleading"
)]
pub type StatisticalNER = HeuristicNER;
pub use tplinker::TPLinker;
pub use w2ner::{W2NERConfig, W2NERRelation, W2NER};

// Advanced backends
#[cfg(feature = "onnx")]
pub use albert::ALBERTNER;
#[cfg(feature = "onnx")]
pub use deberta_v3::DeBERTaV3NER;
pub use gliner_poly::GLiNERPoly;
pub use glirel::GLiREL;
pub use universal_ner::UniversalNER;

// Backwards compatibility
#[allow(deprecated)]
pub use stacked::{CompositeNER, LayeredNER, TieredNER};

// Re-exports (feature-gated)
#[cfg(feature = "onnx")]
pub use gliner_onnx::GLiNEROnnx;

#[cfg(feature = "onnx")]
pub use onnx::BertNEROnnx;

#[cfg(feature = "candle")]
pub use candle::CandleNER;

#[cfg(feature = "candle")]
pub use encoder_candle::{EncoderArchitecture, EncoderConfig};

#[cfg(feature = "candle")]
pub use gliner_candle::GLiNERCandle;

// GLiNER2 multi-task model
#[cfg(any(feature = "onnx", feature = "candle"))]
pub use gliner2::{
    ClassificationResult, ClassificationTask, EntityTask, ExtractedStructure, ExtractionResult,
    FieldType, GLiNER2, StructureTask, StructureValue, TaskSchema,
};

#[cfg(feature = "onnx")]
pub use gliner2::GLiNER2Onnx;

#[cfg(feature = "candle")]
pub use gliner2::GLiNER2Candle;

// Production infrastructure re-exports
#[cfg(feature = "production")]
pub use async_adapter::{batch_extract, batch_extract_limited, AsyncNER, IntoAsync};

#[cfg(all(feature = "production", feature = "onnx"))]
pub use session_pool::{GLiNERPool, PoolConfig, SessionPool};

// T5 coreference
#[cfg(feature = "onnx")]
pub use coref::t5::{CorefCluster, T5Coref, T5CorefConfig};

// F-coref neural coreference
#[cfg(feature = "onnx")]
pub use coref::fcoref::{FCoref, FCorefConfig};

// Config re-exports (for quantization control)
#[cfg(feature = "onnx")]
pub use gliner_onnx::GLiNERConfig;

#[cfg(feature = "onnx")]
pub use onnx::BertNERConfig;

// Warmup utilities (always available)
pub use warmup::{warmup_model, warmup_with_callback, WarmupConfig, WarmupResult};

// Coreference resolution trait (from anno-core, always available)
pub use anno_core::CoreferenceResolver;

// Unified coref backend trait
pub use coref::resolve::CorefBackend;

// Classical HMM NER (zero deps)
pub use hmm::{HmmConfig, HmmNER};

// Streaming NER utilities
pub use streaming::{
    deduplicate_overlapping, ChunkConfig, EntityIterator, OverlapStrategy, StreamingExtractor,
};

// Middleware pipeline
pub use middleware::{
    AddProvenance, Callback, FilterByConfidence, FilterByType, HookEvent, HookedPipeline,
    MergeAdjacent, Middleware, MiddlewareContext, NormalizeWhitespace,
    Pipeline as MiddlewarePipeline, PipelineStageAdapter, RemoveOverlaps,
};

// Burn ML framework (trainable)
#[cfg(feature = "burn")]
pub use burn::{BurnConfig, BurnNER};

// Simple rule-based coreference resolvers.
#[cfg(all(feature = "eval", feature = "discourse"))]
pub use crate::eval::coref_resolver::{DiscourseAwareResolver, DiscourseCorefConfig};
#[cfg(any(feature = "analysis", feature = "eval"))]
pub use coref::simple::{CorefConfig, SimpleCorefResolver};
