"""Load repos + knowledge folder into text blocks for the Claude system prompt."""

from __future__ import annotations

import logging
from pathlib import Path

logger = logging.getLogger(__name__)

SKIP_DIRS = {
    ".git", "__pycache__", "node_modules", ".venv", "venv",
    "dist", "build", ".next", "target", ".idea", ".gradle",
}
CODE_EXT = {
    ".py", ".java", ".ts", ".tsx", ".js", ".go", ".rs",
    ".kt", ".swift", ".cpp", ".c", ".cs", ".rb", ".scala",
}


def _read_single_repo(path: str, max_chars: int = 20_000) -> str:
    """Read directory tree + key source files from a single repo."""
    root = Path(path).expanduser().resolve()
    if not root.is_dir():
        return ""

    # --- File tree (depth <= 4) ---
    tree_lines: list[str] = []

    def _tree(d: Path, prefix: str = "", depth: int = 0) -> None:
        if depth > 4:
            return
        try:
            children = sorted(d.iterdir(), key=lambda p: (p.is_file(), p.name))
        except PermissionError:
            return
        for i, child in enumerate(children):
            if child.name.startswith(".") or child.name in SKIP_DIRS:
                continue
            connector = "└── " if i == len(children) - 1 else "├── "
            tree_lines.append(prefix + connector + child.name)
            if child.is_dir() and depth < 4 and len(tree_lines) < 200:
                ext = "    " if i == len(children) - 1 else "│   "
                _tree(child, prefix + ext, depth + 1)

    _tree(root)
    sections: list[str] = [
        f"# Codebase: {root.name}\n",
        "## File tree\n```\n" + root.name + "/\n" + "\n".join(tree_lines) + "\n```\n",
    ]
    total = sum(len(s) for s in sections)

    # --- Key doc files ---
    for fname in ("README.md", "README.rst", "package.json", "pyproject.toml", "pom.xml"):
        fpath = root / fname
        if fpath.exists() and total < max_chars:
            try:
                content = fpath.read_text(errors="ignore")[:5_000]
                sec = f"## {fname}\n```\n{content}\n```\n"
                sections.append(sec)
                total += len(sec)
            except Exception:
                pass

    # --- Source files (entry points first, then by size) ---
    source_files = [
        p for p in root.rglob("*")
        if p.suffix in CODE_EXT
        and not any(s in p.parts for s in SKIP_DIRS)
    ]

    def _priority(p: Path) -> tuple:
        if p.stem.lower() in ("main", "app", "index", "server", "api", "application"):
            return (0, p.stat().st_size)
        return (1, p.stat().st_size)

    source_files.sort(key=_priority)

    for fpath in source_files[:40]:
        if total >= max_chars:
            break
        try:
            content = fpath.read_text(errors="ignore")
            rel = fpath.relative_to(root)
            remaining = max_chars - total
            excerpt = content[:min(4_000, remaining)]
            sec = f"## {rel}\n```{fpath.suffix[1:]}\n{excerpt}\n```\n"
            sections.append(sec)
            total += len(sec)
        except Exception:
            pass

    return "\n".join(sections)


def read_repos_context(repos: list[str], max_chars_per_repo: int = 20_000) -> str:
    """Read file tree + key source files from each repo. Returns combined text."""
    if not repos:
        return ""
    parts: list[str] = []
    for repo_path in repos:
        block = _read_single_repo(repo_path, max_chars=max_chars_per_repo)
        if block:
            parts.append(block)
    if not parts:
        return ""
    return "\n\n---\n\n".join(parts)


def read_knowledge_folder(path: str, max_chars: int = 20_000) -> str:
    """Recursively read all .md and .txt files from the knowledge folder."""
    root = Path(path).expanduser().resolve()
    if not root.is_dir():
        return ""

    files = sorted(
        [p for p in root.rglob("*") if p.suffix in (".md", ".txt") and p.is_file()],
        key=lambda p: str(p.relative_to(root)),
    )

    if not files:
        return ""

    sections: list[str] = []
    total = 0

    for fpath in files:
        if total >= max_chars:
            break
        try:
            content = fpath.read_text(errors="ignore")
            rel = fpath.relative_to(root)
            remaining = max_chars - total
            excerpt = content[:remaining]
            sec = f"## {rel}\n\n{excerpt}\n\n---"
            sections.append(sec)
            total += len(sec)
        except Exception:
            pass

    if not sections:
        return ""

    return f"# Knowledge Base ({len(sections)} documents)\n\n" + "\n\n".join(sections)


