# ashyterm/utils/syntax_utils.py
"""Pango markup generator for bash command syntax highlighting.

Shared by the Command Manager dialogs and BashTextView.
"""

import re
from typing import Dict, List, Optional

import gi

gi.require_version("Gtk", "4.0")
from gi.repository import GLib

# Pre-compiled regex patterns for syntax highlighting (compiled once at module load)
_PATTERN_URL = re.compile(r"(https?://[^\s]+)")
_PATTERN_SINGLE_QUOTE = re.compile(r"('(?:[^'\\]|\\.)*')")
_PATTERN_DOUBLE_QUOTE = re.compile(r'("(?:[^"\\]|\\.)*")')
_PATTERN_VARIABLE = re.compile(r"(\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)")
_PATTERN_SPECIAL_VAR = re.compile(r"(\$[\?\!\$@\*#0-9])")
_PATTERN_FLAG_SPACE = re.compile(r"(\s)(--?[a-zA-Z][a-zA-Z0-9-]*)")
_PATTERN_FLAG_START = re.compile(r"^(--?[a-zA-Z][a-zA-Z0-9-]*)")
_PATTERN_NUMBER_NEG = re.compile(r"(\s)(-\d+)(\s|$)")
_PATTERN_NUMBER = re.compile(r"(\s)(\d+[KMGTkmgt]?)(\s|$)")
_PATTERN_PATH_SPACE = re.compile(r"(\s)(/[^\s'\"]+)")
_PATTERN_PATH_START = re.compile(r"^(/[^\s'\"]+)")
_PATTERN_COMMAND = re.compile(
    r"^(find|grep|ls|cat|echo|cd|rm|mkdir|touch|cp|mv|chmod|chown|tar|curl|wget)\b"
)
_PATTERN_OPERATOR = re.compile(r"(\||&gt;|&lt;|&amp;&amp;|&gt;&gt;|\|\|)")
_PATTERN_BACKTICK = re.compile(r"(`[^`]*`)")
_PATTERN_SUBSHELL = re.compile(r"(\$\([^\)]*\))")


def get_bash_pango_markup(
    command: str,
    palette: Optional[List[str]] = None,
    foreground: str = "#ffffff",
) -> str:
    """Bash command → Pango markup using a 16-color terminal palette."""
    colors = _build_color_map(palette)
    result = GLib.markup_escape_text(command)

    # Order matters — more specific patterns first so they win the replace.
    # URLs (before paths)
    result = _PATTERN_URL.sub(
        f'<span foreground="{colors["path"]}">' + r"\1</span>", result
    )
    # Single-quoted strings
    result = _PATTERN_SINGLE_QUOTE.sub(
        f'<span foreground="{colors["string"]}">' + r"\1</span>", result
    )
    # Double-quoted strings
    result = _PATTERN_DOUBLE_QUOTE.sub(
        f'<span foreground="{colors["string"]}">' + r"\1</span>", result
    )
    # Variables $VAR or ${VAR}
    result = _PATTERN_VARIABLE.sub(
        f'<span foreground="{colors["variable"]}">' + r"\1</span>", result
    )
    # Special variables $?, $!, $$, $@, $*, $#, $0-9
    result = _PATTERN_SPECIAL_VAR.sub(
        f'<span foreground="{colors["special_var"]}">' + r"\1</span>", result
    )
    # Flags/options after space
    result = _PATTERN_FLAG_SPACE.sub(
        r"\1" + f'<span foreground="{colors["flag"]}">' + r"\2</span>", result
    )
    # Flags/options at start
    result = _PATTERN_FLAG_START.sub(
        f'<span foreground="{colors["flag"]}">' + r"\1</span>", result
    )
    # Negative numbers (like -10 in -mtime -10)
    result = _PATTERN_NUMBER_NEG.sub(
        r"\1" + f'<span foreground="{colors["number"]}">' + r"\2</span>" + r"\3", result
    )
    # Standalone numbers
    result = _PATTERN_NUMBER.sub(
        r"\1" + f'<span foreground="{colors["number"]}">' + r"\2</span>" + r"\3", result
    )
    # Paths after space
    result = _PATTERN_PATH_SPACE.sub(
        r"\1" + f'<span foreground="{colors["path"]}">' + r"\2</span>", result
    )
    # Paths at start
    result = _PATTERN_PATH_START.sub(
        f'<span foreground="{colors["path"]}">' + r"\1</span>", result
    )
    # Common commands at start
    result = _PATTERN_COMMAND.sub(
        f'<span foreground="{colors["command"]}">' + r"\1</span>", result
    )
    # Redirections and pipes
    result = _PATTERN_OPERATOR.sub(
        f'<span foreground="{colors["operator"]}">' + r"\1</span>", result
    )
    # Backticks
    result = _PATTERN_BACKTICK.sub(
        f'<span foreground="{colors["substitution"]}">' + r"\1</span>", result
    )
    # Subshell
    result = _PATTERN_SUBSHELL.sub(
        f'<span foreground="{colors["substitution"]}">' + r"\1</span>", result
    )

    return result


def _build_color_map(palette: Optional[List[str]] = None) -> Dict[str, str]:
    """Pick syntax colors from a 16-color palette (0-7 normal, 8-15 bright)."""
    if palette and len(palette) >= 8:
        return {
            "command": palette[2] if len(palette) > 2 else "#8ae234",  # Green
            "string": palette[3] if len(palette) > 3 else "#e9b96e",  # Yellow
            "variable": palette[5] if len(palette) > 5 else "#ad7fa8",  # Magenta
            "special_var": palette[13]
            if len(palette) > 13
            else "#ff69b4",  # Bright magenta
            "flag": palette[14] if len(palette) > 14 else "#98d8c8",  # Bright cyan
            "number": palette[11] if len(palette) > 11 else "#f4d03f",  # Bright yellow
            "path": palette[6] if len(palette) > 6 else "#87ceeb",  # Cyan
            "operator": palette[3] if len(palette) > 3 else "#fcaf3e",  # Yellow
            "substitution": palette[11]
            if len(palette) > 11
            else "#b8860b",  # Bright yellow
        }

    # Default colors (no palette provided)
    return {
        "command": "#8ae234",
        "string": "#e9b96e",
        "variable": "#ad7fa8",
        "special_var": "#ff69b4",
        "flag": "#98d8c8",
        "number": "#f4d03f",
        "path": "#87ceeb",
        "operator": "#fcaf3e",
        "substitution": "#b8860b",
    }
