import copy
import re
import os
import pathlib
import Orange
from Orange.data import Domain, Table, StringVariable, ContinuousVariable
from chonkie import TokenChunker, SentenceChunker, RecursiveChunker, SemanticChunker, LateChunker


def create_chunks(table, column_name, tokenizer="character", chunk_size=300, chunk_overlap=100, mode="Token", progress_callback=None, argself=None):
    """
    Chunk the text in `column_name` of an Orange Table using a specialized chunker.

    Splits each row's text into chunks based on the selected mode (Token, Sentence,
    Recursive, or Markdown). Adds the chunked text and its metadata as new meta
    columns to the table.

    Parameters:
        table (Table): Input data table.
        column_name (str): Name of the text column to chunk.
        tokenizer (str): Tokenizer type (e.g., "character").
        chunk_size (int): Target chunk size.
        chunk_overlap (int): Overlap between chunks (not used in all modes).
        mode (str): Chunking strategy ("Token", "Sentence", "Recursive", "Markdown").
        progress_callback (callable): Optional progress reporter.
        argself: Optional caller reference.

    Returns:
        Table: The table with added meta columns: "Chunks", "Chunks size", and "Metadata".
    """
    # Définir la fonction de chunking selon le mode
    if mode == "Token":
        chunker = TokenChunker(tokenizer=tokenizer, chunk_size=chunk_size, chunk_overlap=chunk_overlap)
    elif mode == "Sentence":
        chunker = SentenceChunker(tokenizer=tokenizer, chunk_size=chunk_size, chunk_overlap=chunk_overlap, min_sentences_per_chunk=1)
    elif mode == "Recursive":
        chunker = RecursiveChunker(tokenizer=tokenizer, chunk_size=chunk_size, min_characters_per_chunk=24)

    ### Les 3 modes suivants sont à tester après montée de version !!
    ### Attention à la gestion des modèles !! Tokenizer doit être un SentenceTransformer (Mpnet ou Qwen-0.6B)
    ### Gérer ça en amont dans le widget !
    ### Ajouter du paramétrage selon la méthode de chunking !!!
    elif mode == "Markdown":
        # from recipe, tester dépendances d'abord
        current_dir = pathlib.Path(__file__).parent.resolve()
        chunker = RecursiveChunker.from_recipe(path=os.path.join(current_dir, r"resources\markdown_recipe.json"), tokenizer=tokenizer, chunk_size=400, min_characters_per_chunk=24)
    elif mode == "Semantic":
        chunker = SemanticChunker(embedding_model=tokenizer, threshold=0.7, chunk_size=chunk_size)
    elif mode == "Late":
        chunker = LateChunker(embedding_model=tokenizer, chunk_size=chunk_size, min_characters_per_chunk=24)
    else:
        raise ValueError(f"Invalid mode: {mode}. Valid modes are: Token, Sentence, Recursive, Markdown")


    new_metas = list(table.domain.metas) + [StringVariable("Chunks"), ContinuousVariable("Chunks size"), ContinuousVariable("Chunks index"), ContinuousVariable("Chunks start"), ContinuousVariable("Chunks end"), StringVariable("Metadata")]
    new_domain = Domain(table.domain.attributes, table.domain.class_vars, new_metas)

    new_rows = []
    for i, row in enumerate(table):
        content = row[column_name].value
        chunks = chunker(content)
        # For each chunk in the chunked data
        for j, chunk in enumerate(chunks):
            # Build new metas with previous data and the chunk
            new_metas_values = list(row.metas) + [chunk.text, chunk.token_count, j, chunk.start_index, chunk.end_index, ""]
            # Create the new row instance
            new_instance = Orange.data.Instance(new_domain, [row[x] for x in table.domain.attributes] + [row[y] for y in table.domain.class_vars] + new_metas_values)
            # Store the new row
            new_rows.append(new_instance)

        if progress_callback is not None:
            progress_value = float(100 * (i + 1) / len(table))
            progress_callback(progress_value)
        if argself is not None:
            if argself.stop:
                break

    return Table.from_list(domain=new_domain, rows=new_rows)


### Previous function - Metadatas were interesting
def chunk_markdown(content, tokenizer=None, chunk_size=300, chunk_overlap=100):
    """
    Découpe un contenu Markdown en chunks :
    - Si des en-têtes Markdown (#, ##, ###...) existent : on respecte la hiérarchie
      et on inclut dans les métadonnées uniquement les titres de la branche courante.
    - Sinon : on délègue à chunk_words().

    Parameters
    ----------
    content : str
        Le contenu (Markdown ou texte brut).
    tokenizer : any
        Tokenizer utilisé par WordChunker si besoin.
    chunk_size : int
        Nombre max de mots par chunk.
    chunk_overlap : int
        Overlap (en mots) entre deux chunks consécutifs.

    Returns
    -------
    (chunks, metadatas) : tuple(list[str], list[str])
        chunks : segments de texte
        metadatas : hiérarchies de titres associées (chaînes " ; " séparées), vide si aucun titre.
    """
    if not content or not isinstance(content, str):
        return [], []

    header_regex = re.compile(r"^(#{1,6})\s+(.*)", re.MULTILINE)
    matches = list(header_regex.finditer(content))

    # Cas SANS en-têtes : appel direct à chunk_words
    if not matches:
        chunks, _ = chunk_words(content, tokenizer, chunk_size=chunk_size, chunk_overlap=chunk_overlap)
        return chunks, [""] * len(chunks)

    # Cas AVEC en-têtes : extraire les sections (level, title, body)
    sections = []
    for i, match in enumerate(matches):
        level = len(match.group(1))
        title = match.group(2).strip()
        start = match.end()
        end = matches[i + 1].start() if i + 1 < len(matches) else len(content)
        body = content[start:end].strip()
        sections.append((level, title, body))

    chunks, metadatas = [], []
    current_titles = {}

    for level, title, body in sections:
        # purge les niveaux >= level
        for l in list(current_titles.keys()):
            if l >= level:
                current_titles.pop(l, None)
        current_titles[level] = title

        metadata = " ; ".join(current_titles[lvl] for lvl in sorted(current_titles) if lvl <= level)

        # déléguer le découpage de body à chunk_words
        body_chunks, _ = chunk_words(body, tokenizer, chunk_size=chunk_size, chunk_overlap=chunk_overlap)

        for ch in body_chunks:
            chunks.append(ch)
            metadatas.append(metadata)

    return chunks, metadatas

def chunk_semantic():
    pass