from __future__ import annotations

import json
from dataclasses import asdict
from pathlib import Path
from typing import Optional

import click

from .client import Client
from .exceptions import LiteFoldError

_ENV_CHOICES = click.Choice(["accelerated_cpu", "accelerated_gpu"], case_sensitive=False)


def _get_client(ctx: click.Context) -> Client:
    if "client" not in ctx.obj:
        api_key = ctx.obj.get("api_key")
        if not api_key:
            raise click.UsageError("Missing --api-key or LITEFOLD_API_KEY env var.")
        ctx.obj["client"] = Client(api_key=api_key, base_url=ctx.obj.get("base_url", "https://agentsapi.litefold.ai"))
    return ctx.obj["client"]


def _echo_json(obj):
    click.echo(json.dumps(obj, indent=2, default=str))


def _echo_table(rows: list[dict], columns: Optional[list[str]] = None):
    if not rows:
        click.echo("(no results)")
        return
    columns = columns or list(rows[0].keys())
    widths = {c: max(len(c), *(len(str(r.get(c, ""))) for r in rows)) for c in columns}
    header = "  ".join(c.ljust(widths[c]) for c in columns)
    click.echo(header)
    click.echo("  ".join("-" * widths[c] for c in columns))
    for r in rows:
        click.echo("  ".join(str(r.get(c, "")).ljust(widths[c]) for c in columns))


def _output(ctx: click.Context, data, columns: Optional[list[str]] = None):
    if ctx.obj.get("json_output"):
        _echo_json(data if isinstance(data, (list, dict)) else asdict(data) if hasattr(data, "__dataclass_fields__") else data)
    else:
        if isinstance(data, list):
            rows = [asdict(d) if hasattr(d, "__dataclass_fields__") else d for d in data]
            _echo_table(rows, columns)
        elif hasattr(data, "__dataclass_fields__"):
            for k, v in asdict(data).items():
                click.echo(f"{k}: {v}")
        else:
            click.echo(data)


# ── Root ─────────────────────────────────────────────────────────

@click.group()
@click.option("--api-key", envvar="LITEFOLD_API_KEY", default=None, help="API key (or set LITEFOLD_API_KEY).")
@click.option("--base-url", envvar="LITEFOLD_BASE_URL", default="https://agentsapi.litefold.ai", help="API base URL.")
@click.option("--json", "json_output", is_flag=True, default=False, help="Output as JSON.")
@click.pass_context
def cli(ctx, api_key: Optional[str], base_url: str, json_output: bool):
    ctx.ensure_object(dict)
    ctx.obj["json_output"] = json_output
    ctx.obj["api_key"] = api_key
    ctx.obj["base_url"] = base_url


# ── Projects ─────────────────────────────────────────────────────

@cli.group()
def projects():
    """Manage projects."""


@projects.command("list")
@click.option("--environment", "-e", default="railway", help="Filter by environment (railway/modal).")
@click.pass_context
def projects_list(ctx, environment: str):
    """List all projects."""
    result = _get_client(ctx).projects.list(environment=environment)
    _output(ctx, result.projects, ["job_name", "description", "created_at"])


@projects.command("get")
@click.argument("name")
@click.pass_context
def projects_get(ctx, name: str):
    """Get project details."""
    result = _get_client(ctx).projects.get(name)
    _output(ctx, result)


@projects.command("create")
@click.argument("name")
@click.option("--description", "-d", default=None, help="Project description.")
@click.pass_context
def projects_create(ctx, name: str, description: Optional[str]):
    """Create a new project."""
    result = _get_client(ctx).projects.create(name, description)
    _output(ctx, result)


# ── Files ────────────────────────────────────────────────────────

@cli.group()
def files():
    """Manage files (upload, list, download)."""


@files.command("upload")
@click.argument("paths", nargs=-1, required=True, type=click.Path(exists=True))
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--folder", "-f", default="upload", help="Destination folder.")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def files_upload(ctx, paths: tuple[str, ...], project: str, folder: str, environment: Optional[str]):
    """Upload one or more files."""
    result = _get_client(ctx).files.upload(project, [Path(p) for p in paths], folder, environment=environment)
    _output(ctx, result)


@files.command("list")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--folder", "-f", default="upload", help="Folder to list.")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def files_list(ctx, project: str, folder: str, environment: Optional[str]):
    """List files in a folder."""
    result = _get_client(ctx).files.list(project, folder, environment=environment)
    _output(ctx, result, ["file_name", "folder", "format_type", "created_at"])


