#!/usr/bin/env python
"""Cyclopts Demo Application - Testing completion features."""

from pathlib import Path
from typing import Annotated, Literal

from cyclopts import App, Parameter, validators

app = App(
    name="cyclopts-demo",
    help="Demo application for testing Cyclopts completion features.",
)
app.register_install_completion_command()


@app.default
def main(
    verbose: bool = False,
    config: Path | None = None,
):
    """Main command.

    Parameters
    ----------
    verbose : bool
        Enable verbose output.
    config : Path, optional
        Configuration file path.
    """
    print(f"Main command: verbose={verbose}, config={config}")


files_app = App(name="files", help="File operations commands.")
app.command(files_app)


@files_app.command
def cp(
    source: Path,
    destination: Path,
    /,
    *,
    recursive: Annotated[bool, Parameter(help="Copy directories recursively")] = False,
    preserve: Annotated[bool, Parameter(help="Preserve file attributes")] = False,
    verbose: bool = False,
):
    """Copy files or directories.

    Parameters
    ----------
    source : Path
        Source file or directory.
    destination : Path
        Destination file or directory.
    recursive : bool
        Copy directories recursively.
    preserve : bool
        Preserve file attributes (timestamps, permissions).
    verbose : bool
        Show verbose output.
    """
    print(f"Copy: {source} -> {destination} (recursive={recursive}, preserve={preserve}, verbose={verbose})")


@files_app.command
def mv(
    source: Path,
    destination: Path,
    /,
    *,
    force: Annotated[bool, Parameter(help="Force overwrite if destination exists")] = False,
    backup: Annotated[bool, Parameter(help="Create backup of existing destination")] = False,
    verbose: bool = False,
):
    """Move files or directories.

    Parameters
    ----------
    source : Path
        Source file or directory.
    destination : Path
        Destination file or directory.
    force : bool
        Force overwrite if destination exists.
    backup : bool
        Create backup of existing destination files.
    verbose : bool
        Show verbose output.
    """
    print(f"Move: {source} -> {destination} (force={force}, backup={backup}, verbose={verbose})")


@files_app.command
def ls(
    path: Path = Path(),
    /,
    *,
    all: Annotated[bool, Parameter(help="Show hidden files")] = False,
    long: Annotated[bool, Parameter(help="Use long listing format")] = False,
    sort_by: Literal["name", "size", "time", "extension"] = "name",
    reverse: bool = False,
):
    """List directory contents.

    Parameters
    ----------
    path : Path
        Directory path to list (defaults to current directory).
    all : bool
        Show hidden files and directories.
    long : bool
        Use long listing format with details.
    sort_by : Literal["name", "size", "time", "extension"]
        Sort order.
    reverse : bool
        Reverse sort order.
    """
    print(f"List: {path} (all={all}, long={long}, sort_by={sort_by}, reverse={reverse})")


@files_app.command
def find(
    pattern: str,
    /,
    *,
    path: Path = Path(),
    type: Literal["file", "directory", "symlink", "any"] = "any",
    case_sensitive: bool = True,
    max_depth: int | None = None,
):
    """Find files matching a pattern.

    Parameters
    ----------
    pattern : str
        Search pattern (supports wildcards).
    path : Path
        Root directory to search from.
    type : Literal["file", "directory", "symlink", "any"]
        Type of filesystem entry to find.
    case_sensitive : bool
        Case-sensitive pattern matching.
    max_depth : int, optional
        Maximum search depth (None for unlimited).
    """
    print(f"Find: pattern={pattern}, path={path}, type={type}, case_sensitive={case_sensitive}, max_depth={max_depth}")


@app.command
def positional_choice(param: Literal["foo", "bar", "baz"], /):
    """Test positional-only parameter with Literal choices.

    Parameters
    ----------
    param : Literal["foo", "bar", "baz"]
        Choose one: foo, bar, or baz.
    """
    print(f"Called with: {param}")


@app.command
def multi_positional(
    first: Literal["alpha", "beta", "gamma"],
    second: Literal["red", "green", "blue"],
    /,
):
    """Test multiple positional-only parameters with Literal choices.

    Parameters
    ----------
    first : Literal["alpha", "beta", "gamma"]
        First choice: Greek letters.
    second : Literal["red", "green", "blue"]
        Second choice: colors.
    """
    print(f"Called with: first={first}, second={second}")


