"""Markdown ingestion — header-based splitting."""

import re


def chunk_markdown(text: str, cfg: dict) -> list[dict]:
    """Split markdown at header boundaries with hierarchy context."""
    chunking_cfg = cfg.get("chunking", {}).get("markdown", {})
    strategy = chunking_cfg.get("strategy", "header")

    if strategy == "fixed" or not _has_headers(text):
        return _fixed_chunk(text, chunking_cfg)

    return _header_chunk(text, chunking_cfg)


def _has_headers(text: str) -> bool:
    """Check if text contains markdown headers."""
    return bool(re.search(r"^#{1,6}\s+", text, re.MULTILINE))


def _header_chunk(text: str, chunking_cfg: dict) -> list[dict]:
    """Split at ## and ### boundaries with hierarchy context."""
    min_tokens = chunking_cfg.get("min_tokens", 50)
    max_tokens = chunking_cfg.get("max_tokens", 1024)

    sections = _split_at_headers(text)

    if not sections:
        return _fixed_chunk(text, chunking_cfg)

    # Merge small sections
    sections = _merge_small_sections(sections, min_tokens)

    # Split large sections
    chunks = []
    for section in sections:
        content = section["content"].strip()
        if not content:
            continue

        # Add hierarchy context
        if section["header_chain"]:
            context = " > ".join(section["header_chain"])
            full_text = f"{context}\n\n{content}"
        else:
            full_text = content

        approx_tokens = len(full_text.split())
        if approx_tokens > max_tokens:
            sub_chunks = _split_large_section(full_text, max_tokens, chunking_cfg)
            chunks.extend(sub_chunks)
        else:
            chunks.append({"text": full_text, "metadata": {
                "section_header": section["header_chain"][-1] if section["header_chain"] else None,
            }})

    # Assign chunk indices
    for i, c in enumerate(chunks):
        c["chunk_index"] = i

    return chunks


def _split_at_headers(text: str) -> list[dict]:
    """Split text into sections at header boundaries."""
    header_pattern = re.compile(r"^(#{1,6})\s+(.*?)$", re.MULTILINE)

    sections = []
    header_stack = []  # Stack of (level, title)
    last_end = 0

    for match in header_pattern.finditer(text):
        # Capture content before this header
        if last_end < match.start():
            content = text[last_end:match.start()].strip()
            if content and sections:
                sections[-1]["content"] += "\n\n" + content
            elif content:
                sections.append({
                    "header_chain": [],
                    "content": content,
                })

        level = len(match.group(1))
        title = match.group(2).strip()

        # Update header stack
        while header_stack and header_stack[-1][0] >= level:
            header_stack.pop()
        header_stack.append((level, title))

        chain = [h[1] for h in header_stack]

        sections.append({
            "header_chain": chain,
            "content": "",
        })
        last_end = match.end()

    # Capture trailing content
    if last_end < len(text):
        trailing = text[last_end:].strip()
        if trailing and sections:
            sections[-1]["content"] += "\n\n" + trailing
        elif trailing:
            sections.append({"header_chain": [], "content": trailing})

    return sections


def _merge_small_sections(sections: list[dict], min_tokens: int) -> list[dict]:
    """Merge sections smaller than min_tokens with next section."""
    if not sections:
        return sections

    merged = []
    pending = None

    for section in sections:
        if pending is not None:
            # Merge pending into this section
            section["content"] = pending["content"] + "\n\n" + section["content"]
            if not section["header_chain"] and pending["header_chain"]:
                section["header_chain"] = pending["header_chain"]
            pending = None

        approx_tokens = len(section["content"].split())
        if approx_tokens < min_tokens:
            pending = section
        else:
            merged.append(section)

    if pending is not None:
        if merged:
            merged[-1]["content"] += "\n\n" + pending["content"]
        else:
            merged.append(pending)

    return merged


def _split_large_section(text: str, max_tokens: int, chunking_cfg: dict) -> list[dict]:
    """Split a large section at paragraph boundaries with overlap."""
    overlap_tokens = chunking_cfg.get("overlap_tokens",
                                       cfg_defaults().get("overlap_tokens", 50))
    paragraphs = re.split(r"\n\n+", text)

    chunks = []
    current_paras = []
    current_tokens = 0

    for para in paragraphs:
        para_tokens = len(para.split())
        if current_tokens + para_tokens > max_tokens and current_paras:
            chunks.append({"text": "\n\n".join(current_paras), "metadata": {}})
            # Keep overlap
            overlap_paras = []
            overlap_count = 0
            for p in reversed(current_paras):
                p_tokens = len(p.split())
                if overlap_count + p_tokens > overlap_tokens:
                    break
                overlap_paras.insert(0, p)
                overlap_count += p_tokens
            current_paras = overlap_paras
            current_tokens = overlap_count

        current_paras.append(para)
        current_tokens += para_tokens

    if current_paras:
        chunks.append({"text": "\n\n".join(current_paras), "metadata": {}})

    return chunks


def cfg_defaults():
    """Return default chunking config."""
    return {"max_tokens": 1024, "overlap_tokens": 50, "min_tokens": 50}


def _fixed_chunk(text: str, chunking_cfg: dict) -> list[dict]:
    """Fixed-size fallback for plain text without headers."""
    max_tokens = chunking_cfg.get("max_tokens", 512)
    overlap_tokens = chunking_cfg.get("overlap_tokens", 50)

    words = text.split()
    if not words:
        return []

    chunks = []
    start = 0
    idx = 0

    while start < len(words):
        end = min(start + max_tokens, len(words))
        chunk_text = " ".join(words[start:end]).strip()
        if chunk_text:
            chunks.append({
                "text": chunk_text,
                "chunk_index": idx,
                "metadata": {},
            })
            idx += 1
        start = end - overlap_tokens
        if start >= len(words) or end == len(words):
            break

    return chunks