@files.command("list-all")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def files_list_all(ctx, project: str, environment: Optional[str]):
    """List all files across all folders."""
    result = _get_client(ctx).files.list_all(project, environment=environment)
    _output(ctx, result, ["file_name", "folder", "format_type", "created_at"])


@files.command("list-by-format")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--format", "format_type", required=True, help="Format type (e.g. pdb, sdf, csv).")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def files_list_by_format(ctx, project: str, format_type: str, environment: Optional[str]):
    """List files filtered by format."""
    result = _get_client(ctx).files.list_by_format(project, format_type, environment=environment)
    _output(ctx, result, ["file_name", "folder", "format_type", "created_at"])


@files.command("get-content")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--file-name", required=True, help="File name.")
@click.option("--folder", "-f", default="upload", help="Folder.")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def files_get_content(ctx, project: str, file_name: str, folder: str, environment: Optional[str]):
    """Get text content of a file."""
    content = _get_client(ctx).files.get_content(project, file_name, folder, environment=environment)
    if ctx.obj.get("json_output"):
        _echo_json({"content": content})
    else:
        click.echo(content)


@files.command("download")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--file-name", required=True, help="File name.")
@click.option("--dest", "-o", default=".", type=click.Path(), help="Destination path (file or directory).")
@click.option("--folder", "-f", default="upload", help="Folder.")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def files_download(ctx, project: str, file_name: str, dest: str, folder: str, environment: Optional[str]):
    """Download a file to local disk."""
    saved = _get_client(ctx).files.download_to(project, file_name, Path(dest), folder, environment=environment)
    if ctx.obj.get("json_output"):
        _echo_json({"saved_to": str(saved)})
    else:
        click.echo(f"Saved to {saved}")


@files.command("get-metadata")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--file-name", required=True, help="File name.")
@click.option("--folder", "-f", default="upload", help="Folder.")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def files_get_metadata(ctx, project: str, file_name: str, folder: str, environment: Optional[str]):
    """Get file metadata."""
    result = _get_client(ctx).files.get_metadata(project, file_name, folder, environment=environment)
    _output(ctx, result)


@files.command("update-metadata")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--file-name", required=True, help="File name.")
@click.option("--metadata", "-m", required=True, help="Metadata as JSON string.")
@click.option("--folder", "-f", default="upload", help="Folder.")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def files_update_metadata(ctx, project: str, file_name: str, metadata: str, folder: str, environment: Optional[str]):
    """Update file metadata."""
    meta = json.loads(metadata)
    result = _get_client(ctx).files.update_metadata(project, file_name, meta, folder, environment=environment)
    if ctx.obj.get("json_output"):
        _echo_json(result)
    else:
        click.echo("Updated.")


# ── Dynamo (Molecular Dynamics) ───────────────────────────────────

@cli.group()
def dynamo():
    """Molecular dynamics simulations (Dynamo)."""


@dynamo.command("create-experiment")
@click.option("--job-name", "-j", required=True, help="Project name.")
@click.option("--experiment-name", "-n", required=True, help="Experiment name.")
@click.option("--description", "-d", default=None, help="Experiment description.")
@click.pass_context
def dynamo_create_experiment(ctx, job_name: str, experiment_name: str, description: Optional[str]):
    """Create a new MD experiment."""
    result = _get_client(ctx).dynamo.create_experiment(job_name, experiment_name, description)
    _output(ctx, result)


@dynamo.command("list-experiments")
@click.option("--job-name", "-j", default=None, help="Filter by project name.")
@click.pass_context
def dynamo_list_experiments(ctx, job_name: Optional[str]):
    """List MD experiments."""
    result = _get_client(ctx).dynamo.list_experiments(job_name)
    _output(ctx, result, ["experiment_id", "experiment_name", "job_name", "simulation_count", "latest_status", "created_at"])


@dynamo.command("get-experiment")
@click.argument("experiment_id")
@click.pass_context
def dynamo_get_experiment(ctx, experiment_id: str):
    """Get experiment details (with simulations)."""
    result = _get_client(ctx).dynamo.get_experiment(experiment_id)
    _output(ctx, result)


@dynamo.command("update-experiment")
@click.argument("experiment_id")
@click.option("--experiment-name", "-n", default=None, help="New experiment name.")
@click.option("--description", "-d", default=None, help="New description.")
@click.pass_context
def dynamo_update_experiment(ctx, experiment_id: str, experiment_name: Optional[str], description: Optional[str]):
    """Update experiment name or description."""
    result = _get_client(ctx).dynamo.update_experiment(experiment_id, experiment_name, description)
    _output(ctx, result)


