"""
Linguistics API endpoints for SLTK.

Provides REST endpoints for phonological analysis, inter-rater reliability metrics,
and non-manual signal analysis for sign language research.
"""

from __future__ import annotations

import logging
from typing import Any, Literal

from fastapi import APIRouter, HTTPException, Query, Request
from pydantic import BaseModel, Field

from sltk.api.dependencies import (
    get_cached_dataset,
    load_dataset_segments,
    rate_limit,
)

router = APIRouter(prefix="/api/linguistics", tags=["linguistics"])
logger = logging.getLogger(__name__)


# ============================================================================
# Pydantic Models - Phonology
# ============================================================================


class PhonologicalFormRequest(BaseModel):
    """Request body for creating/validating a phonological form."""

    handshape_dom: str = Field(..., description="Dominant hand handshape")
    handshape_nondom: str | None = Field(None, description="Non-dominant hand handshape")
    location: str = Field(..., description="Place of articulation")
    movement: str = Field(..., description="Movement type")
    orientation_palm: str = Field(..., description="Palm orientation")
    orientation_fingers: str = Field(..., description="Finger orientation")
    hand_arrangement: str | None = Field(None, description="Hand arrangement for two-handed signs")
    nms_cooccurring: list[str] = Field(
        default_factory=list, description="Co-occurring non-manual signals"
    )
    metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata")


class PhonologicalDistanceRequest(BaseModel):
    """Request body for calculating phonological distance."""

    form1: PhonologicalFormRequest
    form2: PhonologicalFormRequest
    include_nondom: bool = Field(True, description="Include non-dominant handshape in comparison")
    include_arrangement: bool = Field(False, description="Include hand arrangement in comparison")


class PhonologicalMinimalPairsRequest(BaseModel):
    """Request body for finding minimal pairs using phonological forms."""

    dataset: str = Field(..., description="Dataset name")
    tier: str = Field("gloss", description="Tier to extract glosses from")
    max_distance: int = Field(1, ge=1, le=5, description="Maximum phonological distance")
    include_nondom: bool = Field(False, description="Include non-dominant handshape in comparison")
    max_pairs: int = Field(100, ge=1, le=500, description="Maximum pairs to return")
    max_samples: int = Field(500, ge=1, le=2000, description="Maximum samples to analyze")


class NeighborhoodRequest(BaseModel):
    """Request body for computing phonological neighborhood."""

    target: PhonologicalFormRequest
    forms: dict[str, PhonologicalFormRequest] = Field(
        ..., description="Dictionary of sign names to phonological forms"
    )
    max_distance: int = Field(
        1, ge=1, le=5, description="Maximum distance to include in neighborhood"
    )


# ============================================================================
# Pydantic Models - Reliability
# ============================================================================


class ReliabilityKappaRequest(BaseModel):
    """Request body for Cohen's/Fleiss' Kappa calculation."""

    ratings: list[list[str]] = Field(
        ..., description="List of rating lists (one per rater)", min_length=2
    )
    method: Literal["cohen", "fleiss"] = Field(
        "cohen", description="Kappa method: cohen (2 raters) or fleiss (n raters)"
    )


class KrippendorffRequest(BaseModel):
    """Request body for Krippendorff's Alpha calculation."""

    ratings: list[list[str | None]] = Field(
        ..., description="List of rating lists with optional None for missing data", min_length=2
    )
    level: Literal["nominal", "ordinal", "interval", "ratio"] = Field(
        "nominal", description="Measurement level for distance metric"
    )


class BoundaryAgreementRequest(BaseModel):
    """Request body for boundary agreement calculation."""

    segments1: list[dict[str, Any]] = Field(
        ..., description="Segments from first rater (each with start, end, label)"
    )
    segments2: list[dict[str, Any]] = Field(..., description="Segments from second rater")
    tolerance_sec: float = Field(
        0.1, ge=0, le=1.0, description="Time tolerance for boundary matching"
    )
    tier: str | None = Field(None, description="Optional tier filter")


