"""DataTools desktop launcher.

This is the entry point PyInstaller wraps for Mac / Windows / Linux
installers. Double-clicking the produced binary boots a local
Streamlit server (``127.0.0.1:<random-free-port>``), opens the user's
default browser at that URL, and keeps the server alive until the
window is closed or the binary is killed.

Why a launcher instead of pointing PyInstaller at ``src/gui/app.py``:

  * Streamlit's CLI normally bootstraps the server via the
    ``streamlit run`` command. PyInstaller-bundled apps can't shell
    out to ``streamlit`` because the CLI script lives inside the
    bundle. We invoke Streamlit's bootstrap directly via
    :func:`streamlit.web.bootstrap.run`.
  * A free port has to be picked at runtime — buyers will have other
    services running on 8501.
  * The "open browser" step is the buyer's only feedback that
    something happened; without it they'd see a black terminal flash
    on Windows and conclude the app didn't start.

Local-dev equivalent (no installer):

    streamlit run src/gui/app.py
"""

from __future__ import annotations

import os
import socket
import sys
import threading
import time
import webbrowser
from pathlib import Path


def _find_free_port(start: int = 8501, span: int = 50) -> int:
    """Return a TCP port that's free on the loopback interface.

    Prefer 8501 (Streamlit's traditional default — buyer recognises
    the URL from any docs they've read) and fall back to the next
    free port in a small range. We don't fall back to OS-allocated
    (port=0) because the buyer's URL should look stable across
    restarts within one session.
    """
    for offset in range(span):
        port = start + offset
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(("127.0.0.1", port))
                return port
            except OSError:
                continue
    # Last resort: kernel-assigned ephemeral port.
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("127.0.0.1", 0))
        return s.getsockname()[1]


def _resolve_app_path() -> Path:
    """Locate ``src/gui/app.py`` whether running from source or a frozen bundle.

    PyInstaller's ``onefile`` mode unpacks resources into a temp
    directory pointed at by ``sys._MEIPASS``. Bundled mode uses that
    directory; source mode walks up from this file.
    """
    if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
        # Frozen: app.py was bundled as a data file (see datatools.spec).
        return Path(sys._MEIPASS) / "src" / "gui" / "app.py"  # type: ignore[attr-defined]
    return Path(__file__).resolve().parent.parent / "src" / "gui" / "app.py"


def _open_browser_when_ready(url: str, delay: float = 1.5) -> None:
    """Open the buyer's default browser to *url* after a short delay.

    The delay gives Streamlit's HTTP server time to bind. Without it,
    the browser races the server and renders a "couldn't connect"
    page that confuses non-technical buyers. 1.5 s is conservative
    on slow Windows machines; faster machines will see a brief
    blank tab.
    """
    def _open() -> None:
        time.sleep(delay)
        webbrowser.open(url, new=2)
    threading.Thread(target=_open, daemon=True).start()


def main() -> int:
    """Boot the local Streamlit server and open the browser."""
    app_path = _resolve_app_path()
    if not app_path.exists():
        sys.stderr.write(
            f"DataTools could not find its UI script at {app_path}.\n"
            "This is usually a bundle-build error. Re-install or "
            "contact support@datatools.app.\n"
        )
        return 2

    port = _find_free_port()
    url = f"http://127.0.0.1:{port}/"

    # Pre-set Streamlit options the bundle ships locked. ``server.address``
    # = 127.0.0.1 enforces "no network exposure" — Streamlit's default
    # is 0.0.0.0 which would expose the GUI to the LAN. The privacy
    # claim on the landing pages depends on this.
    os.environ.setdefault("STREAMLIT_SERVER_ADDRESS", "127.0.0.1")
    os.environ.setdefault("STREAMLIT_SERVER_PORT", str(port))
    os.environ.setdefault("STREAMLIT_SERVER_HEADLESS", "true")
    os.environ.setdefault("STREAMLIT_BROWSER_GATHER_USAGE_STATS", "false")

    # Print before opening the browser so the terminal log doesn't
    # scroll behind the new browser tab on macOS.
    print(f"DataTools is running at {url}")
    print("Close this window or press Ctrl+C to stop.")

    _open_browser_when_ready(url)

    # Streamlit's bootstrap entry point — equivalent to running
    # ``streamlit run app.py`` but in-process so PyInstaller's bundled
    # interpreter handles it without shelling out to a separate script.
    from streamlit.web import bootstrap
    bootstrap.run(
        str(app_path),
        is_hello=False,
        args=[],
        flag_options={
            "server.address": "127.0.0.1",
            "server.port": port,
            "server.headless": True,
            "browser.gatherUsageStats": False,
        },
    )
    return 0


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