"""Project management commands for CleverAgents CLI.

Implements spec-aligned project CLI commands that use the namespaced project
model (NamespacedProject) with namespaced_name as the identifier.

Commands:
- ``agents project create <name>``
- ``agents project link-resource <project> <resource>``
- ``agents project unlink-resource <project> <resource>``
- ``agents project list [--namespace NS] [REGEX]``
- ``agents project show <project>``
- ``agents project delete [--force|-f] [--yes|-y] <name>``

Legacy file-filter sub-app is preserved for backward compatibility.

Based on ADR-009 (CLI Framework) and implementation_plan.md task B0.cli.projects.
"""

from __future__ import annotations

import re
from datetime import UTC, datetime
from pathlib import Path
from typing import Annotated, Any

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

from cleveragents.application.services.context_service import DEFAULT_IGNORE_PATTERNS
from cleveragents.cli.commands.project_context import app as context_app
from cleveragents.cli.formatting import OutputFormat, format_output
from cleveragents.cli.renderers import _get_console, _get_err_console
from cleveragents.core.exceptions import (
    CleverAgentsError,
    ConfigurationError,
    DatabaseError,
    NotFoundError,
    ValidationError,
)

# Create sub-app for project commands
app = typer.Typer(help="Project management commands")
file_filter_app = typer.Typer(
    help="Manage project include/exclude filters", name="file-filter"
)
console = _get_console()
err_console = _get_err_console()

# Reusable --format option description
_FORMAT_HELP = "Output format: json, yaml, plain, table, or rich (default: rich)"


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------


def _get_project_service_and_current_project() -> tuple[Any, Any]:
    from cleveragents.application.container import get_container
    from cleveragents.application.services.project_service import ProjectService

    container = get_container()
    project_service: ProjectService = container.project_service()
    project = project_service.get_current_project()
    if not project:
        err_console.print("[red]Error: No project found in current directory.[/red]")
        raise typer.Exit(1)
    return project_service, project


def _get_namespaced_project_repo() -> Any:
    """Return a NamespacedProjectRepository from the DI container."""
    from cleveragents.application.container import get_container

    container = get_container()
    return container.namespaced_project_repo()


def _get_resource_link_repo() -> Any:
    """Return a ProjectResourceLinkRepository from the DI container."""
    from cleveragents.application.container import get_container

    container = get_container()
    return container.project_resource_link_repo()


def _get_resource_registry_service() -> Any:
    """Return a ResourceRegistryService from the DI container."""
    from cleveragents.application.container import get_container

    container = get_container()
    return container.resource_registry_service()


def _store_project_extras(
    namespaced_name: str,
    invariant_texts: list[str] | None = None,
    inv_actor: str | None = None,
) -> None:
    """Persist invariant list and invariant_actor directly via SQL session.

    These fields live on the ``ns_projects`` table but are not exposed on
    the ``NamespacedProject`` Pydantic domain model (they're JSON columns),
    so we update them via a lightweight session outside the repository API.
    """
    import json as _json

    from sqlalchemy import create_engine, text
    from sqlalchemy.orm import sessionmaker

    from cleveragents.application.container import get_database_url

    db_url = get_database_url()
    engine = create_engine(db_url, echo=False)
    session = sessionmaker(bind=engine, expire_on_commit=False)()
    try:
        updates: list[str] = []
        params: dict[str, str] = {"ns_name": namespaced_name}
        if invariant_texts:
            inv_list = [i for i in invariant_texts if i.strip()]
            updates.append("invariants_json = :inv_json")
            params["inv_json"] = _json.dumps(inv_list)
        if inv_actor:
            updates.append("invariant_actor = :inv_actor")
            params["inv_actor"] = inv_actor
        if updates:
            sql = text(
                f"UPDATE ns_projects SET {', '.join(updates)} "
                f"WHERE namespaced_name = :ns_name"
            )
            session.execute(sql, params)
            session.commit()
    finally:
        session.close()


def _project_spec_dict(project: Any) -> dict[str, object]:
    """Return project data as a dict using spec field names.

    Keys: namespaced_name, namespace, name, description,
    linked_resources, created_at, updated_at.
    """
    linked: list[dict[str, object]] = []
    for lr in project.linked_resources:
        linked.append(
            {
                "resource_id": lr.resource_id,
                "read_only": lr.project_read_only,
                "alias": lr.alias,
                "linked_at": lr.linked_at.isoformat()
                if hasattr(lr.linked_at, "isoformat")
                else str(lr.linked_at),
            }
        )

    return {
        "namespaced_name": project.namespaced_name,
        "namespace": project.namespace,
        "name": project.name,
        "description": project.description,
        "linked_resources": linked,
        "created_at": project.created_at.isoformat()
        if hasattr(project.created_at, "isoformat")
        else str(project.created_at),
        "updated_at": project.updated_at.isoformat()
        if hasattr(project.updated_at, "isoformat")
        else str(project.updated_at),
    }


