# SPDX-License-Identifier: GPL-2.0-only
#
# llamachat - a small native chat client for a local llama.cpp router
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
"""Finding the interpreter that has PySide6.

PySide6 is usually installed in a virtualenv rather than the system Python,
so a script started with `#!/usr/bin/env python3` has to hand over to that
interpreter. Keeping the search here means the launcher and the test script
agree on where to look, and neither hardcodes a path.

`LLAMACHAT_PYTHON` names an interpreter explicitly. `LLAMACHAT_NO_REEXEC`
disables the handover, which is also how the re-executed process avoids
looping.
"""

import os
import sys
from pathlib import Path

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

# Checked in order; the first interpreter that exists wins.
CANDIDATES = (
    HERE / ".venv" / "bin" / "python3",
    HERE / "venv" / "bin" / "python3",
    Path.home() / ".local" / "share" / "llamachat-venv" / "bin" / "python3",
)


def have_pyside() -> bool:
    try:
        import PySide6  # noqa: F401
    except ImportError:
        return False
    return True


def find_interpreter() -> Path | None:
    """The first candidate interpreter that exists and is not this one.

    Paths are compared unresolved on purpose. A venv created with
    --system-site-packages has a bin/python3 that symlinks to the system
    interpreter, so resolving both sides would make the venv look identical
    to the interpreter we are trying to escape. What differs is sys.prefix,
    which follows from the path used to start it, not from the real binary.
    """
    override = os.environ.get("LLAMACHAT_PYTHON")
    candidates = [Path(override)] if override else list(CANDIDATES)
    current = Path(sys.executable)
    for candidate in candidates:
        if candidate.is_file() and candidate != current:
            return candidate
    return None


def reexec(script: str) -> None:
    """Restart `script` under an interpreter that has PySide6.

    Returns normally when the current interpreter is already usable, when
    the handover is disabled, or when no other interpreter was found; in
    that last case the caller fails later with a clear ImportError.
    """
    if os.environ.get("LLAMACHAT_NO_REEXEC") or have_pyside():
        return
    interpreter = find_interpreter()
    if interpreter is None:
        return
    # Set before exec so the new process cannot bounce again.
    os.environ["LLAMACHAT_NO_REEXEC"] = "1"
    path = str(Path(script).resolve())
    os.execv(str(interpreter), [str(interpreter), path] + sys.argv[1:])
