"""Agent instruction file generation for gjalla attestation guidance."""

import re
from pathlib import Path


# Maps agent name → relative file path from repo root
AGENT_FILE_MAP: dict[str, str] = {
    "claude-code": "CLAUDE.md",
    "gemini-cli": "GEMINI.md",
    "cursor": ".cursor/rules/gjalla.mdc",
    "codex": "AGENTS.md",
}

_SECTION_MARKER = "## Gjalla System-level Version Control"

_CORE_TEMPLATE = """\
## Gjalla System-level Version Control

This project uses gjalla to track system-level changes, such as architecture, tech stack, external dependencies, and rules/constraints. Project ID: $PROJECT_ID

### Setup
Ensure gjalla CLI is installed: `pip install gjalla` (or `pipx install gjalla`)

### Before Every Commit
1. Stage your changes
2. Compute diff hash: `git diff --staged | shasum -a 256 | cut -d' ' -f1`
3. Get project rules and architecture using **one of these** (in order of preference):
   - `gjalla show rules` and `gjalla show arch` (works in both local-only and remote modes)
   - Read `.gjalla/cache/rules.md` and `.gjalla/cache/architecture.txt` directly (remote mode only)
   - Fetch from the gjalla MCP server (`get_project_rules`, `get_architecture_spec`)
   If the cache is missing or stale, run `gjalla sync` to refresh it.
4. Write `.gjalla/.commit-attestation.md` using the format in `.gjalla/example-attestation.md` — fill in ALL primitives: architecture (elements, connections, decisions), data_flows, tech_stack, external_dependencies, services, capabilities
5. The pre-commit hook validates the diff hash. Retry `git commit` after writing the attestation.

### System-level Context
Use the gjalla CLI (`gjalla show rules`, `gjalla show arch`, `gjalla show state`) or the gjalla MCP server for rules, architecture specs, and other guidance before making changes. The CLI works in both local-only and remote modes — just call it and it will return the right data. Fall back to the MCP server if the CLI is unavailable.
Your attestation lets you confirm your adherence to the system's source of truth and report any meaningful changes to the system.

### Windows Note
On Windows, Git for Windows includes its own POSIX shell (MSYS2), so the shell commands above (e.g. `shasum`, `cut`) work inside git hooks and Git Bash. You do not need WSL or a separate Unix environment.
"""

_CURSOR_FRONTMATTER = """\
---
description: Gjalla architecture governance and attestation requirements
alwaysApply: true
---
"""

# Sanitize project_id: only allow alphanumeric, dash, underscore, dot
_PROJECT_ID_RE = re.compile(r"[^a-zA-Z0-9._-]")


def _sanitize_project_id(project_id: str) -> str:
    """Strip characters that could inject content into markdown templates."""
    return _PROJECT_ID_RE.sub("", project_id)


def generate_agent_guidance(agent: str, project_id: str) -> str:
    """Return the full file content for the given agent type."""
    safe_id = _sanitize_project_id(project_id)
    body = _CORE_TEMPLATE.replace("$PROJECT_ID", safe_id)
    if agent == "cursor":
        return _CURSOR_FRONTMATTER + body
    return body


def detect_agents(repo_root: Path) -> list[str]:
    """Return agent names whose instruction files already exist."""
    found = []
    for agent, rel_path in AGENT_FILE_MAP.items():
        if (repo_root / rel_path).exists():
            found.append(agent)
    return found


def write_agent_guidance(repo_root: Path, agent: str, project_id: str) -> Path:
    """Write or update the agent guidance file. Returns the path written.

    Raises ValueError if agent is not in AGENT_FILE_MAP.
    """
    if agent not in AGENT_FILE_MAP:
        raise ValueError(f"Unknown agent {agent!r}. Valid: {', '.join(AGENT_FILE_MAP)}")

    rel_path = AGENT_FILE_MAP[agent]
    target = repo_root / rel_path

    content = generate_agent_guidance(agent, project_id)

    # Cursor: always write as standalone file (won't conflict with other rule files)
    if agent == "cursor":
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(content, encoding="utf-8")
        return target

    # For markdown files: append or replace section
    if target.exists():
        existing = target.read_text(encoding="utf-8")
        if _SECTION_MARKER in existing:
            # Replace from marker to the next non-Gjalla ## heading or end of file.
            # Use a greedy match up to the next `\n## ` that isn't `## Gjalla`,
            # preserving everything after that boundary.
            pattern = re.escape(_SECTION_MARKER) + r".*?(?=\n## (?!Gjalla)|\Z)"
            new_content = content.rstrip()
            replaced = re.sub(pattern, new_content, existing, count=1, flags=re.DOTALL)
            # Safety: ensure trailing newline so any following content isn't joined
            if not replaced.endswith("\n"):
                replaced += "\n"
            target.write_text(replaced, encoding="utf-8")
        else:
            # Append with separator
            separator = "\n" if existing.endswith("\n") else "\n\n"
            target.write_text(existing + separator + content, encoding="utf-8")
    else:
        target.write_text(content, encoding="utf-8")

    return target
