from __future__ import annotations

"""
Tkinter GUI wrapper around the new processing pipeline.

Flow:
1) The user selects the Dartdoc folder (doc/api).
2) The user chooses where the cleaned ZIP archive should be saved.
3) The GUI launches `run_pipeline` in a background thread
   (the window stays responsive; no additional Python processes are spawned).
4) When processing finishes, the GUI displays either a success or error message.
"""
import threading
from pathlib import Path
import tkinter as tk
from tkinter import ttk, filedialog, messagebox

from dartdoc_advanced.pipeline.runner import run_pipeline

# Default thresholds for stub detection used by the GUI controls.
DEFAULT_MIN_CHARS = 350
DEFAULT_TINY_CHARS = 150


class App(tk.Tk):
    """
    Simple Tkinter main window for invoking the cleaning pipeline.

    The GUI is intentionally minimal:
    - lets the user pick the Dartdoc source folder (doc/api),
    - lets the user choose where to save the resulting ZIP file,
    - exposes two numeric fields to tune stub detection thresholds,
    - runs the heavy pipeline work in a background thread so the UI does not freeze.
    """

    def __init__(self) -> None:
        super().__init__()
        self.title("dartdoc advanced - cleaner")
        self.resizable(False, False)

        # StringVar and IntVar instances hold current form state and allow
        # automatic UI updates when values change.
        self.var_src = tk.StringVar()
        self.var_min_chars = tk.IntVar(value=DEFAULT_MIN_CHARS)
        self.var_tiny_chars = tk.IntVar(value=DEFAULT_TINY_CHARS)
        self.var_status = tk.StringVar(value="Ready.")

        # Reference to the main "Generate ZIP" button so that it can be enabled
        # or disabled depending on whether a background job is running.
        self.btn_run: ttk.Button | None = None

        # Build and layout all UI controls.
        self._build_ui()

    # ───────────────────── UI ─────────────────────

    def _build_ui(self) -> None:
        """
        Construct the main form layout and wire up basic interactions.

        The layout contains:
        - an entry and browse button for selecting the Dartdoc folder,
        - two spinboxes for stub detection thresholds,
        - a main button to start the pipeline,
        - a status label that reports the current state.
        """
        pad = 8
        frm = ttk.Frame(self, padding=pad)
        frm.grid(row=0, column=0, sticky="nsew")

        # Dartdoc source directory selector (usually doc/api).
        ttk.Label(frm, text="Dartdoc folder (doc/api):").grid(
            row=0, column=0, columnspan=2, sticky="w"
        )
        entry_src = ttk.Entry(frm, textvariable=self.var_src, width=50)
        entry_src.grid(row=1, column=0, columnspan=2, sticky="we", pady=(0, pad))
        ttk.Button(frm, text="Browse…", command=self._choose_src).grid(
            row=1, column=2, sticky="e", padx=(pad, 0)
        )

        # Stub thresholds controls.
        ttk.Label(frm, text="min chars (sidebar/library):").grid(
            row=2, column=0, sticky="w"
        )
        ttk.Spinbox(
            frm, from_=0, to=10000, textvariable=self.var_min_chars, width=8
        ).grid(row=2, column=1, sticky="w")

        ttk.Label(frm, text="tiny chars (other stubs):").grid(
            row=3, column=0, sticky="w"
        )
        ttk.Spinbox(
            frm, from_=0, to=10000, textvariable=self.var_tiny_chars, width=8
        ).grid(row=3, column=1, sticky="w")

        # Main action button that triggers the background pipeline run.
        self.btn_run = ttk.Button(frm, text="Generate ZIP...", command=self._run_clicked)
        self.btn_run.grid(
            row=4, column=0, columnspan=3, pady=(pad, pad), sticky="we"
        )

        # Status label used to communicate current progress or result to the user.
        ttk.Label(frm, textvariable=self.var_status).grid(
            row=5, column=0, columnspan=3, sticky="w"
        )

    # ───────────────────── Actions ─────────────────────

    def _choose_src(self) -> None:
        """
        Show a directory picker dialog and store the selected path
        into the source folder text field if the user makes a choice.
        """
        path = filedialog.askdirectory(title="Choose dartdoc folder (doc/api)")
        if path:
            self.var_src.set(path)

    def _run_clicked(self) -> None:
        """
        Handle the "Generate ZIP" button click.

        Workflow:
        1) Validate the Dartdoc source directory.
        2) Ask the user where to save the resulting ZIP file.
        3) Read the threshold values from the spinboxes.
        4) Start a background thread that calls run_pipeline.
        5) Update status label and re-enable the UI when done.
        """
        # 1) Validate source directory.
        src = Path(self.var_src.get()).expanduser().resolve()
        if not src.exists():
            messagebox.showerror(
                "Invalid source", "Please choose an existing dartdoc folder."
            )
            return

        # 2) Save As - where to store the resulting ZIP archive.
        dest = filedialog.asksaveasfilename(
            title="Save cleaned docs as ZIP",
            defaultextension=".zip",
            filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")],
        )
        if not dest:
            # User cancelled the save dialog.
            return

        # run_pipeline expects a base path without the `.zip` suffix.
        zip_base = Path(dest).with_suffix("")
        min_chars = self.var_min_chars.get()
        tiny_chars = self.var_tiny_chars.get()

        # 3) Mark the UI as busy and update the status label.
        self._set_running(True)
        self.var_status.set("Running...")

        # Worker function executed in a background thread.
        # It performs the heavy pipeline work outside the main GUI loop.
        def worker() -> None:
            try:
                zip_path = run_pipeline(src, zip_base, min_chars, tiny_chars)
            except Exception as exc:  # noqa: BLE001 - show error to the user explicitly
                # All UI updates must be scheduled via 'after' to run on the main thread.
                self.after(0, lambda: self._on_error(exc))
            else:
                self.after(0, lambda: self._on_success(zip_path))
            finally:
                # Re-enable controls regardless of success or failure.
                self.after(0, lambda: self._set_running(False))

        # Launch the worker in a daemon thread so the process can exit cleanly.
        threading.Thread(target=worker, daemon=True).start()

    # ───────────────────── Helpers ─────────────────────

    def _set_running(self, running: bool) -> None:
        """
        Enable or disable the main action button based on the running state.

        When a background job is active, the button is disabled to prevent
        multiple concurrent pipeline runs and to give a clear user feedback.
        """
        state = "disabled" if running else "normal"
        if self.btn_run:
            self.btn_run.config(state=state)

    def _on_success(self, zip_path: Path) -> None:
        """
        Handle successful completion of the pipeline.

        - Updates the status label with the resulting ZIP path.
        - Shows an informational dialog with the ZIP location.
        """
        self.var_status.set(f"Done: {zip_path}")
        messagebox.showinfo("Done", f"ZIP created:\n{zip_path}")

    def _on_error(self, err: BaseException) -> None:
        """
        Handle errors that occur during the pipeline run.

        - Updates the status label to indicate an error.
        - Displays a message box with the exception message so that
          the user has some insight into what went wrong.
        """
        self.var_status.set("Error")
        messagebox.showerror("Error", f"Generation failed:\n{err}")


def main() -> None:
    """
    GUI entry point for compatibility with historical usage.

    Allows the module to be launched with:
        python -m dartdoc_advanced.gui.app
    """
    app = App()
    app.mainloop()


if __name__ == "__main__":
    main()
