"""
Kodara CLI

Commands:
  kodara init     — Scan project, build memory, create .kodara/
  kodara ask      — Get relevant context for a question
  kodara watch    — Watch for changes, keep memory fresh
  kodara context  — Generate ARCHITECTURE.md overview
  kodara history  — Git history for a file
  kodara status   — Show what's indexed
  kodara team     — Share memory with your team
  kodara serve    — Start API server
"""

from __future__ import annotations

import json
import sys
import time
from pathlib import Path
from typing import Optional

import click

try:
    from rich.console import Console
    from rich.panel import Panel
    HAS_RICH = True
except ImportError:
    HAS_RICH = False

console = Console() if HAS_RICH else None


def _print(msg: str, style: str = "") -> None:
    if HAS_RICH and console:
        console.print(msg, style=style)
    else:
        click.echo(msg)


@click.group()
@click.version_option("0.2.2", prog_name="kodara")
def main():
    """Kodara — Memory Layer for AI Development.

    Index once. Ask anything. Your AI always knows the project.
    """
    pass


# ── kodara init ───────────────────────────────────────────────────────────────


@main.command()
@click.argument("path", default=".", type=click.Path(exists=True))
@click.option("--no-summaries", is_flag=True, help="Skip summary generation")
@click.option("--full", is_flag=True, help="Force full re-index (ignore cache)")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
def init(path, no_summaries, full, as_json):
    """Scan project, analyse code, build memory in .kodara/

    Run once per project. Use 'kodara watch' to keep it fresh.
    """
    _run_init(Path(path).resolve(), no_summaries=no_summaries, full=full, as_json=as_json, verbose=not as_json)


# ── kodara watch ──────────────────────────────────────────────────────────────


@main.command()
@click.argument("path", default=".", type=click.Path(exists=True))
@click.option("--no-summaries", is_flag=True)
@click.option("--context", "do_context", is_flag=True,
              help="Regenerate ARCHITECTURE.md on every change")
def watch(path, no_summaries, do_context):
    """Watch for file changes and automatically update memory."""
    from kodara.incremental.watcher import FileWatcher

    root = Path(path).resolve()
    store = _store(root)

    if not store.exists():
        _print(f"[yellow]Not initialised. Running kodara init first...[/yellow]")
        _run_init(root, no_summaries=no_summaries, full=False, as_json=False, verbose=True)

    _print(f"\n[bold cyan]Kodara Watch[/bold cyan] — [bold]{root.name}[/bold]")
    if do_context:
        _print("  [dim]ARCHITECTURE.md will update on every change[/dim]")
    _print("  Press Ctrl+C to stop.\n")

    def on_change(changed: list[str], deleted: list[str]) -> None:
        if changed:
            _print(f"  [yellow]~[/yellow] {', '.join(Path(f).name for f in changed[:4])}")
        if deleted:
            _print(f"  [red]-[/red] {', '.join(Path(f).name for f in deleted[:3])}")
        try:
            _run_init(root, no_summaries=no_summaries, full=False, as_json=False, verbose=False)
            _print("  [green]✓[/green] Memory updated")
            if do_context:
                _regenerate_context(root)
        except Exception as e:
            _print(f"  [red]Error:[/red] {e}")

    watcher = FileWatcher(str(root), on_change)
    watcher.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        _print("\n[dim]Stopped.[/dim]")
        watcher.stop()


# ── kodara ask ────────────────────────────────────────────────────────────────