# ---------------------------------------------------------------------------
# Legacy init helpers (preserved)
# ---------------------------------------------------------------------------


def init_command(
    name: str | None = None,
    path: Path | None = None,
    force: bool = False,
    create_ignore_file: bool = False,
    default_filters: bool = False,
    yes: bool = False,
) -> None:
    """Programmatic interface for initializing a new CleverAgents project."""
    from cleveragents.application.container import get_container
    from cleveragents.application.services.project_service import ProjectService

    if path is None:
        path = Path.cwd()

    project_name = name or path.name

    container = get_container()

    # When --yes is passed, disable the migration confirmation prompt
    # entirely so the command never blocks waiting for interactive
    # input (bug #783).  ``require_confirmation=False`` tells the
    # migration runner to skip the prompt; the ``prompt_for_migration``
    # callback is set as a belt-and-suspenders fallback that
    # auto-approves in case any code path still reaches the prompt.
    if yes:
        container.unit_of_work.add_kwargs(
            require_confirmation=False,
            prompt_for_migration=lambda _: True,
        )

    project_service: ProjectService = container.project_service()

    project = project_service.initialize_project(
        name=project_name,
        path=path,
        force=force,
        create_ignore_file=create_ignore_file,
        apply_default_filters=default_filters,
    )

    if yes:
        # Non-interactive mode: produce spec-aligned output
        # (specification.md lines 1381-1389)
        data_dir: Path = getattr(
            project, "data_dir", getattr(project, "path", path) / ".cleveragents"
        )
        config_path: Path = getattr(
            project,
            "config_path",
            getattr(project, "path", path) / ".cleveragents" / "config.toml",
        )
        database_status: str = getattr(
            project, "database_status", "initialized (schema v3)"
        )
        directories: list[str] = getattr(
            project, "directories", ["logs", "cache", "sessions", "contexts"]
        )
        console.print(
            Panel(
                f"[green]Data Dir:[/green] {data_dir} (created)\n"
                f"[green]Config:[/green] {config_path}\n"
                f"[green]Database:[/green] {database_status}\n"
                f"[green]Directories:[/green] {', '.join(directories)}",
                title="Initialized",
                expand=False,
            )
        )
        console.print("[green]✓ OK[/green] Initialized (non-interactive)")
    else:
        console.print(
            Panel(
                f"[green]✓[/green] Project '{project.name}' "
                f"initialized successfully!\n\n"
                f"Location: {project.path / '.cleveragents'}\n"
                f"Database: SQLite\n"
                f"Status: Ready",
                title="Project Initialized",
                expand=False,
            )
        )


# ---------------------------------------------------------------------------
# File-filter sub-app (legacy, preserved as-is)
# ---------------------------------------------------------------------------


@file_filter_app.command("show")
def file_filter_show() -> None:
    """Show current include/exclude filters for this project."""

    project_service, project = _get_project_service_and_current_project()
    includes, excludes = project_service.get_project_filters(project)
    console.print(
        Panel(
            f"Includes: {includes or '[]'}\nExcludes: {excludes or '[]'}",
            title="Project File Filters",
            expand=False,
        )
    )


@file_filter_app.command("add")
def file_filter_add(
    include: Annotated[
        list[str] | None,
        typer.Option(
            help="Include globs to add (repeatable)",
        ),
    ] = None,
    exclude: Annotated[
        list[str] | None,
        typer.Option(
            help="Exclude globs to add (repeatable)",
        ),
    ] = None,
    defaults: Annotated[
        bool,
        typer.Option(
            "--defaults",
            help="Add the recommended default exclude globs",
        ),
    ] = False,
) -> None:
    """Add include/exclude globs to the project settings."""

    project_service, project = _get_project_service_and_current_project()
    include_list = [p for p in (include or []) if p]
    exclude_list = [p for p in (exclude or []) if p]
    if defaults:
        exclude_list.extend(DEFAULT_IGNORE_PATTERNS)

    updated = project_service.update_file_filters(
        project,
        include_add=include_list,
        exclude_add=exclude_list if exclude_list else None,
    )
    includes, excludes = project_service.get_project_filters(updated)
    console.print(
        Panel(
            f"Includes: {includes or '[]'}\nExcludes: {excludes or '[]'}",
            title="Updated Project File Filters",
            expand=False,
        )
    )


