#!/usr/bin/env python3
"""scripts/bootstrap.py

Unified project bootstrap helper (Python-only).

This script installs Python dependencies from `scripts/requirements.txt` and then
downloads Playwright browser binaries by running `python -m playwright install`.
By default this script installs **Chromium** only to conserve space; pass
`--browsers all` to install all supported engines (chromium, firefox, webkit).

Note: This Python script is the canonical installer for the project — prefer
running `python ./scripts/bootstrap.py` locally. The platform scripts
(`scripts/bootstrap.ps1` and `scripts/bootstrap.sh`) are now thin wrappers
that delegate to this script (they call it with `--no-delegate -q`).

When invoked without any arguments, `bootstrap.py` will automatically select and
run the platform-specific bootstrap helper (`scripts/bootstrap.ps1` on Windows
or `scripts/bootstrap.sh` on POSIX) in **non-interactive (quiet)** mode so a
single `python ./scripts/bootstrap.py` call does the usual bootstrap on your OS.

The platform bootstrap scripts also attempt (best-effort) to install `mpv` if
it is not found on your PATH, since some workflows use it.

This file replaces the old `scripts/setup.py` to ensure the repository only has
one `setup.py` (at the repository root) for packaging.

Usage:
  python ./scripts/bootstrap.py           # install deps and playwright browsers (or run platform bootstrap if no args)
  python ./scripts/bootstrap.py --skip-deps
  python ./scripts/bootstrap.py --playwright-only

Optional flags:
    --skip-deps        Skip `pip install -r scripts/requirements.txt` step
  --no-playwright    Skip running `python -m playwright install` (still installs deps)
  --playwright-only  Install only Playwright browsers (installs playwright package if missing)
  --browsers         Comma-separated list of Playwright browsers to install (default: chromium)
    --install-editable Install the project in editable mode (pip install -e scripts) for running tests
  --install-deno     Install the Deno runtime using the official installer
  --no-deno          Skip installing the Deno runtime
  --deno-version     Pin a specific Deno version to install (e.g., v1.34.3)
  --upgrade-pip      Upgrade pip, setuptools, and wheel before installing deps
"""

from __future__ import annotations

import argparse
import os
import platform
from pathlib import Path
import shutil
import subprocess
import sys
import time


def run(cmd: list[str]) -> None:
    print(f"> {' '.join(cmd)}")
    subprocess.check_call(cmd)


# Helpers to find shell executables and to run the platform-specific
# bootstrap script (scripts/bootstrap.sh or scripts/bootstrap.ps1).
def _find_powershell() -> str | None:
    for name in ("pwsh", "powershell"):
        p = shutil.which(name)
        if p:
            return p
    return None


def _find_shell() -> str | None:
    for name in ("bash", "sh"):
        p = shutil.which(name)
        if p:
            return p
    return None


def run_platform_bootstrap(repo_root: Path) -> int:
    """Run the platform bootstrap script in quiet/non-interactive mode if present.

    Returns the script exit code (0 on success). If no script is present this is a
    no-op and returns 0.
    """

    ps1 = repo_root / "scripts" / "bootstrap.ps1"
    sh_script = repo_root / "scripts" / "bootstrap.sh"

    system = platform.system().lower()

    if system == "windows" and ps1.exists():
        exe = _find_powershell()
        if not exe:
            print("PowerShell not found; cannot run bootstrap.ps1", file=sys.stderr)
            return 1
        cmd = [
            exe,
            "-NoProfile",
            "-NonInteractive",
            "-ExecutionPolicy",
            "Bypass",
            "-File",
            str(ps1),
            "-Quiet",
        ]
    elif sh_script.exists():
        shell = _find_shell()
        if not shell:
            print("Shell not found; cannot run bootstrap.sh", file=sys.stderr)
            return 1
        # Use -q (quiet) to skip interactive prompts when supported.
        cmd = [shell, str(sh_script), "-q"]
    else:
        # Nothing to run
        return 0

    print("Running platform bootstrap script:", " ".join(cmd))
    rc = subprocess.run(cmd, cwd=str(repo_root))
    if rc.returncode != 0:
        print(
            f"Bootstrap script failed with exit code {rc.returncode}",
            file=sys.stderr
        )
    return int(rc.returncode or 0)


def playwright_package_installed() -> bool:
    try:
        import playwright  # type: ignore

        return True
    except Exception:
        return False