@main.command()
@click.argument("query")
@click.option("--path", default=".", type=click.Path(exists=True))
@click.option("--max-modules", default=8, show_default=True)
@click.option("--depth", default=1, show_default=True)
@click.option("--json", "as_json", is_flag=True, help="Machine-readable JSON output")
@click.option("--verbose", "-v", is_flag=True, help="Show full code snippets")
def ask(query, path, max_modules, depth, as_json, verbose):
    """Get relevant context for a question about your codebase."""
    from kodara.memory.store import MemoryStore
    from kodara.context_engine.engine import ContextEngine

    root = Path(path).resolve()
    store = _store(root)
    engine = ContextEngine(store)

    try:
        result = engine.query(query, max_modules, depth)
    except ValueError as e:
        _print(f"[red]Error:[/red] {e}")
        _print(f"[dim]Run: kodara init[/dim]")
        sys.exit(1)

    if as_json:
        click.echo(json.dumps({
            "query": result.query,
            "modules": result.modules,
            "relevant_files": result.relevant_files,
            "token_estimate": result.token_estimate,
            "graph_fragment": result.graph_fragment,
        }, indent=2))
        return

    from kodara.snippets.extractor import extract_snippets
    from kodara.stats.tracker import StatsTracker

    # Record usage for stats
    try:
        tracker = StatsTracker(store.kodara_dir)
        tracker.record(
            query=query,
            tokens_used=result.token_estimate,
            total_modules=result.total_modules_searched,
            modules_selected=len(result.modules),
            index_total_files=store.load().file_count,
            relevant_files=result.relevant_files,
        )
    except Exception:
        pass

    # Attach notes to modules in context
    from kodara.notes.store import NotesStore
    notes_store = NotesStore(store.kodara_dir)

    # Context block: ready to paste into any AI
    output_lines: list[str] = [f"\n# Context: {query}\n"]

    for mod in result.modules:
        file_p = mod.get("file", "")
        summary = mod.get("summary", "")
        classes = mod.get("classes", [])
        fns = mod.get("functions", [])[:8]
        deps = mod.get("dependencies", [])
        git = mod.get("git", {})
        language = mod.get("language", "")

        output_lines.append(f"## {file_p}")
        if summary:
            output_lines.append(f"_{summary}_")
        if deps:
            dep_names = ", ".join(Path(d).name for d in deps[:4])
            output_lines.append(f"Depends on: {dep_names}")
        if git.get("last_author"):
            output_lines.append(
                f"Last change: {git['last_author']} — \"{git.get('last_message','')}\" ({git.get('last_date','')})"
            )

        # Show notes for this file
        file_notes = notes_store.get(file_p)
        for note in file_notes[:2]:
            output_lines.append(f"> NOTE ({note.author}, {note.date}): {note.text}")

        # Extract actual code snippets (only in verbose mode)
        if verbose:
            top_symbols = (classes + fns)[:6]
            snippet = extract_snippets(file_p, top_symbols, language)
            if snippet:
                output_lines.append(f"```{language}")
                output_lines.append(snippet)
                output_lines.append("```")

        output_lines.append("")

    # Dependency flow
    edges = result.graph_fragment.get("edges", [])
    if edges:
        flow = " -> ".join(
            Path(e.get("from", "")).stem for e in edges[:4]
        )
        output_lines.append(f"_Flow: {flow}_\n")

    pct = 0
    if result.total_modules_searched:
        pct = 100 * (1 - len(result.modules) / result.total_modules_searched)
    output_lines.append(
        f"[{len(result.modules)}/{result.total_modules_searched} modules · ~{result.token_estimate:,} tokens · {pct:.0f}% reduction]"
    )

    click.echo("\n".join(output_lines))


# ── kodara impact ────────────────────────────────────────────────────────────


@main.command()
@click.argument("file")
@click.option("--path", default=".", type=click.Path(exists=True), help="Project root")
@click.option("--json", "as_json", is_flag=True)
def impact(file, path, as_json):
    """Show what breaks if you change a file.

    \b
    Example:
      kodara impact auth/middleware.py
      kodara impact user_model
    """
    from kodara.impact.analyzer import analyze

    root = Path(path).resolve()
    store = _store(root)
    idx = store.load()

    if not idx:
        _print(f"[red]Not initialised.[/red] Run: kodara init")
        sys.exit(1)

    try:
        result = analyze(idx, file)
    except ValueError as e:
        _print(f"[red]{e}[/red]")
        sys.exit(1)

    if as_json:
        import dataclasses
        click.echo(json.dumps(dataclasses.asdict(result), indent=2))
        return

    # Risk badge
    risk_colors = {"NONE": "green", "LOW": "green", "MEDIUM": "yellow", "HIGH": "red", "CRITICAL": "bold red"}
    color = risk_colors.get(result.risk_level, "white")

    _print(f"\n[bold]Impact Analysis[/bold] — {result.target_module}")
    if result.target_summary:
        _print(f"[dim]{result.target_summary}[/dim]")
    _print(f"\nRisk: [{color}]{result.risk_level}[/{color}]  {result.risk_reason}\n")

    if result.total_affected == 0:
        _print("[green]No other modules import this file. Safe to change.[/green]\n")
        return

    if result.direct:
        _print(f"[bold]Direct dependents[/bold] ({len(result.direct)}) — will break immediately:")
        for m in result.direct[:10]:
            hub_tag = " [yellow][hub][/yellow]" if m.is_hub else ""
            _print(f"  {m.module_name}{hub_tag}")
            if m.summary:
                _print(f"    [dim]{m.summary}[/dim]")
        if len(result.direct) > 10:
            _print(f"  [dim]... and {len(result.direct) - 10} more (use --json for full list)[/dim]")
        _print("")

    if result.indirect:
        _print(f"[bold]Indirect dependents[/bold] ({len(result.indirect)}) — affected through the chain:")
        for m in result.indirect[:8]:
            hub_tag = " [yellow][hub][/yellow]" if m.is_hub else ""
            _print(f"  {'  ' * (m.distance - 1)}{m.module_name} (depth {m.distance}){hub_tag}")
        if len(result.indirect) > 8:
            _print(f"  [dim]... and {len(result.indirect) - 8} more[/dim]")
        _print("")

    if result.critical_paths:
        _print("[bold]Critical chains:[/bold]")
        for chain in result.critical_paths:
            names = [Path(p).stem for p in chain]
            _print(f"  {' → '.join(names)}")
        _print("")


