"""HTML related implementations.

Mainly following interfaces are defined:

- remove_first_selector_symbol_char : Remove a first
    selector symbol (`.` or `#`) from a string.
- append_html_to_str : Add an HTML string to another
    string with the line break and specified number's indentation.
- append_indent_to_each_script_line : Append indentation spaces to
    each script lines of specified html.
- ScriptLineUtil : The class for HTML's script line utility.
- is_script_start_tag_line : Get a boolean whether the specified
    line contains script start tag (`<script ...>`).
- is_script_end_tag_line : Get a boolean whether the specified line
    contains script end tag (`</script>`).
- wrap_expression_by_script_tag : Wrap an expression string by
    script start and end tag.
"""

import re
from typing import List
from typing import Match
from typing import Optional
from typing import Tuple
from typing import TypeVar

from typing_extensions import final

from apysc._type.string import String
from apysc._type.variable_name_mixin import VariableNameMixIn

StrOrString = TypeVar("StrOrString", str, String)


def remove_first_selector_symbol_char(*, str_val: StrOrString) -> StrOrString:
    """
    Remove a first selector symbol (`.` or `#`) from a string.

    Parameters
    ----------
    str_val : str or String
        Target string value. e.g., '#container'

    Returns
    -------
    str_val : str or String
        The string removed a first selector symbol character.

    Raises
    ------
    TypeError
        If a specified value is other than str or String
        type value.
    """
    from apysc._type import value_util

    if isinstance(str_val, str):
        if str_val.startswith(".") or str_val.startswith("#"):
            str_val = str_val[1:]  # type: ignore
        return str_val

    if isinstance(str_val, String):
        str_val_: String = value_util.get_copy(value=str_val)
        if str_val_._value.startswith(".") or str_val_._value.startswith("#"):
            str_val_._value = str_val_._value[1:]
        _append_remove_first_selector_symbol_char_expression(str_val=str_val_)
        return str_val_  # type: ignore

    raise TypeError(
        "Other than str or String type value is specified: " f"{type(str_val)}"
    )


def _append_remove_first_selector_symbol_char_expression(
    *, str_val: VariableNameMixIn
) -> None:
    """
    Append remove_first_selector_symbol_char function's
    expression.

    Parameters
    ----------
    str_val : String
        A first character removed string instance.
    """
    from apysc._expression import expression_data_util

    var_name: str = str_val.variable_name
    expression: str = (
        f"var first_char = {var_name}.slice(0, 1);"
        '\nif (first_char === "." || first_char === "#") {'
        f"\n  {var_name} = {var_name}.slice(1);"
        "\n}"
    )
    expression_data_util.append_js_expression(expression=expression)


def append_html_to_str(*, to_append_html: str, dest_html: str, indent_num: int) -> str:
    """
    Add an HTML string to another string with the line
    break and specified number's indentation.

    Parameters
    ----------
    to_append_html : str
        HTML string to append.
    dest_html : str
        `to_append_html` will be appended to this string.
    indent_num : int
        Indentation's number. The spaces that multiplied this
        number by 2 will be added.

    Returns
    -------
    result : str
        HTML appended string.
    """
    from apysc._string import indent_util

    result: str = dest_html
    if result != "":
        result += "\n"
    result += indent_util.make_spaces_for_html(indent_num=indent_num)
    result += to_append_html
    return result


def append_indent_to_each_script_line(*, html: str, indent_num: int) -> str:
    """
    Append indentation spaces to each script lines of specified html.

    e.g., if the html is following string, then only `console.log` line
    will be added indentation.
    <html>
    <script type="text/javascript">
    console.log('Hello!');
    </script>
    </html>

    Parameters
    ----------
    html : str
        Target html string.
    indent_num : int
        Indentation number. e.g., if specified 1, then will be added
        two spaces.

    Returns
    -------
    result_html : str
        Indentation added html string.
    """
    from apysc._string import indent_util

    each_lines: List[str] = html.splitlines()
    result_html: str = ""
    script_line_util: ScriptLineUtil = ScriptLineUtil(html=html)
    for i, line in enumerate(each_lines):
        if result_html != "":
            result_html += "\n"
        if script_line_util.is_script_line(line_number=i + 1):
            result_html += indent_util.make_spaces_for_html(indent_num=indent_num)
        result_html += line
    return result_html


class ScriptLineUtil:
    html: str
    script_line_ranges: List[Tuple[int, int]]

    @final
    def __init__(self, *, html: str) -> None:
        """
        The class for HTML's script line utility.

        Parameters
        ----------
        html : str
            Target HTML string.
        """
        self.html = html
        self._set_script_line_ranges()

    @final
    def _set_script_line_ranges(self) -> None:
        """
        Set each script's start and end line numbers.
        """
        self.script_line_ranges = []
        each_lines: List[str] = self.html.splitlines()
        start_line_num: int = 0
        for i, line in enumerate(each_lines):
            line_number: int = i + 1
            if is_script_start_tag_line(line=line):
                start_line_num = line_number + 1
                continue
            if is_script_end_tag_line(line=line):
                end_line_num: int = line_number - 1
                self.script_line_ranges.append((start_line_num, end_line_num))

    @final
    def is_script_line(self, *, line_number: int) -> bool:
        """
        Get a boolean value whether a specified line number
        is a script line or not.

        Parameters
        ----------
        line_number : int
            Target line number (start at 1, not 0).

        Returns
        -------
        result : bool
            If the target line is a script line, this interface
            returns True.
        """
        for script_line_start, script_line_end in self.script_line_ranges:
            if script_line_start <= line_number and line_number <= script_line_end:
                return True
        return False


def is_script_start_tag_line(*, line: str) -> bool:
    """
    Get a boolean whether the specified line contains script start
    tag (`<script ...>`).

    Notes
    -----
    External js script tag will not be target.
    e.g., `<script type="text/javascript" src="any_script.js"></script>`

    Parameters
    ----------
    line : str
        Target line string.

    Returns
    -------
    result : bool
        If a specified line contains the script start tag,
        this interface returns True.
    """
    match: Optional[Match] = re.search(pattern=r"<script ", string=line)
    if match is None:
        return False
    if "src=" in line:
        return False
    return True


def is_script_end_tag_line(*, line: str) -> bool:
    """
    Get a boolean whether the specified line contains script end
    tag (`</script>`).

    Notes
    -----
    External js script tag will not be target.
    e.g., `<script type="text/javascript" src="any_script.js"></script>`

    Parameters
    ----------
    line : str
        Target line string.

    Returns
    -------
    result : bool
        If a specified line contains the script end tag,
        this interface returns True.
    """
    match: Optional[Match] = re.search(pattern=r"</script>", string=line)
    if match is None:
        return False
    if "src=" in line:
        return False
    return True


def wrap_expression_by_script_tag(*, expression: str) -> str:
    """
    Wrap an expression string by script start and end tag.

    Parameters
    ----------
    expression : str
        An expression to wrap.

    Returns
    -------
    expression : str
        Wrapped expression string.
    """
    from apysc._html import html_const

    expression = (
        f"{html_const.SCRIPT_START_TAG}"
        f"\n{expression}"
        f"\n{html_const.SCRIPT_END_TAG}"
    )
    return expression
