# coding: utf-8
# Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
"""
Text Splitter Abstract Base Class

Provides unified interface for text splitting, subclasses need to implement specific splitting logic.
"""
from abc import ABC, abstractmethod
from typing import List, Tuple, Callable, Optional, Any

from openjiuwen.core.retrieval.common.document import Document, TextChunk
from openjiuwen.core.common.logging import logger


class Splitter(ABC):
    """Text splitter abstract base class"""
    
    def __init__(
        self,
        tokenizer: Optional[Callable] = None,
        chunk_size: int = 512,
        chunk_overlap: int = 50,
        **kwargs: Any,
    ):
        """
        Initialize text splitter
        
        Args:
            tokenizer: Tokenizer, must have encode and decode methods
            chunk_size: Chunk size (number of tokens or characters)
            chunk_overlap: Chunk overlap size
            **kwargs: Other parameters
        """
        if chunk_size <= 0:
            raise ValueError(f"chunk_size must be greater than 0, current value: {chunk_size}")
        if chunk_overlap < 0:
            raise ValueError(f"chunk_overlap must be greater than or equal to 0, current value: {chunk_overlap}")
        if chunk_overlap >= chunk_size:
            raise ValueError(f"chunk_overlap ({chunk_overlap}) must be less than chunk_size ({chunk_size})")
        
        self.chunk_size = chunk_size
        self.chunk_overlap = chunk_overlap
        
        if tokenizer is not None:
            self._validate_tokenizer(tokenizer)
            self.tokenizer = tokenizer
            if hasattr(tokenizer, "encode") and hasattr(tokenizer, "decode"):
                self.tokenizer_enc = tokenizer.encode
                self.tokenizer_dec = tokenizer.decode
            else:
                self.tokenizer_enc = tokenizer
                self.tokenizer_dec = None
        else:
            self.tokenizer = None
            self.tokenizer_enc = None
            self.tokenizer_dec = None
    
    def _validate_tokenizer(self, tokenizer: Callable) -> None:
        """
        Validate if tokenizer is valid
        
        Args:
            tokenizer: Tokenizer object
            
        Raises:
            ValueError: If tokenizer is invalid
        """
        if tokenizer is None:
            return
        
        # Check if has encode method or is callable
        if not (hasattr(tokenizer, "encode") or callable(tokenizer)):
            raise ValueError("Tokenizer must have encode method or be callable")
    
    @abstractmethod
    def __call__(self, doc: str) -> List[Tuple[str, int, int]]:
        """
        Split document, return list of (text, start position, end position) tuples
        
        Args:
            doc: Document text to be split
            
        Returns:
            List of chunks, each element is (text, start char position, end char position)
        """
        pass
    
    def get_nodes_from_documents(
        self, docs: List[Document]
    ) -> List[TextChunk]:
        """
        Get split nodes from document list
        
        Args:
            docs: List of documents
            
        Returns:
            List of split text chunks
        """
        returned_nodes = []
        for doc in docs:
            if not doc or not hasattr(doc, 'text') or not doc.text:
                logger.warning(f"Skipping empty document: {doc}")
                continue
                
            chunk_tuples = self.__call__(doc.text)
            
            for chunk_text, start_idx, end_idx in chunk_tuples:
                _node = TextChunk.from_document(doc, chunk_text)
                returned_nodes.append(_node)
        
        logger.info(f"Generated {len(returned_nodes)} text chunks from {len(docs)} documents")
        return returned_nodes
    
    def split_text(self, text: str) -> List[str]:
        """
        Split text, return only text list (without position info)
        
        Args:
            text: Text to be split
            
        Returns:
            List of split texts
        """
        chunks = self.__call__(text)
        return [chunk[0] for chunk in chunks]
    
    def _get_token_count(self, text: str) -> int:
        """
        Get token count of text
        
        Args:
            text: Text content
            
        Returns:
            Token count, returns character count if no tokenizer
        """
        if self.tokenizer_enc is not None:
            tokens = self.tokenizer_enc(text)
            return len(tokens) if isinstance(tokens, (list, tuple)) else len(str(tokens))
        return len(text)