# ── kodara onboard ────────────────────────────────────────────────────────────


@main.command()
@click.option("--path", default=".", type=click.Path(exists=True), help="Project root")
@click.option("--json", "as_json", is_flag=True)
def onboard(path, as_json):
    """Generate a reading guide for a new developer.

    Shows where to start and in what order to explore the codebase.
    """
    from kodara.onboard.guide import generate

    root = Path(path).resolve()
    store = _store(root)
    idx = store.load()

    if not idx:
        _print(f"[red]Not initialised.[/red] Run: kodara init")
        sys.exit(1)

    guide = generate(idx)

    if as_json:
        import dataclasses
        click.echo(json.dumps(dataclasses.asdict(guide), indent=2))
        return

    _print(f"\n[bold cyan]Onboarding Guide[/bold cyan] — {guide.project_name}\n")
    _print(f"{guide.architecture_summary}\n")

    if guide.top_contributors:
        contribs = ", ".join(f"{c['name']} ({c['commits']} commits)" for c in guide.top_contributors[:3])
        _print(f"[dim]Contributors: {contribs}[/dim]\n")

    layer_icons = {"entry": ">>", "core": "**", "foundation": "--", "utility": "  "}
    layer_labels = {"entry": "ENTRY", "core": "CORE ", "foundation": "BASE ", "utility": "UTIL "}

    _print("[bold]Reading order:[/bold]\n")
    for step in guide.steps:
        icon = layer_icons.get(step.layer, "  ")
        label = layer_labels.get(step.layer, "     ")
        _print(f"  {step.order:2}. [{label}] {icon} {step.module_name}")
        _print(f"          [dim]{step.path}[/dim]")
        if step.summary:
            _print(f"          {step.summary}")
        _print(f"          [dim]{step.reason}[/dim]")
        _print("")


# ── kodara note ──────────────────────────────────────────────────────────────


@main.group(invoke_without_command=True)
@click.argument("file", default="")
@click.argument("text", default="")
@click.option("--path", default=".", type=click.Path(exists=True))
@click.option("--list", "do_list", is_flag=True, help="List all notes")
@click.option("--search", default="", help="Search notes")
@click.option("--delete", "del_index", default=-1, type=int,
              help="Delete note by index (0=newest)")
@click.pass_context
def note(ctx, file, text, path, do_list, search, del_index):
    """Add or view developer notes on files.

    \b
    Examples:
      kodara note auth/middleware.py "Rewritten after security incident"
      kodara note auth/middleware.py          # show notes for file
      kodara note --list                      # show all notes
      kodara note --search "security"         # search notes
      kodara note auth/middleware.py --delete 0   # delete newest note
    """
    from kodara.notes.store import NotesStore

    root = Path(path).resolve()
    store = _store(root)

    if not store.exists():
        _print(f"[red]Not initialised.[/red] Run: kodara init")
        sys.exit(1)

    idx = store.load()
    ns = NotesStore(store.kodara_dir)

    # --list: show all notes
    if do_list or (not file and not search):
        all_notes = ns.get_all()
        if not all_notes:
            _print("\n[dim]No notes yet.[/dim]")
            _print('[dim]Add one: kodara note <file> "your note"[/dim]\n')
            return
        _print(f"\n[bold]All Notes[/bold] ({sum(len(v) for v in all_notes.values())} total)\n")
        for fkey, fnotes in all_notes.items():
            _print(f"[bold cyan]{Path(fkey).name}[/bold cyan]  [dim]{fkey}[/dim]")
            for i, n in enumerate(fnotes):
                _print(f"  [{i}] [dim]{n.author} · {n.date}[/dim]")
                _print(f"      {n.text}")
            _print("")
        return

    # --search
    if search:
        results = ns.search(search)
        if not results:
            _print(f"\n[dim]No notes matching '{search}'[/dim]\n")
            return
        _print(f"\n[bold]Notes matching '{search}'[/bold]\n")
        for fkey, n in results:
            _print(f"  [cyan]{Path(fkey).name}[/cyan]  [dim]{n.date} · {n.author}[/dim]")
            _print(f"  {n.text}\n")
        return

    # Resolve file key
    file_key = ns.resolve_key(file, idx)

    # --delete
    if del_index >= 0:
        if ns.delete(file_key, del_index):
            _print(f"[green]Note deleted.[/green]")
        else:
            _print(f"[red]Note not found.[/red]")
        return

    # Show notes for file (no text given)
    if not text:
        fnotes = ns.get(file_key)
        if not fnotes:
            _print(f"\n[dim]No notes for {Path(file_key).name}[/dim]")
            _print(f'[dim]Add one: kodara note {file} "your note"[/dim]\n')
            return
        _print(f"\n[bold cyan]{Path(file_key).name}[/bold cyan]  [dim]{file_key}[/dim]\n")
        for i, n in enumerate(fnotes):
            _print(f"  [{i}] [dim]{n.author} · {n.date}[/dim]")
            _print(f"      {n.text}")
        _print("")
        return

    # Add note
    n = ns.add(file_key, text)
    _print(f"\n[bold green]Note saved[/bold green] → {Path(file_key).name}")
    _print(f"[dim]{n.author} · {n.date}[/dim]")
    _print(f"{n.text}\n")


