"""CLI commands for cokodo-agent."""

from pathlib import Path
from typing import Optional

import typer
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

from cokodo_agent.config import (
    AI_TOOLS,
    BUNDLED_PROTOCOL_VERSION,
    IDE_SPEC_VERSIONS,
    VERSION,
)
from cokodo_agent.fetcher import get_protocol
from cokodo_agent.generator import generate_adapters_for_tools, generate_protocol
from cokodo_agent.parser import HybridParser
from cokodo_agent.prompts import prompt_config

app = typer.Typer(
    name="cokodo",
    help="Cokodo Agent - AI collaboration protocol generator",
    add_completion=False,
)
console = Console()


def find_agent_dir(path: Optional[Path] = None) -> Path:
    """Find .agent directory from given path or current directory."""
    target = Path(path) if path else Path.cwd()
    target = target.resolve()

    agent_dir = target / ".agent"
    if agent_dir.exists():
        return agent_dir

    raise FileNotFoundError(f".agent directory not found at {target}")


@app.command()
def init(
    path: Optional[Path] = typer.Argument(
        None,
        help="Target directory (default: current directory)",
    ),
    yes: bool = typer.Option(
        False,
        "--yes",
        "-y",
        help="Skip prompts, use defaults",
    ),
    name: Optional[str] = typer.Option(
        None,
        "--name",
        "-n",
        help="Project name",
    ),
    stack: Optional[str] = typer.Option(
        None,
        "--stack",
        "-s",
        help="Tech stack (python/rust/qt/mixed/other)",
    ),
    force: bool = typer.Option(
        False,
        "--force",
        "-f",
        help="Overwrite existing .agent directory",
    ),
    offline: bool = typer.Option(
        False,
        "--offline",
        help="Use built-in protocol (no network)",
    ),
) -> None:
    """Create .agent protocol in target directory."""

    # Header
    console.print()
    console.print(
        Panel.fit(
            f"[bold blue]Cokodo Agent[/bold blue] v{VERSION}",
            border_style="blue",
        )
    )
    console.print()

    # Resolve target path
    target_path = Path(path) if path else Path.cwd()
    target_path = target_path.resolve()

    # Check existing .agent
    agent_dir = target_path / ".agent"
    if agent_dir.exists() and not force:
        console.print(f"[red]Error:[/red] .agent already exists at {agent_dir}")
        console.print("Use --force to overwrite")
        raise typer.Exit(1)

    # Fetch protocol
    console.print("[bold]Fetching protocol...[/bold]")
    try:
        protocol_path, protocol_version = get_protocol(offline=offline)
        console.print(f"  [green]OK[/green] Protocol v{protocol_version}")
    except Exception as e:
        console.print(f"  [red]Error:[/red] {e}")
        raise typer.Exit(1)

    console.print()

    # Get configuration
    if yes:
        # Use defaults or provided options
        config = {
            "project_name": name or target_path.name,
            "description": "",
            "tech_stack": stack or "python",
            "ai_tools": ["cokodo"],  # Default: protocol only, no extra files
        }
    else:
        # Interactive prompts
        config = prompt_config(
            default_name=name or target_path.name,
            default_stack=stack,
        )

    console.print()

    # Generate
    console.print("[bold]Generating .agent/[/bold]")
    try:
        generate_protocol(
            source_path=protocol_path,
            target_path=target_path,
            config=config,
            force=force,
        )
        console.print("  [green]OK[/green] Created .agent/")
    except Exception as e:
        console.print(f"  [red]Error:[/red] {e}")
        raise typer.Exit(1)

    console.print()

    # Success message
    console.print(
        Panel(
            f"[green]Success![/green] Created .agent in [bold]{target_path}[/bold]\n\n"
            "[bold]Next steps:[/bold]\n"
            "  1. Review [cyan].agent/project/context.md[/cyan]\n"
            "  2. Customize [cyan].agent/project/tech-stack.md[/cyan]\n"
            "  3. Start coding with AI assistance!",
            title="Done",
            border_style="green",
        )
    )


@app.command()
def lint(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
    rule: Optional[str] = typer.Option(
        None,
        "--rule",
        "-r",
        help="Check specific rule only",
    ),
    format: str = typer.Option(
        "text",
        "--format",
        "-f",
        help="Output format (text/json/github)",
    ),
) -> None:
    """Check protocol compliance."""
    import json as json_module

    from cokodo_agent.linter import ProtocolLinter

    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    linter = ProtocolLinter(agent_dir)

    if rule:
        results = linter.lint_rule(rule)
    else:
        results = linter.lint_all()

    errors = [r for r in results if not r.passed]

    if format == "json":
        print(json_module.dumps([r._asdict() for r in results], indent=2, ensure_ascii=False))

    elif format == "github":
        for r in results:
            if not r.passed:
                file_part = f"file={r.file}" if r.file else ""
                line_part = f",line={r.line}" if r.line else ""
                print(f"::error {file_part}{line_part}::[{r.rule}] {r.message}")
        if not errors:
            print("::notice ::All protocol checks passed")

    else:  # text format
        console.print()
        console.print(
            Panel.fit(
                "[bold]Protocol Compliance Check[/bold]\n"
                f"Based on: agent-protocol-rules.md v{BUNDLED_PROTOCOL_VERSION}",
                border_style="blue",
            )
        )
        console.print()

        # Group by rule
        rules = sorted(set(r.rule for r in results))
        for rule_name in rules:
            rule_results = [r for r in results if r.rule == rule_name]
            passed = sum(1 for r in rule_results if r.passed)
            total = len(rule_results)

            if passed == total:
                console.print(f"[green][OK][/green] {rule_name}: {passed}/{total} passed")
            else:
                console.print(f"[red][FAIL][/red] {rule_name}: {passed}/{total} passed")

                # Show errors
                for r in rule_results:
                    if not r.passed:
                        loc = f"  {r.file}" if r.file else ""
                        if r.line:
                            loc += f":{r.line}"
                        console.print(f"    [red]x[/red]{loc}: {r.message}")

        console.print()
        total_passed = len(results) - len(errors)
        console.print(f"Total: {total_passed}/{len(results)} passed")

        if errors:
            console.print(f"\n[red][FAIL][/red] {len(errors)} error(s) found")
            raise typer.Exit(1)
        else:
            console.print("\n[green][OK][/green] All checks passed")


