#!/usr/bin/env python3
"""Instalátor Bábofkátoru s grafickým průběhem — bez viditelného Terminálu.

Spouští se **systémovým** Pythonem (3.9+), používá jen standardní knihovnu.
Vytvoří izolované prostředí v ~/Library/Application Support/Babofkator,
doinstaluje výpočetní knihovny a nakonec spustí aplikaci.

Když tkinter chybí, vrátí návratový kód 3 a spouštěč přepne na textovou
instalaci v Terminálu.
"""
from __future__ import annotations

import os
import queue
import subprocess
import sys
import threading
import time

VER = "0.4"
RES = os.path.dirname(os.path.abspath(__file__))
APP = os.path.abspath(os.path.join(RES, "..", ".."))
SUP = os.path.expanduser("~/Library/Application Support/Babofkator")
VENV = os.path.join(SUP, "venv")
VPY = os.path.join(VENV, "bin", "python")
LOG = os.path.join(SUP, "instalace.log")
REQ = os.path.join(RES, "app", "requirements.txt")

BG = "#12161d"
BG2 = "#1b212c"
FG = "#e6ecf5"
DIM = "#8b97a8"
ACC = "#4da3ff"
BAD = "#e05c4b"


# --------------------------------------------------------------------------- #
#  Vlastní výpočet instalace (běží ve vlákně)
# --------------------------------------------------------------------------- #
def _log(fh, *a):
    fh.write(" ".join(str(x) for x in a) + "\n")
    fh.flush()


def install(emit) -> str | None:
    """Vrátí None při úspěchu, jinak text chyby."""
    os.makedirs(SUP, exist_ok=True)
    with open(LOG, "w", encoding="utf-8") as fh:
        _log(fh, "Bábofkátor", VER, "— instalace", time.strftime("%Y-%m-%d %H:%M"))
        _log(fh, "systémový python:", sys.executable, sys.version)

        emit(4, "Připravuji izolované prostředí…", "nic mimo tuto složku se nemění")
        if not os.path.exists(VPY):
            r = subprocess.run([sys.executable, "-m", "venv", VENV],
                               capture_output=True, text=True)
            _log(fh, "venv rc =", r.returncode, r.stdout, r.stderr)
            if r.returncode != 0 or not os.path.exists(VPY):
                return ("Nepodařilo se vytvořit prostředí.\n"
                        + (r.stderr or r.stdout or "")[-400:])

        emit(12, "Aktualizuji instalátor balíčků…", "pip")
        subprocess.run([VPY, "-m", "pip", "install", "--upgrade", "pip"],
                       capture_output=True, text=True)

        emit(18, "Stahuji výpočetní knihovny…", "numpy, scipy, flask, matplotlib")
        pkgs = {"numpy": 40, "scipy": 58, "flask": 68, "matplotlib": 78,
                "pillow": 74, "contourpy": 72, "werkzeug": 66}
        proc = subprocess.Popen([VPY, "-m", "pip", "install", "-r", REQ],
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT, text=True,
                                bufsize=1)
        pct = 18
        for line in proc.stdout:
            _log(fh, line.rstrip())
            low = line.lower()
            for name, target in pkgs.items():
                if name in low and ("collecting" in low or "downloading" in low):
                    pct = max(pct, target)
                    emit(pct, "Stahuji výpočetní knihovny…", line.strip()[:70])
            if "installing collected packages" in low:
                pct = max(pct, 82)
                emit(pct, "Rozbaluji a instaluji…", "chvilku to potrvá")
        proc.wait()
        if proc.returncode != 0:
            return ("Instalace knihoven selhala.\n"
                    "Podrobnosti jsou v souboru instalace.log.")

        emit(88, "Doplňuji nativní okno aplikace…", "pywebview (nepovinné)")
        r = subprocess.run([VPY, "-m", "pip", "install", "pywebview"],
                           capture_output=True, text=True)
        _log(fh, "pywebview rc =", r.returncode)

        emit(94, "Ověřuji instalaci…", "")
        env = dict(os.environ, PYTHONPATH=os.path.join(RES, "app"))
        r = subprocess.run(
            [VPY, "-c", "import babofkator,numpy,scipy,flask,matplotlib;"
                        "print(babofkator.__version__)"],
            capture_output=True, text=True, env=env)
        _log(fh, "overeni rc =", r.returncode, r.stdout, r.stderr)
        if r.returncode != 0:
            return "Ověření instalace selhalo.\n" + (r.stderr or "")[-400:]

        # pohodlný příkaz pro Terminál
        cli = os.path.join(SUP, "babofkator")
        with open(cli, "w", encoding="utf-8") as f:
            f.write("#!/bin/bash\n"
                    f'export PYTHONPATH="{os.path.join(RES, "app")}"\n'
                    f'exec "{VPY}" -m babofkator "$@"\n')
        os.chmod(cli, 0o755)

        open(os.path.join(SUP, f".nainstalovano-{VER}"), "w").close()
        emit(100, "Hotovo, spouštím Bábofkátor…", "")
        return None