@file_filter_app.command("clear")
def file_filter_clear(
    clear_include: Annotated[
        bool,
        typer.Option(help="Clear include globs"),
    ] = False,
    clear_exclude: Annotated[
        bool,
        typer.Option(help="Clear exclude globs"),
    ] = False,
) -> None:
    """Clear include/exclude globs for the project."""

    project_service, project = _get_project_service_and_current_project()
    if not clear_include and not clear_exclude:
        clear_include = True
        clear_exclude = True

    updated = project_service.update_file_filters(
        project,
        clear_include=clear_include,
        clear_exclude=clear_exclude,
    )
    includes, excludes = project_service.get_project_filters(updated)
    console.print(
        Panel(
            f"Includes: {includes or '[]'}\nExcludes: {excludes or '[]'}",
            title="Cleared Project File Filters",
            expand=False,
        )
    )


@file_filter_app.command("remove")
def file_filter_remove(
    include: Annotated[
        list[str] | None,
        typer.Option(
            help="Include glob(s) to remove (repeatable)",
        ),
    ] = None,
    exclude: Annotated[
        list[str] | None,
        typer.Option(
            help="Exclude glob(s) to remove (repeatable)",
        ),
    ] = None,
) -> None:
    """Remove include/exclude globs from the project settings."""

    project_service, project = _get_project_service_and_current_project()
    include_list = [p for p in (include or []) if p]
    exclude_list = [p for p in (exclude or []) if p]

    updated = project_service.update_file_filters(
        project,
        include_remove=include_list if include_list else None,
        exclude_remove=exclude_list if exclude_list else None,
    )
    includes, excludes = project_service.get_project_filters(updated)
    console.print(
        Panel(
            f"Includes: {includes or '[]'}\nExcludes: {excludes or '[]'}",
            title="Updated Project File Filters",
            expand=False,
        )
    )


app.add_typer(file_filter_app, name="file-filter")
app.add_typer(context_app, name="context")


# ---------------------------------------------------------------------------
# Legacy commands (init, status, clean - preserved for backward compat)
# ---------------------------------------------------------------------------


@app.command(name="init")
def init(
    name: Annotated[
        str | None,
        typer.Argument(help="Project name (defaults to current directory name)"),
    ] = None,
    path: Annotated[
        Path | None,
        typer.Option(
            "--path",
            "-p",
            help="Project path (defaults to current directory)",
        ),
    ] = None,
    force: Annotated[
        bool, typer.Option("--force", "-f", help="Force reinitialization")
    ] = False,
    create_ignore_file: Annotated[
        bool,
        typer.Option(
            "--create-ignore-file",
            help="Write a default .agentsignore with recommended patterns",
        ),
    ] = False,
    default_filters: Annotated[
        bool,
        typer.Option(
            "--default-filters",
            help="Seed project exclude filters with recommended defaults",
        ),
    ] = False,
    yes: Annotated[
        bool,
        typer.Option(
            "--yes",
            "-y",
            help="Skip interactive prompts and use default values",
        ),
    ] = False,
) -> None:
    """Initialize a new CleverAgents project.

    Creates the .cleveragents directory structure with database and configuration.
    """
    try:
        init_command(
            name=name,
            path=path,
            force=force,
            create_ignore_file=create_ignore_file,
            default_filters=default_filters,
            yes=yes,
        )
    except ValidationError as e:
        from cleveragents.shared.redaction import redact_dict, redact_value

        err_console.print(f"[red]Validation Error:[/red] {redact_value(e.message)}")
        if e.details:
            safe = redact_dict(e.details)
            for key, value in safe.items():
                err_console.print(f"  {key}: {value}")
        raise typer.Abort() from e
    except ConfigurationError as e:
        console.print(f"[red]Configuration Error:[/red] {e.message}")
        raise typer.Abort() from e
    except CleverAgentsError as e:
        console.print(f"[red]Error:[/red] {e.message}")
        raise typer.Abort() from e
    except Exception as e:
        console.print(f"[red]Unexpected error:[/red] {e}")
        raise typer.Abort() from e