@app.command("update-checksums")
def update_checksums(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
) -> None:
    """Update checksums in manifest.json (maintainer only)."""
    from cokodo_agent.linter import update_checksums as do_update

    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    try:
        checksums = do_update(agent_dir)
        console.print(f"[green]OK[/green] Updated checksums for {len(checksums)} locked files")
        console.print(f"    Written to {agent_dir / 'manifest.json'}")
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)


@app.command()
def diff(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
    offline: bool = typer.Option(
        False,
        "--offline",
        help="Use built-in protocol (no network)",
    ),
) -> None:
    """Compare local .agent with latest protocol."""
    from cokodo_agent.sync import diff_protocol

    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    console.print("[bold]Comparing with latest protocol...[/bold]")
    console.print()

    try:
        results, local_version, remote_version = diff_protocol(agent_dir, offline=offline)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    console.print(f"Local version:  [cyan]{local_version}[/cyan]")
    console.print(f"Remote version: [cyan]{remote_version}[/cyan]")
    console.print()

    # Count changes
    added = [r for r in results if r.status == "added"]
    removed = [r for r in results if r.status == "removed"]
    modified = [r for r in results if r.status == "modified"]
    unchanged = [r for r in results if r.status == "unchanged"]

    if not added and not removed and not modified:
        console.print("[green]No changes detected. Protocol is up to date.[/green]")
        return

    # Show summary
    table = Table(title="Changes")
    table.add_column("Status", style="bold")
    table.add_column("Count")

    if added:
        table.add_row("[green]Added[/green]", str(len(added)))
    if removed:
        table.add_row("[red]Removed[/red]", str(len(removed)))
    if modified:
        table.add_row("[yellow]Modified[/yellow]", str(len(modified)))
    table.add_row("Unchanged", str(len(unchanged)))

    console.print(table)
    console.print()

    # Show details
    if added:
        console.print("[green]Added files:[/green]")
        for r in added:
            console.print(f"  + {r.path}")
        console.print()

    if removed:
        console.print("[red]Removed files:[/red]")
        for r in removed:
            console.print(f"  - {r.path}")
        console.print()

    if modified:
        console.print("[yellow]Modified files:[/yellow]")
        for r in modified:
            console.print(f"  ~ {r.path}")
        console.print()

    console.print("Run [cyan]co sync[/cyan] to update your protocol.")


@app.command()
def sync(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
    offline: bool = typer.Option(
        False,
        "--offline",
        help="Use built-in protocol (no network)",
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run",
        help="Show what would be updated without making changes",
    ),
    yes: bool = typer.Option(
        False,
        "--yes",
        "-y",
        help="Skip confirmation prompt",
    ),
) -> None:
    """Sync local .agent with latest protocol."""
    from cokodo_agent.sync import diff_protocol, sync_protocol

    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    # First show diff
    console.print("[bold]Checking for updates...[/bold]")
    console.print()

    try:
        diff_results, local_version, remote_version = diff_protocol(agent_dir, offline=offline)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    console.print(f"Local version:  [cyan]{local_version}[/cyan]")
    console.print(f"Remote version: [cyan]{remote_version}[/cyan]")
    console.print()

    # Count changes (excluding project/ files)
    changes = [
        r for r in diff_results if r.status != "unchanged" and not r.path.startswith("project/")
    ]

    if not changes:
        console.print("[green]Protocol is up to date. No changes needed.[/green]")
        return

    console.print(f"[yellow]{len(changes)} file(s) will be updated[/yellow]")
    console.print()

    # Confirm
    if not yes and not dry_run:
        confirm = typer.confirm("Proceed with sync?")
        if not confirm:
            console.print("Aborted.")
            raise typer.Exit(0)

    # Sync
    if dry_run:
        console.print("[bold]Dry run - no changes will be made[/bold]")
        console.print()

    try:
        result, _, _ = sync_protocol(agent_dir, offline=offline, dry_run=dry_run)
    except Exception as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    # Show results
    if result.updated:
        console.print("[green]Updated:[/green]")
        for f in result.updated:
            console.print(f"  {f}")
        console.print()

    if result.skipped:
        console.print("[yellow]Skipped:[/yellow]")
        for f in result.skipped:
            console.print(f"  {f}")
        console.print()

    if result.errors:
        console.print("[red]Errors:[/red]")
        for err in result.errors:
            console.print(f"  {err}")
        raise typer.Exit(1)

    if not dry_run:
        console.print(f"[green]OK[/green] Synced to v{remote_version}")


