from ralium.util.models import NamedDict
from ralium.util.const import RE_HTML_PATTERN
import copy
import re

class PyHTML:
    __slots__ = ("_html", "_window", "_webhook",)

    def __init__(self, window, webhook):
        self._html = webhook.html
        self._window = window
        self._webhook = webhook
    
    def compile(self):
        """
        Compiles the HTML content of the webhook by processing embedded Ralium tags.

        This method extracts blocks of code enclosed by `{%ralium` and `%}`, processes these blocks,
        executes them, and replaces the original blocks with the resulting HTML content.

        :returns: The compiled HTML content as a string.
        """

        _locals = PyHTML.locals(self._window, self._webhook)
        _blocks = PyHTML.get_enclosed(self._html, "{%ralium", "%}")
        _data = self.process(_blocks)

        for code, block in zip(_data, _blocks):
            outcome = filter(lambda v: v is not None, PyHTML.get_outcome(code, PyHTML.deepcopy(_locals)))
            self._html = self._html.replace(block, "\n".join(outcome))
        
        return self._html
    
    def process(self, blocks):
        """
        Processes blocks of Ralium code to convert HTML tags into Python code.

        This method takes a list of blocks containing Ralium code, processes each block to 
        convert HTML tags into corresponding Python code that appends the tags to a list.

        :param blocks: A list of code blocks to process.

        :returns: A list of processed code blocks as strings.
        """

        data = []

        for block in blocks:
            block = PyHTML.removeaffix(block, "{%ralium", "%}")

            if block.startswith(".inline"):
                data.append(self.process_inline(block.removeprefix(".inline")))
                continue

            html = PyHTML.get_enclosed(block, "{$html", "$}")

            if not html:
                lines = []
                
                for line in block.split("\n"):
                    tags = re.findall(RE_HTML_PATTERN, line)
                    
                    if not tags: 
                        lines.append(line)
                        continue

                    for tag in tags:
                        rep = PyHTML.removeaffix(tag[0], '<">', '</">').replace('"', '\\"')
                        lines.append(line.replace(tag[0], f'__ralium_html__.append("{rep}")'))
                
                data.append("\n".join(lines))
                continue
            
            for tag in html:
                rep = PyHTML.removeaffix(tag, "{$html", "$}").replace('"', '\\"')
                block = block.replace(tag, f'__ralium_html__.append("""{rep}""")')
            
            data.append(block)
        
        return data
    
    def process_inline(self, block):
        """
        Processes an inline Ralium code block to execute it and retrieve the output.

        This method takes an inline block of Ralium code, executes it within a specific local 
        context created from the `Window` and `WebHook` objects, and returns the resulting output.

        :param block: The inline Ralium code block to execute.

        :returns: The output generated by executing the inline code block. Defaults to an empty string.
        """
        
        _locals = PyHTML.locals(self._window, self._webhook)
        _locals["output"] = None

        exec(f"def __ralium_executor__():\n{block}", _locals)
        output = eval("__ralium_executor__()", _locals)

        return f"#[type=inline]{output or ""}"
    
    @staticmethod
    def locals(window, webhook):
        """
        Creates a locals dictionary from a `Window` and `WebHook` object.

        This method generates a dictionary of local variables that includes the functions 
        and namespaces provided by the `WebHook` object, as well as the `Window` object.
        These local variables can be used when executing Python code blocks within the HTML.

        :param window: The `Window` object to expose to the Ralium code block.
        :param webhook: The `WebHook` object containing the functions and namespaces.

        :returns: A dictionary of local variables including the `Window` object, functions, 
        and namespaces from the `WebHook`.
        """

        return {
            "window": window,
            "__ralium_html__": [],
            **{f.__name__: f for f in webhook.functions},
            **{n.alias: NamedDict(n) for n in webhook.namespaces}
        }
    
    @staticmethod
    def deepcopy(locals):
        """
        Creates a deep copy of a locals dictionary without copying the `Window` object.

        This method removes the `Window` object from the dictionary, creates a deep copy of the 
        remaining locals dictionary, and then adds the `Window` object back into the copied 
        dictionary.

        :param locals: The locals dictionary to copy.

        :returns: A deep copy of the locals dictionary with the `Window` object preserved.
        """

        window = locals.pop("window")
        locals_copy = copy.deepcopy(locals)
        locals_copy["window"] = window
        locals["window"] = window

        return locals_copy

    @staticmethod
    def get_outcome(code, locals):
        """
        Executes a Python code block and collects the resulting HTML elements.

        This method executes a given Python code block within a specified local context. If the 
        code block is marked as inline (starting with `#[type=inline]`), it simply returns the 
        code block after removing the inline marker. Otherwise, it executes the code block and 
        collects the resulting HTML elements generated during the execution.

        :param code: The code block to execute.
        :param locals: The dictionary of local variables including functions and namespaces.

        :returns: A list of HTML elements generated by executing the code block, or the code 
                block itself if it is marked as inline.
        """

        if code.startswith("#[type=inline]"):
            return [code.removeprefix("#[type=inline]")]

        exec(f"def __ralium_executor__():\n{code}", locals)
        eval("__ralium_executor__()", locals)

        return locals["__ralium_html__"]

    @staticmethod
    def get_enclosed(text, start, end):
        """
        Extracts all substrings from the given text that are enclosed by the specified start and end markers.

        :param text: The input string to search within.
        :param start: The starting delimiter of the enclosed substrings.
        :param end: The ending delimiter of the enclosed substrings.

        :returns: A list of substrings found within the start and end delimiters.

        :raises SyntaxError: If an ending delimiter is missing for a corresponding start delimiter.
        """

        found = []
        _start = 0

        while (_start := text.find(start, _start + len(start))) != -1:
            _end = text.find(end, _start + len(start))

            if _end == -1:
                raise SyntaxError(f"failed to find ending symbol '{end}'")
            
            _end += len(end)
            found.append(text[_start:_end])
        
        return found

    @staticmethod
    def removeaffix(text, prefix, suffix):
        """
        Removes a prefix and a suffix from a string.

        :param text: The string to process.
        :param prefix: The prefix to remove.
        :param suffix: The suffix to remove.

        :returns: The string without the specified prefix and suffix.
        """

        return text.removeprefix(prefix).removesuffix(suffix)