"""
textfsmgen.libs.utils
=====================

General-purpose utility functions used across TextFSMGen.
"""

import re
from textwrap import wrap
from pprint import pprint

from .pat import PATTERN


def split_by_matches(text, pattern=r"(?u)\s+"):
    """Split text into alternating segments of non-matching and matching substrings."""
    parts = []
    last_end = 0

    for m in re.finditer(pattern, text):
        if m.start() > last_end:
            parts.append(text[last_end:m.start()])
        parts.append(m.group())
        last_end = m.end()

    if last_end < len(text):
        parts.append(text[last_end:])

    return parts


def extract_non_whitespace(text: str) -> list[str]:
    """Return all contiguous non‑whitespace segments from the text."""
    return re.findall(r"(?u)\S+", text)


def extract_segments(line: str, parts: list[str]):
    """Split the line into (left, matched subcontent, right) around the given parts."""
    escaped = r"\s+".join(re.escape(p) for p in parts)
    pattern = rf"(?P<left>.*?)(?P<subcontent>{escaped})(?P<right>.*)"

    match = re.search(pattern, line)
    if match:
        return (
            match.group("left"),
            match.group("subcontent"),
            match.group("right"),
        )

    return line, "", ""


def text_to_pattern(source: str, *, ignore_case: bool = True) -> str:
    """Convert a text string into a regex-safe generalized pattern."""
    token_re = rf"(?ix)\s+|[a-z]+|{PATTERN.PUNCTS}|[0-9]+"
    parts = []

    for tok in split_by_matches(source, pattern=token_re):
        if re.fullmatch(PATTERN.PUNCTS, tok):
            parts.append(re.escape(tok))
        elif tok.isdigit():
            parts.append(r"[0-9]+")
        elif tok.isspace():
            parts.append(r"\s+")
        elif re.fullmatch(r"(?i)[a-z]+", tok):
            parts.append(tok)
        else:
            parts.append(re.escape(tok))

    pattern = "".join(parts)
    if ignore_case:
        pattern = "(?i)" + pattern

    try:
        re.compile(pattern)
    except re.error as err:
        raise ValueError(f"Invalid generated regex pattern: {pattern!r}") from err

    return pattern


class TextMatcher:
    """Collect and match text patterns against input strings."""

    def __init__(self, text: str = ""):
        self._patterns = []
        self.add_pattern(text)

    def __bool__(self):
        return bool(self._patterns)

    def __len__(self):
        return 1 if self else 0

    def clear(self):
        """Remove all stored patterns."""
        self._patterns.clear()

    def add_pattern(self, text: str) -> None:
        """Convert text to a regex pattern and store it."""
        if text:
            pattern = text_to_pattern(text)
            self._patterns.append(pattern)

    def matches(self, text: str) -> bool:
        """Return True if any stored pattern matches the given text."""
        return any(re.search(pattern, text) for pattern in self._patterns)


class Tabular:
    """A utility class for constructing and displaying tabular data."""
    def __init__(self, data, columns=None, justify='left', missing='not_found'):
        self.result = ''
        if isinstance(data, dict):
            self.data = [data]
        else:
            self.data = data
        self.columns = columns
        self.justify = str(justify).lower()
        self.missing = missing
        self.is_ready = True
        self.is_tabular = False
        self.failure = ''
        self.validate_list_of_dicts()
        self.process()

    def validate_list_of_dicts(self):
        """Validate that data is a non‑empty list of dicts with identical keys."""
        if not isinstance(self.data, (list, tuple)):
            self.is_ready = False
            self.failure = "data MUST be a list."
            return

        if not self.data:
            self.is_ready = False
            self.failure = "data MUST NOT be empty."
            return

        expected_keys = None
        for item in self.data:
            if not isinstance(item, dict):
                self.is_ready = False
                self.failure = "all elements MUST be dictionaries."
                return

            if not item:
                self.is_ready = False
                self.failure = "dictionary elements MUST NOT be empty."
                return

            keys = list(item.keys())
            if expected_keys is None:
                expected_keys = keys
            elif keys != expected_keys:
                self.is_ready = False
                self.failure = "all dictionaries MUST have identical keys."
                return

    def compute_column_widths(self, columns):
        """Return max display width for each column based on data and defaults."""
        widths = {col: len(str(col)) for col in columns}

        for row in self.data:
            for col in columns:
                value = row.get(col, self.missing)
                widths[col] = max(widths[col], len(str(value)))

        return widths

    def format_cell(self, text, width):
        """Return text aligned to the given width using the current justification."""
        text = str(text)

        if self.justify == "center":
            return text.center(width)
        if self.justify == "right":
            return text.rjust(width)
        return text.ljust(width)

    def format_header_row(self, columns, widths):
        """Return a formatted header row using column names and computed widths."""
        cells = [self.format_cell(col, widths[col]) for col in columns]
        return f"| {' | '.join(cells)} |"

    def format_body_rows(self, columns, widths):
        """Return formatted body rows using column values and computed widths."""
        rows = []

        for row in self.data:
            cells = []
            for col in columns:
                value = row.get(col, self.missing)
                cells.append(self.format_cell(value, widths[col]))
            rows.append(f"| {' | '.join(cells)} |")

        return "\n".join(rows)

    def process(self):
        """Assemble the full tabular output using columns, widths, and formatted rows."""
        if not self.is_ready:
            return

        try:
            columns = self.columns or list(self.data[0].keys())
            widths = self.compute_column_widths(columns)

            border = "+-{}-+".format(
                "-+-".join("-" * widths[c] for c in columns))
            header = self.format_header_row(columns, widths)
            body = self.format_body_rows(columns, widths)

            parts = [border, header, border, body, border]
            self.result = "\n".join(parts)
            self.is_tabular = True

        except Exception as ex:
            self.failure = f"{type(ex).__name__}: {ex}"
            self.is_tabular = False

    def get(self):
        """Retrieve the processed tabular output or the raw data."""
        tabular_data = self.result if self.is_tabular else self.data
        return tabular_data

    def print(self):
        """Print the tabular content or raw data."""
        tabular_data = self.get()
        if isinstance(tabular_data, (dict, list, tuple, set)):
            pprint(tabular_data)
        else:
            print(tabular_data)


def get_data_as_tabular(data, columns=None, justify='left', missing='not_found'):
    """Convert structured data into a tabular string representation."""
    node = Tabular(data, columns=columns, justify=justify, missing=missing)
    result = node.get()
    return result


def print_data_as_tabular(data, columns=None, justify='left', missing='not_found'):
    """Print structured data in a tabular format."""
    node = Tabular(data, columns=columns, justify=justify, missing=missing)
    node.print()