class LabelAgreementRequest(BaseModel):
    """Request body for temporal label agreement calculation."""

    segments1: list[dict[str, Any]] = Field(..., description="Segments from first rater")
    segments2: list[dict[str, Any]] = Field(..., description="Segments from second rater")
    tolerance_sec: float = Field(
        0.1, ge=0, le=1.0, description="Time tolerance for segment alignment"
    )
    tier: str | None = Field(None, description="Optional tier filter")


class ConfusionMatrixRequest(BaseModel):
    """Request body for confusion matrix generation."""

    rater1_labels: list[str] = Field(..., description="Labels from first rater")
    rater2_labels: list[str] = Field(..., description="Labels from second rater (same order)")


class ReliabilityAnalysisRequest(BaseModel):
    """Request body for full reliability analysis."""

    dataset: str = Field(..., description="Dataset name")
    tiers: list[str] | None = Field(None, description="Tiers to analyze (None for all)")
    max_samples: int = Field(100, ge=1, le=500, description="Maximum samples to compare")


# ============================================================================
# Pydantic Models - Non-Manual Signals
# ============================================================================


class NMSCooccurrenceRequest(BaseModel):
    """Request body for NMS co-occurrence analysis."""

    dataset: str = Field(..., description="Dataset name")
    sample_ids: list[str] | None = Field(None, description="Optional sample IDs to analyze")
    manual_tier: str = Field("gloss", description="Tier containing manual signs")
    nms_tiers: list[str] | None = Field(None, description="NMS tier names (auto-detected if None)")
    overlap_tolerance: float = Field(
        0.05, ge=0, le=1.0, description="Time tolerance for co-occurrence"
    )
    max_samples: int = Field(500, ge=1, le=2000, description="Maximum samples to analyze")


class NMSScopeRequest(BaseModel):
    """Request body for NMS scope analysis."""

    dataset: str = Field(..., description="Dataset name")
    nms_tier: str = Field(..., description="NMS tier to analyze")
    manual_tier: str = Field("gloss", description="Tier containing manual signs")
    max_examples: int = Field(10, ge=1, le=50, description="Maximum scope examples to return")
    max_samples: int = Field(500, ge=1, le=2000, description="Maximum samples to analyze")


class NMSPatternsRequest(BaseModel):
    """Request body for grammatical pattern detection."""

    dataset: str = Field(..., description="Dataset name")
    pattern_type: Literal["question", "negation", "topic", "conditional"] | None = Field(
        None, description="Specific pattern type (None for all)"
    )
    confidence_threshold: float = Field(
        0.3, ge=0, le=1.0, description="Minimum confidence for pattern detection"
    )
    max_samples: int = Field(500, ge=1, le=2000, description="Maximum samples to analyze")


class NMSTimingRequest(BaseModel):
    """Request body for NMS timing analysis."""

    dataset: str = Field(..., description="Dataset name")
    nms_tier: str = Field(..., description="NMS tier to analyze")
    manual_tier: str = Field("gloss", description="Tier containing manual signs")
    coincidence_tolerance: float = Field(
        0.05, ge=0, le=0.5, description="Time window for coincident classification"
    )
    max_samples: int = Field(500, ge=1, le=2000, description="Maximum samples to analyze")


class NMSDurationRequest(BaseModel):
    """Request body for NMS duration statistics."""

    dataset: str = Field(..., description="Dataset name")
    nms_tier: str = Field(..., description="NMS tier to analyze")
    max_samples: int = Field(500, ge=1, le=2000, description="Maximum samples to analyze")


# ============================================================================
# Phonology Endpoints
# ============================================================================


