"""
Command-line interface for gallery generation.

Provides a CLI entry point for gallery generation and config management.
Shell autocomplete: add the following line to your .bashrc / .zshrc:
    eval "$(register-python-argcomplete gallery)"
"""

import argparse
import os
import sys
from pathlib import Path
from typing import List, Optional, cast

import argcomplete

from gallery import generate
from gallery.config import (
    ConfigManager,
    GalleryConfig,
    GallerySource,
    ensure_user_config,
    get_active_config_path,
)

_WELCOME = """\
Gallery - Scientific Plot Gallery Generator
===========================================

Generates responsive static HTML galleries from collections of PDFs and HTMLs.

Getting started:
  1. Set your web output directory:
       gallery config set paths.web_folder /path/to/your/public_html

  2. Add one or more plot sources:
       gallery config add-source --path /path/to/plots

  3. Generate your gallery:
       gallery generate

Config commands:
  gallery config list                           Show all settings
  gallery config get <key>                      Get a single value  (e.g. gallery.png_dpi)
  gallery config set <key> <value>              Update a setting    (e.g. paths.web_folder /my/web)
  gallery config add-source --path P            Add a plot source   (--name defaults to dir name)
  gallery config remove-source <name>           Remove a plot source
  gallery config sources                        List configured sources
  gallery config path                           Show config file location

Generate commands:
  gallery generate                              Generate gallery from config
  gallery generate --config myconfig.yaml       Use a custom config file
  gallery generate --clean                      Clean and regenerate everything
  gallery generate --source /path/to/plots      Regenerate one source only
  gallery generate --verbose                    Print detailed output

Shell autocomplete (run once after install):
  gallery install-completion

Config file: {config_path}
"""


def _set_completer(action: argparse.Action, completer) -> None:
    """argcomplete reads `.completer` dynamically; argparse.Action has no such attribute."""
    setattr(action, "completer", completer)


def _is_configured(config_path: Path) -> bool:
    """Return True if web_folder is set to a non-empty value."""
    try:
        web_folder = ConfigManager(config_path).get("paths.web_folder")
        return bool(web_folder and str(web_folder).strip())
    except Exception:
        return False


def _source_names(prefix, parsed_args, **kwargs):
    """Autocomplete helper: return configured source names."""
    try:
        config_path = Path(parsed_args.config) if getattr(parsed_args, "config", None) else get_active_config_path()
        return [s["name"] for s in ConfigManager(config_path).list_sources()]
    except Exception:
        return []


def _config_keys(prefix, parsed_args, **kwargs):
    """Autocomplete helper: return known dot-notation config keys."""
    try:
        config_path = Path(parsed_args.config) if getattr(parsed_args, "config", None) else get_active_config_path()
        data = ConfigManager(config_path).list_all()
        keys = []
        for section, values in data.items():
            if isinstance(values, dict):
                for k in values:
                    keys.append(f"{section}.{k}")
            else:
                keys.append(section)
        return [k for k in keys if k.startswith(prefix)]
    except Exception:
        return []


# ---------------------------------------------------------------------------
# Parser construction  (separated so TUI can reuse the structure)
# ---------------------------------------------------------------------------


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="gallery",
        description="Scientific Plot Gallery Generator",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    _set_completer(
        parser.add_argument(
            "--config",
            type=str,
            default=None,
            metavar="FILE",
            help="Path to config file (default: package bundled config)",
        ),
        argcomplete.completers.FilesCompleter(["yaml", "yml"]),
    )

    sub = parser.add_subparsers(dest="command", metavar="COMMAND")

    # --- generate -----------------------------------------------------------
    gen = sub.add_parser(
        "generate",
        help="Generate the gallery",
        description="Generate scientific gallery from plot collections",
    )
    gen.add_argument("--clean", action="store_true", help="Clean gallery directory before generation")
    _set_completer(
        gen.add_argument(
            "--source",
            type=str,
            default=None,
            metavar="DIR",
            help="Only recompute a specific source directory (name defaults to dir name)",
        ),
        argcomplete.completers.DirectoriesCompleter(),
    )
    gen.add_argument("-v", "--verbose", action="store_true", help="Print verbose output")

    # --- config -------------------------------------------------------------
    cfg = sub.add_parser(
        "config",
        help="Read and write configuration",
        description="Read and write gallery configuration",
    )
    cfg_sub = cfg.add_subparsers(dest="action", metavar="ACTION")

    sub.add_parser("install-completion", help="Install shell tab-completion (bash/zsh)")
    sub.add_parser("tui", help="Launch the interactive TUI")

    cfg_sub.add_parser("list", help="Print all config values")
    cfg_sub.add_parser("path", help="Print the resolved config file path")
    cfg_sub.add_parser("sources", help="List all configured sources")

    p_get = cfg_sub.add_parser("get", help="Get a config value")
    _set_completer(p_get.add_argument("key", help="Dot-separated key, e.g. gallery.png_dpi"), _config_keys)

    p_set = cfg_sub.add_parser("set", help="Set a config value")
    _set_completer(p_set.add_argument("key", help="Dot-separated key, e.g. gallery.png_dpi"), _config_keys)
    p_set.add_argument("value", help="New value (YAML-parsed: use true/false for bools)")

    p_add = cfg_sub.add_parser("add-source", help="Add a plot source")
    p_add.add_argument("--name", default=None, help="Source name (default: bottom-level directory name)")
    _set_completer(
        p_add.add_argument("--path", required=True, metavar="DIR", help="Path to source directory"),
        argcomplete.completers.DirectoriesCompleter(),
    )

    p_rm = cfg_sub.add_parser("remove-source", help="Remove a plot source by name")
    _set_completer(p_rm.add_argument("name", help="Source name to remove"), _source_names)

    return parser