@app.command()
def adapt(
    tool: str = typer.Argument(
        ...,
        help="IDE to generate: cursor | claude | copilot | gemini | all",
    ),
    path: Optional[Path] = typer.Argument(
        None,
        help="Project path (default: current directory)",
    ),
) -> None:
    """Generate IDE entry file(s) from existing .agent protocol."""
    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    target_path = agent_dir.parent
    tool_lower = tool.strip().lower()

    # Support legacy alias
    if tool_lower == "antigravity":
        tool_lower = "gemini"

    if tool_lower == "all":
        tool_keys = [k for k in AI_TOOLS if AI_TOOLS[k].get("file")]
    elif tool_lower in AI_TOOLS:
        tool_keys = [tool_lower]
    else:
        console.print(
            f"[red]Error:[/red] Unknown tool '{tool}'. "
            f"Choose: cursor, claude, copilot, gemini, all"
        )
        raise typer.Exit(1)

    generate_adapters_for_tools(target_path, agent_dir, tool_keys)

    names = [AI_TOOLS[k]["name"] for k in tool_keys]
    console.print(f"[green]OK[/green] Generated: {', '.join(names)}")
    for k in tool_keys:
        info = AI_TOOLS[k]
        file_path = info.get("file")
        if file_path:
            # For directory-mode adapters, show the directory
            out = target_path / file_path
            console.print(f"  -> {out}")


@app.command()
def detect(
    path: Optional[Path] = typer.Argument(
        None,
        help="Project path (default: current directory)",
    ),
) -> None:
    """Detect IDE instruction files (Cursor, Claude, Copilot, Gemini) in the project."""
    target = Path(path) if path else Path.cwd()
    target = target.resolve()
    hybrid = HybridParser()
    detected = hybrid.detect_all(target)
    if not detected:
        console.print("[yellow]No IDE instruction files detected.[/yellow]")
        return
    console.print("[bold]Detected IDE instruction files:[/bold]")
    for tool_name, files in sorted(detected.items()):
        console.print(f"  [cyan]{tool_name}[/cyan]:")
        for f in files:
            console.print(f"    {f.path} ({f.format_version})")


@app.command("import")
def import_rules(
    path: Optional[Path] = typer.Argument(
        None,
        help="Project path (default: current directory)",
    ),
    source: Optional[str] = typer.Option(
        None,
        "--source",
        "-s",
        help="Import from: cursor | claude | copilot | gemini | auto (default: auto = all detected)",
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run",
        help="Show what would be imported without writing",
    ),
) -> None:
    """Import rules from existing IDE instruction files into .agent protocol."""
    target = Path(path) if path else Path.cwd()
    target = target.resolve()
    hybrid = HybridParser()
    detected = hybrid.detect_all(target)
    if not detected:
        console.print("[yellow]No IDE instruction files detected. Run from a project that has CLAUDE.md, AGENTS.md, GEMINI.md, or .cursor/rules/.[/yellow]")
        raise typer.Exit(0)

    agent_dir = target / ".agent"
    if not agent_dir.exists():
        console.print("[red]Error:[/red] No .agent directory. Run [cyan]co init[/cyan] first.")
        raise typer.Exit(1)

    tools_to_parse = list(detected.keys()) if source in (None, "auto") else [source]
    if source and source != "auto" and source not in detected:
        console.print(f"[red]Error:[/red] No files detected for '{source}'.")
        raise typer.Exit(1)

    parsed_list: list = []
    for tool in tools_to_parse:
        if tool in detected:
            parsed_list.extend(hybrid.parse_tool(target, tool))

    if not parsed_list:
        console.print("[yellow]Nothing to import.[/yellow]")
        return

    project_name: str | None = None
    all_rules: list[str] = []
    all_refs: set[str] = set()
    for p in parsed_list:
        if p.project_name:
            project_name = project_name or p.project_name
        all_rules.extend(p.rules)
        all_refs.update(p.referenced_files)

    console.print()
    console.print(Panel.fit("[bold]Import summary[/bold]", border_style="blue"))
    console.print(f"  Project name: {project_name or '(not found)'}")
    console.print(f"  Rules extracted: {len(all_rules)}")
    console.print(f"  Referenced .agent files: {len(all_refs)}")
    for r in sorted(all_refs)[:10]:
        console.print(f"    - {r}")
    if len(all_refs) > 10:
        console.print(f"    ... and {len(all_refs) - 10} more")
    console.print()

    if dry_run:
        console.print("[yellow]Dry run. No files written. Remove --dry-run to apply.[/yellow]")
        return

    written: list[str] = []
    if project_name and (agent_dir / "project" / "context.md").exists():
        context_path = agent_dir / "project" / "context.md"
        content = context_path.read_text(encoding="utf-8")
        if "## Project Name" in content:
            lines = content.splitlines()
            out: list[str] = []
            i = 0
            while i < len(lines):
                if "## Project Name" in lines[i]:
                    out.append(lines[i])
                    i += 1
                    if i < len(lines) and lines[i].strip() and not lines[i].startswith("#"):
                        i += 1  # skip old value
                    out.append(project_name)
                    i += 1
                    continue
                out.append(lines[i])
                i += 1
            new_content = "\n".join(out)
            if new_content != content:
                context_path.write_text(new_content, encoding="utf-8")
                written.append("project/context.md (project name)")
    if all_rules and (agent_dir / "project" / "conventions.md").exists():
        conv_path = agent_dir / "project" / "conventions.md"
        content = conv_path.read_text(encoding="utf-8")
        if "## Imported Rules" not in content:
            content = content.rstrip() + "\n\n## Imported Rules\n\n"
        for r in all_rules[:20]:
            if r.strip() and f"- {r}" not in content:
                content += f"- {r}\n"
        conv_path.write_text(content, encoding="utf-8")
        written.append("project/conventions.md (imported rules)")
    elif all_rules:
        conv_path = agent_dir / "project" / "conventions.md"
        conv_path.parent.mkdir(parents=True, exist_ok=True)
        block = "## Imported Rules\n\n" + "\n".join(f"- {r}" for r in all_rules[:20])
        conv_path.write_text("# Project Conventions\n\n" + block + "\n", encoding="utf-8")
        written.append("project/conventions.md (created with imported rules)")

    if written:
        console.print(f"[green]OK[/green] Updated: {', '.join(written)}")
    else:
        console.print("[yellow]No .agent/project files updated (name already set, or no conventions.md).[/yellow]")


