"""
Filename: tokenizer.py
Author: William Bowley

Description:
    Low-level tokenization for .uiv format.
    Handles string parsing, bracket matching,
    delimiter splitting.
"""

from __future__ import annotations
from picounits.extensions.parser_errors import ParserError

class Tokenizer:
    """ Stateless tokenization utilities for parsing """

    @classmethod
    def is_quoted(cls, text: str) -> bool:
        """ Check if text is surrounded by quotes """
        text = text.strip()

        if len(text) < 2:
            # Empty quoted text Ex. ("")
            return False

        if text.startswith('"') and text.endswith('"'):
            # Start and end with double quotes
            return True

        elif text.startswith("'") and text.endswith("'"):
            # Start and end with single quotes
            return True

        return False

    @classmethod
    def strip_quotes(cls, text: str) -> str:
        """ Removes surrounding quotes if present """
        text = text.strip()
        if cls.is_quoted(text):
            # Removes double quotation
            return text[1:-1]
        return text

    @classmethod
    def count_brackets(cls, text: str) -> tuple[int, int]:
        """ Count opening and closing brackets """
        return text.count('['), text.count(']')

    @classmethod
    def split_key_value_pairs(cls, line: str) -> tuple[str, str] | None:
        """ Split line on first unquoted colon Ex. speed: 10 M(ms^-1)"""
        quote_character = None

        for index, character in enumerate(line):
            # Handles if character is escaped
            if index > 0 and line[index-1] == '\\':
                continue

            # Handles ':' inside quotation via toggle logic
            if character in "\"'":
                # Ex. "Hello" true inside 'hello' none outside.
                if not quote_character:
                    quote_character = character
                elif character == quote_character:
                    quote_character = None

            elif character == ':' and not quote_character:
                return line[:index].strip(), line[index+1:].strip()

        return None

    @classmethod
    def extract_bracket_content(
        cls, text: str, start_index: int = 0
    ) -> tuple[str, int] | None:
        """
        Extracts content between matching brackets Ex. '[1,2,3]' -> ('1,2,3', 6)
        """
        open_index = text.find('[', start_index)

        if open_index == -1:
            # Handles when '[' is not found
            return None

        bracket_pair_depth = 0
        quote_character = None
        for index in range(open_index, len(text)):
            character = text[index]

            # Handles if character is escaped
            if index > open_index and text[index-1] == '\\':
                continue

            # Handles brackets inside quotes
            if character in "\"'":
                # Ex. "Hello" true inside 'hello' none outside.
                if not quote_character:
                    quote_character = character
                elif character == quote_character:
                    quote_character = None

            # skip bracket depth check if inside quotes
            if quote_character:
                continue

            if text[index] == '[':
                bracket_pair_depth += 1

            elif text[index] == ']':
                bracket_pair_depth -= 1
                if bracket_pair_depth == 0:
                    # Found matching brackets
                    content = text[open_index + 1:index]
                    return content, index

        msg = f"{text!r} requires balanced brackets to be parsed."
        raise ParserError(cls.__name__, msg)

    @classmethod
    def extract_paren_groups(cls, text: str) -> list[str]:
        """ Extracts all top-level parenthesized groups. """
        groups = []
        index = 0

        while index < len(text):
            # Skip non-paren characters
            while index < len(text) and text[index] not in '(':
                index += 1

            if index >= len(text):
                break

            # Found opening paren - track depth
            depth = 1
            start = index + 1
            index += 1
            quote_character = None

            while index < len(text) and depth > 0:
                character = text[index]

                # Handle escaped characters
                if index > 0 and text[index-1] == '\\':
                    index += 1
                    continue

                # Handle quotes
                if character in "\"'":
                    if not quote_character:
                        quote_character = character
                    elif character == quote_character:
                        quote_character = None

                # Handle parens (only if not in quotes)
                elif not quote_character:
                    if character == '(':
                        depth += 1
                    elif character == ')':
                        depth -= 1
                        if depth == 0:
                            # Found matching close paren
                            groups.append(text[start:index])
                            index += 1
                            break

                index += 1

            # Check for unclosed paren
            if depth > 0:
                msg = f"Unbalanced parentheses in: {text}"
                raise ParserError(cls.__name__, msg)

        return groups