@router.post("/phonological-form")
async def create_phonological_form(body: PhonologicalFormRequest):
    """
    Create and validate a phonological form.

    Returns the normalized form with enum-validated values where possible.
    """
    from sltk.linguistics import PhonologicalForm

    try:
        form = PhonologicalForm(
            handshape_dom=body.handshape_dom,
            handshape_nondom=body.handshape_nondom,
            location=body.location,
            movement=body.movement,
            orientation_palm=body.orientation_palm,
            orientation_fingers=body.orientation_fingers,
            hand_arrangement=body.hand_arrangement,
            nms_cooccurring=body.nms_cooccurring,
            metadata=body.metadata,
        )

        return {
            "valid": True,
            "form": form.to_dict(),
            "is_one_handed": form.is_one_handed,
            "is_two_handed": form.is_two_handed,
            "has_nms": form.has_nms,
            "parameters": form.parameters,
        }
    except Exception as e:
        return {
            "valid": False,
            "error": str(e),
        }


@router.get("/inventory")
async def get_phonological_inventory():
    """
    Get the standard phonological inventory enums.

    Returns all available values for handshapes, locations, movements,
    palm orientations, finger orientations, hand arrangements, and non-manual signals.
    """
    from sltk.linguistics import (
        FingerOrientation,
        HandArrangement,
        Handshape,
        Location,
        Movement,
        NonManualSignal,
        PalmOrientation,
    )

    return {
        "handshapes": [h.value for h in Handshape],
        "locations": [loc.value for loc in Location],
        "movements": [m.value for m in Movement],
        "palm_orientations": [po.value for po in PalmOrientation],
        "finger_orientations": [fo.value for fo in FingerOrientation],
        "hand_arrangements": [ha.value for ha in HandArrangement],
        "non_manual_signals": [nms.value for nms in NonManualSignal],
    }


@router.post("/phonological-distance")
async def calculate_phonological_distance(body: PhonologicalDistanceRequest):
    """
    Calculate phonological distance between two forms.

    Returns the Hamming distance (number of differing parameters) and
    which parameters differ.
    """
    from sltk.linguistics import (
        PhonologicalForm,
        get_differing_parameters,
        phonological_distance,
    )

    form1 = PhonologicalForm(
        handshape_dom=body.form1.handshape_dom,
        handshape_nondom=body.form1.handshape_nondom,
        location=body.form1.location,
        movement=body.form1.movement,
        orientation_palm=body.form1.orientation_palm,
        orientation_fingers=body.form1.orientation_fingers,
        hand_arrangement=body.form1.hand_arrangement,
        nms_cooccurring=body.form1.nms_cooccurring,
    )

    form2 = PhonologicalForm(
        handshape_dom=body.form2.handshape_dom,
        handshape_nondom=body.form2.handshape_nondom,
        location=body.form2.location,
        movement=body.form2.movement,
        orientation_palm=body.form2.orientation_palm,
        orientation_fingers=body.form2.orientation_fingers,
        hand_arrangement=body.form2.hand_arrangement,
        nms_cooccurring=body.form2.nms_cooccurring,
    )

    distance = phonological_distance(
        form1,
        form2,
        include_nondom=body.include_nondom,
        include_arrangement=body.include_arrangement,
    )

    differing = get_differing_parameters(
        form1,
        form2,
        include_nondom=body.include_nondom,
        include_arrangement=body.include_arrangement,
    )

    return {
        "distance": distance,
        "differing_parameters": differing,
        "is_minimal_pair": distance == 1,
        "form1": form1.to_dict(),
        "form2": form2.to_dict(),
    }