@app.command()
def context(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
    stack: Optional[str] = typer.Option(
        None,
        "--stack",
        "-s",
        help="Tech stack (python/rust/qt/mixed)",
    ),
    task: Optional[str] = typer.Option(
        None,
        "--task",
        "-t",
        help="Task type (coding/testing/review/documentation/bug_fix/feature_development)",
    ),
    output: str = typer.Option(
        "list",
        "--output",
        "-o",
        help="Output format (list/paths/content)",
    ),
) -> None:
    """Get context files based on stack and task type."""
    from cokodo_agent.context import get_context_files

    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    files = get_context_files(agent_dir, stack=stack, task=task)

    if not files:
        console.print("[yellow]No context files found for the given criteria.[/yellow]")
        return

    if output == "paths":
        # Output absolute paths, one per line (for scripting)
        for f in files:
            print(agent_dir / f)

    elif output == "content":
        # Output file contents (for piping to AI)
        for f in files:
            file_path = agent_dir / f
            if file_path.exists():
                console.print(f"[bold]# {f}[/bold]")
                console.print(file_path.read_text(encoding="utf-8"))
                console.print()

    else:  # list
        console.print(
            f"[bold]Context files for stack={stack or 'all'}, task={task or 'all'}:[/bold]"
        )
        console.print()
        for f in files:
            file_path = agent_dir / f
            exists = "[green]OK[/green]" if file_path.exists() else "[red]MISSING[/red]"
            console.print(f"  {exists} {f}")


@app.command()
def journal(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
    title: Optional[str] = typer.Option(
        None,
        "--title",
        "-t",
        help="Session title (e.g., 'Feature X implementation')",
    ),
    completed: Optional[str] = typer.Option(
        None,
        "--completed",
        "-c",
        help="Completed items (comma-separated)",
    ),
    debt: Optional[str] = typer.Option(
        None,
        "--debt",
        "-d",
        help="Technical debt items (comma-separated)",
    ),
    decisions: Optional[str] = typer.Option(
        None,
        "--decisions",
        help="Key decisions made (comma-separated)",
    ),
    interactive: bool = typer.Option(
        False,
        "--interactive",
        "-i",
        help="Interactive mode with prompts",
    ),
) -> None:
    """Record a session entry to session-journal.md."""
    from datetime import datetime

    import questionary

    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    journal_path = agent_dir / "project" / "session-journal.md"
    if not journal_path.exists():
        console.print(f"[red]Error:[/red] session-journal.md not found at {journal_path}")
        raise typer.Exit(1)

    # Interactive mode
    if interactive or (not title and not completed):
        console.print()
        console.print(
            Panel.fit(
                "[bold blue]Session Journal Entry[/bold blue]",
                border_style="blue",
            )
        )
        console.print()

        title = questionary.text(
            "Session title:",
            default=title or "",
        ).ask()

        if not title:
            console.print("[yellow]Aborted.[/yellow]")
            raise typer.Exit(0)

        completed_input = questionary.text(
            "Completed items (comma-separated):",
            default=completed or "",
        ).ask()

        debt_input = questionary.text(
            "Technical debt (comma-separated, or leave empty):",
            default=debt or "",
        ).ask()

        decisions_input = questionary.text(
            "Key decisions (comma-separated, or leave empty):",
            default=decisions or "",
        ).ask()

        completed = completed_input
        debt = debt_input
        decisions = decisions_input

    # Build entry
    today = datetime.now().strftime("%Y-%m-%d")

    entry_lines = [
        f"\n## {today} Session: {title}",
        "",
        "### Completed",
    ]

    if completed:
        for item in completed.split(","):
            item = item.strip()
            if item:
                entry_lines.append(f"- {item}")
    else:
        entry_lines.append("- (no items recorded)")

    entry_lines.append("")
    entry_lines.append("### Technical Debt")

    if debt:
        for item in debt.split(","):
            item = item.strip()
            if item:
                entry_lines.append(f"- {item}")
    else:
        entry_lines.append("- None")

    entry_lines.append("")
    entry_lines.append("### Decisions")

    if decisions:
        for item in decisions.split(","):
            item = item.strip()
            if item:
                entry_lines.append(f"- {item}")
    else:
        entry_lines.append("- (no decisions recorded)")

    entry_lines.append("")
    entry_lines.append("---")
    entry_lines.append("")

    entry_text = "\n".join(entry_lines)

    # Append to journal
    try:
        content = journal_path.read_text(encoding="utf-8")

        # Find the append marker
        marker = "*Append new sessions below this line.*"
        if marker in content:
            content = content.replace(marker, marker + entry_text)
        else:
            # Just append at the end
            content = content.rstrip() + "\n" + entry_text

        journal_path.write_text(content, encoding="utf-8")

        console.print()
        console.print(f"[green]OK[/green] Added session entry to {journal_path}")
        console.print()
        console.print("[bold]Entry preview:[/bold]")
        console.print(entry_text)

    except Exception as e:
        console.print(f"[red]Error:[/red] Failed to write journal: {e}")
        raise typer.Exit(1)


