"""Command-line entry point that maps commands to internal operations."""

from __future__ import annotations

import argparse
import sys
from pathlib import Path
from typing import Optional

from .config import add_import_entry, load_config, remove_import_entry
from .operations import (
    do_doctor,
    do_mount,
    do_status,
    do_unmount,
)
from .registry import (
    find_names_for_path,
    load_registry,
    normalize_path,
    resolve_registered_path,
    save_registry,
)


def prompt_yes_no(message: str, default: bool = False) -> bool:
    """
    Prompt the user for a yes/no answer and return the normalized decision.

    Args:
        message: Prompt text displayed before the yes/no suffix.
        default: Value returned when the user presses Enter or input is unavailable.

    Returns:
        True for yes, False for no, or the provided default when no explicit answer is given.
    """
    suffix = " [Y/n]" if default else " [y/N]"
    while True:
        try:
            reply = input(message + suffix + " ").strip().lower()
        except (EOFError, KeyboardInterrupt):
            print("\n[registry] Input unavailable; using default answer.", file=sys.stderr)
            return default
        if not reply:
            return default
        if reply in {"y", "yes"}:
            return True
        if reply in {"n", "no"}:
            return False
        print("Please answer yes or no (y/n).")


def handle_register(name: Optional[str]) -> None:
    """
    Register the current working directory under a project name.

    Args:
        name: Optional registry key. Uses the current folder name when omitted.
    """
    registry = load_registry()
    project_path = Path.cwd().resolve()
    entry_name = name or project_path.name
    normalized = normalize_path(project_path)
    existing = registry.get(entry_name)
    if existing is not None:
        same_path = normalize_path(Path(existing)) == normalized
        if same_path:
            print(f"[registry] {entry_name} already registered at {existing}")
            return
        update_msg = (
            f"[registry] {entry_name} already points to {existing}. "
            "Update to current path?"
        )
        if not prompt_yes_no(update_msg):
            print("[registry] Register aborted.")
            return
    duplicates = [n for n in find_names_for_path(project_path, registry) if n != entry_name]
    if duplicates:
        dup_str = ", ".join(duplicates)
        if not prompt_yes_no(
            f"[registry] Path already registered as {dup_str}. Add another alias?"
        ):
            print("[registry] Register aborted.")
            return
    registry[entry_name] = normalized
    save_registry(registry)
    print(f"[registry] Saved {entry_name} -> {normalized}")


def handle_unregister(name: Optional[str]) -> None:
    """
    Remove one registry entry or every entry matching the current directory.

    Args:
        name: Registry key to delete. When omitted, deletes aliases pointing to cwd.
    """
    registry = load_registry()
    changed = False
    if name:
        if name in registry:
            del registry[name]
            changed = True
            print(f"[registry] Removed {name}")
        else:
            print(f"[registry] No entry named {name}")
    else:
        project_path = Path.cwd().resolve()
        matches = find_names_for_path(project_path, registry)
        if not matches:
            print("[registry] Current folder is not registered.")
        else:
            for entry_name in matches:
                del registry[entry_name]
                changed = True
                print(f"[registry] Removed {entry_name}")
    if changed:
        save_registry(registry)


def _read_registry_path(name: str) -> Optional[str]:
    """
    Resolve a project name from the registry.

    Args:
        name: Registry key to resolve.

    Returns:
        The stored absolute path when the name exists, otherwise None.
    """
    registry = load_registry()
    return resolve_registered_path(name, registry)


def handle_add(name: str, path: Optional[str]) -> None:
    """
    Append an import entry to the local config using a path or registry name.

    Args:
        name: Import name used in `externai.json`.
        path: Optional explicit source directory. Falls back to the registry when omitted.
    """
    try:
        if path:
            candidate = Path(path).resolve()
            if not candidate.exists():
                print(f"[add] path {candidate} does not exist; create it or register it first.")
                return
            if not candidate.is_dir():
                print(f"[add] path {candidate} is not a directory.")
                return
            success = add_import_entry(name, str(candidate))
        else:
            registered = _read_registry_path(name)
            if registered is None:
                print(
                    f"[add] {name} is not registered yet. run `externai register {name}` or pass a path."
                )
                return
            success = add_import_entry(name)
        if success:
            print(f"[add] {name} added to externai.json")
        else:
            print(f"[add] {name} already exists in externai.json")
    except FileNotFoundError:
        print("[add] externai.json not found; create one at the project root first.")


def handle_remove(name: str) -> None:
    """
    Remove an import entry from the local config by name.

    Args:
        name: Import name to delete from `externai.json`.
    """
    try:
        success = remove_import_entry(name)
    except FileNotFoundError:
        print("[remove] externai.json not found; create one at the project root first.")
        return
    if success:
        print(f"[remove] {name} removed from externai.json")
    else:
        print(f"[remove] {name} was not found in externai.json")


def handle_registry_list() -> None:
    """Print the full registry as a two-column table."""
    registry = load_registry()
    if not registry:
        print("[registry] No registered projects yet.")
        return
    width = max(4, max(len(name) for name in registry))
    print(f"{'name':{width}}  path")
    print(f"{'-' * width}  ----")
    for name in sorted(registry):
        print(f"{name:{width}}  {registry[name]}")


def main() -> None:
    """
    Parse arguments and execute the requested subcommand.

    The available commands (`mount`, `unmount`, `status`, `doctor`, `register`,
    `unregister`, `registry`) operate on the entries declared in `externai.json`
    located at the project root and the global registry.
    """
    parser = argparse.ArgumentParser(
        description="Manage read-only bind mounts for the configured externals directory"
    )
    sub = parser.add_subparsers(dest="cmd", required=True)
    sub.add_parser("mount")
    sub.add_parser("unmount")
    sub.add_parser("status")
    sub.add_parser("doctor")
    add_parser = sub.add_parser("add")
    add_parser.add_argument("name")
    add_parser.add_argument("path", nargs="?")
    remove_parser = sub.add_parser("remove")
    remove_parser.add_argument("name")
    register_parser = sub.add_parser("register")
    register_parser.add_argument("name", nargs="?")
    unregister_parser = sub.add_parser("unregister")
    unregister_parser.add_argument("name", nargs="?")
    sub.add_parser("registry")
    args = parser.parse_args()

    if args.cmd == "mount":
        config = load_config()
        do_mount(config)
    elif args.cmd == "unmount":
        config = load_config()
        do_unmount(config)
    elif args.cmd == "status":
        config = load_config()
        do_status(config)
    elif args.cmd == "doctor":
        config = load_config()
        do_doctor(config)
    elif args.cmd == "register":
        handle_register(args.name)
    elif args.cmd == "unregister":
        handle_unregister(args.name)
    elif args.cmd == "registry":
        handle_registry_list()
    elif args.cmd == "add":
        handle_add(args.name, args.path)
    elif args.cmd == "remove":
        handle_remove(args.name)
    else:
        parser.print_help()
