"""Tableau CLI commands for g-gremlin."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any, Dict, List, Optional

import click

from g_gremlin.credentials.provider import CredentialProvider
from g_gremlin.tableau.client import TableauClient
from g_gremlin.tableau.hyper_helpers import create_hyper_from_csv, export_hyper_to_csv, inspect_hyper
from g_gremlin.tableau.metadata_query import METADATA_DOCTOR_QUERY
from g_gremlin.tableau.template_cli import template as template_cmd


def _emit_json(payload: Any) -> None:
    click.echo(json.dumps(payload, indent=2))


def _handle_client_error(exc: Exception) -> None:
    raise click.ClickException(str(exc))


def _get_tableau_client() -> TableauClient:
    return TableauClient()


def _parse_params(params: List[str]) -> Dict[str, str]:
    parsed: Dict[str, str] = {}
    for item in params:
        if "=" not in item:
            raise click.ClickException(f"Invalid param '{item}'. Use key=value")
        key, value = item.split("=", 1)
        parsed[key] = value
    return parsed


def _parse_intervals(interval_args: List[str]) -> List[Dict[str, str]]:
    intervals: List[Dict[str, str]] = []
    for item in interval_args:
        if "=" not in item:
            raise click.ClickException(f"Invalid interval '{item}'. Use key=value")
        key, value = item.split("=", 1)
        key = key.strip()
        value = value.strip()
        if not key or not value:
            raise click.ClickException(f"Invalid interval '{item}'. Use key=value")
        intervals.append({key: value})
    return intervals


@click.group()
def tableau() -> None:
    """Tableau Cloud helpers."""


@tableau.command("doctor")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def tableau_doctor(json_output: bool) -> None:
    """Health check (auth, list projects, metadata smoke test)."""
    client = _get_tableau_client()
    report: Dict[str, Any] = {"status": "ready", "auth": "ok", "projects": None, "metadata_api": "unknown"}

    try:
        client.sign_in()
    except Exception as exc:
        report["status"] = "error"
        report["auth"] = "error"
        report["error"] = str(exc)
        if json_output:
            _emit_json(report)
            return
        raise click.ClickException(str(exc))

    try:
        projects = client.list_projects()
        report["projects"] = len(projects)
    except Exception as exc:
        report["status"] = "warn"
        report["projects"] = "error"
        report["projects_error"] = str(exc)

    try:
        client.query_metadata(METADATA_DOCTOR_QUERY)
        report["metadata_api"] = "available"
    except Exception as exc:
        report["status"] = "warn"
        report["metadata_api"] = "unavailable"
        report["metadata_error"] = str(exc)

    if json_output:
        _emit_json(report)
    else:
        click.echo(f"Status: {report['status']}")
        click.echo(f"Auth: {report['auth']}")
        click.echo(f"Projects: {report['projects']}")
        click.echo(f"Metadata API: {report['metadata_api']}")


@tableau.group()
def auth() -> None:
    """Tableau auth management."""


@auth.command("connect")
@click.option("--site-url", prompt=True, help="Tableau server base URL (pod URL).")
@click.option("--site-name", default="", prompt="Site content URL (blank for Default)")
@click.option("--token-name", prompt=True, help="Personal Access Token name.")
@click.option("--access-token", prompt=True, hide_input=True, help="Personal Access Token secret.")
@click.option("--api-version", default=None, help="Tableau REST API version (optional).")
def auth_connect(
    site_url: str,
    site_name: str,
    token_name: str,
    access_token: str,
    api_version: Optional[str],
) -> None:
    """Interactive PAT setup."""
    provider = CredentialProvider()

    if not provider.has_keychain_backend():
        raise click.ClickException(
            "No keychain backend available. Keyring is installed but no OS credential store was detected."
        )

    client = TableauClient(
        site_url=site_url,
        site_name=site_name,
        token_name=token_name,
        access_token=access_token,
        api_version=api_version,
    )

    try:
        client.sign_in()
    except Exception as exc:
        _handle_client_error(exc)

    provider.set_secret("tableau", "site_url", site_url.strip())
    provider.set_secret("tableau", "access_token", access_token.strip())
    provider.set_secret("tableau", "token_name", token_name.strip())
    if site_name.strip():
        provider.set_secret("tableau", "site_name", site_name.strip())
    if api_version:
        provider.set_secret("tableau", "api_version", api_version.strip())

    click.echo("Tableau credentials stored in keychain.")


@auth.command("test")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def auth_test(json_output: bool) -> None:
    """Validate stored Tableau credentials."""
    client = _get_tableau_client()
    try:
        info = client.sign_in()
    except Exception as exc:
        _handle_client_error(exc)

    payload = {
        "status": "ok",
        "site_id": info.get("site_id"),
        "user_id": info.get("user_id"),
        "server": client.site_url,
        "site": client.site_name or "Default",
    }

    if json_output:
        _emit_json(payload)
    else:
        click.echo(f"Auth OK. Site: {payload['site']}  Server: {payload['server']}")


@auth.command("disconnect")
def auth_disconnect() -> None:
    """Remove stored Tableau credentials."""
    provider = CredentialProvider()
    removed = False
    for key in ["site_url", "access_token", "token_name", "site_name", "api_version"]:
        if provider.delete_secret("tableau", key):
            removed = True
            click.echo(f"Removed tableau.{key}")
    if not removed:
        click.echo("No Tableau secrets found")


@tableau.group()
def jobs() -> None:
    """Job inspection helpers."""


@jobs.command("get")
@click.argument("job_id")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def jobs_get(job_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        job = client.get_job(job_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(job)
    else:
        click.echo(json.dumps(job, indent=2))


@jobs.command("wait")
@click.argument("job_id")
@click.option("--timeout", default=600, show_default=True, help="Timeout in seconds.")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def jobs_wait(job_id: str, timeout: int, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        result = client.wait_for_job(job_id, timeout=timeout)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@tableau.group()
def workbooks() -> None:
    """Workbook operations."""


@workbooks.command("list")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def workbooks_list(json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        workbooks_list = client.list_workbooks()
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(workbooks_list)
    else:
        for wb in workbooks_list:
            click.echo(f"{wb.get('id')}\t{wb.get('name')}")


@workbooks.command("get")
@click.argument("workbook_id")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def workbooks_get(workbook_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        workbook = client.get_workbook(workbook_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(workbook)
    else:
        click.echo(json.dumps(workbook, indent=2))


@workbooks.command("download")
@click.argument("workbook_id")
@click.option("--output", type=click.Path(path_type=Path), help="Output path for .twb/.twbx")
def workbooks_download(workbook_id: str, output: Optional[Path]) -> None:
    client = _get_tableau_client()
    output_path = output or Path(f"{workbook_id}.twbx")
    try:
        client.download_workbook(workbook_id, output_path)
    except Exception as exc:
        _handle_client_error(exc)
    click.echo(f"Saved to {output_path}")


@workbooks.command("publish")
@click.argument("file_path", type=click.Path(exists=True, path_type=Path))
@click.option("--project", "project_id", required=True, help="Target project ID.")
@click.option("--name", help="Workbook name (defaults to file stem).")
@click.option(
    "--publish-mode",
    default="create",
    show_default=True,
    type=click.Choice(["create", "overwrite"], case_sensitive=False),
)
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def workbooks_publish(
    file_path: Path,
    project_id: str,
    name: Optional[str],
    publish_mode: str,
    json_output: bool,
) -> None:
    client = _get_tableau_client()
    try:
        result = client.publish_workbook(file_path, project_id=project_id, name=name, publish_mode=publish_mode)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@workbooks.command("refresh")
@click.argument("workbook_id")
@click.option("--wait", is_flag=True, help="Wait for job completion.")
@click.option("--timeout", default=600, show_default=True, help="Timeout in seconds.")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def workbooks_refresh(workbook_id: str, wait: bool, timeout: int, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        result = client.refresh_workbook(workbook_id, wait=wait, timeout=timeout)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@tableau.group()
def datasources() -> None:
    """Datasource operations."""


@datasources.command("list")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def datasources_list(json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        datasources_list = client.list_datasources()
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(datasources_list)
    else:
        for ds in datasources_list:
            click.echo(f"{ds.get('id')}\t{ds.get('name')}")


@datasources.command("get")
@click.argument("datasource_id")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def datasources_get(datasource_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        datasource = client.get_datasource(datasource_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(datasource)
    else:
        click.echo(json.dumps(datasource, indent=2))


@datasources.command("publish")
@click.argument("file_path", type=click.Path(exists=True, path_type=Path))
@click.option("--project", "project_id", required=True, help="Target project ID.")
@click.option("--name", help="Datasource name (defaults to file stem).")
@click.option(
    "--publish-mode",
    default="create",
    show_default=True,
    type=click.Choice(["create", "overwrite"], case_sensitive=False),
)
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def datasources_publish(
    file_path: Path,
    project_id: str,
    name: Optional[str],
    publish_mode: str,
    json_output: bool,
) -> None:
    client = _get_tableau_client()
    try:
        result = client.publish_datasource(file_path, project_id=project_id, name=name, publish_mode=publish_mode)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@datasources.command("refresh")
@click.argument("datasource_id")
@click.option("--wait", is_flag=True, help="Wait for job completion.")
@click.option("--timeout", default=600, show_default=True, help="Timeout in seconds.")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def datasources_refresh(datasource_id: str, wait: bool, timeout: int, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        result = client.refresh_datasource(datasource_id, wait=wait, timeout=timeout)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@datasources.command("download")
@click.argument("datasource_id")
@click.option("--output", type=click.Path(path_type=Path), help="Output path for .hyper/.tds")
def datasources_download(datasource_id: str, output: Optional[Path]) -> None:
    client = _get_tableau_client()
    output_path = output or Path(f"{datasource_id}.hyper")
    try:
        client.download_datasource(datasource_id, output_path)
    except Exception as exc:
        _handle_client_error(exc)
    click.echo(f"Saved to {output_path}")


@tableau.group()
def schedules() -> None:
    """Extract refresh schedule operations."""


@schedules.command("list")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def schedules_list(json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        task_list = client.list_extract_refresh_tasks()
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(task_list)
    else:
        for task in task_list:
            click.echo(f"{task.get('id')}\t{task.get('type')}\t{task.get('target_type')}:{task.get('target_id')}")


@schedules.command("get")
@click.argument("task_id")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def schedules_get(task_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        task = client.get_extract_refresh_task(task_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(task)
    else:
        click.echo(json.dumps(task, indent=2))


@schedules.command("create")
@click.option("--datasource-id", help="Datasource ID to schedule.")
@click.option("--workbook-id", help="Workbook ID to schedule.")
@click.option(
    "--type",
    "refresh_type",
    default="FullRefresh",
    show_default=True,
    type=click.Choice(["FullRefresh", "IncrementalRefresh"], case_sensitive=False),
    help="Extract refresh type.",
)
@click.option(
    "--frequency",
    required=True,
    type=click.Choice(["Hourly", "Daily", "Weekly", "Monthly"], case_sensitive=False),
    help="Schedule frequency.",
)
@click.option("--start", required=True, help="Start time (HH:MM:SS).")
@click.option("--end", help="Optional end time (HH:MM:SS).")
@click.option("--interval", "interval_args", multiple=True, required=True, help="Interval key=value (repeatable).")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def schedules_create(
    datasource_id: Optional[str],
    workbook_id: Optional[str],
    refresh_type: str,
    frequency: str,
    start: str,
    end: Optional[str],
    interval_args: List[str],
    json_output: bool,
) -> None:
    if bool(datasource_id) == bool(workbook_id):
        raise click.ClickException("Provide exactly one of --datasource-id or --workbook-id")

    target_type = "datasource" if datasource_id else "workbook"
    target_id = datasource_id or workbook_id or ""
    intervals = _parse_intervals(list(interval_args))

    client = _get_tableau_client()
    try:
        result = client.create_extract_refresh_task(
            target_type=target_type,
            target_id=target_id,
            refresh_type=refresh_type,
            frequency=frequency,
            start=start,
            end=end,
            intervals=intervals,
        )
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@schedules.command("update")
@click.argument("task_id")
@click.option("--datasource-id", help="Datasource ID target for the task.")
@click.option("--workbook-id", help="Workbook ID target for the task.")
@click.option(
    "--type",
    "refresh_type",
    type=click.Choice(["FullRefresh", "IncrementalRefresh"], case_sensitive=False),
    help="Extract refresh type.",
)
@click.option(
    "--frequency",
    type=click.Choice(["Hourly", "Daily", "Weekly", "Monthly"], case_sensitive=False),
    help="Schedule frequency.",
)
@click.option("--start", help="Start time (HH:MM:SS).")
@click.option("--end", help="Optional end time (HH:MM:SS).")
@click.option("--interval", "interval_args", multiple=True, help="Interval key=value (repeatable).")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def schedules_update(
    task_id: str,
    datasource_id: Optional[str],
    workbook_id: Optional[str],
    refresh_type: Optional[str],
    frequency: Optional[str],
    start: Optional[str],
    end: Optional[str],
    interval_args: List[str],
    json_output: bool,
) -> None:
    if datasource_id and workbook_id:
        raise click.ClickException("Provide only one of --datasource-id or --workbook-id")

    target_type: Optional[str] = None
    target_id: Optional[str] = None
    if datasource_id:
        target_type = "datasource"
        target_id = datasource_id
    elif workbook_id:
        target_type = "workbook"
        target_id = workbook_id

    intervals = _parse_intervals(list(interval_args)) if interval_args else None
    if not any([target_type, refresh_type, frequency, start, end, intervals is not None]):
        raise click.ClickException("Provide at least one field to update")

    client = _get_tableau_client()
    try:
        result = client.update_extract_refresh_task(
            task_id,
            target_type=target_type,
            target_id=target_id,
            refresh_type=refresh_type,
            frequency=frequency,
            start=start,
            end=end,
            intervals=intervals,
        )
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@schedules.command("delete")
@click.argument("task_id")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def schedules_delete(task_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        result = client.delete_extract_refresh_task(task_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(f"Deleted task {task_id}")


@schedules.command("run-now")
@click.argument("task_id")
@click.option("--wait", is_flag=True, help="Wait for job completion.")
@click.option("--timeout", default=600, show_default=True, help="Timeout in seconds.")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def schedules_run_now(task_id: str, wait: bool, timeout: int, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        result = client.run_extract_refresh_task(task_id, wait=wait, timeout=timeout)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@tableau.group()
def views() -> None:
    """View operations."""


@views.command("list")
@click.argument("workbook_id")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def views_list(workbook_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        view_list = client.list_views(workbook_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(view_list)
    else:
        for view in view_list:
            click.echo(f"{view.get('id')}\t{view.get('name')}")


@views.command("download-image")
@click.argument("view_id")
@click.option("--output", type=click.Path(path_type=Path), help="Output path for image.")
@click.option("--width", type=int, help="Image width in pixels.")
@click.option("--height", type=int, help="Image height in pixels.")
@click.option("--resolution", type=int, help="Image resolution multiplier.")
def views_download_image(
    view_id: str,
    output: Optional[Path],
    width: Optional[int],
    height: Optional[int],
    resolution: Optional[int],
) -> None:
    client = _get_tableau_client()
    output_path = output or Path(f"{view_id}.png")
    try:
        client.download_view_image(view_id, output_path, width=width, height=height, resolution=resolution)
    except Exception as exc:
        _handle_client_error(exc)
    click.echo(f"Saved to {output_path}")


@views.command("download-data")
@click.argument("view_id")
@click.option("--output", type=click.Path(path_type=Path), help="Output path for CSV.")
@click.option("--param", "params", multiple=True, help="Filter parameter (key=value).")
def views_download_data(view_id: str, output: Optional[Path], params: List[str]) -> None:
    client = _get_tableau_client()
    output_path = output or Path(f"{view_id}.csv")
    try:
        param_dict = _parse_params(list(params)) if params else None
        client.download_view_data(view_id, output_path, params=param_dict)
    except Exception as exc:
        _handle_client_error(exc)
    click.echo(f"Saved to {output_path}")


@tableau.group()
def projects() -> None:
    """Project operations."""


@projects.command("list")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def projects_list(json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        projects_list = client.list_projects()
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(projects_list)
    else:
        for project in projects_list:
            click.echo(f"{project.get('id')}\t{project.get('name')}")


@projects.command("get")
@click.argument("project_id")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def projects_get(project_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        project = client.get_project(project_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(project)
    else:
        click.echo(json.dumps(project, indent=2))


@projects.command("create")
@click.argument("name")
@click.option("--description", help="Project description.")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def projects_create(name: str, description: Optional[str], json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        project = client.create_project(name=name, description=description)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(project)
    else:
        click.echo(json.dumps(project, indent=2))


@tableau.group()
def metadata() -> None:
    """Metadata API helpers."""


@metadata.command("doctor")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def metadata_doctor(json_output: bool) -> None:
    client = _get_tableau_client()
    report = {"status": "ok"}
    try:
        client.query_metadata(METADATA_DOCTOR_QUERY)
    except Exception as exc:
        report["status"] = "error"
        report["error"] = str(exc)
    if json_output:
        _emit_json(report)
    else:
        click.echo(json.dumps(report, indent=2))


@metadata.command("query")
@click.option("--query", "query_text", help="GraphQL query string.")
@click.option("--file", "query_file", type=click.Path(exists=True, path_type=Path), help="Read query from file.")
@click.option("--variables", help="JSON string with GraphQL variables.")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def metadata_query(
    query_text: Optional[str],
    query_file: Optional[Path],
    variables: Optional[str],
    json_output: bool,
) -> None:
    if not query_text and not query_file:
        raise click.ClickException("Provide --query or --file")
    query = query_text or query_file.read_text(encoding="utf-8")
    vars_payload = json.loads(variables) if variables else None
    client = _get_tableau_client()
    try:
        result = client.query_metadata(query, vars_payload)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(result)
    else:
        click.echo(json.dumps(result, indent=2))


@metadata.command("fields")
@click.option("--datasource-id", required=True, help="Datasource ID.")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def metadata_fields(datasource_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        fields = client.get_datasource_fields(datasource_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(fields)
    else:
        click.echo(json.dumps(fields, indent=2))


@metadata.command("lineage")
@click.option("--datasource-id", required=True, help="Datasource ID.")
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def metadata_lineage(datasource_id: str, json_output: bool) -> None:
    client = _get_tableau_client()
    try:
        lineage = client.get_lineage(datasource_id)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(lineage)
    else:
        click.echo(json.dumps(lineage, indent=2))


@tableau.group()
def hyper() -> None:
    """Hyper extract helpers."""


@hyper.command("create")
@click.option("--csv", "csv_path", type=click.Path(exists=True, path_type=Path), required=True)
@click.option("--output", "output_path", type=click.Path(path_type=Path), required=True)
@click.option("--table-name", default="Extract", show_default=True)
def hyper_create(csv_path: Path, output_path: Path, table_name: str) -> None:
    try:
        create_hyper_from_csv(csv_path, output_path, table_name=table_name)
    except Exception as exc:
        _handle_client_error(exc)
    click.echo(f"Created {output_path}")


@hyper.command("inspect")
@click.argument("hyper_path", type=click.Path(exists=True, path_type=Path))
@click.option("--json", "json_output", is_flag=True, help="Output as JSON.")
def hyper_inspect(hyper_path: Path, json_output: bool) -> None:
    try:
        info = inspect_hyper(hyper_path)
    except Exception as exc:
        _handle_client_error(exc)
    if json_output:
        _emit_json(info)
    else:
        click.echo(json.dumps(info, indent=2))


@hyper.command("export")
@click.argument("hyper_path", type=click.Path(exists=True, path_type=Path))
@click.option("--output", "output_path", type=click.Path(path_type=Path), required=True)
def hyper_export(hyper_path: Path, output_path: Path) -> None:
    try:
        export_hyper_to_csv(hyper_path, output_path)
    except Exception as exc:
        _handle_client_error(exc)
    click.echo(f"Exported to {output_path}")


tableau.add_command(template_cmd)


__all__ = ["tableau"]