def _build_playwright_install_cmd(browsers: str | None) -> list[str]:
    """Return the command to install Playwright browsers.

    - If browsers is None or empty: default to install Chromium only.
    - If browsers contains 'all': install all engines by running 'playwright install' with no extra args.
    - Otherwise, validate entries and return a command that installs the named engines.
    """

    base = [sys.executable, "-m", "playwright", "install"]
    if not browsers:
        return base + ["chromium"]

    items = [b.strip().lower() for b in browsers.split(",") if b.strip()]
    if not items:
        return base + ["chromium"]
    if "all" in items:
        return base

    allowed = {"chromium",
               "firefox",
               "webkit"}
    invalid = [b for b in items if b not in allowed]
    if invalid:
        raise ValueError(
            f"invalid browsers specified: {invalid}. Valid choices: chromium, firefox, webkit, or 'all'"
        )
    return base + items


def _install_deno(version: str | None = None) -> int:
    """Install Deno runtime for the current platform.

    Uses the official Deno install scripts:
      - Unix/macOS: curl -fsSL https://deno.land/x/install/install.sh | sh [-s <version>]
      - Windows: powershell iwr https://deno.land/x/install/install.ps1 -useb | iex; Install-Deno [-Version <version>]

    Returns exit code 0 on success, non-zero otherwise.
    """

    system = platform.system().lower()

    try:
        if system == "windows":
            # Use official PowerShell installer
            if version:
                ver = version if version.startswith("v") else f"v{version}"
                ps_cmd = f"iwr https://deno.land/x/install/install.ps1 -useb | iex; Install-Deno -Version {ver}"
            else:
                ps_cmd = "iwr https://deno.land/x/install/install.ps1 -useb | iex"
            run(
                [
                    "powershell",
                    "-NoProfile",
                    "-ExecutionPolicy",
                    "Bypass",
                    "-Command",
                    ps_cmd
                ]
            )
        else:
            # POSIX: use curl + sh installer
            if version:
                ver = version if version.startswith("v") else f"v{version}"
                cmd = f"curl -fsSL https://deno.land/x/install/install.sh | sh -s {ver}"
            else:
                cmd = "curl -fsSL https://deno.land/x/install/install.sh | sh"
            run(["sh", "-c", cmd])

        # Check that 'deno' is now available in PATH
        if shutil.which("deno"):
            print(f"Deno installed at: {shutil.which('deno')}")
            return 0

        print(
            "Deno installation completed but 'deno' not found in PATH. You may need to add Deno's bin directory to your PATH manually.",
            file=sys.stderr,
        )
        return 1
    except subprocess.CalledProcessError as exc:
        print(f"Deno install failed: {exc}", file=sys.stderr)
        return int(exc.returncode or 1)


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Bootstrap Medios-Macina: install deps and Playwright browsers"
    )
    parser.add_argument(
        "--skip-deps",
        action="store_true",
        help="Skip installing Python dependencies from scripts/requirements.txt",
    )
    parser.add_argument(
        "--no-playwright",
        action="store_true",
        help="Skip running 'playwright install' (only install packages)",
    )
    parser.add_argument(
        "--playwright-only",
        action="store_true",
        help="Only run 'playwright install' (skips dependency installation)",
    )
    parser.add_argument(
        "--no-delegate",
        action="store_true",
        help="Do not delegate to platform bootstrap scripts; run the Python bootstrap directly.",
    )
    parser.add_argument(
        "-q",
        "--quiet",
        action="store_true",
        help="Quiet mode: minimize informational output (useful when called from platform wrappers)",
    )
    parser.add_argument(
        "--browsers",
        type=str,
        default="chromium",
        help=
        "Comma-separated list of browsers to install: chromium,firefox,webkit or 'all' (default: chromium)",
    )
    parser.add_argument(
        "--install-editable",
        action="store_true",
        help="Install the project in editable mode (pip install -e scripts) for running tests",
    )
    deno_group = parser.add_mutually_exclusive_group()
    deno_group.add_argument(
        "--install-deno",
        action="store_true",
        help="Install the Deno runtime (default behavior; kept for explicitness)",
    )
    deno_group.add_argument(
        "--no-deno",
        action="store_true",
        help="Skip installing Deno runtime (opt out)"
    )
    parser.add_argument(
        "--deno-version",
        type=str,
        default=None,
        help="Specific Deno version to install (e.g., v1.34.3)",
    )
    parser.add_argument(
        "--upgrade-pip",
        action="store_true",
        help="Upgrade pip/setuptools/wheel before installing requirements",
    )
    parser.add_argument(
        "--uninstall",
        action="store_true",
        help="Uninstall local .venv and user shims (non-interactive)",
    )
    parser.add_argument(
        "-y",
        "--yes",
        action="store_true",
        help="Assume yes for confirmation prompts during uninstall",
    )
    args = parser.parse_args()

    repo_root = Path(__file__).resolve().parent.parent

    # Helpers for interactive menu and uninstall detection
    def _venv_python_path(p: Path) -> Path | None:
        """Return the path to a python executable inside a venv directory if present."""
        if (p / "Scripts" / "python.exe").exists():
            return p / "Scripts" / "python.exe"
        if (p / "bin" / "python").exists():
            return p / "bin" / "python"
        return None

    def _is_installed() -> bool:
        """Return True if the project appears installed into the local .venv."""
        vdir = repo_root / ".venv"
        py = _venv_python_path(vdir)
        if py is None:
            return False
        try:
            rc = subprocess.run([str(py), "-m", "pip", "show", "medeia-macina"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
            return rc.returncode == 0
        except Exception:
            return False

    def _do_uninstall() -> int:
        """Attempt to remove the local venv and any shims written to the user's bin.

        If this script is running using the Python inside the local `.venv`, we
        attempt to re-run the uninstall using a Python interpreter outside the
        venv (so files can be removed on Windows). If no suitable external
        interpreter can be found, the user is asked to deactivate the venv and
        re-run the uninstall.
        """
        vdir = repo_root / ".venv"
        if not vdir.exists():
            if not args.quiet:
                print("No local .venv found; nothing to uninstall.")
            return 0

        # If the current interpreter is the one inside the local venv, try to
        # run the uninstall via a Python outside the venv so files (including
        # the interpreter binary) can be removed on Windows.
        try:
            current_exe = Path(sys.executable).resolve()
            in_venv = str(current_exe).lower().startswith(str(vdir.resolve()).lower())
        except Exception:
            in_venv = False

        if in_venv:
            if not args.quiet:
                print(f"Detected local venv Python in use: {current_exe}")

            if not args.yes:
                try:
                    resp = input("Uninstall will be attempted using a system Python outside the .venv. Continue? [Y/n]: ")
                except EOFError:
                    print("Non-interactive environment; pass --uninstall --yes to uninstall without prompts.", file=sys.stderr)
                    return 2
                if resp.strip().lower() in ("n", "no"):
                    print("Uninstall aborted.")
                    return 1

            def _find_external_python() -> list[str] | None:
                """Return a command (list) for a Python interpreter outside the venv, or None."""
                try:
                    base = Path(sys.base_prefix)
                    candidates: list[Path | str] = []
                    if platform.system().lower() == "windows":
                        candidates.append(base / "python.exe")
                    else:
                        candidates.extend([base / "bin" / "python3", base / "bin" / "python"])

                    for name in ("python3", "python"):
                        p = shutil.which(name)
                        if p:
                            candidates.append(Path(p))

                    # Special-case the Windows py launcher: ensure it resolves
                    # to a Python outside the venv before returning ['py','-3']
                    if platform.system().lower() == "windows":
                        py_launcher = shutil.which("py")
                        if py_launcher:
                            try:
                                out = subprocess.check_output(["py", "-3", "-c", "import sys; print(sys.executable)"], text=True).strip()
                                if out and not str(Path(out).resolve()).lower().startswith(str(vdir.resolve()).lower()):
                                    return ["py", "-3"]
                            except Exception:
                                pass

                    for c in candidates:
                        try:
                            if isinstance(c, Path) and c.exists():
                                c_resolved = Path(c).resolve()
                                if not str(c_resolved).lower().startswith(str(vdir.resolve()).lower()) and c_resolved != current_exe:
                                    return [str(c_resolved)]
                        except Exception:
                            continue
                except Exception:
                    pass
                return None

            ext = _find_external_python()
            if ext:
                cmd = ext + [str(repo_root / "scripts" / "bootstrap.py"), "--uninstall", "--yes"]
                if not args.quiet:
                    print("Attempting uninstall using external Python:", " ".join(cmd))
                rc = subprocess.run(cmd)
                if rc.returncode != 0:
                    print(
                        f"External uninstall exited with {rc.returncode}; ensure no processes are using files in {vdir} and try again.",
                        file=sys.stderr,
                    )
                return int(rc.returncode or 0)

            print(
                "Could not find a Python interpreter outside the local .venv. Please deactivate your venv (run 'deactivate') or run the uninstall from a system Python:\n  python ./scripts/bootstrap.py --uninstall --yes",
                file=sys.stderr,
            )
            return 2

        # Normal (non-venv) uninstall flow: confirm and remove launchers, shims, and venv
        if not args.yes:
            try:
                prompt = input(f"Remove local virtualenv at {vdir} and installed user shims? [y/N]: ")
            except EOFError:
                print("Non-interactive environment; pass --uninstall --yes to uninstall without prompts.", file=sys.stderr)
                return 2
            if prompt.strip().lower() not in ("y", "yes"):
                print("Uninstall aborted.")
                return 1

        # Remove repo-local launchers
        def _remove_launcher(path: Path) -> None:
            if path.exists():
                try:
                    path.unlink()
                    if not args.quiet:
                        print(f"Removed local launcher: {path}")
                except Exception as exc:
                    print(f"Warning: failed to remove {path}: {exc}", file=sys.stderr)

        scripts_launcher = repo_root / "scripts" / "mm.ps1"
        _remove_launcher(scripts_launcher)
        for legacy in ("mm", "mm.ps1", "mm.bat"):
            _remove_launcher(repo_root / legacy)

        # Remove user shims that the installer may have written
        try:
            system = platform.system().lower()
            if system == "windows":
                user_bin = Path(os.environ.get("USERPROFILE", str(Path.home()))) / "bin"
                if user_bin.exists():
                    for name in ("mm.ps1",):
                        p = user_bin / name
                        if p.exists():
                            try:
                                p.unlink()
                                if not args.quiet:
                                    print(f"Removed user shim: {p}")
                            except Exception as exc:
                                print(f"Warning: failed to remove {p}: {exc}", file=sys.stderr)
            else:
                user_bin = Path(os.environ.get("XDG_BIN_HOME", str(Path.home() / ".local/bin")))
                if user_bin.exists():
                    p = user_bin / "mm"
                    if p.exists():
                        p.unlink()
                        if not args.quiet:
                            print(f"Removed user shim: {p}")
        except Exception as exc:
            print(f"Warning: failed to remove user shims: {exc}", file=sys.stderr)

        # Remove .venv directory
        try:
            shutil.rmtree(vdir)
            if not args.quiet:
                print(f"Removed local virtualenv: {vdir}")
        except Exception as exc:
            print(f"Failed to remove venv: {exc}", file=sys.stderr)
            return 1
        return 0

    def _interactive_menu() -> str | int:
        """Show a simple interactive menu to choose install/uninstall or delegate."""
        try:
            installed = _is_installed()
            while True:
                print("\nMedeia-Macina bootstrap - interactive menu")
                if installed:
                    print("1) Install / Reinstall")
                    print("2) Uninstall")
                    print("3) Status")
                    print("q) Quit")
                    choice = input("Choose an option: ").strip().lower()
                    if not choice or choice in ("1", "install", "reinstall"):
                        return "install"
                    if choice in ("2", "uninstall"):
                        return "uninstall"
                    if choice in ("3", "status"):
                        print("Installation detected." if installed else "Not installed.")
                        continue
                    if choice in ("q", "quit", "exit"):
                        return 0
                else:
                    print("1) Install")
                    print("q) Quit")
                    choice = input("Choose an option: ").strip().lower()
                    if not choice or choice in ("1", "install"):
                        return "install"
                    if choice in ("q", "quit", "exit"):
                        return 0
        except EOFError:
            # Non-interactive, fall back to delegating to platform helper
            return "delegate"

    # If the user passed --uninstall explicitly, perform non-interactive uninstall and exit
    if args.uninstall:
        return _do_uninstall()

    # If invoked without any arguments and not asked to skip delegation, prefer
    # the interactive menu when running in a TTY; otherwise delegate to the
    # platform-specific bootstrap helper (non-interactive).
    if len(sys.argv) == 1 and not args.no_delegate:
        if sys.stdin.isatty() and not args.quiet:
            sel = _interactive_menu()
            if sel == "install":
                # user chose to install/reinstall; set defaults and continue
                args.skip_deps = False
                args.install_editable = True
                args.no_playwright = False
            elif sel == "uninstall":
                return _do_uninstall()
            elif sel == "delegate":
                rc = run_platform_bootstrap(repo_root)
                if rc != 0:
                    return rc
                if not args.quiet:
                    print("Platform bootstrap completed successfully.")
                return 0
            else:
                return int(sel or 0)
        else:
            rc = run_platform_bootstrap(repo_root)
            if rc != 0:
                return rc
            if not args.quiet:
                print("Platform bootstrap completed successfully.")
            return 0

    if sys.version_info < (3, 8):
        print("Warning: Python 3.8+ is recommended.", file=sys.stderr)

    # Opinionated: always create or use a local venv at the project root (.venv)
    venv_dir = repo_root / ".venv"

    def _venv_python(p: Path) -> Path:
        if platform.system().lower() == "windows":
            return p / "Scripts" / "python.exe"
        return p / "bin" / "python"

    def _ensure_local_venv() -> Path:
        """Create (if missing) and return the path to the venv's python executable."""

        try:
            if not venv_dir.exists():
                if not args.quiet:
                    print(f"Creating local virtualenv at: {venv_dir}")
                run([sys.executable, "-m", "venv", str(venv_dir)])
            else:
                if not args.quiet:
                    print(f"Using existing virtualenv at: {venv_dir}")

            py = _venv_python(venv_dir)
            if not py.exists():
                # Try recreating venv if python is missing
                if not args.quiet:
                    print(f"Local venv python not found at {py}; recreating venv")
                run([sys.executable, "-m", "venv", str(venv_dir)])
            py = _venv_python(venv_dir)
            if not py.exists():
                raise RuntimeError(f"Unable to locate venv python at {py}")
            return py
        except subprocess.CalledProcessError as exc:
            print(f"Failed to create or prepare local venv: {exc}", file=sys.stderr)
            raise
    
    def _ensure_pip_available(python_path: Path) -> None:
        """Ensure pip is available inside the venv; fall back to ensurepip if needed."""

        try:
            subprocess.run(
                [str(python_path), "-m", "pip", "--version"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                check=True,
            )
            return
        except Exception:
            pass

        if not args.quiet:
            print("Bootstrapping pip inside the local virtualenv...")
        try:
            run([str(python_path), "-m", "ensurepip", "--upgrade"])
        except subprocess.CalledProcessError as exc:
            print(
                "Failed to install pip inside the local virtualenv via ensurepip; ensure your Python build includes ensurepip and retry.",
                file=sys.stderr,
            )
            raise

    # Ensure a local venv is present and use it for subsequent installs.
    venv_python = _ensure_local_venv()
    if not args.quiet:
        print(f"Using venv python: {venv_python}")
    _ensure_pip_available(venv_python)

    # Enforce opinionated behavior: install deps, playwright, deno, and install project in editable mode.
    # Ignore `--skip-deps` and `--install-editable` flags to keep the setup deterministic.
    args.skip_deps = False
    args.install_editable = True
    args.no_playwright = False

    try:
        if args.playwright_only:
            if not playwright_package_installed():
                if not args.quiet:
                    print("'playwright' package not found; installing it via pip...")
                run([sys.executable, "-m", "pip", "install", "playwright"])

            if not args.quiet:
                print(
                    "Installing Playwright browsers (this may download several hundred MB)..."
                )
            try:
                cmd = _build_playwright_install_cmd(args.browsers)
            except ValueError as exc:
                print(f"Error: {exc}", file=sys.stderr)
                return 2

            run(cmd)
            if not args.quiet:
                print("Playwright browsers installed successfully.")
            return 0

        if args.upgrade_pip:
            if not args.quiet:
                print("Upgrading pip, setuptools, and wheel in local venv...")
            run(
                [
                    str(venv_python),
                    "-m",
                    "pip",
                    "install",
                    "--upgrade",
                    "pip",
                    "setuptools",
                    "wheel",
                ]
            )

        if not args.skip_deps:
            req_file = repo_root / "scripts" / "requirements.txt"
            if not req_file.exists():
                print(
                    f"requirements.txt not found at {req_file}; skipping dependency installation.",
                    file=sys.stderr,
                )
            else:
                if not args.quiet:
                    print(
                        f"Installing Python dependencies into local venv from {req_file}..."
                    )
                run([str(venv_python), "-m", "pip", "install", "-r", str(req_file)])

        if not args.no_playwright:
            if not playwright_package_installed():
                if not args.quiet:
                    print("'playwright' package not installed in venv; installing it...")
                run([str(venv_python), "-m", "pip", "install", "playwright"])

            if not args.quiet:
                print(
                    "Installing Playwright browsers (this may download several hundred MB)..."
                )
            try:
                cmd = _build_playwright_install_cmd(args.browsers)
            except ValueError as exc:
                print(f"Error: {exc}", file=sys.stderr)
                return 2

            # Run Playwright install using the venv's python so binaries are available in venv
            cmd[0] = str(venv_python)
            run(cmd)

        # Install the project into the local venv (editable mode is the default, opinionated)
        if not args.quiet:
            print("Installing project into local venv (editable mode)")
        run([str(venv_python), "-m", "pip", "install", "-e", str(repo_root / "scripts")])

        # Verify top-level 'CLI' import and, if missing, attempt to make it available
        if not args.quiet:
            print("Verifying top-level 'CLI' import in venv...")
        try:
            rc = subprocess.run(
                [
                    str(venv_python),
                    "-c",
                    "import importlib; importlib.import_module('CLI')"
                ],
                check=False,
            )
            if rc.returncode == 0:
                print("OK: top-level 'CLI' is importable in the venv.")
            else:
                print(
                    "Top-level 'CLI' not importable; attempting to add repo path to venv site-packages via a .pth file..."
                )
                cmd = [
                    str(venv_python),
                    "-c",
                    (
                        "import site, sysconfig\n"
                        "out=[]\n"
                        "try:\n    out.extend(site.getsitepackages())\nexcept Exception:\n    pass\n"
                        "try:\n    p = sysconfig.get_paths().get('purelib')\n    if p:\n        out.append(p)\nexcept Exception:\n    pass\n"
                        "seen=[]; res=[]\n"
                        "for x in out:\n    if x and x not in seen:\n        seen.append(x); res.append(x)\n"
                        "for s in res:\n    print(s)\n"
                    ),
                ]
                out = subprocess.check_output(cmd, text=True).strip().splitlines()
                site_dir: Path | None = None
                for sp in out:
                    if sp and Path(sp).exists():
                        site_dir = Path(sp)
                        break
                if site_dir is None:
                    print(
                        "Could not determine venv site-packages directory; skipping .pth fallback"
                    )
                else:
                    pth_file = site_dir / "medeia_repo.pth"
                    if pth_file.exists():
                        txt = pth_file.read_text(encoding="utf-8")
                        if str(repo_root) in txt:
                            print(f".pth already contains repo root: {pth_file}")
                        else:
                            with pth_file.open("a", encoding="utf-8") as fh:
                                fh.write(str(repo_root) + "\n")
                            print(f"Appended repo root to existing .pth: {pth_file}")
                    else:
                        with pth_file.open("w", encoding="utf-8") as fh:
                            fh.write(str(repo_root) + "\n")
                        print(
                            f"Wrote .pth adding repo root to venv site-packages: {pth_file}"
                        )

                    # Re-check whether CLI can be imported now
                    rc2 = subprocess.run(
                        [
                            str(venv_python),
                            "-c",
                            "import importlib; importlib.import_module('CLI')",
                        ],
                        check=False,
                    )
                    if rc2.returncode == 0:
                        print("Top-level 'CLI' import works after adding .pth")
                    else:
                        print(
                            "Adding .pth did not make top-level 'CLI' importable; consider creating an egg-link or checking the venv."
                        )
        except Exception as exc:
            print(
                f"Warning: failed to verify or modify site-packages for top-level CLI: {exc}"
            )

        # Optional: install Deno runtime (default: install unless --no-deno is passed)
        install_deno_requested = True
        if getattr(args, "no_deno", False):
            install_deno_requested = False
        elif getattr(args, "install_deno", False):
            install_deno_requested = True

        if install_deno_requested:
            if not args.quiet:
                print("Installing Deno runtime (local/system)...")
            rc = _install_deno(args.deno_version)
            if rc != 0:
                print("Deno installation failed.", file=sys.stderr)
                return rc

        # Write project-local launcher script under scripts/ to keep the repo root uncluttered.
        def _write_launchers() -> None:
            launcher_dir = repo_root / "scripts"
            launcher_dir.mkdir(parents=True, exist_ok=True)
            ps1 = launcher_dir / "mm.ps1"

            ps1_text = r"""Param([Parameter(ValueFromRemainingArguments=$true)] $args)
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repo = (Resolve-Path (Join-Path $scriptDir "..")).Path
$venv = Join-Path $repo '.venv'
# Ensure venv Scripts dir is on PATH for provider discovery
$venvScripts = Join-Path $venv 'Scripts'
if (Test-Path $venvScripts) { $env:PATH = $venvScripts + ';' + $env:PATH }
$py = Join-Path $venv 'Scripts\python.exe'
$cli = Join-Path $repo 'CLI.py'
if (Test-Path $py) { & $py -m scripts.cli_entry @args; exit $LASTEXITCODE }
if (Test-Path $cli) { & python $cli @args; exit $LASTEXITCODE }
# fallback
python -m scripts.cli_entry @args
"""
            try:
                ps1.write_text(ps1_text, encoding="utf-8")
            except Exception:
                pass

        _write_launchers()

        # Install user-global shims so `mm` can be executed from any shell session.
        def _install_user_shims(repo: Path) -> None:
            try:
                home = Path.home()
                system = platform.system().lower()

                if system == "windows":
                    user_bin = Path(os.environ.get("USERPROFILE", str(home))) / "bin"
                    user_bin.mkdir(parents=True, exist_ok=True)

                    # Write mm.ps1 (PowerShell shim)
                    mm_ps1 = user_bin / "mm.ps1"
                    ps1_text = (
                        "Param([Parameter(ValueFromRemainingArguments=$true)] $args)\n"
                        f'$repo = "{repo}"\n'
                        "$venv = Join-Path $repo '.venv'\n"
                        "$exe = Join-Path $venv 'Scripts\\mm.exe'\n"
                        "if (Test-Path $exe) { & $exe @args; exit $LASTEXITCODE }\n"
                        "$py = Join-Path $venv 'Scripts\\python.exe'\n"
                        "if (Test-Path $py) {\n"
                        "  if ($env:MM_DEBUG) {\n"
                        '    Write-Host "MM_DEBUG: diagnostics" -ForegroundColor Yellow\n'
                        "    & $py -c \"import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);\"\n"
                        "  }\n"
                        "  & $py -m scripts.cli_entry @args; exit $LASTEXITCODE\n"
                        "}\n"
                        "python -m scripts.cli_entry @args\n"
                    )
                    if mm_ps1.exists():
                        bak = mm_ps1.with_suffix(f".bak{int(time.time())}")
                        mm_ps1.replace(bak)
                    mm_ps1.write_text(ps1_text, encoding="utf-8")

                    # Attempt to add user_bin to the user's PATH if it's not present.
                    try:
                        cur = os.environ.get("PATH", "")
                        str_bin = str(user_bin)
                        if str_bin not in cur:
                            ps_cmd = (
                                "$bin = '{bin}';"
                                "$cur = [Environment]::GetEnvironmentVariable('PATH','User');"
                                "if ($cur -notlike \"*$bin*\") {[Environment]::SetEnvironmentVariable('PATH', ($bin + ';' + ($cur -ne $null ? $cur : '')), 'User')}"
                            ).format(bin=str_bin.replace("\\",
                                                         "\\\\"))
                            subprocess.run(
                                ["powershell",
                                 "-NoProfile",
                                 "-Command",
                                 ps_cmd],
                                check=False
                            )
                    except Exception:
                        pass

                    if not args.quiet:
                        print(f"Installed global launchers to: {user_bin}")

                else:
                    # POSIX
                    user_bin = Path(
                        os.environ.get("XDG_BIN_HOME",
                                       str(home / ".local/bin"))
                    )
                    user_bin.mkdir(parents=True, exist_ok=True)

                    mm_sh = user_bin / "mm"
                    sh_text = (
                        "#!/usr/bin/env bash\n"
                        "set -e\n"
                        f'REPO="{repo}"\n'
                        "# Prefer git top-level when available to avoid embedding a parent path.\n"
                        "if command -v git >/dev/null 2>&1; then\n"
                        '  gitroot=$(git -C "$REPO" rev-parse --show-toplevel 2>/dev/null || true)\n'
                        '  if [ -n "$gitroot" ]; then\n'
                        '    REPO="$gitroot"\n'
                        "  fi\n"
                        "fi\n"
                        "# If git not available or didn't resolve, walk up from CWD to find a project root.\n"
                        'if [ ! -f "$REPO/CLI.py" ] && [ ! -f "$REPO/pyproject.toml" ] && [ ! -f "$REPO/scripts/pyproject.toml" ]; then\n'
                        '  CUR="$(pwd -P)"\n'
                        '  while [ "$CUR" != "/" ] && [ "$CUR" != "" ]; do\n'
                        '    if [ -f "$CUR/CLI.py" ] || [ -f "$CUR/pyproject.toml" ] || [ -f "$CUR/scripts/pyproject.toml" ]; then\n'
                        '      REPO="$CUR"\n'
                        "      break\n"
                        "    fi\n"
                        '    CUR="$(dirname "$CUR")"\n'
                        "  done\n"
                        "fi\n"
                        'VENV="$REPO/.venv"\n'
                        "# Debug mode: set MM_DEBUG=1 to print repository, venv, and import diagnostics\n"
                        'if [ -n "${MM_DEBUG:-}" ]; then\n'
                        '  echo "MM_DEBUG: diagnostics" >&2\n'
                        '  echo "Resolved REPO: $REPO" >&2\n'
                        '  echo "Resolved VENV: $VENV" >&2\n'
                        '  echo "VENV exists: $( [ -d "$VENV" ] && echo yes || echo no )" >&2\n'
                        '  echo "Candidates:" >&2\n'
                        '  echo "  VENV/bin/mm: $( [ -x "$VENV/bin/mm" ] && echo yes || echo no )" >&2\n'
                        '  echo "  VENV/bin/python3: $( [ -x "$VENV/bin/python3" ] && echo yes || echo no )" >&2\n'
                        '  echo "  VENV/bin/python: $( [ -x "$VENV/bin/python" ] && echo yes || echo no )" >&2\n'
                        '  echo "  system python3: $(command -v python3 || echo none)" >&2\n'
                        '  echo "  system python: $(command -v python || echo none)" >&2\n'
                        '  for pycmd in "$VENV/bin/python3" "$VENV/bin/python" "$(command -v python3 2>/dev/null)" "$(command -v python 2>/dev/null)"; do\n'
                        '    if [ -n "$pycmd" ] && [ -x "$pycmd" ]; then\n'
                        '      echo "---- Testing with: $pycmd ----" >&2\n'
                        "      $pycmd - <<'PY'\nimport sys, importlib, traceback, importlib.util\nprint('sys.executable:', sys.executable)\nprint('sys.path (first 8):', sys.path[:8])\nfor mod in ('CLI','medeia_macina','scripts.cli_entry'):\n    try:\n        spec = importlib.util.find_spec(mod)\n        print(mod, 'spec:', spec)\n        if spec:\n            m = importlib.import_module(mod)\n            print(mod, 'loaded at', getattr(m, '__file__', None))\n    except Exception:\n        print(mod, 'import failed')\n        traceback.print_exc()\nPY\n"
                        "    fi\n"
                        "  done\n"
                        '  echo "MM_DEBUG: end diagnostics" >&2\n'
                        "fi\n"
                        "# Packaged console script in the venv if available\n"
                        'if [ -x "$VENV/bin/mm" ]; then\n'
                        '  exec "$VENV/bin/mm" "$@"\n'
                        "fi\n"
                        "# Prefer venv's python3, then venv's python\n"
                        'if [ -x "$VENV/bin/python3" ]; then\n'
                        '  exec "$VENV/bin/python3" -m scripts.cli_entry "$@"\n'
                        "fi\n"
                        'if [ -x "$VENV/bin/python" ]; then\n'
                        '  exec "$VENV/bin/python" -m scripts.cli_entry "$@"\n'
                        "fi\n"
                        "# Fallback to system python3, then system python (only if it's Python 3)\n"
                        "if command -v python3 >/dev/null 2>&1; then\n"
                        '  exec python3 -m scripts.cli_entry "$@"\n'
                        "fi\n"
                        "if command -v python >/dev/null 2>&1; then\n"
                        "  if python -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)'; then\n"
                        '    exec python -m scripts.cli_entry "$@"\n'
                        "  fi\n"
                        "fi\n"
                        "echo 'Error: no suitable Python 3 interpreter found. Please install Python 3 or use the venv.' >&2\n"
                        "exit 127\n"
                    )
                    if mm_sh.exists():
                        bak = mm_sh.with_suffix(f".bak{int(time.time())}")
                        mm_sh.replace(bak)
                    mm_sh.write_text(sh_text, encoding="utf-8")
                    mm_sh.chmod(mm_sh.stat().st_mode | 0o111)

                    # Ensure the user's bin is on PATH for future sessions by adding to ~/.profile
                    cur_path = os.environ.get("PATH", "")
                    if str(user_bin) not in cur_path:
                        profile = home / ".profile"
                        snippet = (
                            "# Added by Medeia-Macina setup: ensure user local bin is on PATH\n"
                            'if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then\n'
                            '  PATH="$HOME/.local/bin:$PATH"\n'
                            "fi\n"
                        )
                        try:
                            txt = profile.read_text() if profile.exists() else ""
                            if snippet.strip() not in txt:
                                with profile.open("a", encoding="utf-8") as fh:
                                    fh.write("\n" + snippet)
                        except Exception:
                            pass

                    if not args.quiet:
                        print(f"Installed global launcher to: {mm_sh}")

            except Exception as exc:  # pragma: no cover - best effort
                print(f"Failed to install global shims: {exc}", file=sys.stderr)

        _install_user_shims(repo_root)

        print("Setup complete.")
        return 0

    except subprocess.CalledProcessError as exc:
        print(
            f"Error: command failed with exit {exc.returncode}: {exc}",
            file=sys.stderr
        )
        return int(exc.returncode or 1)
    except Exception as exc:  # pragma: no cover - defensive
        print(f"Unexpected error: {exc}", file=sys.stderr)
        return 2


if __name__ == "__main__":
    raise SystemExit(main())