@dynamo.command("start")
@click.option("--job-name", "-j", required=True, help="Project name.")
@click.option("--experiment-name", "-n", required=True, help="Experiment name.")
@click.option("--protein-file", "-p", required=True, help="Protein PDB file name (already uploaded).")
@click.option("--sim-type", default="md", type=click.Choice(["md", "openbpmd"]), help="Simulation type.")
@click.option("--total-time", default=10.0, type=float, help="Total simulation time.")
@click.option("--total-time-unit", default="nanosecond", help="Time unit (nanosecond/picosecond).")
@click.option("--temperature", default=300.0, type=float, help="Temperature in Kelvin.")
@click.option("--water-model", default="tip3p", help="Water model.")
@click.option("--has-membrane", is_flag=True, default=False, help="Include membrane.")
@click.option("--resume-from", default=None, type=int, help="MD ID to resume from.")
@click.pass_context
def dynamo_start(
    ctx, job_name: str, experiment_name: str, protein_file: str,
    sim_type: str, total_time: float, total_time_unit: str,
    temperature: float, water_model: str, has_membrane: bool,
    resume_from: Optional[int],
):
    """Start a molecular dynamics simulation."""
    result = _get_client(ctx).dynamo.start_simulation(
        job_name=job_name,
        experiment_name=experiment_name,
        protein_file_name=protein_file,
        sim_type=sim_type,
        total_time=total_time,
        total_time_unit=total_time_unit,
        temperature=temperature,
        water_model=water_model,
        has_membrane=has_membrane,
        resume_from_md_id=resume_from,
    )
    _output(ctx, result)


@dynamo.command("restart")
@click.argument("md_id", type=int)
@click.pass_context
def dynamo_restart(ctx, md_id: int):
    """Restart a simulation from scratch."""
    result = _get_client(ctx).dynamo.restart_simulation(md_id)
    _output(ctx, result)


@dynamo.command("continue")
@click.argument("md_id", type=int)
@click.option("--total-time", default=None, type=float, help="Additional simulation time.")
@click.option("--total-time-unit", default="nanosecond", help="Time unit.")
@click.pass_context
def dynamo_continue(ctx, md_id: int, total_time: Optional[float], total_time_unit: str):
    """Continue a completed simulation for more time."""
    result = _get_client(ctx).dynamo.continue_simulation(md_id, total_time, total_time_unit)
    _output(ctx, result)


@dynamo.command("reanalyze")
@click.argument("md_id", type=int)
@click.pass_context
def dynamo_reanalyze(ctx, md_id: int):
    """Re-run analysis on an existing simulation trajectory."""
    result = _get_client(ctx).dynamo.reanalyze_simulation(md_id)
    _output(ctx, result)


@dynamo.command("cancel")
@click.argument("md_id", type=int)
@click.pass_context
def dynamo_cancel(ctx, md_id: int):
    """Cancel a running simulation."""
    result = _get_client(ctx).dynamo.cancel_simulation(md_id)
    _output(ctx, result)


@dynamo.command("status")
@click.argument("experiment_id")
@click.pass_context
def dynamo_status(ctx, experiment_id: str):
    """Get latest simulation status for an experiment."""
    result = _get_client(ctx).dynamo.get_status(experiment_id)
    _output(ctx, result)


@dynamo.command("list-simulations")
@click.argument("experiment_id")
@click.pass_context
def dynamo_list_simulations(ctx, experiment_id: str):
    """List all simulation runs for an experiment."""
    result = _get_client(ctx).dynamo.list_simulations(experiment_id)
    _output(ctx, result, ["md_id", "run_number", "status", "sim_type", "simulation_ns", "progress_percent", "created_at"])


@dynamo.command("results")
@click.argument("experiment_id")
@click.pass_context
def dynamo_results(ctx, experiment_id: str):
    """Get analysis results (RMSD, RMSF, energies, etc.)."""
    result = _get_client(ctx).dynamo.get_results(experiment_id)
    _output(ctx, result)


@dynamo.command("topology")
@click.argument("experiment_id")
@click.pass_context
def dynamo_topology(ctx, experiment_id: str):
    """Get the topology PDB content."""
    result = _get_client(ctx).dynamo.get_topology(experiment_id)
    if not ctx.obj.get("json_output") and result.content:
        click.echo(result.content)
    else:
        _output(ctx, result)


@dynamo.command("resource")
@click.argument("experiment_id")
@click.option("--filename", "-f", required=True, help="Resource filename to retrieve.")
@click.pass_context
def dynamo_resource(ctx, experiment_id: str, filename: str):
    """Get a specific resource file from a simulation."""
    result = _get_client(ctx).dynamo.get_resource(experiment_id, filename)
    if not ctx.obj.get("json_output") and result.content:
        click.echo(result.content)
    else:
        _output(ctx, result)


