from typing import Optional, List, Dict, Any
from ._emb_models import EmbModels


class EmbText:
    """
    EmbText is a specialized data type for storing and embedding text in CapybaraDB.
    
    It enables semantic search capabilities by automatically chunking, embedding, and indexing text.
    When stored in the database, the text is processed asynchronously in the background:
    1. The text is chunked based on the specified parameters
    2. Each chunk is embedded using the specified embedding model
    3. The embeddings are indexed for efficient semantic search
    
    The 'chunks' property is auto-populated by the database after processing and is not meant 
    to be set by the user directly.
    
    Usage example:
        from capybaradb import CapybaraDB, EmbText, EmbModels
        
        # Connect to CapybaraDB
        db = CapybaraDB()
        
        # Create a document with an EmbText field
        document = {
            "title": "Sample Document",
            "content": EmbText(
                "This is a sample text that will be automatically embedded for semantic search.",
                emb_model=EmbModels.TEXT_EMBEDDING_3_SMALL,
                max_chunk_size=200,
                chunk_overlap=20
            )
        }
        
        # Insert the document into a collection
        db.my_database.my_collection.insert_one(document)
        
        # Later, you can perform semantic searches on this text
    """
    
    SUPPORTED_EMB_MODELS = [
        EmbModels.TEXT_EMBEDDING_3_SMALL,
        EmbModels.TEXT_EMBEDDING_3_LARGE,
        EmbModels.TEXT_EMBEDDING_ADA_002,
    ]

    def __init__(
        self,
        text: str,
        emb_model: str = "text-embedding-3-small",
        max_chunk_size: int = 200,
        chunk_overlap: int = 20,
        is_separator_regex: bool = False,
        separators: Optional[List[str]] = None,
        keep_separator: bool = False,
    ):
        """
        Initialize an EmbText object for semantic text embedding.
        
        Parameters:
            text (str): The text content to be embedded. Must be a non-empty string.
            
            emb_model (str): The embedding model to use. Defaults to "text-embedding-3-small".
                             Available models are defined in EmbModels.
            
            max_chunk_size (int): Maximum character length for each text chunk.
                                 Larger chunks reduce total chunk count but may reduce search efficiency.
                                 Defaults to 200.
            
            chunk_overlap (int): Number of overlapping characters between consecutive chunks.
                                Helps preserve context at chunk boundaries.
                                Defaults to 20.
            
            is_separator_regex (bool): Whether to treat separators as regex patterns.
                                      If True, each separator in the list is treated as a regex pattern.
                                      Defaults to False.
            
            separators (List[str], optional): List of separator strings (or regex patterns if is_separator_regex=True)
                                             used to split the text. For example: ["\n\n", "\n"]
                                             Defaults to None.
            
            keep_separator (bool): If True, separators remain in the chunked text.
                                  If False, they are stripped out.
                                  Defaults to False.
        
        Note:
            The 'chunks' property is auto-populated by the database after processing and
            is not meant to be set by the user directly.
        """
        if not self.is_valid_text(text):
            raise ValueError("Invalid text: must be a non-empty string.")

        if not self.is_valid_emb_model(emb_model):
            raise ValueError(f"Invalid embedding model: {emb_model} is not supported.")

        self.text = text
        self._chunks: List[str] = []  # Private attribute: updated only internally by the database.
        self.emb_model = emb_model
        self.max_chunk_size = max_chunk_size
        self.chunk_overlap = chunk_overlap
        self.is_separator_regex = is_separator_regex
        self.separators = separators
        self.keep_separator = keep_separator

    def __repr__(self):
        return f'EmbText("{self.text}")'

    @property
    def chunks(self) -> List[str]:
        """
        Read-only property for accessing text chunks.
        
        This property is auto-populated by the database after the text is processed.
        Initially empty when the EmbText object is first created.
        
        Returns:
            List[str]: The list of text chunks generated by the database.
        """
        return self._chunks

    @staticmethod
    def is_valid_text(text: str) -> bool:
        """
        Validate that the text is a non-empty string.
        
        Parameters:
            text (str): The text to validate.
            
        Returns:
            bool: True if the text is valid, False otherwise.
        """
        return isinstance(text, str) and text.strip() != ""

    @classmethod
    def is_valid_emb_model(cls, emb_model: str) -> bool:
        """
        Validate that the embedding model is supported.
        
        Parameters:
            emb_model (str): The embedding model to validate.
            
        Returns:
            bool: True if the embedding model is supported, False otherwise.
        """
        return emb_model in cls.SUPPORTED_EMB_MODELS

    def to_json(self) -> Dict[str, Any]:
        """
        Convert the EmbText instance to a JSON-serializable dictionary.
        
        This is primarily used internally by the CapybaraDB SDK.
        
        Returns:
            Dict[str, Any]: A JSON-serializable dictionary representing the EmbText object.
        """
        return {
            "@embText": {
                "text": self.text,
                "chunks": self._chunks,
                "emb_model": self.emb_model,
                "max_chunk_size": self.max_chunk_size,
                "chunk_overlap": self.chunk_overlap,
                "is_separator_regex": self.is_separator_regex,
                "separators": self.separators,
                "keep_separator": self.keep_separator,
            }
        }

    @classmethod
    def from_json(cls, data: Dict[str, Any]) -> "EmbText":
        """
        Create an EmbText instance from a JSON-serializable dictionary.
        
        This is primarily used internally by the CapybaraDB SDK when deserializing
        data received from the database.
        
        Parameters:
            data (Dict[str, Any]): The JSON data containing EmbText properties.
            
        Returns:
            EmbText: A new EmbText instance created from the JSON data.
            
        Raises:
            ValueError: If the required 'text' field is missing.
        """
        text = data.get("text")
        if text is None:
            raise ValueError("JSON data must include 'text' under '@embText'.")

        emb_model = data.get("emb_model", "text-embedding-3-small")
        max_chunk_size = data.get("max_chunk_size", 200)
        chunk_overlap = data.get("chunk_overlap", 20)
        is_separator_regex = data.get("is_separator_regex", False)
        separators = data.get("separators", None)
        keep_separator = data.get("keep_separator", False)

        # Create instance without letting the client set chunks.
        instance = cls(
            text,
            emb_model,
            max_chunk_size,
            chunk_overlap,
            is_separator_regex,
            separators,
            keep_separator,
        )
        # Update _chunks internally (e.g., based on API server data).
        instance._chunks = data.get("chunks", [])
        return instance