@app.command
def deploy(
    environment: Literal["dev", "staging", "production"],
    region: Literal["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"] = "us-east-1",
    dry_run: bool = False,
    skip_tests: bool = False,
    confirm: bool = True,
):
    """Deploy application.

    Parameters
    ----------
    environment : Literal["dev", "staging", "production"]
        Target environment.
    region : Literal["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"]
        AWS region.
    dry_run : bool
        Perform a dry run without making changes.
    skip_tests : bool
        Skip running tests.
    confirm : bool
        Require confirmation before deploying.
    """
    print(f"Deploy: env={environment}, region={region}, dry_run={dry_run}, skip_tests={skip_tests}, confirm={confirm}")


@app.command
def process(
    items: list[str],
    count: Annotated[int, Parameter(validator=validators.Number(gt=0, lte=100))] = 1,
    threshold: float = 0.5,
    tags: list[str] | None = None,
    exclude: list[str] | None = None,
):
    """Process items.

    Parameters
    ----------
    items : list[str]
        Items to process.
    count : int
        Number of iterations (1-100).
    threshold : float
        Processing threshold.
    tags : list[str], optional
        Tags to apply.
    exclude : list[str]
        Items to exclude.
    """
    if exclude is None:
        exclude = []
    print(f"Process: items={items}, count={count}, threshold={threshold}, tags={tags}, exclude={exclude}")


@app.command
def concat(
    files: Annotated[list[Path], Parameter(help="Files to concatenate")],
    output: Annotated[Path | None, Parameter(help="Output file (defaults to stdout)")] = None,
    add_separators: bool = False,
):
    """Concatenate multiple files.

    This command demonstrates list[Path] completion (issue #654).
    Try tab-completing after --files to see file completion work!

    Parameters
    ----------
    files : list[Path]
        Files to concatenate together.
    output : Path, optional
        Output file path. If not specified, prints to stdout.
    add_separators : bool
        Add separator lines between files.
    """
    print(f"Concat: files={files}, output={output}, add_separators={add_separators}")


@app.command
def image_convert(
    input_file: Annotated[Path, Parameter(help="Input image file")],
    output_file: Annotated[Path, Parameter(help="Output image file")],
    # Image Settings Group
    width: Annotated[int | None, Parameter(group="Image Settings", help="Output width in pixels")] = None,
    height: Annotated[int | None, Parameter(group="Image Settings", help="Output height in pixels")] = None,
    quality: Annotated[int, Parameter(group="Image Settings", help="Output quality (1-100)")] = 90,
    # Processing Options Group
    grayscale: Annotated[bool, Parameter(group="Processing Options", help="Convert to grayscale")] = False,
    rotate: Annotated[int, Parameter(group="Processing Options", help="Rotation angle (degrees)")] = 0,
    flip: Annotated[
        Literal["none", "horizontal", "vertical", "both"],
        Parameter(group="Processing Options", help="Flip direction"),
    ] = "none",
    # Format Options Group
    format: Annotated[
        Literal["auto", "jpeg", "png", "webp"],
        Parameter(group="Format Options", help="Output format"),
    ] = "auto",
    progressive: Annotated[bool, Parameter(group="Format Options", help="Use progressive encoding")] = False,
):
    """Convert and transform images.

    This command demonstrates parameter grouping for better organization of related options.

    Parameters
    ----------
    input_file : Path
        Path to the input image file.
    output_file : Path
        Path where the converted image will be saved.
    width : int, optional
        Desired output width in pixels. Maintains aspect ratio if height not specified.
    height : int, optional
        Desired output height in pixels. Maintains aspect ratio if width not specified.
    quality : int
        JPEG/WebP quality level from 1 (lowest) to 100 (highest).
    grayscale : bool
        Convert the image to grayscale.
    rotate : int
        Rotation angle in degrees (clockwise).
    flip : Literal["none", "horizontal", "vertical", "both"]
        Flip the image horizontally, vertically, both, or none.
    format : Literal["auto", "jpeg", "png", "webp"]
        Output format. Auto-detects from file extension if set to 'auto'.
    progressive : bool
        Use progressive/interlaced encoding for web optimization.
    """
    print(
        f"Image Convert: {input_file} -> {output_file}, "
        f"size={width}x{height}, quality={quality}, "
        f"grayscale={grayscale}, rotate={rotate}, flip={flip}, "
        f"format={format}, progressive={progressive}"
    )


database_app = App(name="database", help="Database commands.")
app.command(database_app)


@database_app.command
def connect(
    host: str = "localhost",
    port: int = 5432,
    username: str = "admin",
    password: str | None = None,
    ssl: bool = True,
):
    """Connect to database.

    Parameters
    ----------
    host : str
        Database host.
    port : int
        Database port.
    username : str
        Username.
    password : str, optional
        Password.
    ssl : bool
        Use SSL connection.
    """
    print(f"DB Connect: host={host}, port={port}, user={username}, ssl={ssl}")


