"""
This module defines the `Page` class, the top-level container for a Nolite
application's UI. It is responsible for assembling the final HTML document,
including the head, body, stylesheets, and scripts.
"""

from typing import List, Optional, Dict, Any

from ..core.component import Component
from ..utils.scripts import NOLITE_RUNTIME_JS, DEFAULT_STYLES_CSS
from ..utils.style_parser import process_styles


class Page:
    """Represents an entire HTML document.

    The `Page` class is the root of the UI tree. A Nolite view function must
    return an instance of this class. It orchestrates the rendering of all
    child components and assembles the final HTML string, including the `<html>`,
    `<head>`, and `<body>` tags.

    It also accepts Pythonic styling keyword arguments, which are applied
    globally to the `<body>` tag.
    """

    def __init__(
        self,
        title: str,
        children: Optional[List[Component]] = None,
        meta: Optional[List[Dict[str, str]]] = None,
        stylesheets: Optional[List[str]] = None,
        scripts: Optional[List[str]] = None,
        favicon: Optional[str] = None,
        lang: str = "en",
        include_defaults: bool = True,
        **kwargs: Any,
    ):
        """Initializes a Page instance, preparing it for rendering.

        Args:
            title: The text to be displayed in the browser tab.
            children: A list of `Component` objects that will form the main
                content of the page's body.
            meta: A list of dictionaries, where each dictionary represents the
                attributes of a `<meta>` tag.
            stylesheets: A list of URLs to external CSS stylesheets to be
                linked in the `<head>`.
            scripts: A list of URLs to external JavaScript files to be
                included at the end of the `<body>`.
            favicon: The URL to the site's favicon.
            lang: The language of the document (e.g., "en").
            include_defaults: If True, automatically injects Nolite's default
                JavaScript runtime and component styles.
            **kwargs: Python-friendly style arguments (e.g., `bg_color`) that
                will be applied to the `<body>` tag.
        """
        self.title = title
        self.children = children if children is not None else []
        self.meta = meta if meta is not None else []
        self.stylesheets = stylesheets if stylesheets is not None else []
        self.scripts = scripts if scripts is not None else []
        self.favicon = favicon
        self.lang = lang
        self.include_defaults = include_defaults
        self.body_style = process_styles({}, **kwargs)

    def _render_head(self, generated_css: str) -> str:
        """Assembles the complete `<head>` section of the HTML document.

        This internal method gathers the page title, metadata, stylesheets,
        favicon link, and any component-generated CSS into a single string.

        Args:
            generated_css: A string containing all CSS generated by child
                components (e.g., for hover effects).

        Returns:
            The fully-formed `<head>` HTML string.
        """
        head_children = [f"<title>{self.title}</title>"]

        # Default meta tags for modern web pages
        head_children.append('<meta charset="UTF-8">')
        head_children.append(
            '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
        )

        # User-defined meta tags
        for m in self.meta:
            attrs = " ".join([f'{key}="{value}"' for key, value in m.items()])
            head_children.append(f"<meta {attrs}>")

        # Favicon link
        if self.favicon:
            # Robustly determine the link attributes based on file extension
            file_extension = self.favicon.split("?")[0].split(".")[-1].lower()
            rel = "icon"
            type_attr = "image/vnd.microsoft.icon"  # Default fallback

            if file_extension == "ico":
                type_attr = "image/x-icon"
            elif file_extension == "png":
                type_attr = "image/png"
            elif file_extension == "svg":
                type_attr = "image/svg+xml"
            elif file_extension == "gif":
                type_attr = "image/gif"
            elif file_extension == "jpg" or file_extension == "jpeg":
                type_attr = "image/jpeg"
            elif file_extension == "apng":
                # Apple touch icons are a common use case
                rel = "apple-touch-icon"
                type_attr = "image/apng"

            head_children.append(
                f'<link rel="{rel}" type="{type_attr}" href="{self.favicon}">'
            )

        # Stylesheet links
        for sheet in self.stylesheets:
            head_children.append(f'<link rel="stylesheet" href="{sheet}">')

        # Add default Nolite styles and component-generated styles
        if self.include_defaults:
            combined_css = f"{DEFAULT_STYLES_CSS}\n{generated_css}"
            head_children.append(f"<style>{combined_css}</style>")
        elif generated_css:
            head_children.append(f"<style>{generated_css}</style>")

        head_content = "\n    ".join(head_children)
        return f"<head>\n    {head_content}\n</head>"

    def _render_body(self) -> str:
        """Assembles the complete `<body>` section of the HTML document.

        This internal method renders all child components, includes script tags,
        and applies any global styles to the `<body>` tag itself.

        Returns:
            The fully-formed `<body>` HTML string.
        """
        # Generate the style attribute string for the body tag
        style_str = ""
        if self.body_style:
            style_attr = "; ".join(
                f"{key}: {value}" for key, value in self.body_style.items()
            )
            style_str = f' style="{style_attr}"'

        body_content = "".join([child.render() for child in self.children])

        # Scripts are typically placed at the end of the body for better performance
        script_tags = []
        for script in self.scripts:
            script_tags.append(f'<script src="{script}"></script>')

        # Add Nolite's JS runtime
        if self.include_defaults:
            script_tags.append(f"<script>{NOLITE_RUNTIME_JS}</script>")

        script_content = "\n".join(script_tags)

        return f"<body{style_str}>\n{body_content}\n{script_content}\n</body>"

    def _collect_generated_css(self) -> str:
        """Recursively traverses all child components to collect their generated CSS."""
        all_css = []
        for child in self.children:
            if isinstance(child, Component):
                all_css.append(child.get_all_descendant_css())
        return "\n".join(filter(None, all_css))

    def render(self) -> str:
        """Renders the entire Page object into a complete HTML document string.

        This is the main public rendering method for a Page. It orchestrates the
        collection of component-generated CSS and the rendering of the head and
        body to produce the final, standards-compliant HTML document.

        Returns:
            The full HTML document as a string, ready to be sent to a browser.
        """
        # First, collect all generated CSS from components for the <head>
        generated_css = self._collect_generated_css()

        # Then, render the head and body
        head = self._render_head(generated_css=generated_css)
        body = self._render_body()

        # Using f-string for a clear and simple HTML structure
        return f"""<!DOCTYPE html>
            <html lang="{self.lang}">
            {head}
            {body}
        </html>"""