@app.command()
def status(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
    raw: bool = typer.Option(
        False,
        "--raw",
        help="Show raw markdown content",
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output as JSON (for scripting)",
    ),
    init_status: bool = typer.Option(
        False,
        "--init",
        help="Create status.md from template if missing",
    ),
) -> None:
    """Show or initialize project development status."""
    import json as json_module
    import re

    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    status_file = agent_dir / "project" / "status.md"

    if init_status:
        if status_file.exists():
            console.print("[yellow]status.md already exists.[/yellow]")
            raise typer.Exit(0)
        from datetime import datetime

        today = datetime.now().strftime("%Y-%m-%d")
        template = (
            "# Project Status\n\n"
            f"> Last updated: {today} | Updated by: co status --init\n\n"
            "---\n\n"
            "## Current Phase\n\n"
            "[Describe the current development phase.]\n\n"
            "## Active Goals\n\n"
            "| # | Goal | Priority | Status |\n"
            "|---|------|----------|--------|\n"
            "| 1 | [Goal description] | High | Pending |\n\n"
            "## Task Board\n\n"
            "### In Progress\n"
            "- [ ] [Task description]\n\n"
            "### Pending\n"
            "- [ ] Define development goals\n\n"
            "### Recently Completed\n"
            "- (none yet)\n\n"
            "## Blockers\n\n"
            "None.\n\n"
            "## Session Context\n\n"
            f"- status.md initialized on {today}.\n\n"
            "## Roadmap Overview\n\n"
            "| Phase | Focus | Status |\n"
            "|-------|-------|--------|\n"
            "| [Phase] | [Description] | Current |\n\n"
            "## Quick Reference\n\n"
            "| Item | Value |\n"
            "|------|-------|\n"
            "| Status | Initialized |\n"
        )
        status_file.parent.mkdir(parents=True, exist_ok=True)
        status_file.write_text(template, encoding="utf-8")
        console.print(f"[green]OK[/green] Created {status_file}")
        return

    if not status_file.exists():
        console.print("[yellow]No status.md found.[/yellow]")
        console.print("Run [cyan]co status --init[/cyan] to create one.")
        raise typer.Exit(1)

    content = status_file.read_text(encoding="utf-8")

    if raw:
        print(content)
        return

    if json_output:
        sections: dict[str, str] = {}
        current_section = ""
        current_lines: list[str] = []
        for line in content.splitlines():
            if line.startswith("## "):
                if current_section:
                    sections[current_section] = "\n".join(current_lines).strip()
                current_section = line[3:].strip()
                current_lines = []
            else:
                current_lines.append(line)
        if current_section:
            sections[current_section] = "\n".join(current_lines).strip()
        print(json_module.dumps(sections, indent=2, ensure_ascii=False))
        return

    # Formatted display
    console.print()
    console.print(
        Panel.fit("[bold blue]Project Status[/bold blue]", border_style="blue")
    )
    console.print()

    current_section = ""
    for line in content.splitlines():
        if line.startswith("# Project Status"):
            continue
        if line.startswith("> Last updated:"):
            console.print(f"  [dim]{line.lstrip('> ')}[/dim]")
            console.print()
            continue
        if line.strip() == "---":
            continue

        if line.startswith("## "):
            section_name = line[3:].strip()
            current_section = section_name
            console.print(f"[bold cyan]{section_name}[/bold cyan]")
            continue
        if line.startswith("### "):
            console.print(f"  [bold]{line[4:].strip()}[/bold]")
            continue

        stripped = line.strip()
        if not stripped:
            continue

        if stripped.startswith("| #") or stripped.startswith("|---"):
            continue
        if stripped.startswith("|") and not stripped.startswith("| Phase") and not stripped.startswith("| Item"):
            cols = [c.strip() for c in stripped.split("|")[1:-1]]
            if len(cols) >= 3:
                console.print(f"  {cols[0]}. {cols[1]} [{cols[-1]}]")
            elif len(cols) >= 2:
                console.print(f"  {cols[0]}: {cols[1]}")
            continue
        if stripped.startswith("| Phase") or stripped.startswith("| Item"):
            continue

        if stripped.startswith("- [x]"):
            console.print(f"  [green][x][/green] {stripped[6:]}")
        elif stripped.startswith("- [ ]"):
            console.print(f"  [yellow][ ][/yellow] {stripped[6:]}")
        elif stripped.startswith("- "):
            console.print(f"  {stripped}")
        else:
            console.print(f"  {stripped}")

    console.print()
    console.print(f"[dim]Source: {status_file}[/dim]")