# ── Rosalind ─────────────────────────────────────────────────────

@cli.group()
def rosalind():
    """Interact with Rosalind agent chats."""


@rosalind.command("create-chat")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.option("--description", "-d", default="", help="Chat description.")
@click.option("--environment", "-e", type=_ENV_CHOICES, default=None, help="Environment (accelerated_cpu/accelerated_gpu).")
@click.pass_context
def rosalind_create_chat(ctx, project: str, chat_name: str, description: str, environment: Optional[str]):
    """Create a new chat session."""
    name = _get_client(ctx).rosalind.create_chat(project, chat_name, description, environment=environment)
    if ctx.obj.get("json_output"):
        _echo_json({"chat_name": name})
    else:
        click.echo(f"Chat created: {name}")


@rosalind.command("send-message")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.option("--message", "-m", required=True, help="Message to send.")
@click.option("--mode", default="pro", type=click.Choice(["pro", "max"], case_sensitive=False), help="Model mode (pro/max).")
@click.option("--depth", default="balanced", type=click.Choice(["balanced", "ultra"], case_sensitive=False), help="Depth (balanced/ultra).")
@click.option("--force-fresh-session", is_flag=True, default=False, help="Force a fresh session.")
@click.option("--callback-url", default=None, envvar="LITEFOLD_CALLBACK_URL", help="URL to POST completion webhook to (or set LITEFOLD_CALLBACK_URL).")
@click.option("--callback-token", default=None, envvar="LITEFOLD_CALLBACK_TOKEN", help="Bearer token for the webhook (or set LITEFOLD_CALLBACK_TOKEN).")
@click.pass_context
def rosalind_send_message(ctx, project: str, chat_name: str, message: str, mode: str, depth: str, force_fresh_session: bool, callback_url: Optional[str], callback_token: Optional[str]):
    """Send a message to a chat (fire-and-forget)."""
    callback_headers = None
    if callback_token:
        callback_headers = {"Authorization": f"Bearer {callback_token}"}
    success = _get_client(ctx).rosalind.send_message(
        project, chat_name, message, mode, depth,
        force_fresh_session=force_fresh_session,
        callback_url=callback_url,
        callback_headers=callback_headers,
    )
    if ctx.obj.get("json_output"):
        _echo_json({"success": success})
    else:
        click.echo("Message sent." if success else "Send failed.")


@rosalind.command("cancel-chat")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.pass_context
def rosalind_cancel_chat(ctx, project: str, chat_name: str):
    """Cancel a running chat task."""
    success = _get_client(ctx).rosalind.cancel_chat(project, chat_name)
    if ctx.obj.get("json_output"):
        _echo_json({"success": success})
    else:
        click.echo("Cancelled." if success else "Nothing to cancel.")


@rosalind.command("list-chats")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--limit", "-l", default=None, type=int, help="Max chats to show.")
@click.pass_context
def rosalind_list_chats(ctx, project: str, limit: Optional[int]):
    """List chats for a project."""
    result = _get_client(ctx).rosalind.list_chats(project, limit)
    _output(ctx, result, ["chat_name", "device", "total_input_tokens", "total_output_tokens", "total_cost_usd"])


@rosalind.command("chat-status")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.pass_context
def rosalind_chat_status(ctx, project: str, chat_name: str):
    """Check if a chat's agent is still running."""
    result = _get_client(ctx).rosalind.chat_status(project, chat_name)
    _output(ctx, result)


@rosalind.command("list-blocks")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.pass_context
def rosalind_list_blocks(ctx, project: str, chat_name: str):
    """Show total number of blocks in a chat."""
    total = _get_client(ctx).rosalind.list_blocks(project, chat_name)
    if ctx.obj.get("json_output"):
        _echo_json({"total": total})
    else:
        click.echo(f"Total blocks: {total}")


@rosalind.command("get-blocks")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.option("--start", default=0, type=int, help="Start index.")
@click.option("--end", default=None, type=int, help="End index.")
@click.pass_context
def rosalind_get_blocks(ctx, project: str, chat_name: str, start: int, end: Optional[int]):
    """Get blocks from chat history."""
    blocks = _get_client(ctx).rosalind.get_blocks(project, chat_name, start, end)
    if ctx.obj.get("json_output"):
        _echo_json([asdict(b) for b in blocks])
    else:
        for b in blocks:
            if b.assistant_message:
                click.secho(f"\n[block {b.index}] assistant:", fg="green", bold=True)
                click.echo(b.assistant_message)
            elif b.content_type == "thinking":
                click.secho(f"\n[block {b.index}] thinking:", fg="yellow", dim=True)
                click.echo(b.text)
            elif b.content_type == "tool_use":
                click.secho(f"[block {b.index}] tool_use: {b.tool_name}", fg="cyan")
            elif b.content_type == "mcp_result":
                label = "output" if b.is_output_panel else "result"
                click.secho(f"[block {b.index}] {label}: {b.tool_name}", fg="blue")
                click.echo(str(b.tool_result)[:500] if b.tool_result else "")
            elif b.content_type == "user_message":
                click.secho(f"\n[block {b.index}] user:", fg="magenta", bold=True)
                click.echo(b.text or "")