@router.post("/minimal-pairs/phonological")
@rate_limit("10/minute")
async def find_phonological_minimal_pairs(request: Request, body: PhonologicalMinimalPairsRequest):
    """
    Find minimal pairs in a dataset using phonological forms.

    This endpoint looks for signs annotated with phonological information
    and identifies pairs that differ in exactly one parameter.

    Rate limited to 10 requests per minute.
    """
    from sltk.linguistics import find_minimal_pairs as find_mp_phon
    from sltk.linguistics import find_near_minimal_pairs

    # Get dataset and load samples with phonological annotations
    segments_by_file, _ = load_dataset_segments(body.dataset, body.max_samples)

    # Extract phonological forms from segments
    forms: dict[str, Any] = {}

    for file_id, segments in segments_by_file.items():
        for seg in segments:
            # Check if segment has phonological form data
            phonology = getattr(seg, "phonological_form", None)
            if phonology is None:
                # Try to get from metadata
                metadata = getattr(seg, "metadata", {}) or {}
                phonology = metadata.get("phonological_form")

            if phonology is not None:
                label = (
                    getattr(seg, "label", None) or seg.get("label")
                    if isinstance(seg, dict)
                    else None
                )
                if label:
                    # Store the phonological form
                    from sltk.linguistics import PhonologicalForm

                    if isinstance(phonology, dict):
                        forms[label] = PhonologicalForm.from_dict(phonology)
                    elif isinstance(phonology, PhonologicalForm):
                        forms[label] = phonology

    if len(forms) < 2:
        return {
            "dataset": body.dataset,
            "message": "Insufficient phonological forms found in dataset",
            "forms_found": len(forms),
            "pairs": [],
        }

    # Find minimal or near-minimal pairs
    if body.max_distance == 1:
        pairs = find_mp_phon(
            forms,
            include_nondom=body.include_nondom,
            include_arrangement=False,
        )
        result_pairs = [
            {
                "sign1": p[0],
                "sign2": p[1],
                "differing_parameter": p[2],
                "distance": 1,
            }
            for p in pairs[: body.max_pairs]
        ]
    else:
        near_pairs = find_near_minimal_pairs(
            forms,
            max_distance=body.max_distance,
            include_nondom=body.include_nondom,
            include_arrangement=False,
        )
        result_pairs = [
            {
                "sign1": p[0],
                "sign2": p[1],
                "differing_parameters": p[2],
                "distance": len(p[2]),
            }
            for p in near_pairs[: body.max_pairs]
        ]

    return {
        "dataset": body.dataset,
        "forms_analyzed": len(forms),
        "pairs_found": len(result_pairs),
        "max_distance": body.max_distance,
        "pairs": result_pairs,
    }


@router.post("/neighborhood")
async def compute_neighborhood(body: NeighborhoodRequest):
    """
    Compute phonological neighborhood for a target form.

    Returns all forms within the specified distance of the target.
    """
    from sltk.linguistics import PhonologicalForm, compute_phonological_neighborhood

    # Build target form
    target = PhonologicalForm(
        handshape_dom=body.target.handshape_dom,
        handshape_nondom=body.target.handshape_nondom,
        location=body.target.location,
        movement=body.target.movement,
        orientation_palm=body.target.orientation_palm,
        orientation_fingers=body.target.orientation_fingers,
        hand_arrangement=body.target.hand_arrangement,
        nms_cooccurring=body.target.nms_cooccurring,
    )

    # Build forms dict
    forms = {}
    for name, form_req in body.forms.items():
        forms[name] = PhonologicalForm(
            handshape_dom=form_req.handshape_dom,
            handshape_nondom=form_req.handshape_nondom,
            location=form_req.location,
            movement=form_req.movement,
            orientation_palm=form_req.orientation_palm,
            orientation_fingers=form_req.orientation_fingers,
            hand_arrangement=form_req.hand_arrangement,
            nms_cooccurring=form_req.nms_cooccurring,
        )

    neighborhood = compute_phonological_neighborhood(target, forms, body.max_distance)

    return {
        "target": target.to_dict(),
        "max_distance": body.max_distance,
        "neighborhood_size": len(neighborhood),
        "neighbors": [
            {
                "name": name,
                "form": form.to_dict(),
                "distance": dist,
            }
            for name, (form, dist) in neighborhood.items()
        ],
    }