@database_app.command
def migrate(
    direction: Literal["up", "down"] = "up",
    steps: int = 1,
    target: str | None = None,
    force: bool = False,
):
    """Run database migrations.

    Parameters
    ----------
    direction : Literal["up", "down"]
        Migration direction.
    steps : int
        Number of migration steps.
    target : str, optional
        Target migration version.
    force : bool
        Force migration even with warnings.
    """
    print(f"DB Migrate: direction={direction}, steps={steps}, target={target}, force={force}")


@database_app.command
def backup(
    output: Path,
    tables: list[str] | None = None,
    compress: bool = True,
    encryption: Literal["none", "aes256", "rsa"] = "none",
):
    """Backup database.

    Parameters
    ----------
    output : Path
        Backup output file.
    tables : list[str], optional
        Specific tables to backup.
    compress : bool
        Compress backup.
    encryption : Literal["none", "aes256", "rsa"]
        Encryption method.
    """
    print(f"DB Backup: output={output}, tables={tables}, compress={compress}, encryption={encryption}")


server_app = App(name="server", help="Server management commands.")
app.command(server_app)


@server_app.command
def start(
    port: int = 8000,
    host: str = "0.0.0.0",
    workers: int = 4,
    reload: bool = False,
    log_level: Literal["debug", "info", "warning", "error", "critical"] = "info",
):
    """Start the server.

    Parameters
    ----------
    port : int
        Server port.
    host : str
        Server host.
    workers : int
        Number of worker processes.
    reload : bool
        Enable auto-reload.
    log_level : Literal["debug", "info", "warning", "error", "critical"]
        Logging level.
    """
    print(f"Server Start: host={host}, port={port}, workers={workers}, reload={reload}, log_level={log_level}")


@server_app.command
def stop(
    graceful: bool = True,
    timeout: int = 30,
):
    """Stop the server.

    Parameters
    ----------
    graceful : bool
        Graceful shutdown.
    timeout : int
        Shutdown timeout in seconds.
    """
    print(f"Server Stop: graceful={graceful}, timeout={timeout}")


@server_app.command
def status(
    detailed: bool = False,
    format: Literal["text", "json", "table"] = "text",
):
    """Show server status.

    Parameters
    ----------
    detailed : bool
        Show detailed status.
    format : Literal["text", "json", "table"]
        Output format.
    """
    print(f"Server Status: detailed={detailed}, format={format}")


# Register with colon in name - this is the key test case for issue #715
@app.command(name="utility:ping")
def utility_ping_action(
    host: Annotated[str, Parameter(help="Host to ping")] = "localhost",
    port: Annotated[int, Parameter(help="Port number")] = 8080,
    timeout: Annotated[int, Parameter(help="Timeout in seconds")] = 5,
    count: Annotated[int, Parameter(help="Number of pings")] = 3,
):
    """Ping a utility service to check connectivity.

    Parameters
    ----------
    host : str
        Hostname or IP address of the service.
    port : int
        Port number to connect to.
    timeout : int
        Connection timeout in seconds.
    count : int
        Number of ping attempts.
    """
    print(f"Pinging utility at {host}:{port} (timeout={timeout}s, count={count})")


@app.command(name="utility:status")
def utility_status_action(
    foo: Literal["beep", "boop"],  # for testing if positional completion still works.
    /,
    service: Annotated[str, Parameter(help="Service name")] = "all",
    verbose: Annotated[bool, Parameter(help="Show detailed status")] = False,
    format: Annotated[Literal["text", "json", "yaml"], Parameter(help="Output format")] = "text",
):
    """Check the status of utility services.

    Parameters
    ----------
    service : str
        Name of the service to check, or 'all' for all services.
    verbose : bool
        Show detailed status information.
    format : Literal["text", "json", "yaml"]
        Output format for the status report.
    """
    print(f"Utility status: service={service}, verbose={verbose}, format={format}")


@app.command
def dump_markdown():
    """Generate markdown documentation and save to intermediate.md.

    This is a debug command to inspect the intermediate markdown output
    that gets generated for the cyclopts-demo app.
    """
    from cyclopts.docs.markdown import generate_markdown_docs

    # Generate markdown for this app
    markdown = generate_markdown_docs(
        app,
        recursive=True,
        heading_level=1,
        generate_toc=True,
    )

    # Write to intermediate.md in current directory
    with Path("intermediate.md").open("w") as f:
        f.write(markdown)

    print("✓ Markdown documentation written to intermediate.md")
    print(f"  ({len(markdown)} characters, {len(markdown.splitlines())} lines)")


if __name__ == "__main__":
    app()