@app.command(name="status")
def status() -> None:
    """Show current project status and information."""
    from cleveragents.application.container import get_container
    from cleveragents.application.services.project_service import ProjectService

    try:
        container = get_container()
        project_service: ProjectService = container.project_service()

        # Get current project
        project = project_service.get_current_project()

        if not project:
            err_console.print(
                "[red]Error: No project found in current directory.[/red]"
            )
            console.print("Run 'cleveragents init' to initialize a project.")
            raise typer.Exit(1)

        # Get project stats
        stats = project_service.get_project_stats(project)

        # Display project information
        info_text = f"""
[bold]Project:[/bold] {project.name}
[bold]Path:[/bold] {project.path}
[bold]Created:[/bold] {project.created_at}

[bold]Statistics:[/bold]
  Plans: {stats.get("plans", 0)}
  Context Files: {stats.get("context_files", 0)}
  Total Changes: {stats.get("changes", 0)}
  Current Plan: {stats.get("current_plan", "None")}
        """

        console.print(Panel(info_text.strip(), title="Project Status", expand=False))

    except CleverAgentsError as e:
        console.print(f"[red]Error:[/red] {e.message}")
        raise typer.Abort() from e


@app.command(name="clean")
def clean(
    confirm: Annotated[
        bool, typer.Option("--yes", "-y", help="Skip confirmation")
    ] = False,
) -> None:
    """Clean project cache and temporary files."""
    console.print("[yellow]Project cleaning not yet implemented.[/yellow]")
    raise typer.Abort()


# ---------------------------------------------------------------------------
# Spec-aligned project commands (B0.cli.projects)
# ---------------------------------------------------------------------------


