"""Linear Canal sidecar file generator.

Generates .{filename}.lc.md files next to source files.
These contain pre-compiled LC context so ANY agent picks
up Linear Canal context just by reading a source file.
"""
from __future__ import annotations

import logging
import os
from pathlib import Path
from typing import Optional

logger = logging.getLogger(__name__)

SOURCE_EXTENSIONS = {
    ".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs", ".java",
    ".php", ".rb", ".cs", ".c", ".h", ".cpp", ".hpp", ".vue",
    ".svelte", ".kt", ".swift", ".scala", ".sh",
}

_SIDECAR_HEADER = """\
<!-- LINEAR CANAL SIDECAR — auto-generated, do not edit manually -->
<!-- This file provides pre-compiled context for AI agents. -->
<!-- Read this alongside {filename} to understand its role in the codebase. -->

"""

_SKIP_DIRS = {
    "node_modules", "__pycache__", ".git", "dist", "build",
    ".venv", "venv", ".mypy_cache", ".pytest_cache", ".ruff_cache",
}


def find_project_ctx(workspace: Path) -> tuple[Optional[str], Optional[Path]]:
    """Find the .linear_canal_data directory and ctx path."""
    lc_data = workspace / ".linear_canal_data"
    if not lc_data.exists():
        return None, None
    dirs = [d for d in lc_data.iterdir() if d.is_dir()]
    if not dirs:
        return None, None
    project_id = dirs[0].name
    ctx_dir = dirs[0] / "ctx"
    return project_id, ctx_dir if ctx_dir.exists() else None


def load_ctx_index(ctx_dir: Path) -> dict[str, str]:
    """Load all ctx files into a dict keyed by stem name."""
    index = {}
    for f in ctx_dir.glob("*.ctx"):
        try:
            index[f.stem] = f.read_text(encoding="utf-8", errors="replace")
        except Exception:
            pass
    return index


def find_module_for_file(file_path: Path, ctx_index: dict[str, str]) -> Optional[str]:
    """Find which module ctx file mentions this source file."""
    rel = str(file_path).replace("\\", "/")
    basename = file_path.name
    _global_ctx = {"ARCHITECTURE", "INTERFACES", "RISKS", "PATTERNS", "DEPENDENCIES", "OWNERSHIP"}
    for module_name, content in ctx_index.items():
        if module_name in _global_ctx:
            continue
        if rel in content or basename in content:
            return module_name
    return None


def generate_sidecar_content(
    file_path: Path,
    workspace: Path,
    project_id: str,
    ctx_dir: Path,
    ctx_index: dict[str, str],
) -> str:
    """Generate sidecar markdown content for a source file."""
    try:
        rel_path = file_path.relative_to(workspace)
    except ValueError:
        rel_path = file_path

    lines = [_SIDECAR_HEADER.format(filename=file_path.name)]
    lines.append(f"# LC Context: `{rel_path}`\n\n")
    lines.append(f"**Project:** {project_id}\n")

    module_name = find_module_for_file(file_path, ctx_index)
    if module_name and module_name in ctx_index:
        lines.append(f"**Module:** {module_name}\n\n")
        module_ctx = ctx_index[module_name][:1500]
        lines.append(f"## Module Context ({module_name})\n\n")
        lines.append(module_ctx)
        lines.append("\n")
    else:
        lines.append("**Module:** (not yet indexed — run `linear-canal setup`)\n\n")

    lines.append("## Full Context Available\n\n")
    lines.append(f"- `.linear_canal_data/{project_id}/ctx/ARCHITECTURE.ctx` — full module map\n")
    lines.append(f"- `.linear_canal_data/{project_id}/ctx/INTERFACES.ctx` — all exports\n")
    lines.append(f"- `.linear_canal_data/{project_id}/ctx/RISKS.ctx` — flagged issues\n")
    if module_name:
        lines.append(f"- `.linear_canal_data/{project_id}/ctx/{module_name}.ctx` — this module\n")

    return "".join(lines)


def write_sidecar(file_path: Path, content: str) -> None:
    """Write sidecar file atomically."""
    sidecar_path = file_path.parent / (file_path.name + ".lc.md")
    tmp = sidecar_path.with_suffix(".tmp")
    try:
        tmp.write_text(content, encoding="utf-8")
        os.replace(tmp, sidecar_path)
    except Exception as exc:
        logger.warning("Failed to write sidecar for %s: %s", file_path, exc)
        try:
            tmp.unlink(missing_ok=True)
        except Exception:
            pass


def generate_all_sidecars(workspace: Path) -> int:
    """Generate sidecar files for all indexed source files. Returns count."""
    project_id, ctx_dir = find_project_ctx(workspace)
    if not project_id or not ctx_dir:
        return 0

    ctx_index = load_ctx_index(ctx_dir)
    count = 0

    for root, dirs, files in os.walk(workspace):
        dirs[:] = [
            d for d in dirs
            if not d.startswith(".") and d not in _SKIP_DIRS
        ]
        root_path = Path(root)
        for fname in files:
            if fname.endswith(".lc.md"):
                continue
            file_path = root_path / fname
            if file_path.suffix not in SOURCE_EXTENSIONS:
                continue
            content = generate_sidecar_content(file_path, workspace, project_id, ctx_dir, ctx_index)
            write_sidecar(file_path, content)
            count += 1

    return count


def delete_all_sidecars(workspace: Path) -> int:
    """Remove all .lc.md sidecar files. Returns count deleted."""
    count = 0
    for root, dirs, files in os.walk(workspace):
        dirs[:] = [d for d in dirs if not d.startswith(".") and d not in _SKIP_DIRS]
        for fname in files:
            if fname.endswith(".lc.md"):
                Path(root, fname).unlink(missing_ok=True)
                count += 1
    return count
