import panel as pn
import os
from typing import Literal


def _request_google_token_in_browser(
    oauth_key: str,
    oauth_extra_params: dict | None = None,
) -> str:
    """Request a Google OAuth access token using the browser's OAuth2 implicit flow.

    Only works inside a Pyodide environment. Uses Google Identity Services
    to open a consent popup and return an access token.

    Parameters
    ----------
    oauth_key : str
        Google OAuth Client ID.
    oauth_extra_params : dict, optional
        Extra params; ``scope`` key is used if present.

    Returns
    -------
    str
        OAuth 2.0 access token.
    """
    from js import window, document  # pyright: ignore [reportMissingImports]
    from pyodide.ffi import create_once_callable  # pyright: ignore [reportMissingImports]
    import asyncio

    scope = "https://www.googleapis.com/auth/bigquery"
    if oauth_extra_params and "scope" in oauth_extra_params:
        scope = oauth_extra_params["scope"]

    loop = asyncio.get_event_loop()
    future = loop.create_future()

    def _on_token_response(response):
        future.set_result(response.access_token)

    # Load Google Identity Services script if not already present.
    if not hasattr(window, "google") or not hasattr(window.google, "accounts"):
        script = document.createElement("script")
        script.src = "https://accounts.google.com/gsi/client"
        script.async_ = True
        document.head.appendChild(script)
        # Wait for the script to load.
        load_future = loop.create_future()
        script.onload = create_once_callable(lambda *_: load_future.set_result(None))
        loop.run_until_complete(load_future)

    client = window.google.accounts.oauth2.initTokenClient(
        client_id=oauth_key,
        scope=scope,
        callback=create_once_callable(_on_token_response),
    )
    client.requestAccessToken()
    return loop.run_until_complete(future)


def share(
    view,
    __name__,
    __file__,
    sharer: Literal["pn.serve", "embed.html", "pyodide"] | int | None = 2,
    pyodide_kwargs={"runtime": "pyodide-worker", "prerender": False, "inline": False},
    panel_serve_kwargs={"port": 8000, "show": True},
    oauth_provider: str | None = None,
    oauth_key: str | None = None,
    oauth_secret: str | None = None,
    cookie_secret: str | None = None,
    oauth_encryption_key: str | None = None,
    oauth_extra_params: dict | None = None,
    oauth_refresh_tokens: bool = False,
    access_token: str | None = None,
):

    if __name__ == "__main__":
        if isinstance(sharer, int):
            sharer = ["pn.serve", "embed.html", "pyodide", None][sharer]
        if pn.state._is_pyodide:
            if access_token:
                pn.state.access_token = access_token
            elif oauth_key:
                pn.state.access_token = _request_google_token_in_browser(
                    oauth_key, oauth_extra_params,
                )
            return view.servable()
        elif sharer == "pyodide":
            import panel.io.convert
            import http.server
            import socketserver
            import webbrowser

            def start_http_server(port=8000, directory=".", show=True, fn=""):
                """
                Start an HTTP server to serve files from a specific directory.

                :param port: The port to serve on (default is 8000).
                :param directory: The directory to serve files from (default is the current directory).
                """
                # Change the working directory to the specified folder
                current_workdir = os.getcwd()
                os.chdir(directory)

                # Set up the HTTP server
                handler = http.server.SimpleHTTPRequestHandler
                with socketserver.TCPServer(("", port), handler) as httpd:
                    print(f"Serving HTTP on port {port} (http://localhost:{port}/) ...")
                    print(f"Serving files from directory: {os.path.abspath(directory)}")
                    try:
                        if show:
                            webbrowser.open(f"http://localhost:{port}/{fn}")
                        httpd.serve_forever()
                    except KeyboardInterrupt:
                        print("\nShutting down the server.")
                        httpd.server_close()
                        os.chdir(current_workdir)

            source_file = os.path.abspath(__file__)
            assert os.path.isfile(source_file), f"Can't find file `{__file__}`."
            workdir = os.path.join(os.path.split(__file__)[0], "dist")
            if not os.path.isdir(workdir):
                os.mkdir(workdir)
            _ = panel.io.convert.convert_app(
                source_file,
                dest_path=workdir,
                **pyodide_kwargs,
            )
            start_http_server(
                directory=workdir,
                fn=os.path.split(source_file)[1].replace(".py", ".html"),
            )
        elif sharer == "pn.serve":
            if "server" in globals():
                globals()["server"].stop()
            oauth_kwargs = {}
            if oauth_provider:
                oauth_kwargs["oauth_provider"] = oauth_provider
            if oauth_key:
                oauth_kwargs["oauth_key"] = oauth_key
            if oauth_secret:
                oauth_kwargs["oauth_secret"] = oauth_secret
            if cookie_secret:
                oauth_kwargs["cookie_secret"] = cookie_secret
            if oauth_encryption_key:
                oauth_kwargs["oauth_encryption_key"] = oauth_encryption_key
            if oauth_extra_params:
                oauth_kwargs["oauth_extra_params"] = oauth_extra_params
            if oauth_refresh_tokens:
                oauth_kwargs["oauth_refresh_tokens"] = oauth_refresh_tokens
            server: pn.io.server.Server = pn.serve(
                view, **panel_serve_kwargs, **oauth_kwargs,
            )
            globals()["server"] = server
        elif sharer == "embed.html":
            fn = os.path.split(os.path.abspath(__file__))[-1].replace(".py", ".html")
            view.save(fn, embed=True, max_opts=50, embed_json=False)
        else:
            ...
    else:
        return view.servable()