# ── kodara snapshot ───────────────────────────────────────────────────────────


@main.group(invoke_without_command=True)
@click.option("--path", default=".", type=click.Path(exists=True))
@click.pass_context
def snapshot(ctx, path):
    """Capture or compare project memory snapshots.

    \b
    Commands:
      kodara snapshot              # create snapshot now
      kodara snapshot list         # show all snapshots
      kodara snapshot diff 2024-01 # compare to a past snapshot
    """
    if ctx.invoked_subcommand is None:
        # Default: create a snapshot
        from kodara.snapshots.store import SnapshotStore

        root = Path(path).resolve()
        store = _store(root)
        idx = store.load()

        if not idx:
            _print(f"[red]Not initialised.[/red] Run: kodara init")
            sys.exit(1)

        snap_store = SnapshotStore(store.kodara_dir)
        filename = snap_store.create(idx)
        _print(f"\n[bold green]Snapshot saved:[/bold green] {filename}")
        _print(f"[dim]{idx.file_count} files · {len(idx.modules)} modules[/dim]\n")


@snapshot.command("list")
@click.option("--path", default=".", type=click.Path(exists=True))
def snapshot_list(path):
    """List all snapshots."""
    from kodara.snapshots.store import SnapshotStore

    root = Path(path).resolve()
    store = _store(root)
    snap_store = SnapshotStore(store.kodara_dir)
    snaps = snap_store.list()

    if not snaps:
        _print("\n[dim]No snapshots yet.[/dim]")
        _print("[dim]Run: kodara snapshot[/dim]\n")
        return

    _print(f"\n[bold]Snapshots[/bold] ({len(snaps)})\n")
    for s in snaps:
        langs = ", ".join(s.languages.keys())
        _print(f"  [cyan]{s.timestamp}[/cyan]  {s.file_count} files  {langs}")
        _print(f"  [dim]{s.filename}[/dim]")
    _print("")


@snapshot.command("diff")
@click.argument("date")
@click.option("--path", default=".", type=click.Path(exists=True))
def snapshot_diff(date, path):
    """Compare current state to a past snapshot.

    \b
    Examples:
      kodara snapshot diff 2024-01-15
      kodara snapshot diff 2024-01
    """
    from kodara.snapshots.store import SnapshotStore

    root = Path(path).resolve()
    store = _store(root)
    idx = store.load()

    if not idx:
        _print(f"[red]Not initialised.[/red] Run: kodara init")
        sys.exit(1)

    snap_store = SnapshotStore(store.kodara_dir)

    try:
        result = snap_store.compare(idx, date)
    except ValueError as e:
        _print(f"[red]{e}[/red]")
        sys.exit(1)

    _print(f"\n[bold]Snapshot Diff[/bold] — since {result['snapshot_date']}\n")
    _print(f"  {result['summary']}\n")

    if result["added_modules"]:
        _print(f"[green]Added ({len(result['added_modules'])}):[/green]")
        for m in result["added_modules"][:10]:
            _print(f"  + {Path(m).stem}")
        _print("")

    if result["removed_modules"]:
        _print(f"[red]Removed ({len(result['removed_modules'])}):[/red]")
        for m in result["removed_modules"][:10]:
            _print(f"  - {Path(m).stem}")
        _print("")

    if result["changed_modules"]:
        _print(f"[yellow]Modified ({len(result['changed_modules'])}):[/yellow]")
        for m in result["changed_modules"][:10]:
            _print(f"  ~ {Path(m).stem}")
        _print("")