@app.command(name="create")
def create(
    name: Annotated[
        str,
        typer.Argument(help="Project name (bare or namespace/name)"),
    ],
    description: Annotated[
        str | None,
        typer.Option("--description", "-d", help="Project description"),
    ] = None,
    resource: Annotated[
        list[str] | None,
        typer.Option(
            "--resource",
            "-r",
            help="Resource name/id to link on creation (repeatable)",
        ),
    ] = None,
    invariant: Annotated[
        list[str] | None,
        typer.Option(
            "--invariant",
            help="Invariant text (repeatable)",
        ),
    ] = None,
    invariant_actor: Annotated[
        str | None,
        typer.Option(
            "--invariant-actor",
            help="Actor for invariant reconciliation",
        ),
    ] = None,
    output_format: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Create a new namespaced project.

    NAME can be a bare name (defaults to local/ namespace) or namespace/name.
    """
    from cleveragents.domain.models.core.project import (
        NamespacedProject,
        parse_namespaced_name,
    )

    try:
        parsed = parse_namespaced_name(name)
    except ValueError as exc:
        err_console.print(f"[red]Invalid project name:[/red] {exc}")
        raise typer.Exit(1) from exc

    repo = _get_namespaced_project_repo()

    project = NamespacedProject(
        name=parsed.name,
        namespace=parsed.namespace,
        server=parsed.server,
        description=description,
    )

    try:
        repo.create(project)
    except DatabaseError as exc:
        err_console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Exit(1) from exc

    # Store invariants and invariant_actor if provided
    if invariant or invariant_actor:
        _store_project_extras(
            project.namespaced_name,
            invariant_texts=invariant,
            inv_actor=invariant_actor,
        )

    # Link resources if specified
    if resource:
        link_repo = _get_resource_link_repo()
        registry = _get_resource_registry_service()
        for res_name in resource:
            try:
                res = registry.show_resource(res_name)
                link_repo.create_link(
                    project_name=project.namespaced_name,
                    resource_id=res.resource_id,
                )
            except (NotFoundError, DatabaseError) as exc:
                err_console.print(
                    f"[yellow]Warning: could not link resource "
                    f"'{res_name}': {exc}[/yellow]"
                )

    # Re-fetch project for display
    try:
        created = repo.get(project.namespaced_name)
    except Exception:
        created = project

    data = _project_spec_dict(created)
    if output_format.lower() == OutputFormat.RICH:
        console.print(
            Panel(
                f"[green]✓[/green] Project '{created.namespaced_name}' created.\n"
                f"Namespace: {created.namespace}\n"
                f"Description: {created.description or '(none)'}\n"
                f"Resources: {len(created.linked_resources)}",
                title="Project Created",
                expand=False,
            )
        )
    else:
        console.print(format_output(data, output_format))


@app.command(name="link-resource")
def link_resource(
    project: Annotated[
        str,
        typer.Argument(help="Project namespaced name"),
    ],
    resource_name: Annotated[
        str,
        typer.Argument(help="Resource name or ULID to link"),
    ],
    read_only: Annotated[
        bool,
        typer.Option("--read-only", help="Link as read-only"),
    ] = False,
    alias: Annotated[
        str | None,
        typer.Option("--alias", help="Alias for the resource within the project"),
    ] = None,
    output_format: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Link a resource to a project."""
    repo = _get_namespaced_project_repo()
    link_repo = _get_resource_link_repo()
    registry = _get_resource_registry_service()

    # Validate project exists
    try:
        proj = repo.get(project)
    except Exception as exc:
        err_console.print(f"[red]Project not found:[/red] {project}")
        raise typer.Exit(1) from exc

    # Resolve resource
    try:
        res = registry.show_resource(resource_name)
    except NotFoundError as exc:
        err_console.print(f"[red]Resource not found:[/red] {resource_name}")
        raise typer.Exit(1) from exc

    # Create link
    try:
        link_repo.create_link(
            project_name=proj.namespaced_name,
            resource_id=res.resource_id,
            alias=alias,
            read_only=read_only,
        )
    except DatabaseError as exc:
        err_console.print(f"[red]Error linking resource:[/red] {exc.message}")
        raise typer.Exit(1) from exc

    if output_format.lower() == OutputFormat.RICH:
        ro_label = " (read-only)" if read_only else ""
        console.print(
            f"[green]✓[/green] Linked resource '{resource_name}'{ro_label} "
            f"to project '{project}'."
        )
    else:
        console.print(
            format_output(
                {
                    "project": proj.namespaced_name,
                    "resource_id": res.resource_id,
                    "resource_name": res.name or resource_name,
                    "read_only": read_only,
                    "alias": alias,
                },
                output_format,
            )
        )


@app.command(name="unlink-resource")
def unlink_resource(
    project: Annotated[
        str,
        typer.Argument(help="Project namespaced name"),
    ],
    resource_name: Annotated[
        str,
        typer.Argument(help="Resource name or ULID to unlink"),
    ],
    yes: Annotated[
        bool,
        typer.Option("--yes", "-y", help="Skip confirmation prompt"),
    ] = False,
    output_format: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Unlink a resource from a project."""
    repo = _get_namespaced_project_repo()
    link_repo = _get_resource_link_repo()
    registry = _get_resource_registry_service()

    # Validate project exists
    try:
        proj = repo.get(project)
    except Exception as exc:
        err_console.print(f"[red]Project not found:[/red] {project}")
        raise typer.Exit(1) from exc

    # Resolve resource
    try:
        res = registry.show_resource(resource_name)
    except NotFoundError as exc:
        err_console.print(f"[red]Resource not found:[/red] {resource_name}")
        raise typer.Exit(1) from exc

    # Find the matching link
    links = link_repo.list_links(proj.namespaced_name)
    target_link: Any = None
    for link in links:
        if str(link.resource_id) == res.resource_id:
            target_link = link
            break

    if target_link is None:
        err_console.print(
            f"[red]Resource '{resource_name}' is not linked to "
            f"project '{project}'.[/red]"
        )
        raise typer.Exit(1)

    if not yes:
        confirm = typer.confirm(
            f"Unlink resource '{resource_name}' from project '{project}'?"
        )
        if not confirm:
            raise typer.Abort()

    try:
        link_repo.remove_link(str(target_link.link_id))
    except DatabaseError as exc:
        err_console.print(f"[red]Error unlinking resource:[/red] {exc.message}")
        raise typer.Exit(1) from exc

    if output_format.lower() == OutputFormat.RICH:
        console.print(
            f"[green]✓[/green] Unlinked resource '{resource_name}' "
            f"from project '{project}'."
        )
    else:
        console.print(
            format_output(
                {
                    "project": proj.namespaced_name,
                    "resource_id": res.resource_id,
                    "unlinked": True,
                },
                output_format,
            )
        )


@app.command(name="list")
def list_projects(
    namespace: Annotated[
        str | None,
        typer.Option("--namespace", "-n", help="Filter by namespace"),
    ] = None,
    regex: Annotated[
        str | None,
        typer.Argument(help="Optional regex filter on project name"),
    ] = None,
    output_format: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """List all projects in the registry."""
    repo = _get_namespaced_project_repo()

    try:
        projects = repo.list_projects(namespace=namespace)
    except DatabaseError as exc:
        err_console.print(f"[red]Error listing projects:[/red] {exc.message}")
        raise typer.Exit(1) from exc

    # Apply regex filter if provided
    if regex:
        try:
            pattern = re.compile(regex)
        except re.error as exc:
            err_console.print(f"[red]Invalid regex:[/red] {exc}")
            raise typer.Exit(1) from exc
        projects = [p for p in projects if pattern.search(p.namespaced_name)]

    if not projects:
        console.print("[yellow]No projects found.[/yellow]")
        return

    if output_format.lower() == OutputFormat.RICH:
        table = Table(title="Projects", show_header=True)
        table.add_column("Name", style="bold")
        table.add_column("Namespace")
        table.add_column("Description")
        table.add_column("Resources", justify="right")
        table.add_column("Created")

        for proj in projects:
            created_str = (
                proj.created_at.strftime("%Y-%m-%d")
                if hasattr(proj.created_at, "strftime")
                else str(proj.created_at)[:10]
            )
            table.add_row(
                proj.namespaced_name,
                proj.namespace,
                proj.description or "",
                str(len(proj.linked_resources)),
                created_str,
            )

        console.print(table)
    else:
        data = [_project_spec_dict(p) for p in projects]
        console.print(format_output(data, output_format))


@app.command(name="show")
def show(
    project: Annotated[
        str,
        typer.Argument(help="Project namespaced name"),
    ],
    output_format: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Show details of a project."""
    repo = _get_namespaced_project_repo()

    try:
        proj = repo.get(project)
    except Exception as exc:
        err_console.print(f"[red]Project not found:[/red] {project}")
        raise typer.Exit(1) from exc

    data = _project_spec_dict(proj)

    if output_format.lower() == OutputFormat.RICH:
        lines: list[str] = [
            f"[bold]Name:[/bold] {proj.namespaced_name}",
            f"[bold]Namespace:[/bold] {proj.namespace}",
            f"[bold]Description:[/bold] {proj.description or '(none)'}",
            f"[bold]Created:[/bold] {proj.created_at}",
            f"[bold]Updated:[/bold] {proj.updated_at}",
        ]

        if proj.linked_resources:
            lines.append(
                f"\n[bold]Linked Resources ({len(proj.linked_resources)}):[/bold]"
            )
            for lr in proj.linked_resources:
                ro_marker = " [dim](read-only)[/dim]" if lr.project_read_only else ""
                alias_marker = f" alias={lr.alias}" if lr.alias else ""
                lines.append(f"  - {lr.resource_id}{ro_marker}{alias_marker}")
        else:
            lines.append("\n[bold]Linked Resources:[/bold] (none)")

        console.print(
            Panel(
                "\n".join(lines),
                title=f"Project: {proj.namespaced_name}",
                expand=False,
            )
        )
    else:
        console.print(format_output(data, output_format))


@app.command(name="delete")
def delete(
    name: Annotated[
        str,
        typer.Argument(help="Project namespaced name to delete"),
    ],
    force: Annotated[
        bool,
        typer.Option("--force", "-f", help="Force delete even if resources are linked"),
    ] = False,
    yes: Annotated[
        bool,
        typer.Option("--yes", "-y", help="Skip confirmation prompt"),
    ] = False,
    output_format: Annotated[
        str,
        typer.Option("--format", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Delete a project from the registry."""
    repo = _get_namespaced_project_repo()

    # Validate project exists
    try:
        proj = repo.get(name)
    except Exception as exc:
        err_console.print(f"[red]Project not found:[/red] {name}")
        raise typer.Exit(1) from exc

    # Check for linked resources (unless --force)
    if proj.linked_resources and not force:
        err_console.print(
            f"[red]Project '{name}' has {len(proj.linked_resources)} "
            f"linked resource(s). Use --force to delete anyway.[/red]"
        )
        raise typer.Exit(1)

    if not yes:
        confirm = typer.confirm(f"Delete project '{name}'?")
        if not confirm:
            raise typer.Abort()

    try:
        deleted = repo.delete(name)
    except DatabaseError as exc:
        err_console.print(f"[red]Error deleting project:[/red] {exc.message}")
        raise typer.Exit(1) from exc

    if not deleted:
        err_console.print(f"[red]Project '{name}' could not be deleted.[/red]")
        raise typer.Exit(1)

    if output_format.lower() == OutputFormat.RICH:
        console.print(f"[green]✓[/green] Project '{name}' deleted.")
    else:
        console.print(
            format_output(
                {
                    "deleted": name,
                    "success": True,
                    "deleted_at": datetime.now(tz=UTC),
                },
                output_format,
            )
        )
