#!/usr/bin/env python3
"""Script discovery + selection helpers for Enter_PowerBi_Selector."""

from __future__ import annotations

import json
from pathlib import Path
from typing import List, Sequence, Tuple


DEFAULT_SELECTION_FILE = "config/selector_defaults.json"
HIDE_FOLDER_MARKER = ".hide_in_selector"


def find_py_scripts(
    base_dir: Path,
    hide_folders: Sequence[str],
) -> List[Path]:
    """Return *.py files in base_dir and exactly one level below."""
    base_dir = base_dir.resolve()
    scripts: List[Path] = []

    scripts.extend(sorted(base_dir.glob("*.py")))

    hidden = {name.strip().lower() for name in hide_folders if name.strip()}
    for subdir in sorted([d for d in base_dir.iterdir() if d.is_dir()]):
        name = subdir.name
        if name.lower() in hidden:
            continue
        if name.startswith((".", "_")):
            continue
        if (subdir / HIDE_FOLDER_MARKER).exists():
            continue
        scripts.extend(sorted(subdir.glob("*.py")))

    seen = set()
    out: List[Path] = []
    for p in scripts:
        rp = p.resolve()
        if rp in seen:
            continue
        seen.add(rp)
        out.append(p)
    return out


def get_displayable_scripts(
    base_dir: Path,
    this_file: Path,
    hide_script_names: Sequence[str],
    hide_script_name_contains: Sequence[str],
    hide_folders: Sequence[str],
    open_powerbi_filename: str = "Open_PowerBi.py",
) -> List[Path]:
    """Get scripts eligible for selection/execution."""
    base_dir = base_dir.resolve()
    this_file = this_file.resolve()
    open_powerbi = (base_dir / open_powerbi_filename).resolve()

    scripts = find_py_scripts(base_dir, hide_folders=hide_folders)

    hide_exact = {n.strip().lower() for n in hide_script_names if n.strip()}
    hide_contains = [t.strip().lower() for t in hide_script_name_contains if t.strip()]

    filtered: List[Path] = []
    for s in scripts:
        if s.resolve() in {this_file, open_powerbi}:
            continue
        if s.name.lower() == "__init__.py":
            continue
        if s.name.lower() in hide_exact:
            continue
        if any(t in s.name.lower() for t in hide_contains):
            continue
        filtered.append(s)
    return filtered


def load_saved_selection(base_dir: Path, filename: str = DEFAULT_SELECTION_FILE) -> Tuple[List[str], bool]:
    """Load saved selected script paths (repo-relative) + keep_open setting."""
    path = (base_dir.resolve() / filename).resolve()
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        rel = [str(x).strip() for x in (data.get("selected_scripts") or []) if str(x).strip()]
        keep_open = bool(data.get("keep_powerbi_open", False))
        return rel, keep_open
    except Exception:
        return [], False


def save_selection(
    base_dir: Path,
    selected_scripts: Sequence[Path],
    keep_open: bool,
    filename: str = DEFAULT_SELECTION_FILE,
) -> Path:
    """Persist selected scripts (repo-relative) + keep_open setting."""
    base_dir = base_dir.resolve()
    path = (base_dir / filename).resolve()
    path.parent.mkdir(parents=True, exist_ok=True)

    rel_paths = [p.resolve().relative_to(base_dir).as_posix() for p in selected_scripts]
    payload = {
        "selected_scripts": rel_paths,
        "keep_powerbi_open": bool(keep_open),
    }
    path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    return path