# ── kodara stats ─────────────────────────────────────────────────────────────


@main.command()
@click.option("--path", default=".", type=click.Path(exists=True))
@click.option("--json", "as_json", is_flag=True)
def stats(path, as_json):
    """Show token savings and money saved by using Kodara."""
    from kodara.stats.tracker import StatsTracker

    root = Path(path).resolve()
    store = _store(root)

    if not store.exists():
        _print(f"[red]Not initialised.[/red] Run: kodara init")
        sys.exit(1)

    tracker = StatsTracker(store.kodara_dir)
    s = tracker.summary()

    if as_json:
        click.echo(json.dumps(s, indent=2))
        return

    if s.get("total_entries", 0) == 0:
        _print("\n[dim]No usage recorded yet. Run kodara ask first.[/dim]\n")
        return

    price = s.get("price_per_1m", 15.0)
    _print(f"\n[bold cyan]Kodara Stats[/bold cyan] — {root.name}")
    _print(f"[dim]Price: ${price}/1M tokens (set KODARA_PRICE_PER_1M to change)[/dim]\n")

    def _row(label: str, data: dict) -> None:
        if not data.get("queries"):
            return
        q = data["queries"]
        saved_t = data.get("tokens_saved", 0)
        saved_m = data.get("money_saved", 0.0)
        used_t = data.get("tokens_used", 0)
        pct = int(saved_t / (saved_t + used_t) * 100) if (saved_t + used_t) else 0
        _print(f"  [bold]{label}[/bold]")
        _print(f"    Queries:      {q}")
        _print(f"    Tokens saved: {saved_t:,}  ({pct}% reduction)")
        _print(f"    [bold green]Money saved:  ${saved_m:.2f}[/bold green]")
        _print("")

    _row("Today", s.get("today", {}))
    _row("This month", s.get("this_month", {}))
    _row("All time", s.get("all_time", {}))

    all_time = s.get("all_time", {})
    total_saved = all_time.get("money_saved", 0.0)
    if total_saved >= 1:
        _print(f"[bold green]Total saved: ${total_saved:.2f}[/bold green]\n")


# ── kodara review ─────────────────────────────────────────────────────────────


@main.command()
@click.option("--path", default=".", type=click.Path(exists=True))
@click.option("--strict", is_flag=True,
              help="Exit with code 1 if HIGH or CRITICAL risk (for pre-commit hooks)")
@click.option("--all-changes", "all_changes", is_flag=True,
              help="Include unstaged changes too (default: staged only)")
def review(path, strict, all_changes):
    """Analyse impact of your current changes before committing.

    \b
    Use as a pre-commit hook:
      echo 'kodara review --strict' >> .git/hooks/pre-commit
      chmod +x .git/hooks/pre-commit
    """
    from kodara.diff.analyzer import analyze_diff

    root = Path(path).resolve()
    store = _store(root)
    idx = store.load()

    if not idx:
        _print(f"[red]Not initialised.[/red] Run: kodara init")
        sys.exit(1)

    base = "HEAD"
    result = analyze_diff(idx, str(root), base=base, staged_only=not all_changes)

    if not result.changed_files:
        _print("\n[dim]No staged changes found.[/dim]")
        _print("[dim]Use --all-changes to include unstaged changes.[/dim]\n")
        return

    _print_diff_result(result, root)

    if strict and result.overall_risk in ("HIGH", "CRITICAL"):
        sys.exit(1)


# ── kodara diff ───────────────────────────────────────────────────────────────


@main.command()
@click.argument("base", default="main")
@click.option("--path", default=".", type=click.Path(exists=True))
@click.option("--json", "as_json", is_flag=True)
def diff(base, path, as_json):
    """Show impact of all changes vs a branch or commit.

    \b
    Examples:
      kodara diff main
      kodara diff HEAD~3
      kodara diff origin/main
    """
    from kodara.diff.analyzer import analyze_diff

    root = Path(path).resolve()
    store = _store(root)
    idx = store.load()

    if not idx:
        _print(f"[red]Not initialised.[/red] Run: kodara init")
        sys.exit(1)

    result = analyze_diff(idx, str(root), base=base)

    if as_json:
        import dataclasses
        # ImpactResult inside FileChange needs special handling
        out = {
            "base": result.base,
            "overall_risk": result.overall_risk,
            "total_affected_modules": result.total_affected_modules,
            "risk_distribution": result.risk_distribution,
            "summary": result.summary,
            "files": [
                {
                    "path": fc.path,
                    "status": fc.status,
                    "in_index": fc.in_index,
                    "risk": fc.impact.risk_level if fc.impact else "N/A",
                    "affected": fc.impact.total_affected if fc.impact else 0,
                }
                for fc in result.changed_files
            ],
        }
        click.echo(json.dumps(out, indent=2))
        return

    _print_diff_result(result, root)


