"""Show directory contents: exports, signatures, and dependencies.

Output formats: text (default), ``--json``. SARIF is deliberately NOT
emitted because module outputs are invocation-scoped symbol registries
— not per-location violations. Editor consumers should use the JSON
envelope directly. See action.yml _SUPPORTED_SARIF allowlist
+ W1175-RESEARCH Bucket B propagation plan + W1148 audit memo.
"""

from __future__ import annotations

import click

from roam.capability import roam_capability
from roam.commands.resolve import ensure_index
from roam.db.connection import batched_in, open_db
from roam.db.queries import (
    FILES_IN_DIR,
    SYMBOLS_IN_DIR,
)
from roam.output.formatter import (
    abbrev_kind,
    format_signature,
    format_table,
    json_envelope,
    loc,
    section,
    to_json,
)


def _module_deps(conn, file_ids):
    """Compute external import/imported-by maps for a set of file IDs.

    Bulk-fetches all incoming/outgoing file_edges in two batched queries
    instead of one per file_id (was 2 * N queries for N files).
    """
    file_id_set = set(file_ids)
    imports_external: dict = {}
    imported_by_external: dict = {}
    if not file_ids:
        return imports_external, imported_by_external

    import_rows = batched_in(
        conn,
        "SELECT fe.source_file_id AS src, f.id AS id, f.path AS path, "
        "fe.symbol_count AS symbol_count "
        "FROM file_edges fe JOIN files f ON fe.target_file_id = f.id "
        "WHERE fe.source_file_id IN ({ph})",
        list(file_ids),
    )
    for row in import_rows:
        if row["id"] not in file_id_set:
            imports_external[row["path"]] = imports_external.get(row["path"], 0) + (row["symbol_count"] or 0)

    imported_by_rows = batched_in(
        conn,
        "SELECT fe.target_file_id AS tgt, f.id AS id, f.path AS path, "
        "fe.symbol_count AS symbol_count "
        "FROM file_edges fe JOIN files f ON fe.source_file_id = f.id "
        "WHERE fe.target_file_id IN ({ph})",
        list(file_ids),
    )
    for row in imported_by_rows:
        if row["id"] not in file_id_set:
            imported_by_external[row["path"]] = imported_by_external.get(row["path"], 0) + (row["symbol_count"] or 0)

    return imports_external, imported_by_external


def _collect_sym_ids(conn, files):
    """Collect all symbol IDs for a list of files (single batched query)."""
    file_ids = [f["id"] for f in files]
    if not file_ids:
        return set()
    rows = batched_in(
        conn,
        "SELECT id FROM symbols WHERE file_id IN ({ph})",
        file_ids,
    )
    return {r["id"] for r in rows}


def _module_cohesion(conn, all_sym_ids):
    """Compute cohesion metrics for a set of symbol IDs."""
    if not all_sym_ids:
        return 0, 0, 0
    ids_list = list(all_sym_ids)
    src_rows = batched_in(
        conn,
        "SELECT DISTINCT source_id, target_id FROM edges WHERE source_id IN ({ph})",
        ids_list,
    )
    tgt_rows = batched_in(
        conn,
        "SELECT DISTINCT source_id, target_id FROM edges WHERE target_id IN ({ph})",
        ids_list,
    )
    all_edges = {(r["source_id"], r["target_id"]) for r in src_rows + tgt_rows}
    total_edges = len(all_edges)
    internal_edges = sum(1 for s, t in all_edges if s in all_sym_ids and t in all_sym_ids)
    cohesion = internal_edges * 100 / total_edges if total_edges else 0
    return cohesion, internal_edges, total_edges


