from __future__ import annotations
from contextlib import contextmanager
import contextvars
from pathlib import Path
from typing import TYPE_CHECKING, Any, Hashable, Optional, Union
from instaui.internal.assets.component_dep import ComponentDependencyInfo
from instaui.internal.assets.css_assets import CssAsset, CssRole
from instaui.internal.assets.plugin import PluginDependencyInfo, PluginOptionsBuilder
from instaui.internal.assets.script_assets import ScriptTag
from instaui.internal.assets.style_assets import StyleTag
from instaui.internal.context import in_page_context
from .base import AssetsDeclaration

if TYPE_CHECKING:
    from instaui.internal.ui.element import Element

GLOBAL_ASSETS = AssetsDeclaration()
LOCAL_ASSETS: contextvars.ContextVar[Optional[AssetsDeclaration]] = (
    contextvars.ContextVar("local_assets", default=None)
)


@contextmanager
def enter_assets_context():
    token = LOCAL_ASSETS.set(AssetsDeclaration())
    try:
        yield
    finally:
        LOCAL_ASSETS.reset(token)


def get_active_assets() -> AssetsDeclaration:
    """
    Return the AssetsPool that should actually be written based on context.
    """
    if in_page_context():
        local = LOCAL_ASSETS.get()
        if local is None:
            local = AssetsDeclaration()
            LOCAL_ASSETS.set(local)
        return local
    else:
        return GLOBAL_ASSETS


def add_js_link(url: Union[str, Path]):
    pool = get_active_assets()
    pool.js_links.add(url)


def remove_js_link(url: Union[str, Path]):
    pool = get_active_assets()
    pool.js_links.discard(url)


def add_css_link(
    css: Union[Path, str],
    *,
    role: Union[str, CssRole] = CssRole.COMPONENT,
    namespace: Optional[Hashable] = None,
):
    """Add a CSS link to the HTML document."""
    role = CssRole(role) if isinstance(role, str) else role
    get_active_assets().css_links.add(css, namespace=namespace, role=role)


def remove_css_link(
    css: Union[Path, str, CssAsset],
    *,
    role: Union[str, CssRole] = CssRole.COMPONENT,
    namespace: Optional[Hashable] = None,
):
    role = CssRole(role) if isinstance(role, str) else role
    get_active_assets().css_links.remove(css, namespace=namespace, role=role)


def use_favicon(path: Path):
    """Set the favicon of the HTML document.

    Args:
        favicon (Path): The path to the favicon.
    """
    get_active_assets().favicon = path


def add_import_map(name: str, link: Union[str, Path]):
    """Add an import map to the HTML document."""
    get_active_assets().import_maps[name] = link


def register_plugin(
    name: str,
    esm: Path,
    *,
    externals: Optional[dict[str, Path]] = None,
    css: Optional[list[Union[str, Path, CssAsset]]] = None,
    options: Optional[dict | PluginOptionsBuilder] = None,
):
    """
    Register a plugin

    Args:
        name (str): Plugin name
        esm (Path): Path to plugin's ESM file. Format should match vue.js plugins
        externals (Optional[dict[str, Path]], optional): External dependencies for plugin JS implementation. Defaults to None.
        css (Optional[list[Path]], optional): List of CSS file paths for the plugin. Defaults to None.
        options (Optional[dict | PluginOptionsBuilder], optional): Plugin options. Defaults to None.

    Example:
    .. code-block:: python
        from instaui import custom

        # Basic usage
        custom.register_plugin("my-plugin", Path(__file__).parent / "my-plugin.esm.js")

        # With external dependencies
        custom.register_plugin(
            "my-plugin",
            Path(__file__).parent / "my-plugin.esm.js",
            externals={"echarts": Path(__file__).parent / "echarts.esm.js"})

        # With install method options
        custom.register_plugin(..., options = {'opt1': 'value1', 'opt2': 'value2'})

        # With component-dependent options (only needed for complex plugins that define components, like vue-router)
        from instaui import custom

        def parse_scope(self, parse_key: StringKeyFn, parse_scope: ParseScopeFn):
            # parse_key ensures JS object keys are properly quoted
            # parse_scope translates Scope objects into component references
            return {parse_key(k): parse_scope(v) for k, v in self.routes.items()}

        custom.register_plugin(..., options = parse_scope)
    """
    info = PluginDependencyInfo(
        f"plugin/{name}", esm, externals or {}, css or [], options=options
    )
    get_active_assets().plugins.add(info)
    return info


def clear_registered_plugins():
    get_active_assets().plugins.clear()


def add_script_tag(content: str, *, script_attrs: Optional[dict[str, Any]] = None):
    """Add a script tag to the HTML document."""
    get_active_assets().script_tags.append(ScriptTag(content, script_attrs))


def add_js_code(code: str, *, script_attrs: Optional[dict[str, Any]] = None):
    """Add a script tag to the HTML document with the given JavaScript code.

    Args:
        code (str): The JavaScript code.
        script_attrs (Optional[dict[str, Any]], optional): The attributes of the script tag. Defaults to None.
    """
    add_script_tag(code, script_attrs=script_attrs)


def add_style_tag(content: str, *, group_id: Optional[str] = None):
    """Add a style tag to the HTML document.

    Args:
        content (str): The content of the style tag.
        group_id (Optional[str], optional): The group id of the style tag. Defaults to None.
    """
    get_active_assets().style_tags.append(StyleTag(content, group_id))


def add_component_dependency(element: type[Element], info: ComponentDependencyInfo):
    get_active_assets().component_dependencies.add(element, info)
    return info


def register_component_extension(
    *,
    target: type[Element],
    kind: str,
    values: list[str],
):
    """
    Invoked by third-party component authors to declare the additional dependencies required for a component family in Zero mode.
    """
    get_active_assets().component_extensions.add(
        target=target,
        kind=kind,
        values=values,
    )