def build_full_context(repos: list[str], knowledge_path: str) -> str:
    """Combine repos + knowledge into a single context block for system prompt."""
    parts: list[str] = []

    repos_ctx = read_repos_context(repos)
    if repos_ctx:
        parts.append(repos_ctx)
        logger.info("Loaded context from %d repo(s) (%d chars)", len(repos), len(repos_ctx))

    if knowledge_path:
        knowledge_ctx = read_knowledge_folder(knowledge_path)
        if knowledge_ctx:
            parts.append(knowledge_ctx)
            logger.info("Loaded knowledge folder (%d chars)", len(knowledge_ctx))

    if not parts:
        return ""

    return "\n\n---\n\n".join(parts)


# ---------------------------------------------------------------------------
# Selective loading (per-meeting context selector)
# ---------------------------------------------------------------------------


def get_knowledge_tree(root_path: str) -> list[dict]:
    """Return nested folder structure of the knowledge base for the GUI tree widget.

    Returns: [{name, path, children, doc_count}, ...]
    Only includes directories that contain .md/.txt files (directly or in descendants).
    """
    root = Path(root_path).expanduser().resolve()
    if not root.is_dir():
        return []

    def _scan(directory: Path) -> list[dict]:
        result = []
        try:
            children = sorted(directory.iterdir(), key=lambda p: p.name)
        except PermissionError:
            return []
        for child in children:
            if not child.is_dir() or child.name.startswith(".") or child.name in SKIP_DIRS:
                continue
            docs = [
                p for p in child.rglob("*")
                if p.suffix in (".md", ".txt") and p.is_file()
            ]
            if not docs:
                continue
            result.append({
                "name": child.name,
                "path": str(child.relative_to(root)),
                "children": _scan(child),
                "doc_count": len(docs),
            })
        return result

    return _scan(root)


def read_knowledge_folders(
    root_path: str,
    folder_relpaths: list[str],
    max_chars: int = 20_000,
) -> str:
    """Read .md/.txt files only from selected subfolders of the knowledge base."""
    root = Path(root_path).expanduser().resolve()
    if not root.is_dir() or not folder_relpaths:
        return ""

    files: list[Path] = []
    for relpath in folder_relpaths:
        folder = root / relpath
        if not folder.is_dir():
            continue
        files.extend(
            p for p in folder.rglob("*")
            if p.suffix in (".md", ".txt") and p.is_file()
        )

    files = sorted(set(files), key=lambda p: str(p.relative_to(root)))
    if not files:
        return ""

    sections: list[str] = []
    total = 0

    for fpath in files:
        if total >= max_chars:
            break
        try:
            content = fpath.read_text(errors="ignore")
            rel = fpath.relative_to(root)
            remaining = max_chars - total
            excerpt = content[:remaining]
            sec = f"## {rel}\n\n{excerpt}\n\n---"
            sections.append(sec)
            total += len(sec)
        except Exception:
            pass

    if not sections:
        return ""

    return f"# Knowledge Base ({len(sections)} documents)\n\n" + "\n\n".join(sections)


def build_selective_context(
    repos: list[str],
    knowledge_root: str,
    knowledge_folders: list[str],
) -> str:
    """Build context from selected repos and selected knowledge subfolders only."""
    parts: list[str] = []

    if repos:
        repos_ctx = read_repos_context(repos)
        if repos_ctx:
            parts.append(repos_ctx)
            logger.info("Selective: loaded %d repo(s) (%d chars)", len(repos), len(repos_ctx))

    if knowledge_root and knowledge_folders:
        knowledge_ctx = read_knowledge_folders(knowledge_root, knowledge_folders)
        if knowledge_ctx:
            parts.append(knowledge_ctx)
            logger.info("Selective: loaded %d knowledge folder(s) (%d chars)", len(knowledge_folders), len(knowledge_ctx))

    if not parts:
        return ""

    return "\n\n---\n\n".join(parts)