@roam_capability(
    name="module",
    category="exploration",
    summary="Show directory contents: exports, signatures, deps",
    maturity="stable",
    mcp_expose=True,
    mcp_preset=("core", "architecture"),
    side_effect=False,
    task_required=False,
    destructive=False,
    stale_sensitive=True,
    ai_safe=True,
    requires_index=True,
)
@click.command()
@click.argument("path")
@click.pass_context
def module(ctx, path):
    """Show directory contents: exports, signatures, deps.

    Unlike ``map`` (which gives a project-wide overview with PageRank rankings), this
    command analyzes a single directory's cohesion, API surface percentage, and external
    coupling.
    """
    json_mode = ctx.obj.get("json") if ctx.obj else False
    ensure_index()

    path = path.replace("\\", "/").rstrip("/")

    with open_db(readonly=True) as conn:
        if path == ".":
            # Root-level files: match paths without any directory separator
            files = conn.execute(
                "SELECT * FROM files WHERE path NOT LIKE '%/%' ORDER BY path",
            ).fetchall()
            if not files:
                # Fall back: all files
                files = conn.execute("SELECT * FROM files ORDER BY path").fetchall()
        else:
            pattern = f"{path}/%"
            files = conn.execute(FILES_IN_DIR, (pattern,)).fetchall()
            if not files:
                files = conn.execute(FILES_IN_DIR, (f"%{path}/%",)).fetchall()
        if not files:
            click.echo(f"No files found under: {path}/")
            raise SystemExit(1)

        # --- Exported symbols ---
        if path == ".":
            symbols = conn.execute(
                """SELECT s.*, f.path as file_path FROM symbols s
                   JOIN files f ON s.file_id = f.id
                   WHERE f.path NOT LIKE '%/%'
                   ORDER BY f.path, s.line_start""",
            ).fetchall()
        else:
            sym_pattern = f"{path}/%"
            symbols = conn.execute(SYMBOLS_IN_DIR, (sym_pattern,)).fetchall()
            if not symbols:
                symbols = conn.execute(SYMBOLS_IN_DIR, (f"%{path}/%",)).fetchall()

        file_ids = [f["id"] for f in files]
        imports_external, imported_by_external = _module_deps(conn, file_ids)
        all_sym_ids = _collect_sym_ids(conn, files)
        total_syms = len(all_sym_ids)
        exported_count = len(symbols) if symbols else 0
        api_surface = exported_count * 100 / total_syms if total_syms else 0
        cohesion, internal_edges, total_edges = _module_cohesion(conn, all_sym_ids)
        ext_importers = len(imported_by_external)

        if json_mode:
            _verdict = f"{path}/: {len(files)} files, {exported_count} symbols, {ext_importers} importers"
            click.echo(
                to_json(
                    json_envelope(
                        "module",
                        summary={
                            "verdict": _verdict,
                            "file_count": len(files),
                            "cohesion_pct": round(cohesion),
                            "external_importers": ext_importers,
                        },
                        path=path,
                        file_count=len(files),
                        files=[{"path": f["path"], "language": f["language"], "lines": f["line_count"]} for f in files],
                        symbols=[
                            {
                                "name": s["name"],
                                "kind": s["kind"],
                                "signature": s["signature"] or "",
                                "location": loc(s["file_path"], s["line_start"]),
                            }
                            for s in (symbols or [])
                        ],
                        external_imports=dict(sorted(imports_external.items(), key=lambda x: -x[1])),
                        imported_by_external=dict(sorted(imported_by_external.items(), key=lambda x: -x[1])),
                        cohesion_pct=round(cohesion),
                        api_surface_pct=round(api_surface),
                        external_importers=ext_importers,
                    )
                )
            )
            return

        # --- Text output ---
        _verdict = f"{path}/: {len(files)} files, {exported_count} symbols, {ext_importers} importers"
        click.echo(f"VERDICT: {_verdict}\n")
        click.echo(f"Module: {path}/  ({len(files)} files)")
        click.echo()

        file_rows = [[f["path"], f["language"] or "?", str(f["line_count"])] for f in files]
        click.echo("Files:")
        click.echo(format_table(["path", "lang", "lines"], file_rows, budget=30))
        click.echo()

        if symbols:
            sym_lines = []
            for s in symbols:
                sig = format_signature(s["signature"], max_len=60)
                parts = [abbrev_kind(s["kind"]), s["name"]]
                if sig:
                    parts.append(sig)
                parts.append(loc(s["file_path"], s["line_start"]))
                sym_lines.append("  " + "  ".join(parts))
            click.echo(section(f"Exports ({len(symbols)}):", sym_lines, budget=40))
        else:
            click.echo("Exports: (none)")
        click.echo()

        if imports_external:
            sorted_imp = sorted(imports_external.items(), key=lambda x: -x[1])
            rows = [[p, str(c)] for p, c in sorted_imp]
            click.echo("External imports:")
            click.echo(format_table(["file", "symbols"], rows, budget=20))
        else:
            click.echo("External imports: (none)")
        click.echo()

        if imported_by_external:
            sorted_by = sorted(imported_by_external.items(), key=lambda x: -x[1])
            rows = [[p, str(c)] for p, c in sorted_by]
            click.echo("Imported by (external):")
            click.echo(format_table(["file", "symbols"], rows, budget=20))
        else:
            click.echo("Imported by (external): (none)")

        click.echo()
        if cohesion >= 80:
            rating = "Excellent — well-encapsulated"
        elif cohesion >= 60:
            rating = "Good — mostly self-contained"
        elif cohesion >= 30:
            rating = "Moderate — significant external coupling"
        else:
            rating = "Low — heavily coupled with external code"
        click.echo(f"Cohesion: {cohesion:.0f}% ({internal_edges}/{total_edges} edges are internal) — {rating}")
        click.echo(f"API surface: {api_surface:.0f}% exported ({exported_count}/{total_syms} symbols)")
        click.echo(f"Reused by: {ext_importers} external files")
