"""AGENTS.md formatter.

AGENTS.md is the emerging cross-tool standard for AI agent instructions,
supported by Claude Code, OpenAI Codex, Gemini CLI, Cursor, and others.
Generated by saar -- https://getsaar.com

Content is identical to CLAUDE.md but uses generic agent framing rather
than Claude-specific language. This makes it the better default: any AI
tool can consume it, and tool-specific files (CLAUDE.md, .cursorrules) can
be generated alongside it via --format all.
"""
from saar.models import CodebaseDNA
from saar.formatters._tribal import render_tribal_knowledge

# Markers we strip from team_rules before embedding -- prevents nested markers
# and the inception loop where saar reads its own output as input
_SAAR_MARKER_START = "<!-- SAAR:AUTO-START -->"
_SAAR_MARKER_END = "<!-- SAAR:AUTO-END -->"

# Max chars to include from hand-written team rules.
# The auto-detected DNA above already covers conventions -- team rules should
# add what's MISSING (data flow, key services, commit rules), not repeat them.
_TEAM_RULES_MAX_CHARS = 3000


def _clean_team_rules(raw: str) -> str:
    """Strip SAAR markers and truncate team rules before embedding.

    Why: team_rules comes from reading CLAUDE.md or .cursorrules verbatim.
    If CLAUDE.md was previously generated by saar, it contains SAAR:AUTO-START
    markers, causing nested markers and triple duplication on re-runs.

    We strip the auto-generated block (between markers) and keep only the
    human-written sections that live outside the markers.
    """
    # strip auto-generated blocks between markers -- those are already
    # covered by the DNA sections above, no need to repeat them
    result = raw
    while _SAAR_MARKER_START in result and _SAAR_MARKER_END in result:
        start = result.find(_SAAR_MARKER_START)
        end = result.find(_SAAR_MARKER_END, start)
        if start == -1 or end == -1:
            break
        result = result[:start] + result[end + len(_SAAR_MARKER_END):]

    result = result.strip()

    # truncate if still very long -- human rules should be concise
    if len(result) > _TEAM_RULES_MAX_CHARS:
        result = result[:_TEAM_RULES_MAX_CHARS].rstrip()
        result += "\n\n*(truncated -- see full file for remaining rules)*"

    return result