@router.post("/inventory/analyze")
@rate_limit("10/minute")
async def analyze_phonological_inventory(
    request: Request,
    dataset: str = Query(..., description="Dataset name"),
    max_samples: int = Query(500, ge=1, le=2000, description="Maximum samples to analyze"),
):
    """
    Analyze phonological inventory distribution in a dataset.

    Returns statistics about handshape, location, movement distributions.

    Rate limited to 10 requests per minute.
    """
    from sltk.linguistics import PhonologicalForm, analyze_inventory

    segments_by_file, _ = load_dataset_segments(dataset, max_samples)

    # Extract phonological forms
    forms = []
    for segments in segments_by_file.values():
        for seg in segments:
            phonology = getattr(seg, "phonological_form", None)
            if phonology is None:
                metadata = getattr(seg, "metadata", {}) or {}
                phonology = metadata.get("phonological_form")

            if phonology is not None:
                if isinstance(phonology, dict):
                    forms.append(PhonologicalForm.from_dict(phonology))
                elif isinstance(phonology, PhonologicalForm):
                    forms.append(phonology)

    if not forms:
        return {
            "dataset": dataset,
            "message": "No phonological forms found in dataset",
            "forms_found": 0,
        }

    stats = analyze_inventory(forms)

    return {
        "dataset": dataset,
        "total_forms": stats.total_forms,
        "inventory": stats.to_dict(),
        "top_handshapes": stats.most_common_handshapes(15),
        "top_locations": stats.most_common_locations(15),
        "top_movements": stats.most_common_movements(15),
        "one_handed_ratio": stats.one_handed_ratio,
        "two_handed_ratio": stats.two_handed_ratio,
    }


# ============================================================================
# Reliability Endpoints
# ============================================================================


@router.post("/reliability/kappa")
async def calculate_kappa(body: ReliabilityKappaRequest):
    """
    Calculate Cohen's or Fleiss' Kappa for inter-rater agreement.

    - Cohen's Kappa: Use for exactly 2 raters
    - Fleiss' Kappa: Use for 2 or more raters
    """
    from sltk.linguistics import cohens_kappa, fleiss_kappa

    if body.method == "cohen":
        if len(body.ratings) != 2:
            raise HTTPException(400, "Cohen's Kappa requires exactly 2 raters")
        try:
            result = cohens_kappa(body.ratings[0], body.ratings[1])
        except ValueError as e:
            raise HTTPException(400, str(e))
    else:
        try:
            result = fleiss_kappa(body.ratings)
        except ValueError as e:
            raise HTTPException(400, str(e))

    return {
        "method": body.method,
        **result.to_dict(),
    }


@router.post("/reliability/krippendorff")
async def calculate_krippendorff(body: KrippendorffRequest):
    """
    Calculate Krippendorff's Alpha for inter-rater agreement.

    Supports missing data (None values) and different measurement levels.
    """
    from sltk.linguistics import krippendorff_alpha

    try:
        alpha = krippendorff_alpha(body.ratings, level=body.level)
    except ValueError as e:
        raise HTTPException(400, str(e))

    # Interpret alpha
    if alpha >= 0.8:
        interpretation = "good"
    elif alpha >= 0.67:
        interpretation = "tentative"
    else:
        interpretation = "poor"

    return {
        "alpha": float(alpha),
        "level": body.level,
        "interpretation": interpretation,
        "n_raters": len(body.ratings),
        "n_items": len(body.ratings[0]) if body.ratings else 0,
    }