@app.command()
def scaffold(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
    force: bool = typer.Option(
        False,
        "--force",
        "-f",
        help="Overwrite existing files (re-create from template)",
    ),
    yes: bool = typer.Option(
        False,
        "--yes",
        "-y",
        help="Skip confirmation prompt",
    ),
) -> None:
    """Audit and scaffold missing project/ files from templates.

    Shows which required files are missing or contain stub content,
    then creates missing files from templates. Use --force to re-create
    all files including existing ones.
    """
    try:
        agent_dir = find_agent_dir(path)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    project_dir = agent_dir / "project"
    project_dir.mkdir(parents=True, exist_ok=True)

    # All files the scaffold command manages
    scaffold_files = [
        ("context.md",        "Project overview and business context"),
        ("tech-stack.md",     "Technology stack and environment"),
        ("status.md",         "Current development state (AI essential)"),
        ("deploy.md",         "Deployment environments and infrastructure"),
        ("commands.md",       "Common development and deployment commands"),
        ("known-issues.md",   "Known issues and workarounds"),
        ("session-journal.md","Session history log"),
    ]

    # Template placeholder patterns — file exists but hasn't been customized
    _STUB_MARKERS = ("{{", "[DATE]", "[Describe", "[Your ", "[Project ", "{{PROJECT_NAME}}")

    def _is_stub(file_path: Path) -> bool:
        """Return True if file contains unreplaced template placeholders."""
        try:
            text = file_path.read_text(encoding="utf-8")
            return any(marker in text for marker in _STUB_MARKERS)
        except OSError:
            return False

    def _find_template(filename: str) -> Optional[Path]:
        """Find template: local .agent/templates/project/ first, then bundled."""
        local = agent_dir / "templates" / "project" / filename
        if local.exists():
            return local
        # Bundled fallback
        from cokodo_agent.fetcher import get_protocol
        try:
            bundled_path, _ = get_protocol(offline=True)
            bundled = bundled_path / "templates" / "project" / filename
            if bundled.exists():
                return bundled
        except Exception:
            pass
        return None

    # Audit all files
    rows: list[tuple[str, str, str, bool]] = []  # (name, status_label, note, will_create)
    for filename, desc in scaffold_files:
        target = project_dir / filename
        if target.exists():
            if force:
                rows.append((filename, "[yellow]EXISTS[/yellow]", "Will overwrite (--force)", True))
            elif _is_stub(target):
                rows.append((filename, "[yellow]STUB[/yellow]", "Contains placeholders, skipping", False))
            else:
                rows.append((filename, "[green]OK[/green]", "", False))
        else:
            rows.append((filename, "[red]MISSING[/red]", "Will create", True))

    # Display audit table
    console.print()
    console.print(Panel.fit(
        f"[bold]Project File Audit[/bold]\n{agent_dir / 'project'}",
        border_style="blue",
    ))
    console.print()

    table = Table(show_header=True, header_style="bold")
    table.add_column("File", style="cyan", width=22)
    table.add_column("Status", width=10)
    table.add_column("Description", style="dim", width=40)
    table.add_column("Note", width=30)

    for filename, status_label, note, _ in rows:
        desc_text = next(d for f, d in scaffold_files if f == filename)
        table.add_row(filename, status_label, desc_text, note)

    console.print(table)
    console.print()

    to_create = [(filename, note) for filename, _, note, will in rows if will]

    if not to_create:
        console.print("[green]All required project files are present.[/green]")
        if any("STUB" in status for _, status, _, _ in rows):
            console.print()
            console.print(
                "[dim]Stub files exist but contain placeholder content. "
                "Edit them manually or use --force to reset from template.[/dim]"
            )
        return

    console.print(f"[bold]{len(to_create)}[/bold] file(s) to create/overwrite.")
    console.print()

    if not yes:
        confirmed = typer.confirm("Proceed?", default=True)
        if not confirmed:
            console.print("[yellow]Cancelled.[/yellow]")
            raise typer.Exit(0)

    # Create files
    created: list[str] = []
    skipped: list[str] = []
    errors: list[str] = []

    for filename, _ in to_create:
        target = project_dir / filename
        template_path = _find_template(filename)

        if template_path is None:
            errors.append(f"{filename}: template not found")
            continue

        try:
            content = template_path.read_text(encoding="utf-8")
            target.write_text(content, encoding="utf-8")
            created.append(filename)
        except OSError as e:
            errors.append(f"{filename}: {e}")

    console.print()
    if created:
        console.print(f"[green]Created {len(created)} file(s):[/green]")
        for f in created:
            console.print(f"  + project/{f}")
    if errors:
        console.print(f"[red]Errors ({len(errors)}):[/red]")
        for e in errors:
            console.print(f"  x {e}")

    console.print()
    console.print("[dim]Next steps:[/dim]")
    console.print("  1. Edit the created files with your project-specific information")
    console.print("  2. Run [bold]co lint[/bold] to verify compliance")
    console.print("  3. Run [bold]co status[/bold] to view current project state")