def render_agents_md(dna: CodebaseDNA) -> str:
    """Render DNA as an AGENTS.md file."""
    lines = [f"# AGENTS.md -- {dna.repo_name}\n"]
    lines.append(
        "Generated by [saar](https://getsaar.com). "
        "Re-run `saar . --format agents` to update auto-detected sections.\n"
    )

    if dna.detected_framework:
        lines.append(f"This is a **{dna.detected_framework}** project.\n")

    # -- codebase stats (size orientation only -- async% and type hint% not actionable) --
    if dna.total_functions:
        parts = [f"{dna.total_functions:,} functions"]
        if dna.total_classes:
            parts.append(f"{dna.total_classes:,} classes")
        total_files = sum(dna.language_distribution.values()) if dna.language_distribution else 0
        if total_files:
            parts.append(f"{total_files} files")
        lines.append(", ".join(parts) + ".\n")

    # -- language distribution --
    if dna.language_distribution:
        lang_str = ", ".join(
            f"{lang} ({count} files)"
            for lang, count in sorted(
                dna.language_distribution.items(), key=lambda x: -x[1]
            )
        )
        lines.append(f"**Languages:** {lang_str}\n")

    # -- frontend stack --
    fp = dna.frontend_patterns
    if fp:
        lines.append("\n## Frontend\n")
        stack_parts = []
        if fp.framework:
            stack_parts.append(fp.framework)
        if fp.language:
            stack_parts.append(fp.language)
        if fp.build_tool:
            stack_parts.append(fp.build_tool)
        if stack_parts:
            lines.append(f"**Stack:** {' + '.join(stack_parts)}")
        if fp.package_manager:
            pm = fp.package_manager
            lines.append(f"- Package manager: `{pm}` -- always use `{pm} install`, never npm/yarn")
        if fp.component_library:
            lines.append(f"- Component library: {fp.component_library} -- use over custom components")
        if fp.state_management:
            lines.append(f"- State management: {fp.state_management}")
        if fp.styling:
            lines.append(f"- Styling: {fp.styling} -- no raw CSS files")
        if fp.test_framework:
            test_line = f"- Frontend tests: {fp.test_framework}"
            if fp.test_command:
                test_line += f" (`{fp.test_command}`)"
            lines.append(test_line)

        # React-specific coding patterns detected from source files
        if fp.uses_react_query:
            lines.append("- Data fetching: use `useQuery`/`useMutation` -- never raw `fetch` in `useEffect`")
        if fp.uses_cn_utility:
            lines.append("- Class merging: use `cn()` from `@/lib/utils` for conditional Tailwind classes")
        if fp.has_custom_hooks and fp.canonical_data_hook:
            lines.append(f"- Custom hooks in `hooks/` -- canonical example: `{fp.canonical_data_hook}`")
        elif fp.has_custom_hooks:
            lines.append("- Custom hooks live in `hooks/` with `use` prefix")
        if fp.shared_types_file:
            lines.append(f"- Shared TypeScript interfaces in `{fp.shared_types_file}` -- don't duplicate types inline")

    # -- coding conventions --
    lines.append("## Coding Conventions\n")
    nc = dna.naming_conventions
    if nc.function_style != "unknown":
        lines.append(f"- Functions: `{nc.function_style}`")
    if nc.class_style != "unknown":
        lines.append(f"- Classes: `{nc.class_style}`")
    if nc.constant_style != "unknown":
        lines.append(f"- Constants: `{nc.constant_style}`")
    if nc.file_style != "unknown":
        lines.append(f"- Files: `{nc.file_style}`")

    if dna.common_imports:
        # Only show imports that are project-specific -- skip stdlib and obvious ones.
        # Generic imports (json, os, re, logging, time) add zero value to an AI:
        # every Python dev already knows them. Project-specific imports (from saar.models,
        # from fastapi, from structlog) are the ones that matter.
        _STDLIB_NOISE = {
            "import json", "import os", "import re", "import time", "import sys",
            "import logging", "import math", "import random", "import copy",
            "import io", "import abc", "import enum", "import uuid",
            "from __future__ import annotations",
            "from typing import Optional", "from typing import List",
            "from typing import Dict", "from typing import Tuple",
            "from typing import Any", "from typing import Union",
            "from dataclasses import dataclass", "from pathlib import Path",
            "from collections import Counter", "from datetime import datetime",
        }
        project_imports = [
            imp for imp in dna.common_imports[:15]
            if imp.strip() not in _STDLIB_NOISE
            and not imp.strip().startswith("from typing import")
            and not imp.strip().startswith("import typing")
        ]
        if project_imports:
            lines.append("\nKey project imports:")
            lines.append("```")
            for imp in project_imports[:6]:
                lines.append(imp)
            lines.append("```")

    # -- logging --
    lp = dna.logging_patterns
    if lp.logger_import or lp.structured_logging:
        lines.append("\n## Logging\n")
        if lp.logger_import:
            lines.append(f"- Use `{lp.logger_import}` -- never bare `print()`")
        if lp.structured_logging:
            lines.append("- Structured logging (JSON format)")

    # -- critical files --
    if dna.critical_files:
        lines.append("\n## Critical Files\n")
        lines.append(
            "These files have the most dependents in the codebase. "
            "Understand them before making changes.\n"
        )
        for cf in dna.critical_files[:8]:
            f = cf.get("file", "") if isinstance(cf, dict) else cf
            d = cf.get("dependents", 0) if isinstance(cf, dict) else 0
            if f:
                dep_note = f" ({d} dependents)" if d else ""
                lines.append(f"- `{f}`{dep_note}")

    # -- canonical examples -- most-imported file per category (OPE-142) --
    if dna.canonical_examples:
        lines.append("\n## Canonical Examples\n")
        lines.append(
            "When adding new code, follow these existing files as reference.\n"
        )
        for ex in dna.canonical_examples[:6]:
            file_path = ex.get("file", "")
            count = ex.get("import_count", 0)
            reason = ex.get("reason", f"follow `{file_path}`")
            if file_path:
                lines.append(f"- {reason} ({count} importers)")

    # -- project structure --
    if dna.project_structure:
        lines.append("\n## Project Structure\n")
        lines.append(dna.project_structure)

    # -- architecture + deep rules (OPE-96) --
    # When deep_rules exist, use them instead of bare labels.
    # Deep rules are full sentences with evidence and reasoning.
    # Fall back to label-based output when deep extraction found nothing.
    sp = dna.service_patterns
    ap = dna.auth_patterns
    deep_rules = getattr(dna, "deep_rules", [])
    deep_by_category: dict = {}
    for r in deep_rules:
        cat = r.get("category", "general")
        deep_by_category.setdefault(cat, []).append(r)

    # Auth section -- prefer deep rules over labels
    auth_deep = deep_by_category.get("auth", [])
    if auth_deep:
        lines.append("\n## Auth\n")
        for rule in auth_deep[:3]:
            lines.append(f"- {rule['text']}")
    elif sp.singleton_services or sp.dependencies_file or ap.middleware_used or ap.auth_decorators:
        lines.append("\n## Architecture\n")
        if sp.singleton_services:
            lines.append(f"- Services are singletons: `{', '.join(sp.singleton_services)}`")
        if sp.dependencies_file:
            lines.append(f"- Dependency wiring: `{sp.dependencies_file}`")
        if sp.injection_pattern:
            lines.append(f"- DI pattern: {sp.injection_pattern}")
        if ap.middleware_used:
            lines.append(f"- Auth middleware: `{', '.join(ap.middleware_used)}`")
        if ap.auth_decorators:
            lines.append(f"- Auth decorators: `{', '.join(ap.auth_decorators)}`")
        if ap.auth_context_type:
            lines.append(f"- Auth context: `{ap.auth_context_type}`")

    # -- database --
    db = dna.database_patterns
    if db.orm_used:
        lines.append("\n## Database\n")
        lines.append(f"- ORM: {db.orm_used}")
        if db.id_type != "unknown":
            lines.append(f"- ID type: `{db.id_type}`")
        if db.timestamp_type != "unknown":
            lines.append(f"- Timestamp type: `{db.timestamp_type}`")
        if db.has_rls:
            lines.append("- Row Level Security enabled -- always respect RLS policies")
        if db.cascade_deletes:
            lines.append("- Cascade deletes on parent-child relationships")

    # -- error handling --
    ep = dna.error_patterns
    if ep.exception_classes or ep.http_exception_usage or ep.logging_on_error:
        lines.append("\n## Error Handling\n")
        if ep.exception_classes:
            all_exc = ep.exception_classes
            total = len(all_exc)
            if total <= 10:
                # few enough -- list them all inline
                lines.append(f"- Use domain exceptions: `{', '.join(all_exc)}`")
            else:
                # too many to list -- show top 10 + search hint (OPE-183)
                top = all_exc[:10]
                lines.append(
                    f"- Use domain exceptions ({total} total). Most common: "
                    f"`{', '.join(top)}`"
                )
                lines.append(
                    "- Never create new exception classes without checking: "
                    "`grep -r 'class.*Error\\|class.*Exception' . --include='*.py' -l`"
                )
        if ep.http_exception_usage:
            lines.append("- Use HTTPException for API errors")
        if ep.logging_on_error:
            lines.append("- Log exceptions before re-raising")

    # -- testing -- prefer deep rules over bare labels
    tp = dna.test_patterns
    testing_deep = deep_by_category.get("testing", [])
    if tp.framework:
        lines.append("\n## Testing\n")
        lines.append(f"- Framework: {tp.framework}")
        lines.append(f"- Pattern: `{tp.test_file_pattern}`")
        if tp.fixture_style:
            lines.append(f"- Fixtures: {tp.fixture_style}")
        if tp.mock_library:
            lines.append(f"- Mocking: {tp.mock_library}")
        if tp.has_conftest:
            lines.append("- Shared fixtures in `conftest.py`")
        # add deep testing rules
        for rule in testing_deep[:2]:
            lines.append(f"- {rule['text']}")

    # -- naming deep rules --
    naming_deep = deep_by_category.get("naming", [])
    if naming_deep:
        lines.append("\n## Naming\n")
        for rule in naming_deep[:3]:
            lines.append(f"- {rule['text']}")

    # -- never_do deep rules (engine-detected, not user-captured) --
    never_do_deep = deep_by_category.get("never_do", [])
    if never_do_deep:
        existing_never = "\n".join(lines)
        for rule in never_do_deep[:3]:
            # only add if not already covered by tribal knowledge
            if rule["text"].lower()[:20] not in existing_never.lower():
                lines.append(f"- {rule['text']}")

    # -- verification workflow --
    if dna.verify_workflow:
        lines.append("\n## How to Verify Changes Work\n")
        lines.append(f"{dna.verify_workflow}")
        lines.append("\nRun these before considering any change done.")

    # -- API --
    if dna.api_versioning or dna.router_pattern:
        lines.append("\n## API\n")
        if dna.api_versioning:
            lines.append(f"- Versioning: `{dna.api_versioning}`")
        if dna.router_pattern:
            lines.append(f"- Router pattern: `{dna.router_pattern}`")

    # -- circular dep warnings --
    if dna.circular_dependencies:
        lines.append("\n## Circular Dependencies (resolve before adding more)\n")
        for pair in dna.circular_dependencies[:5]:
            lines.append(f"- `{pair[0]}` <-> `{pair[1]}`")

    # -- tribal knowledge from guided interview (highest value content) --
    tribal = render_tribal_knowledge(dna.interview)
    if tribal:
        lines.append(tribal)

    # -- team rules verbatim (human-written sections only) --
    if dna.team_rules:
        cleaned = _clean_team_rules(dna.team_rules)
        if cleaned:
            lines.append("\n## Project-Specific Rules\n")
            if dna.team_rules_source:
                lines.append(f"*From `{dna.team_rules_source}`*\n")
            # Cap team rules at 40 lines -- prevents a huge existing CLAUDE.md
            # from blowing the budget. The most important rules are at the top.
            rule_lines = cleaned.splitlines()
            if len(rule_lines) > 40:
                trimmed = "\n".join(rule_lines[:40])
                lines.append(trimmed)
                lines.append(
                    f"\n*[{len(rule_lines) - 40} lines omitted -- "
                    f"run `saar extract --verbose` for full rules]*"
                )
            else:
                lines.append(cleaned)

    return "\n".join(lines) + "\n"