@router.post("/reliability/boundary-agreement")
async def calculate_boundary_agreement(body: BoundaryAgreementRequest):
    """
    Calculate boundary agreement between two annotation sets.

    Measures how well two annotators agree on segment boundaries.
    """
    from sltk.data.core import Segment, SegmentList
    from sltk.linguistics import boundary_agreement

    # Convert request data to SegmentList
    try:
        segs1 = SegmentList(
            [
                Segment(
                    start=s["start"],
                    end=s["end"],
                    label=s.get("label"),
                    tier=s.get("tier", "default"),
                )
                for s in body.segments1
            ]
        )
        segs2 = SegmentList(
            [
                Segment(
                    start=s["start"],
                    end=s["end"],
                    label=s.get("label"),
                    tier=s.get("tier", "default"),
                )
                for s in body.segments2
            ]
        )
    except (KeyError, TypeError) as e:
        raise HTTPException(400, f"Invalid segment format: {e}")

    result = boundary_agreement(segs1, segs2, body.tolerance_sec, body.tier)

    return result.to_dict()


@router.post("/reliability/label-agreement")
async def calculate_label_agreement(body: LabelAgreementRequest):
    """
    Calculate label agreement for temporally aligned segments.

    First aligns segments by temporal overlap, then computes agreement on labels.
    """
    from sltk.data.core import Segment, SegmentList
    from sltk.linguistics import temporal_label_agreement

    try:
        segs1 = SegmentList(
            [
                Segment(
                    start=s["start"],
                    end=s["end"],
                    label=s.get("label"),
                    tier=s.get("tier", "default"),
                )
                for s in body.segments1
            ]
        )
        segs2 = SegmentList(
            [
                Segment(
                    start=s["start"],
                    end=s["end"],
                    label=s.get("label"),
                    tier=s.get("tier", "default"),
                )
                for s in body.segments2
            ]
        )
    except (KeyError, TypeError) as e:
        raise HTTPException(400, f"Invalid segment format: {e}")

    result = temporal_label_agreement(segs1, segs2, body.tolerance_sec, body.tier)

    return result.to_dict()


@router.post("/reliability/confusion-matrix")
async def generate_confusion_matrix(body: ConfusionMatrixRequest):
    """
    Generate confusion matrix showing disagreement patterns between raters.

    Identifies which labels are commonly confused.
    """
    from sltk.linguistics import annotation_confusion_matrix

    try:
        result = annotation_confusion_matrix(body.rater1_labels, body.rater2_labels)
    except ValueError as e:
        raise HTTPException(400, str(e))

    return result.to_dict()


@router.post("/reliability/analyze")
@rate_limit("5/minute")
async def analyze_reliability(request: Request, body: ReliabilityAnalysisRequest):
    """
    Full reliability analysis for a dataset with multiple annotators.

    Computes both boundary and label agreement for specified tiers.

    Rate limited to 5 requests per minute.
    """
    from sltk.data.core import SegmentList
    from sltk.linguistics import analyze_reliability_by_tier

    # Load dataset samples
    dataset = get_cached_dataset(body.dataset, split="all")

    # We need at least 2 annotation sets - typically different annotator files
    # For this example, we'll check for signer_id variation or multiple annotation sources
    annotation_sets: list[SegmentList] = []

    sample_ids = list(dataset._index[: body.max_samples])
    for sample_id in sample_ids:
        try:
            sample = dataset._load_sample(sample_id)
            if sample.segments:
                annotation_sets.append(SegmentList(sample.segments))
        except Exception:
            continue

    if len(annotation_sets) < 2:
        return {
            "dataset": body.dataset,
            "message": "Need at least 2 annotation sets for reliability analysis",
            "annotation_sets_found": len(annotation_sets),
        }

    try:
        results = analyze_reliability_by_tier(annotation_sets, body.tiers)
    except ValueError as e:
        raise HTTPException(400, str(e))

    return {
        "dataset": body.dataset,
        "annotation_sets_analyzed": len(annotation_sets),
        "tiers_analyzed": list(results.keys()),
        "results": {tier: r.to_dict() for tier, r in results.items()},
    }


# ============================================================================
# Non-Manual Signal Analysis Endpoints
# ============================================================================


