from typing import Any, cast
import webview
from instaui.internal.backend.base import BaseBackend
from instaui.internal.backend.common import python_handler_utils
from instaui.internal.backend.invocation import BackendInvocation, BackendInvocationKind
from instaui.internal.runtime.webview.config import WebViewUserLaunchConfig
from instaui.internal.runtime.webview.options import WebViewUserRunOptions
from instaui.protocol.invocation.spec import ComputedSpec, EventSpec
from .watch_handler import register_watch_handler
from .event_handler import register_event_handler
from . import api


class WebViewBackend(BaseBackend):
    def __init__(self, config: WebViewUserLaunchConfig) -> None:
        self.launch_config = config

    def create_window(
        self,
        *,
        page_url: str,
        url: str,
    ):
        """Create a new window. Returns the window object of pywebview."""
        filtered_config = self._get_filtered_window_config(page_url)

        window = webview.create_window(
            self.launch_config.title,
            url,
            js_api=api.Api(self.launch_config.async_executor),
            **filtered_config,
        )
        assert window is not None, "Failed to create window"

        if self.launch_config.on_page_mounted:

            def on_app_mounted():
                self.launch_config.on_page_mounted(window)  # type: ignore

            window.expose(on_app_mounted)

        return window

    def run(self, run_options: WebViewUserRunOptions):
        webview.start(**run_options.webview_start_args, debug=self.launch_config.debug)

    def _run_with_index(
        self, index_url: str, run_options: WebViewUserRunOptions | None
    ):
        webview.create_window(
            self.launch_config.title,
            index_url,
            js_api=api.Api(self.launch_config.async_executor),
        )

        opts = {}
        if run_options:
            opts.update(run_options.webview_start_args)

        opts.update({"debug": self.launch_config.debug})

        webview.start(**opts)

    def register_invocation(self, ref_id: int, invocation: BackendInvocation) -> Any:
        route = invocation.render_ctx.route
        assert route is not None, "route must be set in render_ctx for invocation"

        if (
            invocation.kind == BackendInvocationKind.COMPUTED
            or invocation.kind == BackendInvocationKind.WATCH
        ):
            spec = cast(ComputedSpec, invocation.spec)

            key = python_handler_utils.create_handler_key(
                route, invocation.fn, self.launch_config.debug, extra_key=spec.extra_key
            )
            register_watch_handler(
                key,
                invocation.fn,
                spec.outputs_binding_count,
                spec.custom_type_adapter_map,
            )

            return key

        if invocation.kind == BackendInvocationKind.EVENT:
            spec = cast(EventSpec, invocation.spec)

            key = python_handler_utils.create_handler_key(
                route, invocation.fn, self.launch_config.debug, extra_key=spec.extra_key
            )

            register_event_handler(
                key,
                invocation.fn,
                spec.outputs_binding_count,
                spec.dataset_input_indexs,
                spec.custom_type_adapter_map,
            )

            return key

        if invocation.kind == BackendInvocationKind.FILE_UPLOAD:
            raise NotImplementedError
            # file_upload_router.register_upload_file_handler(
            #     key, invocation.fn, self.endpoints.UPLOAD_URL_WITH_PREFIX
            # )

            # return f"{self.endpoints.UPLOAD_URL_WITH_PREFIX}?hkey={key}"

    def _get_filtered_window_config(self, url: str) -> dict[str, Any]:
        """
        Gets and returns the filtered window configuration dictionary based on the given URL.

        This method looks up the corresponding configuration from the `window_config`
        in `launch_config` based on the provided URL. It first applies the global
        configuration specified under the 'default' key, then overrides it with any
        specific configuration found for the exact URL path. Finally, it removes
        all core parameters that are not allowed to be set by the user, ensuring
        the safety of window creation.

        Args:
            url (str): The URL path for the window, e.g., '/home' or '/settings'.
                    This path is used for an exact match lookup within `window_config`.

        Returns:
            dict[str, Any]: A dictionary containing all valid and filtered window
                            configuration parameters, ready to be passed directly
                            to the `pywebview.create_window` function.
        """
        if not self.launch_config.window_config:
            return {}

        config_to_use = {}

        # Apply "default" global configuration
        default_config = self.launch_config.window_config.get("default", {})
        config_to_use.update(default_config)

        # Load specific configuration based on the URL, overriding the default
        specific_config = self.launch_config.window_config.get(url, {})
        config_to_use.update(specific_config)

        # Filter out core parameters that the user is not allowed to set
        blocked_params = {
            "title",
            "url",
            "js_api",
            "server",
            "http_port",
            "server_args",
        }

        filtered_config = {}
        for key, value in config_to_use.items():
            if key not in blocked_params:
                filtered_config[key] = value

        return filtered_config