def choose_scripts_gui(
    base_dir: Path,
    scripts: Sequence[Path],
    default_selected_rel: Sequence[str] | None = None,
    default_keep_open: bool = False,
) -> Tuple[List[Path], bool, bool]:
    """Interactive GUI to select and order scripts."""
    try:
        import tkinter as tk  # type: ignore
        from tkinter import messagebox  # type: ignore  # noqa: F401
    except ImportError as exc:  # pragma: no cover
        raise RuntimeError(
            "tkinter is required for the GUI. On Ubuntu/Mint: sudo apt install python3-tk\n"
            "(or run with --headless to avoid the GUI)."
        ) from exc

    selected_rel = set(default_selected_rel or [])
    base_dir = base_dir.resolve()
    scripts = list(scripts)

    root = tk.Tk()
    root.title("PowerBI: Configure default scripts")
    root.minsize(640, 450)

    selected: List[Path] = []
    cancelled = True
    keep_open_var = tk.BooleanVar(value=bool(default_keep_open))

    container = tk.Frame(root)
    container.pack(fill=tk.BOTH, expand=True)

    canvas = tk.Canvas(container, borderwidth=0)
    scrollbar = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
    scroll_frame = tk.Frame(canvas)

    scroll_frame.bind(
        "<Configure>",
        lambda _e: canvas.configure(scrollregion=canvas.bbox("all")),
    )
    canvas.create_window((0, 0), window=scroll_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar.set)

    canvas.pack(side="left", fill=tk.BOTH, expand=True)
    scrollbar.pack(side="right", fill=tk.Y)

    row_items: List[Tuple[object, object, Path]] = []

    def refresh_order() -> None:
        for frame, _, _ in row_items:
            frame.pack_forget()
        for frame, _, _ in row_items:
            frame.pack(anchor="w", padx=10, pady=2, fill=tk.X)

    def move_up(frame_obj: object) -> None:
        idx = next((i for i, (frm, _, _) in enumerate(row_items) if frm is frame_obj), -1)
        if idx > 0:
            row_items[idx - 1], row_items[idx] = row_items[idx], row_items[idx - 1]
            refresh_order()

    def move_down(frame_obj: object) -> None:
        idx = next((i for i, (frm, _, _) in enumerate(row_items) if frm is frame_obj), -1)
        if 0 <= idx < len(row_items) - 1:
            row_items[idx + 1], row_items[idx] = row_items[idx], row_items[idx + 1]
            refresh_order()

    if not scripts:
        tk.Label(
            scroll_frame,
            text="No other .py scripts found in this folder (or one level down).",
            anchor="w",
            justify="left",
        ).pack(anchor="w", padx=10, pady=10)
    else:
        for s in scripts:
            rel = s.relative_to(base_dir).as_posix()
            checked = rel in selected_rel
            var = tk.BooleanVar(value=checked)

            row = tk.Frame(scroll_frame)
            tk.Checkbutton(row, text=rel, variable=var).pack(side="left", anchor="w")
            spacer = tk.Frame(row)
            spacer.pack(side="left", expand=True, fill=tk.X)
            tk.Button(row, text="↑", width=2, command=lambda frame=row: move_up(frame)).pack(
                side="right", padx=(2, 0)
            )
            tk.Button(row, text="↓", width=2, command=lambda frame=row: move_down(frame)).pack(
                side="right"
            )

            row.pack(anchor="w", padx=10, pady=2, fill=tk.X)
            row_items.append((row, var, s))

    bottom = tk.Frame(root)
    bottom.pack(fill=tk.X, padx=10, pady=10)

    tk.Label(
        bottom,
        text=(
            "This screen configures your default script list.\n"
            "After saving, normal runs use these defaults automatically.\n\n"
            "Flow:\n"
            "  1) Start Open_PowerBi.py\n"
            "  2) Wait for powerbi_session.json + reachable geckodriver port\n"
            "  3) Run selected scripts one-by-one in the order shown above\n"
            "  4) Close Open_PowerBi + Firefox when done (unless you keep it open)\n"
        ),
        justify="left",
        anchor="w",
    ).pack(anchor="w")

    tk.Checkbutton(
        bottom,
        text="Keep Power BI open when finished",
        variable=keep_open_var,
    ).pack(anchor="w", pady=(8, 0))

    btn_row = tk.Frame(bottom)
    btn_row.pack(fill=tk.X, pady=(10, 0))

    def select_all(val: bool) -> None:
        for _, var, _ in row_items:
            var.set(val)

    def on_run() -> None:
        nonlocal selected, cancelled
        selected = [path for _, var, path in row_items if bool(var.get())]
        cancelled = False
        root.destroy()

    def on_cancel() -> None:
        nonlocal cancelled
        selected.clear()
        cancelled = True
        root.destroy()

    tk.Button(btn_row, text="Select All", command=lambda: select_all(True)).pack(side=tk.LEFT)
    tk.Button(btn_row, text="Select None", command=lambda: select_all(False)).pack(
        side=tk.LEFT, padx=(6, 0)
    )
    tk.Button(btn_row, text="Cancel", command=on_cancel).pack(side=tk.RIGHT)
    tk.Button(btn_row, text="Run", command=on_run).pack(side=tk.RIGHT, padx=(0, 6))

    root.mainloop()
    return selected, bool(keep_open_var.get()), bool(cancelled)