@router.post("/nms/cooccurrence")
@rate_limit("10/minute")
async def analyze_nms_cooccurrence_endpoint(request: Request, body: NMSCooccurrenceRequest):
    """
    Analyze NMS co-occurrence with manual signs.

    Returns which non-manual signals typically accompany which manual signs.

    Rate limited to 10 requests per minute.
    """
    from sltk.data.core import SegmentList
    from sltk.linguistics import analyze_nms_cooccurrence

    # Load dataset segments
    segments_by_file, _ = load_dataset_segments(body.dataset, body.max_samples)

    # Filter to specific sample IDs if provided
    if body.sample_ids:
        segments_by_file = {k: v for k, v in segments_by_file.items() if k in body.sample_ids}

    if not segments_by_file:
        raise HTTPException(400, f"No segments found for dataset: {body.dataset}")

    # Combine all segments into one SegmentList
    all_segments = []
    for segs in segments_by_file.values():
        all_segments.extend(segs)

    segment_list = SegmentList(all_segments)

    result = analyze_nms_cooccurrence(
        segment_list,
        manual_tier=body.manual_tier,
        nms_tiers=body.nms_tiers,
        overlap_tolerance=body.overlap_tolerance,
    )

    return {
        "dataset": body.dataset,
        "samples_analyzed": len(segments_by_file),
        **result.to_dict(),
    }


@router.post("/nms/scope")
@rate_limit("10/minute")
async def analyze_nms_scope_endpoint(request: Request, body: NMSScopeRequest):
    """
    Analyze NMS scope over manual signs.

    Returns statistics about how many manual signs each NMS typically spans.

    Rate limited to 10 requests per minute.
    """
    from sltk.data.core import SegmentList
    from sltk.linguistics import analyze_nms_scope

    segments_by_file, _ = load_dataset_segments(body.dataset, body.max_samples)

    all_segments = []
    for segs in segments_by_file.values():
        all_segments.extend(segs)

    segment_list = SegmentList(all_segments)

    result = analyze_nms_scope(
        segment_list,
        nms_tier=body.nms_tier,
        manual_tier=body.manual_tier,
        max_examples=body.max_examples,
    )

    return {
        "dataset": body.dataset,
        "nms_tier": body.nms_tier,
        "manual_tier": body.manual_tier,
        **result.to_dict(),
    }


@router.post("/nms/patterns")
@rate_limit("10/minute")
async def detect_grammatical_patterns_endpoint(request: Request, body: NMSPatternsRequest):
    """
    Detect grammatical patterns marked by NMS.

    Identifies wh-questions, y/n questions, negation, topics, and conditionals.

    Rate limited to 10 requests per minute.
    """
    from sltk.data.core import SegmentList
    from sltk.linguistics import detect_grammatical_patterns

    segments_by_file, _ = load_dataset_segments(body.dataset, body.max_samples)

    all_segments = []
    for segs in segments_by_file.values():
        all_segments.extend(segs)

    segment_list = SegmentList(all_segments)

    patterns = detect_grammatical_patterns(
        segment_list,
        pattern_type=body.pattern_type,
        confidence_threshold=body.confidence_threshold,
    )

    # Group by pattern type
    from collections import Counter

    pattern_counts = Counter(p.pattern_type for p in patterns)

    return {
        "dataset": body.dataset,
        "pattern_type_filter": body.pattern_type,
        "confidence_threshold": body.confidence_threshold,
        "total_patterns_detected": len(patterns),
        "pattern_type_counts": dict(pattern_counts),
        "patterns": [p.to_dict() for p in patterns[:100]],  # Limit to 100 examples
    }