@rosalind.command("get-bash-outputs")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.option("--start", default=0, type=int, help="Start index.")
@click.option("--end", default=None, type=int, help="End index.")
@click.pass_context
def rosalind_get_bash_outputs(ctx, project: str, chat_name: str, start: int, end: Optional[int]):
    """Extract bash command inputs/outputs from chat."""
    results = _get_client(ctx).rosalind.get_bash_outputs(project, chat_name, start, end)
    if ctx.obj.get("json_output"):
        _echo_json([asdict(b) for b in results])
    else:
        if not results:
            click.echo("No bash outputs.")
            return
        for b in results:
            click.secho(f"$ {b.command}", fg="green")
            if b.exit_code is not None:
                click.echo(f"  exit_code: {b.exit_code}")
            if b.output:
                click.echo(b.output)
            click.echo()


# ── Workspace (sub-group of rosalind) ────────────────────────────

@rosalind.group("workspace")
def rosalind_workspace():
    """Browse workspace files for a chat."""


@rosalind_workspace.command("list-folders")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.pass_context
def workspace_list_folders(ctx, project: str, chat_name: str):
    """List top-level folders in the workspace."""
    ws = _get_client(ctx).rosalind.workspace(project, chat_name)
    folders = ws.list_folders()
    if ctx.obj.get("json_output"):
        _echo_json(folders)
    else:
        for f in folders:
            click.echo(f"  {f}/")
        if not folders:
            click.echo("(no folders)")


@rosalind_workspace.command("list-files")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.option("--folder", "-f", default=None, help="Filter by folder.")
@click.pass_context
def workspace_list_files(ctx, project: str, chat_name: str, folder: Optional[str]):
    """List files in the workspace."""
    ws = _get_client(ctx).rosalind.workspace(project, chat_name)
    result = ws.list_files(folder)
    _output(ctx, result, ["name", "location", "folder", "size"])


@rosalind_workspace.command("get-file-content")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.option("--file-path", required=True, help="File path in workspace.")
@click.pass_context
def workspace_get_file_content(ctx, project: str, chat_name: str, file_path: str):
    """Get the content of a text-viewable workspace file."""
    from .namespaces.workspace import is_text_viewable

    if not is_text_viewable(file_path):
        click.secho(
            f"File '{file_path}' is not a text-viewable format.\n"
            f"Use `litefold rosalind workspace download-file-content` instead.",
            fg="yellow", err=True,
        )
        raise SystemExit(1)

    try:
        ws = _get_client(ctx).rosalind.workspace(project, chat_name)
        fc = ws.get_file_content(file_path)
    except LiteFoldError as e:
        click.secho(f"Error fetching file content: {e}", fg="red", err=True)
        raise SystemExit(1)

    if ctx.obj.get("json_output"):
        _echo_json(asdict(fc))
    else:
        click.echo(fc.content)


@rosalind_workspace.command("download-file-content")
@click.option("--project", "-p", required=True, help="Project name.")
@click.option("--chat-name", "-n", required=True, help="Chat name.")
@click.option("--file-path", required=True, help="File path in workspace.")
@click.option("--save-as", required=True, help="Local path to save the file to.")
@click.pass_context
def workspace_download_file_content(
    ctx, project: str, chat_name: str, file_path: str, save_as: str
):
    """Download a workspace file to a local path."""
    try:
        ws = _get_client(ctx).rosalind.workspace(project, chat_name)
        dest = ws.download_file_content(file_path, save_as)
        click.secho(f"Downloaded to {dest}", fg="green")
    except LiteFoldError as e:
        click.secho(f"Error downloading file: {e}", fg="red", err=True)
        raise SystemExit(1)


# ── Entrypoint ───────────────────────────────────────────────────

def main():
    try:
        cli(standalone_mode=False)
    except LiteFoldError as e:
        click.secho(f"Error: {e}", fg="red", err=True)
        raise SystemExit(1)
    except click.exceptions.Exit:
        pass


if __name__ == "__main__":
    main()