# --------------------------------------------------------------------------- #
#  Okno s průběhem
# --------------------------------------------------------------------------- #
def main() -> int:
    try:
        import tkinter as tk
        from tkinter import ttk
    except Exception:
        return 3                       # spouštěč přepne na Terminál

    q: "queue.Queue[tuple]" = queue.Queue()

    root = tk.Tk()
    root.title("Bábofkátor — instalace")
    root.configure(bg=BG)
    w, h = 540, 302
    sw, sh = root.winfo_screenwidth(), root.winfo_screenheight()
    root.geometry(f"{w}x{h}+{(sw - w) // 2}+{(sh - h) // 3}")
    root.resizable(False, False)

    icon = None
    try:
        icon = tk.PhotoImage(file=os.path.join(RES, "icon128.png"))
    except Exception:
        pass

    head = tk.Frame(root, bg=BG)
    head.pack(fill="x", padx=26, pady=(24, 6))
    if icon is not None:
        tk.Label(head, image=icon, bg=BG).pack(side="left", padx=(0, 14))
    box = tk.Frame(head, bg=BG)
    box.pack(side="left", anchor="w")
    tk.Label(box, text="Bábofkátor", bg=BG, fg=FG,
             font=("Helvetica", 20, "bold")).pack(anchor="w")
    tk.Label(box, text="první spuštění · připravuji výpočetní jádro",
             bg=BG, fg=DIM, font=("Helvetica", 11)).pack(anchor="w")

    status = tk.Label(root, text="Startuji…", bg=BG, fg=FG,
                      font=("Helvetica", 12), anchor="w")
    status.pack(fill="x", padx=26, pady=(18, 4))

    style = ttk.Style(root)
    try:
        style.theme_use("clam")
    except Exception:
        pass
    style.configure("B.Horizontal.TProgressbar", troughcolor=BG2,
                    background=ACC, bordercolor=BG2, lightcolor=ACC,
                    darkcolor=ACC, thickness=8)
    bar = ttk.Progressbar(root, style="B.Horizontal.TProgressbar",
                          length=w - 52, mode="determinate", maximum=100)
    bar.pack(padx=26)

    detail = tk.Label(root, text="", bg=BG, fg=DIM, font=("Helvetica", 10),
                      anchor="w")
    detail.pack(fill="x", padx=26, pady=(8, 0))

    btns = tk.Frame(root, bg=BG)
    btns.pack(fill="x", padx=26, pady=(16, 0))

    state = {"target": 0.0, "shown": 0.0, "done": False, "err": None}

    def emit(pct, text, det=""):
        q.put((float(pct), text, det))

    def worker():
        try:
            err = install(emit)
        except Exception as exc:                              # noqa: BLE001
            err = f"Neočekávaná chyba: {exc}"
        q.put(("END", err))

    def open_log():
        subprocess.Popen(["open", "-t", LOG])

    def fail(msg):
        state["done"] = True
        status.configure(text="Instalace se nezdařila", fg=BAD)
        detail.configure(text=msg.strip().splitlines()[0][:78] +
                              "   (podrobnosti v protokolu)")
        tk.Button(btns, text="Zobrazit protokol", command=open_log,
                  highlightbackground=BG).pack(side="left")
        tk.Button(btns, text="Zavřít", command=root.destroy,
                  highlightbackground=BG).pack(side="right")

    def finish():
        state["done"] = True
        subprocess.Popen(["open", APP])
        root.after(700, root.destroy)

    def tick():
        try:
            while True:
                item = q.get_nowait()
                if item[0] == "END":
                    if item[1]:
                        fail(item[1])
                    else:
                        state["target"] = 100.0
                        root.after(500, finish)
                    return_ = True                      # noqa: F841
                else:
                    pct, text, det = item
                    state["target"] = pct
                    status.configure(text=text)
                    detail.configure(text=det)
        except queue.Empty:
            pass
        # plynulé dojíždění ukazatele, aby se pořád hýbal
        if not state["done"]:
            gap = state["target"] - state["shown"]
            state["shown"] += max(0.06, gap * 0.08) if gap > 0.2 else 0.0
            bar["value"] = min(state["shown"], 100.0)
        root.after(60, tick)

    threading.Thread(target=worker, daemon=True).start()
    root.after(60, tick)
    root.lift()
    root.attributes("-topmost", True)
    root.after(400, lambda: root.attributes("-topmost", False))
    try:
        root.focus_force()
    except Exception:
        pass
    root.mainloop()
    return 0


if __name__ == "__main__":
    sys.exit(main())
