"""Markdown parsing and heading-aware chunking."""

import hashlib
import re
from dataclasses import dataclass, field
from pathlib import Path

import frontmatter


@dataclass
class ParsedDocument:
    title: str
    content: str  # raw content without frontmatter
    category: str
    tags: list[str] = field(default_factory=list)
    frontmatter: dict = field(default_factory=dict)


@dataclass
class Chunk:
    text: str
    index: int


# Rough token estimation: ~4 chars per token for English, ~2 for CJK
def estimate_tokens(text: str) -> int:
    cjk_chars = len(re.findall(r'[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]', text))
    other_chars = len(text) - cjk_chars
    return cjk_chars // 2 + other_chars // 4


def parse_markdown(file_path: Path) -> ParsedDocument:
    """Parse a markdown file, extracting frontmatter and metadata."""
    raw = file_path.read_text(encoding='utf-8')
    post = frontmatter.loads(raw)

    fm = dict(post.metadata)
    content = post.content

    # Extract title: frontmatter > first heading > filename
    title = fm.get('title', '')
    if not title:
        heading_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
        if heading_match:
            title = heading_match.group(1).strip()
        else:
            title = file_path.stem

    # Extract tags from frontmatter and inline
    tags = []
    fm_tags = fm.get('tags', [])
    if isinstance(fm_tags, list):
        tags.extend(fm_tags)
    elif isinstance(fm_tags, str):
        tags.extend([t.strip() for t in fm_tags.split(',')])

    # Inline tags: #tag (but not headings ##)
    inline_tags = re.findall(r'(?<!\w)#([a-zA-Z0-9_\-/]+)(?!\w)', content)
    # Filter out things that look like heading artifacts
    for t in inline_tags:
        if t not in tags:
            tags.append(t)

    category = fm.get('category', 'note')

    return ParsedDocument(
        title=title,
        content=content,
        category=category,
        tags=tags,
        frontmatter=fm,
    )


def split_by_headings(content: str) -> list[str]:
    """Split content by ## or ### headings, keeping heading with its section."""
    # Split on heading lines (## or ###)
    parts = re.split(r'(?=^#{2,3}\s)', content, flags=re.MULTILINE)
    # Filter empty parts
    return [p.strip() for p in parts if p.strip()]


def chunk_text(text: str, max_tokens: int = 800, overlap_ratio: float = 0.15) -> list[str]:
    """Split text into chunks respecting token limits with overlap."""
    if estimate_tokens(text) <= max_tokens:
        return [text]

    overlap_tokens = int(max_tokens * overlap_ratio)
    paragraphs = text.split('\n\n')

    chunks = []
    current_parts = []
    current_tokens = 0

    for para in paragraphs:
        para_tokens = estimate_tokens(para)

        if current_tokens + para_tokens > max_tokens and current_parts:
            chunks.append('\n\n'.join(current_parts))

            # Build overlap from the end of current_parts
            overlap_parts = []
            overlap_count = 0
            for p in reversed(current_parts):
                pt = estimate_tokens(p)
                if overlap_count + pt > overlap_tokens:
                    break
                overlap_parts.insert(0, p)
                overlap_count += pt

            current_parts = overlap_parts
            current_tokens = overlap_count

        current_parts.append(para)
        current_tokens += para_tokens

    if current_parts:
        chunks.append('\n\n'.join(current_parts))

    return chunks


def chunk_document(content: str, max_tokens: int = 800, overlap_ratio: float = 0.15) -> list[Chunk]:
    """Chunk a document using heading-aware splitting + token-based sub-chunking."""
    total_tokens = estimate_tokens(content)

    # Short document: single chunk
    if total_tokens <= max_tokens:
        return [Chunk(text=content.strip(), index=0)]

    sections = split_by_headings(content)
    chunks = []
    idx = 0

    for section in sections:
        sub_chunks = chunk_text(section, max_tokens, overlap_ratio)
        for text in sub_chunks:
            chunks.append(Chunk(text=text.strip(), index=idx))
            idx += 1

    return chunks


def content_hash(text: str) -> str:
    return hashlib.md5(text.encode('utf-8')).hexdigest()[:12]