@router.get("/nms/inventory/{dataset}")
@rate_limit("10/minute")
async def get_nms_inventory(
    request: Request,
    dataset: str,
    max_samples: int = Query(500, ge=1, le=2000, description="Maximum samples to analyze"),
):
    """
    Get NMS inventory for a dataset.

    Returns all NMS labels used, their frequencies, and cross-tier combinations.

    Rate limited to 10 requests per minute.
    """
    from sltk.data.core import SegmentList
    from sltk.linguistics import analyze_nms_inventory

    segments_by_file, _ = load_dataset_segments(dataset, max_samples)

    all_segments = []
    for segs in segments_by_file.values():
        all_segments.extend(segs)

    segment_list = SegmentList(all_segments)

    result = analyze_nms_inventory(segment_list)

    return {
        "dataset": dataset,
        "samples_analyzed": len(segments_by_file),
        **result.to_dict(),
    }


@router.post("/nms/timing")
@rate_limit("10/minute")
async def analyze_nms_timing_endpoint(request: Request, body: NMSTimingRequest):
    """
    Analyze timing relationship between NMS and manual signs.

    Returns whether NMS typically leads, lags, or coincides with manual signs.

    Rate limited to 10 requests per minute.
    """
    from sltk.data.core import SegmentList
    from sltk.linguistics import analyze_nms_timing

    segments_by_file, _ = load_dataset_segments(body.dataset, body.max_samples)

    all_segments = []
    for segs in segments_by_file.values():
        all_segments.extend(segs)

    segment_list = SegmentList(all_segments)

    result = analyze_nms_timing(
        segment_list,
        nms_tier=body.nms_tier,
        manual_tier=body.manual_tier,
        coincidence_tolerance=body.coincidence_tolerance,
    )

    return {
        "dataset": body.dataset,
        "nms_tier": body.nms_tier,
        "manual_tier": body.manual_tier,
        **result.to_dict(),
    }


@router.post("/nms/duration-stats")
@rate_limit("10/minute")
async def get_nms_duration_stats(request: Request, body: NMSDurationRequest):
    """
    Get statistical analysis of NMS durations.

    Returns mean, median, std, and histogram of NMS durations.

    Rate limited to 10 requests per minute.
    """
    from sltk.data.core import SegmentList
    from sltk.linguistics import nms_duration_stats

    segments_by_file, _ = load_dataset_segments(body.dataset, body.max_samples)

    all_segments = []
    for segs in segments_by_file.values():
        all_segments.extend(segs)

    segment_list = SegmentList(all_segments)

    result = nms_duration_stats(segment_list, body.nms_tier)

    return {
        "dataset": body.dataset,
        **result.to_dict(),
    }


@router.get("/nms/tiers")
async def get_standard_nms_tiers():
    """
    Get standard NMS tier names and their aliases.

    Useful for understanding how NMS tiers are auto-detected.
    """
    from sltk.linguistics import get_standard_nms_tiers

    return {
        "tiers": get_standard_nms_tiers(),
    }


@router.get("/nms/grammatical-markers")
async def get_nms_grammatical_markers():
    """
    Get grammatical marker definitions.

    Returns the NMS patterns that indicate various grammatical constructions.
    """
    from sltk.linguistics import get_grammatical_markers

    return {
        "markers": get_grammatical_markers(),
    }


@router.post("/nms/summarize")
@rate_limit("5/minute")
async def summarize_nms_endpoint(
    request: Request,
    dataset: str = Query(..., description="Dataset name"),
    max_samples: int = Query(500, ge=1, le=2000, description="Maximum samples to analyze"),
):
    """
    Generate comprehensive NMS summary for a dataset.

    Combines inventory, scope, and pattern detection analyses.

    Rate limited to 5 requests per minute.
    """
    from sltk.data.core import SegmentList
    from sltk.linguistics import summarize_nms

    segments_by_file, _ = load_dataset_segments(dataset, max_samples)

    all_segments = []
    for segs in segments_by_file.values():
        all_segments.extend(segs)

    segment_list = SegmentList(all_segments)

    summary = summarize_nms(segment_list)

    return {
        "dataset": dataset,
        "samples_analyzed": len(segments_by_file),
        **summary,
    }