# ── Shared diff printer ───────────────────────────────────────────────────────


def _print_diff_result(result, root: Path) -> None:
    risk_colors = {
        "NONE": "green", "LOW": "green",
        "MEDIUM": "yellow", "HIGH": "red", "CRITICAL": "bold red"
    }
    status_labels = {"M": "modified", "A": "added", "D": "deleted", "R": "renamed"}

    color = risk_colors.get(result.overall_risk, "white")
    _print(f"\n[bold]Diff Analysis[/bold] vs [dim]{result.base}[/dim]")
    _print(f"Overall risk: [{color}]{result.overall_risk}[/{color}]\n")

    if not result.changed_files:
        _print("[dim]No changes found.[/dim]\n")
        return

    for fc in result.changed_files:
        status = status_labels.get(fc.status, fc.status)
        if fc.status == "D" or not fc.in_index:
            tag = "[dim](not in index)[/dim]" if not fc.in_index else "[dim](deleted)[/dim]"
            _print(f"  {fc.path}  {tag}")
            continue

        imp = fc.impact
        if not imp:
            continue

        risk_color = risk_colors.get(imp.risk_level, "white")
        _print(
            f"  [{risk_color}]{imp.risk_level:8}[/{risk_color}]  "
            f"[bold]{imp.target_module}[/bold]  "
            f"[dim]({status})[/dim]"
        )
        if imp.total_affected:
            direct_n = len(imp.direct)
            indirect_n = len(imp.indirect)
            _print(f"             Affects: {imp.total_affected} modules "
                   f"({direct_n} direct, {indirect_n} indirect)")
        if imp.risk_level in ("HIGH", "CRITICAL") and imp.direct:
            top = ", ".join(m.module_name for m in imp.direct[:3])
            _print(f"             Direct:  {top}")
        _print("")

    if result.total_affected_modules:
        _print(f"[dim]Total blast radius: {result.total_affected_modules} unique modules affected[/dim]")

    # Risk breakdown
    non_zero = {k: v for k, v in result.risk_distribution.items() if v > 0}
    if non_zero:
        breakdown = "  ".join(f"{k}: {v}" for k, v in non_zero.items())
        _print(f"[dim]Risk breakdown: {breakdown}[/dim]")
    _print("")


# ── kodara context ────────────────────────────────────────────────────────────


@main.command("context")
@click.argument("path", default=".", type=click.Path(exists=True))
def context_cmd(path):
    """Generate ARCHITECTURE.md — human-readable project overview."""
    root = Path(path).resolve()
    _regenerate_context(root, verbose=True)


# ── kodara status ─────────────────────────────────────────────────────────────


@main.command()
@click.argument("path", default=".", type=click.Path(exists=True))
def status(path):
    """Show index status for this project."""
    from datetime import datetime

    root = Path(path).resolve()
    store = _store(root)
    idx = store.load()

    if not idx:
        _print(f"\n[yellow]Not initialised.[/yellow] Run: [bold]kodara init[/bold]\n")
        return

    age = time.time() - idx.indexed_at
    age_str = f"{int(age/60)}m ago" if age < 3600 else f"{int(age/3600)}h ago"
    langs = ", ".join(f"{l}({c})" for l, c in idx.language_counts.items())

    _print(f"\n[bold cyan]{root.name}[/bold cyan]  [green]✓ indexed[/green]")
    _print(f"  Files:    {idx.file_count}  |  Languages: {langs}")
    _print(f"  Updated:  {age_str}  ({datetime.fromtimestamp(idx.indexed_at).strftime('%Y-%m-%d %H:%M')})")
    _print(f"  Memory:   {store.kodara_dir}\n")


# ── kodara history ────────────────────────────────────────────────────────────