@app.command()
def serve(
    path: Optional[Path] = typer.Argument(
        None,
        help="Path to project (default: current directory)",
    ),
    transport: str = typer.Option(
        "stdio",
        "--transport",
        "-t",
        help="Transport mode (stdio/streamable-http)",
    ),
) -> None:
    """Start MCP server for IDE integration.

    Exposes .agent/ protocol context via Model Context Protocol.
    IDEs like Cursor and Claude Code connect automatically via stdio.

    Requires: pip install cokodo-agent[mcp]
    """
    try:
        import mcp  # noqa: F401
    except ImportError:
        # Write to stderr — stdout must be clean for MCP stdio transport
        err = Console(stderr=True)
        err.print("[red]Error:[/red] MCP support not installed.")
        err.print()
        err.print("Install with:")
        err.print("  [bold]pip install cokodo-agent[mcp][/bold]")
        raise typer.Exit(1)

    if path:
        agent_dir: Optional[Path] = Path(path).resolve() / ".agent"
        if not agent_dir.is_dir():
            err = Console(stderr=True)
            err.print(f"[red]Error:[/red] No .agent/ directory found in {path}")
            raise typer.Exit(1)
    else:
        agent_dir = None

    from cokodo_agent.mcp_server import create_server

    server = create_server(agent_dir=agent_dir)
    server.run(transport=transport)


@app.command()
def version() -> None:
    """Show version information."""
    console.print(f"cokodo-agent v{VERSION}")
    console.print()
    console.print("Protocol versions:")
    console.print(f"  Built-in: v{BUNDLED_PROTOCOL_VERSION}")
    console.print()
    console.print("IDE spec versions (parser/generator target):")
    for tool, info in sorted(IDE_SPEC_VERSIONS.items()):
        console.print(f"  {tool}: {info['spec_version']} (validated {info['spec_date']})")


