"""
Central registry for text splitters.

This module provides a factory function to create different types of text splitters
based on configuration, similar to how embeddings_config.py works for embeddings.
"""

from __future__ import annotations

import threading
from typing import Optional, List, Any, TYPE_CHECKING

from loguru import logger

# ``langchain_text_splitters`` is imported lazily inside ``get_text_splitter``
# (see below), NOT at module load. Importing *any* of its submodules runs the
# package ``__init__``, which eagerly pulls sentence-transformers, torch,
# spacy, nltk and konlpy — ~4-8 s and hundreds of MB of RSS. Because this
# module sits on the app-startup import chain (scheduler / blueprints /
# search engines all import ``LibraryRAGService`` → ``embeddings.splitters``),
# importing it eagerly added ~19 s to server boot on CI and tripped the
# UI-test startup watchdog. Deferring the import to call time keeps boot
# cheap and also means a broken optional splitter dependency (spacy / konlpy)
# can no longer crash startup — only the indexing path that actually needs
# it (the original intent of issue #4490, which the concrete-submodule
# imports did not actually achieve since they still run the package init).

# Serializes the cold ``langchain_text_splitters`` import. Its package
# ``__init__`` eagerly imports ~14 splitter submodules with enough internal
# cross-referencing that importing different submodules from multiple threads
# at once observes a partially-initialized package and raises
# ``ImportError: cannot import name ... from partially initialized module``.
# The module-level import this replaced warmed ``sys.modules`` single-threaded
# at boot; deferring it to call time reintroduced the race for the RAG
# auto-index ``ThreadPoolExecutor`` and the per-user document scheduler, which
# call ``get_text_splitter`` from several threads. Warm the package once under
# this lock before the function-local submodule imports; once warm the lock is
# uncontended (a ``sys.modules`` hit).
_LANGCHAIN_TEXT_SPLITTERS_IMPORT_LOCK = threading.Lock()

if TYPE_CHECKING:
    from langchain_core.embeddings import Embeddings

# Valid splitter type options
VALID_SPLITTER_TYPES = [
    "recursive",
    "token",
    "sentence",
    "semantic",
]


def get_text_splitter(
    splitter_type: str = "recursive",
    chunk_size: int = 1000,
    chunk_overlap: int = 200,
    text_separators: Optional[List[str]] = None,
    embeddings: Optional[Embeddings] = None,
    **kwargs,
) -> Any:
    """
    Get text splitter based on type.

    Args:
        splitter_type: Type of splitter ('recursive', 'token', 'sentence', 'semantic')
        chunk_size: Maximum size of chunks
        chunk_overlap: Overlap between chunks
        text_separators: Custom separators (only used for 'recursive' type)
        embeddings: Embeddings instance (required for 'semantic' type)
        **kwargs: Additional splitter-specific parameters

    Returns:
        A text splitter instance

    Raises:
        ValueError: If splitter_type is invalid or required parameters are missing
        ImportError: If required dependencies are not installed
    """
    # Normalize splitter type
    splitter_type = splitter_type.strip().lower()

    # Validate splitter type
    if splitter_type not in VALID_SPLITTER_TYPES:
        logger.error(f"Invalid splitter type: {splitter_type}")
        raise ValueError(
            f"Invalid splitter type: {splitter_type}. "
            f"Must be one of: {VALID_SPLITTER_TYPES}"
        )

    logger.info(
        f"Creating text splitter: type={splitter_type}, "
        f"chunk_size={chunk_size}, chunk_overlap={chunk_overlap}"
    )

    # Create the appropriate splitter. The ``langchain_text_splitters``
    # imports are intentionally function-local — see the module docstring:
    # importing the package eagerly pulls torch/sentence-transformers and
    # must stay off the app-startup import path.
    #
    # Every branch except "semantic" (which uses langchain_experimental)
    # imports from ``langchain_text_splitters``. Warm the whole package once
    # under the lock first so concurrent first-callers can't observe a
    # half-initialized package (see the lock's definition); after this the
    # per-branch ``from ... import`` are plain ``sys.modules`` lookups.
    if splitter_type != "semantic":
        with _LANGCHAIN_TEXT_SPLITTERS_IMPORT_LOCK:
            import langchain_text_splitters  # noqa: F401

    if splitter_type == "token":
        from langchain_text_splitters.base import TokenTextSplitter

        return TokenTextSplitter(
            chunk_size=chunk_size,
            chunk_overlap=chunk_overlap,
        )

    if splitter_type == "sentence":
        from langchain_text_splitters.sentence_transformers import (
            SentenceTransformersTokenTextSplitter,
        )

        return SentenceTransformersTokenTextSplitter(
            chunk_overlap=chunk_overlap,
            tokens_per_chunk=chunk_size,
        )

    if splitter_type == "semantic":
        # Semantic chunking requires embeddings
        if embeddings is None:
            raise ValueError(
                "Semantic splitter requires 'embeddings' parameter. "
                "Please provide an embeddings instance."
            )

        try:
            # Try to import experimental semantic chunker
            from langchain_experimental.text_splitter import SemanticChunker

            # Get breakpoint threshold from kwargs or use default
            breakpoint_threshold_type = kwargs.get(
                "breakpoint_threshold_type", "percentile"
            )
            breakpoint_threshold_amount = kwargs.get(
                "breakpoint_threshold_amount", None
            )

            # Create semantic chunker
            chunker_kwargs = {"embeddings": embeddings}

            if breakpoint_threshold_type:
                chunker_kwargs["breakpoint_threshold_type"] = (
                    breakpoint_threshold_type
                )

            if breakpoint_threshold_amount is not None:
                chunker_kwargs["breakpoint_threshold_amount"] = (
                    breakpoint_threshold_amount
                )

            logger.info(
                f"Creating SemanticChunker with threshold_type={breakpoint_threshold_type}, "
                f"threshold_amount={breakpoint_threshold_amount}"
            )

            return SemanticChunker(**chunker_kwargs)

        except ImportError as e:
            logger.exception("Failed to import SemanticChunker")
            raise ImportError(
                "Semantic chunking requires langchain-experimental. "
                "Install it with: pip install langchain-experimental"
            ) from e

    else:  # "recursive" or default
        from langchain_text_splitters.character import (
            RecursiveCharacterTextSplitter,
        )

        # Use custom separators if provided, otherwise use defaults
        if text_separators is None:
            text_separators = ["\n\n", "\n", ". ", " ", ""]

        return RecursiveCharacterTextSplitter(
            chunk_size=chunk_size,
            chunk_overlap=chunk_overlap,
            length_function=len,
            separators=text_separators,
        )


def is_semantic_chunker_available() -> bool:
    """Check if semantic chunking is available."""
    import importlib.util

    return importlib.util.find_spec("langchain_experimental") is not None