@main.command()
@click.argument("file", type=click.Path())
@click.option("--path", default=".", type=click.Path(exists=True), help="Project root")
@click.option("--json", "as_json", is_flag=True)
def history(file, path, as_json):
    """Show git history and authorship for a file."""
    from kodara.git.history import is_git_repo, collect_file_histories, get_recent_commits

    root = Path(path).resolve()
    file_path = Path(file).resolve()

    if not is_git_repo(str(root)):
        _print(f"[yellow]No git repository at {root}[/yellow]")
        sys.exit(1)

    store = _store(root)
    idx = store.load()
    git_info = {}

    if idx and str(file_path) in idx.modules:
        git_info = idx.modules[str(file_path)].get("git", {})

    if not git_info:
        all_info = collect_file_histories(str(root))
        git_info = all_info.get(str(file_path), {})

    if as_json:
        click.echo(json.dumps({"file": str(file_path), "git": git_info}, indent=2))
        return

    if not git_info:
        _print(f"[yellow]No git history found for {file_path.name}[/yellow]")
        return

    _print(f"\n[bold cyan]Git History[/bold cyan] — [bold]{file_path.name}[/bold]\n")
    _print(f"  Commits:     {git_info.get('commit_count', 0)}")
    _print(f"  Last commit: [dim]{git_info.get('last_commit', '')}[/dim]  {git_info.get('last_date', '')}")
    _print(f"  Last author: {git_info.get('last_author', '')}")
    _print(f"  Last change: {git_info.get('last_message', '')}")

    for a in git_info.get("top_authors", []):
        _print(f"  {a['name']}  ({a['commits']} commits)")
    _print("")


# ── kodara team ───────────────────────────────────────────────────────────────


@main.group()
def team():
    """Share project memory with your team."""
    pass


@team.command("config")
@click.option("--shared-dir", required=True)
@click.option("--path", default=".", type=click.Path(exists=True))
def team_config(shared_dir, path):
    """Set the shared directory for team sync."""
    from kodara.team.sync import set_shared_dir
    resolved = set_shared_dir(shared_dir, Path(path).resolve())
    _print(f"\n[bold green]✓[/bold green] Shared dir: {resolved}\n")


@team.command("push")
@click.option("--path", default=".", type=click.Path(exists=True))
def team_push(path):
    """Push this project's memory to the shared directory."""
    from kodara.team.sync import push
    root = Path(path).resolve()
    try:
        dst = push(root)
        _print(f"\n[bold green]✓ Pushed[/bold green] → {dst}\n")
    except ValueError as e:
        _print(f"[red]Error:[/red] {e}")
        sys.exit(1)


@team.command("pull")
@click.option("--path", default=".", type=click.Path(exists=True))
def team_pull(path):
    """Pull team memory into this project."""
    from kodara.team.sync import pull
    root = Path(path).resolve()
    try:
        pulled = pull(root)
        _print(f"\n[bold green]✓ Pulled {len(pulled)} index(es)[/bold green]\n")
    except ValueError as e:
        _print(f"[red]Error:[/red] {e}")
        sys.exit(1)


@team.command("status")
@click.option("--path", default=".", type=click.Path(exists=True))
def team_status(path):
    """Show sync status."""
    from kodara.team.sync import status
    st = status(Path(path).resolve())
    _print(f"\n  Shared dir: [dim]{st.get('shared_dir') or 'not configured'}[/dim]")
    _print(f"  Local:  {'✓ indexed' if st.get('local_exists') else '✗ not indexed'}")
    _print(f"  Shared: {'✓ present' if st.get('shared_exists') else '✗ not pushed'}\n")


# ── kodara serve ──────────────────────────────────────────────────────────────


@main.command()
@click.option("--host", default="127.0.0.1", show_default=True)
@click.option("--port", default=8742, show_default=True)
@click.option("--path", default=".", type=click.Path(exists=True))
def serve(host, port, path):
    """Start the Kodara API server."""
    _print(f"\n[bold cyan]Kodara API[/bold cyan] — http://{host}:{port}")
    from kodara.api.server import start_server
    start_server(host=host, port=port)


# ── Shared helpers ────────────────────────────────────────────────────────────


def _store(root: Path):
    from kodara.memory.store import MemoryStore
    return MemoryStore(root)


