"""Build or rebuild the codebase index.

Output formats: text (default), ``--json``. SARIF is deliberately NOT
emitted because ``roam index`` is a setup/state command — its output is
human-facing index-build status (files discovered, symbols extracted,
edges resolved, elapsed seconds), not analysis findings with file:line
coordinates. SARIF is reserved for scanning results. See action.yml
_SUPPORTED_SARIF allowlist + W1175-RESEARCH propagation plan +
W1224-audit memo.
"""

from __future__ import annotations

import time

import click

from roam.capability import roam_capability
from roam.output.formatter import json_envelope, to_json


@roam_capability(
    name="index",
    category="getting-started",
    summary="Build or rebuild the codebase index",
    maturity="stable",
    mcp_expose=True,
    mcp_preset=("core",),
    side_effect=True,
    task_required=False,
    destructive=False,
    stale_sensitive=True,
    ai_safe=True,
    requires_index=False,
)
@click.command(name="index")
@click.option("--force", is_flag=True, help="Force full reindex")
@click.option("--verbose", is_flag=True, help="Show detailed warnings during indexing")
@click.option("--quiet", "-q", is_flag=True, help="Suppress progress output")
@click.pass_context
def run_index_command(ctx, force, verbose, quiet):
    """Build or rebuild the codebase index.

    Unlike ``init`` (which bootstraps the project with config files and
    workflows), this command only rebuilds the codebase index and metrics
    database.

    \b
    Examples:
      roam index
      roam index --force
      roam index --verbose
      roam index -q

    See also ``init`` (first-run bootstrap), ``watch`` (auto-reindex
    on file changes), and ``index-stats`` (DB size + symbol counts).
    """
    json_mode = ctx.obj.get("json") if ctx.obj else False
    include_excluded = ctx.obj.get("include_excluded") if ctx.obj else False
    from roam.db.connection import db_exists, open_db
    from roam.index.indexer import Indexer

    # Suppress progress in JSON mode (consumers don't want progress text)
    suppress_progress = quiet or json_mode

    t0 = time.monotonic()
    indexer = Indexer()
    indexer.run(
        force=force,
        verbose=verbose,
        include_excluded=include_excluded,
        quiet=suppress_progress,
    )
    elapsed = time.monotonic() - t0

    # Show summary stats
    if db_exists():
        with open_db(readonly=True) as conn:
            file_count = conn.execute("SELECT COUNT(*) FROM files").fetchone()[0]
            sym_count = conn.execute("SELECT COUNT(*) FROM symbols").fetchone()[0]
            edge_count = conn.execute("SELECT COUNT(*) FROM edges").fetchone()[0]

            lang_rows = conn.execute(
                "SELECT language, COUNT(*) as cnt FROM files WHERE language IS NOT NULL GROUP BY language ORDER BY cnt DESC"
            ).fetchall()

            avg_sym = sym_count / file_count if file_count else 0

            # Parse coverage: only count files with a parseable language
            from roam.languages.registry import _SUPPORTED_LANGUAGES

            parseable_langs = ",".join(f"'{l}'" for l in _SUPPORTED_LANGUAGES)
            parseable_count = conn.execute(
                f"SELECT COUNT(*) FROM files WHERE language IN ({parseable_langs})"
            ).fetchone()[0]
            parsed_ok = conn.execute(
                f"SELECT COUNT(DISTINCT f.id) FROM files f "
                f"JOIN symbols s ON s.file_id = f.id "
                f"WHERE f.language IN ({parseable_langs})"
            ).fetchone()[0]
            coverage = (parsed_ok * 100 / parseable_count) if parseable_count else 0

            _index_verdict = f"indexed {file_count} files, {sym_count} symbols, {edge_count} edges"

            if json_mode:
                click.echo(
                    to_json(
                        json_envelope(
                            "index",
                            summary={
                                "verdict": _index_verdict,
                                "files": file_count,
                                "symbols": sym_count,
                                "edges": edge_count,
                            },
                            elapsed_s=round(elapsed, 1),
                            files=file_count,
                            symbols=sym_count,
                            edges=edge_count,
                            languages={r["language"]: r["cnt"] for r in lang_rows[:8]},
                            avg_symbols_per_file=round(avg_sym, 1),
                            parse_coverage_pct=round(coverage, 0),
                        )
                    )
                )
            elif not quiet:
                lang_str = ", ".join(f"{r['language']}={r['cnt']}" for r in lang_rows[:8])
                click.echo(f"VERDICT: {_index_verdict}")
                click.echo()
                click.echo(f"  Completed in {elapsed:.1f}s")
                click.echo(f"  Files: {file_count}  Symbols: {sym_count}  Edges: {edge_count}")
                click.echo(f"  Languages: {lang_str}")
                click.echo(f"  Avg symbols/file: {avg_sym:.1f}  Parse coverage: {coverage:.0f}%")

            # Auto-snapshot after every index for trend tracking
            try:
                from roam.commands.metrics_history import append_snapshot

                with open_db() as wconn:
                    snap = append_snapshot(wconn, source="index")
                if not json_mode and not quiet:
                    click.echo(f"  Health: {snap['health_score']}/100 (snapshot saved)")
            except Exception as _exc:  # noqa: BLE001 — defensive
                # Don't fail indexing if snapshot fails
                from roam.observability import log_swallowed

                log_swallowed("cmd_index:auto_snapshot", _exc)
    else:
        # Pattern 1C (CLAUDE.md): never emit empty stdout. If the indexer ran
        # but the DB isn't visible (race with cloud-sync, write to a different
        # location, etc.) emit a structured envelope so MCP wrappers and
        # piped consumers don't crash on json.loads("").
        _index_verdict = "indexer ran but no index database is visible"
        if json_mode:
            click.echo(
                to_json(
                    json_envelope(
                        "index",
                        summary={
                            "verdict": _index_verdict,
                            "state": "no_db_after_index",
                            "partial_success": True,
                        },
                        elapsed_s=round(elapsed, 1),
                        partial_success=True,
                        resolution="unresolved",
                    )
                )
            )
        elif not quiet:
            click.echo(f"VERDICT: {_index_verdict}")
            click.echo(f"  Completed in {elapsed:.1f}s")
            click.echo("  Hint: check ROAM_DB_DIR / cloud-sync; run `roam doctor` for a diagnostic dump.")


index = run_index_command