# ---------------------------------------------------------------------------
# Command handlers
# ---------------------------------------------------------------------------


def _run_generate(args: argparse.Namespace) -> int:
    config_path = Path(args.config) if args.config else get_active_config_path()
    try:
        config = GalleryConfig.from_yaml(config_path)

        source_to_update: Optional[GallerySource] = None
        if args.source:
            source_path = Path(args.source).resolve()
            # config.sources is always List[GallerySource] after GalleryConfig.__post_init__ normalizes it.
            typed_sources = cast(List[GallerySource], config.sources)
            matching = next((s for s in typed_sources if Path(s.path).resolve() == source_path), None)
            if matching is None:
                source_to_update = GallerySource(name=source_path.name, path=source_path)
                config.sources.append(source_to_update)
                if args.verbose:
                    print(f"Source {args.source} not in config. Adding temporarily as '{source_path.name}'")
            else:
                source_to_update = matching

        success = generate(
            config=config,
            clean_first=args.clean,
            verbose=args.verbose,
            source_to_update=source_to_update,
        )
        return 0 if success else 1

    except (FileNotFoundError, ValueError) as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1


def _run_config(args: argparse.Namespace) -> int:
    if args.config:
        config_path = Path(args.config)
    elif args.action in ("set", "add-source", "remove-source"):
        # Writes go to the user config; create it from the template if needed
        config_path = ensure_user_config()
    else:
        config_path = get_active_config_path()
    mgr = ConfigManager(config_path)

    if args.action == "path":
        print(mgr.path)

    elif args.action == "list":
        import yaml

        print(yaml.dump(mgr.list_all(), default_flow_style=False).rstrip())

    elif args.action == "sources":
        sources = mgr.list_sources()
        if not sources:
            print("No sources configured.")
        else:
            for s in sources:
                print(f"  {s['name']}: {s['path']}")

    elif args.action == "get":
        try:
            print(mgr.get(args.key))
        except KeyError as e:
            print(f"Error: {e}", file=sys.stderr)
            return 1

    elif args.action == "set":
        try:
            mgr.set(args.key, args.value)
        except Exception as e:
            print(f"Error: {e}", file=sys.stderr)
            return 1

    elif args.action == "add-source":
        try:
            name = args.name or Path(args.path).resolve().name
            mgr.add_source(name, args.path)
        except ValueError as e:
            print(f"Error: {e}", file=sys.stderr)
            return 1

    elif args.action == "remove-source":
        try:
            mgr.remove_source(args.name)
        except KeyError as e:
            print(f"Error: {e}", file=sys.stderr)
            return 1

    else:
        # `gallery config` with no action → show config help
        build_parser().parse_args(["config", "--help"])

    return 0


# ---------------------------------------------------------------------------
# install-completion handler
# ---------------------------------------------------------------------------

_COMPLETION_LINE = 'eval "$(register-python-argcomplete gallery)"'

_SHELL_RC = {
    "zsh": ".zshrc",
    "bash": ".bashrc",
    "fish": ".config/fish/config.fish",
}


def _run_install_completion() -> int:
    shell = Path(os.environ.get("SHELL", "")).name  # e.g. "bash", "zsh"
    rc_name = _SHELL_RC.get(shell, ".bashrc")
    rc = Path.home() / rc_name

    if rc.exists() and _COMPLETION_LINE in rc.read_text():
        print(f"Shell completion already configured in {rc}")
        return 0

    with open(rc, "a") as f:
        f.write(f"\n# gallery shell completion\n{_COMPLETION_LINE}\n")

    print(f"Shell completion installed to {rc}")
    print(f"Reload with:  source {rc}")
    return 0


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------


def main():
    parser = build_parser()
    argcomplete.autocomplete(parser)  # no-op when not completing; exits during completion
    args = parser.parse_args()

    if args.command == "generate":
        sys.exit(_run_generate(args))
    elif args.command == "config":
        sys.exit(_run_config(args))
    elif args.command == "install-completion":
        sys.exit(_run_install_completion())
    elif args.command == "tui":
        from gallery.tui import GalleryTUI

        config_path = Path(args.config) if args.config else None
        GalleryTUI(config_path=config_path).run()
        sys.exit(0)
    else:
        print(_WELCOME.format(config_path=get_active_config_path()))
        sys.exit(0)


if __name__ == "__main__":
    main()