def _run_init(root: Path, *, no_summaries: bool, full: bool, as_json: bool, verbose: bool) -> None:
    """Core indexing logic — used by both init and watch."""
    from kodara.indexer.scanner import RepositoryScanner, FreePlanLimitError, FREE_PLAN_LIMIT
    from kodara.graph.dependency import DependencyGraph
    from kodara.memory.store import MemoryStore, KodaraIndex
    from kodara.summarizer import generate_summaries
    from kodara.git.history import is_git_repo, collect_file_histories, get_repo_stats

    store = MemoryStore(root)
    existing = store.load() if not full else None

    t0 = time.time()

    if verbose:
        _print(f"\n[bold cyan]Kodara[/bold cyan] — Initialising [bold]{root.name}[/bold]")

    # Scan
    scanner = RepositoryScanner(root)
    try:
        scan = scanner.scan()
    except FreePlanLimitError as e:
        _print(f"\n[bold yellow]Free Plan Limit[/bold yellow]")
        _print(f"  This project has [bold]{e.file_count}+[/bold] files.")
        _print(f"  Free plan supports up to [bold]{FREE_PLAN_LIMIT} files[/bold].")
        _print(f"")
        _print(f"  Upgrade to [bold]Kodara Pro[/bold] for unlimited projects:")
        _print(f"  [dim]getkodara.dev/pro[/dim]")
        _print(f"")
        _print(f"  ROI: Pro costs $19/month. Average dev saves $638/month. [bold]33x return.[/bold]")
        raise SystemExit(1)

    # Graph
    graph = DependencyGraph()
    graph.build(scan)
    graph_data = graph.to_dict()

    # Git
    git_file_info: dict = {}
    git_stats: dict = {}
    if is_git_repo(str(root)):
        git_file_info = collect_file_histories(str(root))
        git_stats = get_repo_stats(str(root))

    # Modules
    modules: dict[str, dict] = {}
    reused = 0
    for file_path, info in scan.files.items():
        cached_summary = ""
        if existing and file_path in existing.modules:
            ex = existing.modules[file_path]
            if ex.get("file_hash") == info.hash:
                cached_summary = ex.get("summary", "")
                reused += 1

        modules[file_path] = {
            "path": file_path,
            "module_name": graph.node_data(file_path).get("module_name", ""),
            "language": info.language,
            "classes": info.classes,
            "functions": info.functions,
            "exports": info.exports,
            "imports": info.imports,
            "file_hash": info.hash,
            "summary": cached_summary,
            "dependencies": graph.get_dependencies(file_path),
            "dependents": graph.get_dependents(file_path),
            "git": git_file_info.get(file_path, {}),
        }

    # Summaries
    if not no_summaries:
        needs = {p: m for p, m in modules.items() if not m["summary"]}
        if needs:
            try:
                sums = generate_summaries(needs)
                for p, s in sums.items():
                    modules[p]["summary"] = s
            except Exception:
                pass

    # Save
    repo_id = MemoryStore.repo_id_from_path(str(root))
    idx = KodaraIndex(
        repo_id=repo_id,
        display_name=root.name,
        source_root=str(root),
        indexed_at=time.time(),
        index_version=2,
        file_count=len(scan.files),
        language_counts=scan.languages,
        modules=modules,
        graph_edges=graph_data["edges"],
        file_hashes={p: f.hash for p, f in scan.files.items()},
    )
    store.save(idx)

    # Auto-snapshot once per day
    try:
        from kodara.snapshots.store import SnapshotStore
        snap_store = SnapshotStore(store.kodara_dir)
        snap_store.auto_snapshot(idx)
    except Exception:
        pass

    elapsed = time.time() - t0

    if as_json:
        click.echo(json.dumps({
            "success": True,
            "file_count": len(scan.files),
            "languages": scan.languages,
            "edge_count": len(graph_data["edges"]),
            "elapsed": round(elapsed, 2),
            "git": git_stats,
        }, indent=2))
    elif verbose:
        _print(f"\n[bold green]✓ Done[/bold green] in {elapsed:.1f}s")
        _print(f"  Files:    {len(scan.files)}")
        _print(f"  Languages: {dict(scan.languages)}")
        _print(f"  Edges:    {len(graph_data['edges'])}")
        if reused:
            _print(f"  Cached:   {reused} summaries reused")
        if git_stats.get("branch"):
            _print(f"  Git:      {git_stats['branch']} · {git_stats.get('total_commits',0)} commits")
        _print(f"  Memory:   {store.kodara_dir}")
        _print(f"\n  [dim]kodara ask \"How does X work?\"[/dim]\n")


def _regenerate_context(root: Path, verbose: bool = False) -> None:
    from kodara.injector.inject import generate_context
    try:
        out = generate_context(str(root))
        if verbose:
            rel = Path(out).relative_to(root)
            _print(f"\n[bold green]✓[/bold green] {rel}\n")
        else:
            _print("  [dim]↳ ARCHITECTURE.md updated[/dim]")
    except ValueError as e:
        _print(f"[red]{e}[/red]")