@app.command()
def help(
    command: Optional[str] = typer.Argument(
        None,
        help="Command to get help for",
    ),
) -> None:
    """Show help information for commands."""
    commands_info = {
        "init": {
            "description": "Create .agent protocol in target directory",
            "usage": "co init [PATH] [OPTIONS]",
            "options": [
                ("-y, --yes", "Skip prompts, use defaults"),
                ("-n, --name", "Project name"),
                ("-s, --stack", "Tech stack (python/rust/qt/mixed/other)"),
                ("-f, --force", "Overwrite existing .agent directory"),
                ("--offline", "Use built-in protocol (no network)"),
            ],
            "examples": [
                ("co init", "Initialize in current directory with prompts"),
                ("co init -y", "Initialize with defaults"),
                ("co init ./myproject -n MyApp -s python", "Initialize with options"),
                ("co init --offline", "Initialize using built-in protocol"),
            ],
        },
        "lint": {
            "description": "Check protocol compliance against rules",
            "usage": "co lint [PATH] [OPTIONS]",
            "options": [
                ("-r, --rule", "Check specific rule only"),
                ("-f, --format", "Output format (text/json/github)"),
            ],
            "examples": [
                ("co lint", "Check current directory"),
                ("co lint -r integrity-violation", "Check specific rule"),
                ("co lint -f json", "Output as JSON"),
                ("co lint -f github", "Output for GitHub Actions"),
            ],
        },
        "diff": {
            "description": "Compare local .agent with latest protocol",
            "usage": "co diff [PATH] [OPTIONS]",
            "options": [
                ("--offline", "Use built-in protocol (no network)"),
            ],
            "examples": [
                ("co diff", "Show differences with latest"),
                ("co diff --offline", "Compare with built-in protocol"),
            ],
        },
        "sync": {
            "description": "Sync local .agent with latest protocol",
            "usage": "co sync [PATH] [OPTIONS]",
            "options": [
                ("--offline", "Use built-in protocol (no network)"),
                ("--dry-run", "Show what would be updated"),
                ("-y, --yes", "Skip confirmation prompt"),
            ],
            "examples": [
                ("co sync", "Sync with confirmation"),
                ("co sync -y", "Sync without confirmation"),
                ("co sync --dry-run", "Preview changes"),
            ],
        },
        "adapt": {
            "description": "Generate IDE entry file(s) from existing .agent",
            "usage": "co adapt <cursor|claude|copilot|gemini|all> [PATH]",
            "options": [],
            "examples": [
                ("co adapt cursor", "Generate .cursor/rules/agent-protocol.mdc"),
                ("co adapt claude", "Generate CLAUDE.md at project root"),
                ("co adapt copilot", "Generate AGENTS.md at project root"),
                ("co adapt gemini", "Generate GEMINI.md at project root"),
                ("co adapt all", "Generate all IDE adapter files"),
            ],
        },
        "detect": {
            "description": "Detect IDE instruction files in the project",
            "usage": "co detect [PATH]",
            "options": [],
            "examples": [
                ("co detect", "List detected CLAUDE.md, AGENTS.md, GEMINI.md, .cursor/rules/"),
            ],
        },
        "import": {
            "description": "Import rules from IDE instruction files into .agent",
            "usage": "co import [PATH] [OPTIONS]",
            "options": [
                ("-s, --source", "cursor | claude | copilot | gemini | auto"),
                ("--dry-run", "Show what would be imported without writing"),
            ],
            "examples": [
                ("co import", "Import from all detected files"),
                ("co import --dry-run", "Preview import"),
                ("co import -s claude", "Import only from CLAUDE.md"),
            ],
        },
        "context": {
            "description": "Get context files based on stack and task type",
            "usage": "co context [PATH] [OPTIONS]",
            "options": [
                ("-s, --stack", "Tech stack (python/rust/qt/mixed)"),
                ("-t, --task", "Task type (coding/testing/review/...)"),
                ("-o, --output", "Output format (list/paths/content)"),
            ],
            "examples": [
                ("co context", "List all context files"),
                ("co context -s python", "Files for Python stack"),
                ("co context -t testing", "Files for testing task"),
                ("co context -o content", "Output file contents"),
            ],
        },
        "journal": {
            "description": "Record a session entry to session-journal.md",
            "usage": "co journal [PATH] [OPTIONS]",
            "options": [
                ("-t, --title", "Session title"),
                ("-c, --completed", "Completed items (comma-separated)"),
                ("-d, --debt", "Technical debt items"),
                ("--decisions", "Key decisions made"),
                ("-i, --interactive", "Interactive mode with prompts"),
            ],
            "examples": [
                ("co journal -i", "Interactive mode"),
                ('co journal -t "Feature X" -c "Task 1, Task 2"', "Quick entry"),
            ],
        },
        "scaffold": {
            "description": "Audit and scaffold missing project/ files from templates",
            "usage": "co scaffold [PATH] [OPTIONS]",
            "options": [
                ("-f, --force", "Overwrite existing files (re-create from template)"),
                ("-y, --yes", "Skip confirmation prompt"),
            ],
            "examples": [
                ("co scaffold", "Show audit table and create missing files"),
                ("co scaffold -y", "Create missing files without confirmation"),
                ("co scaffold --force -y", "Re-create ALL project files from templates"),
            ],
        },
        "status": {
            "description": "Show or initialize project development status",
            "usage": "co status [PATH] [OPTIONS]",
            "options": [
                ("--raw", "Show raw markdown content"),
                ("--json", "Output as JSON (for scripting)"),
                ("--init", "Create status.md from template if missing"),
            ],
            "examples": [
                ("co status", "Show formatted project status"),
                ("co status --raw", "Output raw markdown"),
                ("co status --json", "Output as JSON"),
                ("co status --init", "Create status.md if missing"),
            ],
        },
        "serve": {
            "description": "Start MCP server for IDE integration",
            "usage": "co serve [PATH] [OPTIONS]",
            "options": [
                ("-t, --transport", "Transport mode (stdio/streamable-http)"),
            ],
            "examples": [
                ("co serve", "Start MCP server (stdio, for Cursor/Claude)"),
                ("co serve -t streamable-http", "Start HTTP server"),
            ],
        },
        "update-checksums": {
            "description": "Update checksums in manifest.json (maintainer only)",
            "usage": "co update-checksums [PATH]",
            "options": [],
            "examples": [
                ("co update-checksums", "Update checksums"),
            ],
        },
        "version": {
            "description": "Show version information",
            "usage": "co version",
            "options": [],
            "examples": [
                ("co version", "Show version"),
            ],
        },
        "help": {
            "description": "Show help information for commands",
            "usage": "co help [COMMAND]",
            "options": [],
            "examples": [
                ("co help", "Show all commands"),
                ("co help init", "Show help for init command"),
            ],
        },
    }

    if command:
        # Show help for specific command
        if command not in commands_info:
            console.print(f"[red]Error:[/red] Unknown command '{command}'")
            console.print()
            console.print("Available commands:")
            for cmd in commands_info:
                console.print(f"  {cmd}")
            raise typer.Exit(1)

        info = commands_info[command]
        console.print()
        console.print(Panel.fit(f"[bold blue]co {command}[/bold blue]", border_style="blue"))
        console.print()
        console.print(f"[bold]Description:[/bold] {info['description']}")
        console.print()
        console.print(f"[bold]Usage:[/bold] {info['usage']}")

        if info["options"]:
            console.print()
            console.print("[bold]Options:[/bold]")
            for opt, desc in info["options"]:
                console.print(f"  [cyan]{opt:20}[/cyan] {desc}")

        console.print()
        console.print("[bold]Examples:[/bold]")
        for example, desc in info["examples"]:
            console.print(f"  [green]{example}[/green]")
            console.print(f"    {desc}")

    else:
        # Show overview of all commands
        console.print()
        console.print(
            Panel.fit(
                f"[bold blue]Cokodo Agent[/bold blue] v{VERSION}\n"
                "AI Collaboration Protocol Generator",
                border_style="blue",
            )
        )
        console.print()
        console.print("[bold]Commands:[/bold]")
        console.print()

        # Group commands by category
        categories = {
            "Setup": ["init", "adapt", "detect", "import"],
            "Protocol Management": ["lint", "diff", "sync", "update-checksums"],
            "Development": ["status", "scaffold", "context", "journal"],
            "MCP Integration": ["serve"],
            "Information": ["version", "help"],
        }

        for category, cmds in categories.items():
            console.print(f"  [bold cyan]{category}[/bold cyan]")
            for cmd in cmds:
                if cmd in commands_info:
                    desc = commands_info[cmd]["description"]
                    console.print(f"    [green]{cmd:18}[/green] {desc}")
            console.print()

        console.print("[bold]Usage:[/bold]")
        console.print("  co <command> [options]")
        console.print()
        console.print("[bold]Get help for a command:[/bold]")
        console.print("  co help <command>")
        console.print()
        console.print("[bold]Quick start:[/bold]")
        console.print("  co init          # Create .agent in current directory")
        console.print("  co lint          # Check protocol compliance")
        console.print("  co sync          # Update to latest protocol")
        console.print()
        console.print("Documentation: https://github.com/dinwind/agent_protocol")


if __name__ == "__main__":
    app()
