#!/usr/bin/env python3
"""
Pre-commit hook to ensure all LDR-specific CSS class names are prefixed with 'ldr-'.
This prevents CSS class name conflicts when Vite bundles CSS from dependencies.
"""

import re
import sys
from pathlib import Path
from typing import List, Tuple

# Allowed patterns - ONLY third-party framework classes
# Everything else MUST have ldr- prefix
ALLOWED_PATTERNS = {
    # Already prefixed with ldr-
    r"^ldr-",
    # Bootstrap 5 specific classes (no wildcards unless necessary)
    # Layout
    r"^(container|container-fluid|container-sm|container-md|container-lg|container-xl|container-xxl)$",
    r"^row$",
    r"^col$",
    r"^col-(1|2|3|4|5|6|7|8|9|10|11|12)$",
    r"^col-(sm|md|lg|xl|xxl)-(1|2|3|4|5|6|7|8|9|10|11|12)$",
    # Bootstrap components (with specific prefixes)
    r"^btn(-primary|-secondary|-success|-danger|-warning|-info|-light|-dark|-link|-outline-primary|-outline-secondary|-outline-success|-outline-danger|-outline-warning|-outline-info|-outline-light|-outline-dark|-sm|-lg|-block|-close|-close-white)?$",
    r"^alert(-primary|-secondary|-success|-danger|-warning|-info|-light|-dark|-dismissible)?$",
    r"^badge(-primary|-secondary|-success|-danger|-warning|-info|-light|-dark|-pill)?$",
    r"^card(-body|-header|-footer|-title|-subtitle|-text|-link|-img-top|-img-bottom)?$",
    r"^navbar(-brand|-nav|-toggler|-collapse|-expand|-expand-sm|-expand-md|-expand-lg|-expand-xl|-dark|-light)?$",
    r"^nav(-link|-item|-pills|-tabs|-fill|-justified)?$",
    r"^dropdown(-toggle|-menu|-menu-end|-menu-start|-item|-divider|-header)?$",
    r"^modal(-dialog|-dialog-centered|-dialog-scrollable|-content|-header|-body|-footer|-title|-backdrop|-static|-sm|-lg|-xl|-fullscreen)?$",
    r"^form(-control|-label|-text|-check|-check-input|-check-label|-check-inline|-switch|-select|-range|-floating|-group|-row)?$",
    r"^input-group(-text|-prepend|-append)?$",
    r"^list-group(-item|-item-action|-flush)?$",
    r"^table(-dark|-striped|-bordered|-borderless|-hover|-sm|-responsive)?$",
    # Bootstrap utilities with exact values
    r"^text-(start|end|center|primary|secondary|success|danger|warning|info|light|dark|body|muted|white|black-50|white-50)$",
    r"^text-(lowercase|uppercase|capitalize|nowrap|truncate|break|monospace)$",
    r"^bg-(primary|secondary|success|danger|warning|info|light|dark|body|white|transparent)$",
    r"^border(-top|-end|-bottom|-start|-primary|-secondary|-success|-danger|-warning|-info|-light|-dark|-white)?$",
    r"^border-(0|1|2|3|4|5)$",
    r"^rounded(-top|-end|-bottom|-start|-circle|-pill|-0|1|2|3)?$",
    r"^shadow(-sm|-lg|-none)?$",
    # Display utilities
    r"^d-(none|inline|inline-block|block|table|table-row|table-cell|flex|inline-flex|grid)$",
    r"^d-(sm|md|lg|xl|xxl)-(none|inline|inline-block|block|table|table-row|table-cell|flex|inline-flex|grid)$",
    # Flexbox utilities
    r"^flex-(row|row-reverse|column|column-reverse|wrap|nowrap|wrap-reverse)$",
    r"^justify-content-(start|end|center|between|around|evenly)$",
    r"^align-items-(start|end|center|baseline|stretch)$",
    r"^align-self-(start|end|center|baseline|stretch)$",
    r"^flex-(fill|grow-0|grow-1|shrink-0|shrink-1)$",
    # Spacing utilities (margins and padding)
    r"^m-(0|1|2|3|4|5|auto)$",
    r"^mt-(0|1|2|3|4|5|auto)$",
    r"^mb-(0|1|2|3|4|5|auto)$",
    r"^ms-(0|1|2|3|4|5|auto)$",
    r"^me-(0|1|2|3|4|5|auto)$",
    r"^mx-(0|1|2|3|4|5|auto)$",
    r"^my-(0|1|2|3|4|5|auto)$",
    r"^p-(0|1|2|3|4|5)$",
    r"^pt-(0|1|2|3|4|5)$",
    r"^pb-(0|1|2|3|4|5)$",
    r"^ps-(0|1|2|3|4|5)$",
    r"^pe-(0|1|2|3|4|5)$",
    r"^px-(0|1|2|3|4|5)$",
    r"^py-(0|1|2|3|4|5)$",
    # Sizing utilities
    r"^w-(25|50|75|100|auto)$",
    r"^h-(25|50|75|100|auto)$",
    r"^mw-100$",
    r"^mh-100$",
    # Position utilities
    r"^position-(static|relative|absolute|fixed|sticky)$",
    r"^top-(0|50|100)$",
    r"^bottom-(0|50|100)$",
    r"^start-(0|50|100)$",
    r"^end-(0|50|100)$",
    # Typography utilities
    r"^fs-(1|2|3|4|5|6)$",
    r"^fw-(light|lighter|normal|bold|bolder)$",
    r"^fst-(normal|italic)$",
    r"^lh-(1|sm|base|lg)$",
    r"^font-monospace$",
    r"^text-decoration-(none|underline|line-through)$",
    # Visibility utilities
    r"^(visible|invisible)$",
    r"^visually-hidden$",
    r"^visually-hidden-focusable$",
    r"^overflow-(auto|hidden|visible|scroll)$",
    r"^user-select-(all|auto|none)$",
    # Interactive utilities
    r"^pe-(none|auto)$",
    r"^(active|disabled|show|hide|collapse|collapsed|fade|collapsing|close)$",
    # Form validation states (Bootstrap 5)
    r"^(is-valid|is-invalid|valid-feedback|invalid-feedback|valid-tooltip|invalid-tooltip)$",
    # Toast component (Bootstrap 5)
    r"^toast(-header|-body)?$",
    # Progress bars (Bootstrap 5)
    r"^progress(-bar|-bar-striped|-bar-animated|-stacked)?$",
    r"^btn-group(-vertical|-sm|-lg)?$",
    # Image utilities (Bootstrap 5)
    r"^img-(fluid|thumbnail)$",
    # Other Bootstrap utilities
    r"^(clearfix|sr-only|sr-only-focusable|small)$",
    r"^spinner-(border|border-sm|grow|grow-sm)$",
    r"^placeholder(-glow|-wave)?$",
    # Bootstrap Icons (bi-*)
    r"^bi(-[a-z0-9-]+)?$",
    # Font Awesome icons
    r"^(fa|fas|far|fab|fal|fad)$",
    r"^fa-[a-z0-9-]+$",
    # KaTeX math rendering
    r"^katex(-[a-z0-9-]+)?$",
}


def is_allowed_class(class_name: str) -> bool:
    """Check if a class name is allowed without ldr- prefix."""
    # Remove any leading/trailing whitespace
    class_name = class_name.strip()

    # Empty class names are invalid
    if not class_name:
        return False

    # Check against allowed patterns
    for pattern in ALLOWED_PATTERNS:
        if re.match(pattern, class_name, re.IGNORECASE):
            return True

    return False


def check_css_file(file_path: Path) -> List[Tuple[int, str, str]]:
    """Check CSS file for non-prefixed class definitions."""
    errors = []

    try:
        content = file_path.read_text(encoding="utf-8")
        lines = content.split("\n")

        # Pattern to match CSS class selectors, including compound
        # selectors like .class1.class2
        # First match the leading dot (not preceded by a word char or /)
        # then capture everything including subsequent .class segments
        class_pattern = re.compile(
            r"(?<![\w/])\.([a-zA-Z][a-zA-Z0-9\-_]*(?:\.[a-zA-Z][a-zA-Z0-9\-_]*)*)"
        )

        for line_num, line in enumerate(lines, 1):
            # Skip comments
            if "/*" in line or "*/" in line or line.strip().startswith("//"):
                continue

            # Skip @import statements
            if "@import" in line:
                continue

            matches = class_pattern.findall(line)
            for match in matches:
                # Split compound selectors like "class1.class2" into
                # individual class names for separate validation
                for class_name in match.split("."):
                    if not class_name:
                        continue
                    if not is_allowed_class(class_name):
                        errors.append(
                            (
                                line_num,
                                class_name,
                                f"CSS class '.{class_name}' should be prefixed with 'ldr-'",
                            )
                        )

    except Exception as e:
        print(f"Error reading {file_path}: {e}", file=sys.stderr)

    return errors


def check_html_file(file_path: Path) -> List[Tuple[int, str, str]]:
    """Check HTML file for non-prefixed class usage and CSS definitions in style tags."""
    errors = []

    try:
        content = file_path.read_text(encoding="utf-8")
        lines = content.split("\n")

        # Pattern to match class attributes in HTML
        # Simplified pattern to avoid exponential backtracking
        # Matches: class="..." or class='...'
        # Uses two separate patterns to handle each quote type correctly
        class_attr_patterns = [
            re.compile(r'class\s*=\s*"([^"]*)"', re.IGNORECASE),
            re.compile(r"class\s*=\s*'([^']*)'", re.IGNORECASE),
        ]

        # Pattern to match CSS class definitions (same as in check_css_file)
        css_class_pattern = re.compile(
            r"(?<![\w/])\.([a-zA-Z][a-zA-Z0-9\-_]*(?:\.[a-zA-Z][a-zA-Z0-9\-_]*)*)"
        )

        in_style_tag = False

        for line_num, line in enumerate(lines, 1):
            # Skip HTML comments
            if "<!--" in line or "-->" in line:
                continue

            # Skip Jinja2 template comments
            if "{#" in line or "#}" in line:
                continue

            # Check for style tag boundaries
            if "<style" in line.lower():
                in_style_tag = True
            if "</style>" in line.lower():
                in_style_tag = False

            # If we're inside a style tag, check for CSS class definitions
            if in_style_tag:
                # Skip CSS comments
                if (
                    "/*" in line
                    or "*/" in line
                    or line.strip().startswith("//")
                ):
                    continue

                # Check for CSS class definitions
                css_matches = css_class_pattern.findall(line)
                for match in css_matches:
                    # Split compound selectors like "class1.class2"
                    for class_name in match.split("."):
                        if not class_name:
                            continue
                        if not is_allowed_class(class_name):
                            errors.append(
                                (
                                    line_num,
                                    class_name,
                                    f"CSS class '.{class_name}' in style tag should be prefixed with 'ldr-'",
                                )
                            )
                continue  # Skip HTML class checking when in style tag

            # Try both patterns to handle different quote types
            matches = []
            for pattern in class_attr_patterns:
                matches.extend(pattern.findall(line))

            for class_attr in matches:
                # Skip if this is a JavaScript template literal (contains ${...})
                if "${" in class_attr:
                    continue

                # Skip if the entire class attribute contains Jinja2 template variables
                # This handles cases like class="alert alert-{{ category }}"
                if "{{" in class_attr or "{%" in class_attr:
                    # Extract only the static class names (those not part of Jinja2 expressions)
                    # Remove the Jinja2 parts and check remaining static classes
                    # Remove Jinja2 expressions but keep the rest
                    cleaned_attr = re.sub(
                        r"\{\{[^}]*\}\}|\{%[^%]*%\}", "", class_attr
                    )
                    classes = cleaned_attr.split()
                else:
                    # No Jinja2 in this attribute, check all classes
                    classes = class_attr.split()

                for class_name in classes:
                    # Additional safety check for individual class names
                    if (
                        "{{" in class_name
                        or "{%" in class_name
                        or "{" in class_name
                        or "}" in class_name
                    ):
                        continue

                    # Skip JavaScript template literals and variables
                    if any(
                        char in class_name
                        for char in [
                            "$",
                            "=",
                            "!",
                            "<",
                            ">",
                            "(",
                            ")",
                            "[",
                            "]",
                            "||",
                            "&&",
                            "?",
                            ":",
                        ]
                    ):
                        continue

                    # Skip common Jinja2 patterns
                    if class_name in [
                        "if",
                        "else",
                        "endif",
                        "for",
                        "endfor",
                        "block",
                        "endblock",
                        "include",
                        "extends",
                    ]:
                        continue

                    # Skip Bootstrap icon classes
                    if class_name.startswith("bi-") or class_name == "bi":
                        continue

                    if not is_allowed_class(class_name):
                        errors.append(
                            (
                                line_num,
                                class_name,
                                f"HTML class '{class_name}' should be prefixed with 'ldr-'",
                            )
                        )

    except Exception as e:
        print(f"Error reading {file_path}: {e}", file=sys.stderr)

    return errors


def check_js_file(file_path: Path) -> List[Tuple[int, str, str]]:
    """Check JavaScript file for non-prefixed class usage."""
    errors = []

    try:
        content = file_path.read_text(encoding="utf-8")
        lines = content.split("\n")

        # Patterns to match class usage in JavaScript (excluding querySelector/jQuery)
        patterns = [
            # classList.add('classname')
            re.compile(
                r'classList\.(add|remove|toggle|contains)\s*\(\s*["\']([^"\']+)["\']'
            ),
            # className = 'classname' or className: 'classname'
            re.compile(r'className\s*[:=]\s*["\']([^"\']+)["\']'),
            # getElementsByClassName('classname')
            re.compile(r'getElementsByClassName\s*\(\s*["\']([^"\']+)["\']'),
            # hasClass('classname'), addClass('classname'), removeClass('classname')
            re.compile(
                r'\.(hasClass|addClass|removeClass|toggleClass)\s*\(\s*["\']([^"\']+)["\']'
            ),
        ]

        # Pattern for template literal className assignments:
        # className = `some-class ${dynamic}` or className: `some-class`
        template_literal_pattern = re.compile(r"className\s*[:=]\s*`([^`]+)`")

        # Special patterns for querySelector and jQuery that need different handling
        querySelector_pattern = re.compile(
            r'querySelector(?:All)?\s*\(\s*["\']([^"\']+)["\']'
        )
        jquery_pattern = re.compile(r'\$\s*\(\s*["\']([^"\']+)["\']')

        for line_num, line in enumerate(lines, 1):
            # Skip comments
            if "//" in line or "/*" in line or "*/" in line:
                # Simple comment detection (not perfect but good enough)
                comment_start = line.find("//")
                if comment_start >= 0:
                    line = line[:comment_start]

            # Handle querySelector and jQuery selectors specially
            for selector_match in querySelector_pattern.findall(line):
                # Extract class names from CSS selectors (e.g., '.class1 .class2', '.class1.class2')
                # Only match class selectors (starting with .)
                class_matches = re.findall(
                    r"\.([a-zA-Z0-9_-]+)", selector_match
                )
                for cls in class_matches:
                    if not is_allowed_class(cls):
                        errors.append(
                            (
                                line_num,
                                cls,
                                f"JavaScript class '.{cls}' should be prefixed with 'ldr-'",
                            )
                        )

            for jquery_match in jquery_pattern.findall(line):
                # Extract class names from jQuery selectors
                class_matches = re.findall(r"\.([a-zA-Z0-9_-]+)", jquery_match)
                for cls in class_matches:
                    if not is_allowed_class(cls):
                        errors.append(
                            (
                                line_num,
                                cls,
                                f"JavaScript class '.{cls}' should be prefixed with 'ldr-'",
                            )
                        )

            # Handle template literal className assignments
            for tl_match in template_literal_pattern.findall(line):
                # Extract static class tokens by removing ${...} expressions
                static_part = re.sub(r"\$\{[^}]*\}", " ", tl_match)
                for cls in static_part.split():
                    # Skip partial tokens that are fragments of dynamic
                    # class names (e.g. "alert-" from "alert-${type}")
                    if cls.endswith("-") or cls.startswith("-"):
                        continue
                    if not is_allowed_class(cls):
                        errors.append(
                            (
                                line_num,
                                cls,
                                f"JavaScript class '{cls}' should be prefixed with 'ldr-'",
                            )
                        )

            # Handle other patterns
            for pattern in patterns:
                matches = pattern.findall(line)
                for match in matches:
                    # Handle different capture groups
                    if isinstance(match, tuple):
                        # For patterns with multiple groups, get the class name
                        class_name = match[-1] if len(match) > 1 else match[0]
                    else:
                        class_name = match

                    # Split multiple classes if present
                    classes = class_name.split()
                    for cls in classes:
                        if not is_allowed_class(cls):
                            errors.append(
                                (
                                    line_num,
                                    cls,
                                    f"JavaScript class '{cls}' should be prefixed with 'ldr-'",
                                )
                            )

    except Exception as e:
        print(f"Error reading {file_path}: {e}", file=sys.stderr)

    return errors


def main():
    """Main function to check files passed as arguments."""
    if len(sys.argv) < 2:
        print("No files to check")
        return 0

    has_errors = False

    for file_arg in sys.argv[1:]:
        file_path = Path(file_arg)

        if not file_path.exists():
            continue

        # Match path components, not substrings: "lib" must be an actual
        # directory segment, not a fragment of "library.html" etc.
        if any(
            vendor in file_path.parts
            for vendor in [
                "vendor",
                "dist",
                "build",
                "lib",
                "libs",
                "node_modules",
            ]
        ):
            continue

        errors = []

        # Check based on file extension
        if file_path.suffix == ".css":
            errors = check_css_file(file_path)
        elif file_path.suffix in [".html", ".htm"]:
            errors = check_html_file(file_path)
        elif file_path.suffix in [".js", ".jsx", ".ts", ".tsx", ".mjs"]:
            errors = check_js_file(file_path)

        if errors:
            has_errors = True
            print(f"\n❌ CSS class prefix errors in {file_path}:")
            for line_num, class_name, message in errors:
                print(f"  Line {line_num}: {message}")

    if has_errors:
        print("\n" + "=" * 60)
        print("CSS Class Naming Convention:")
        print("  All LDR-specific CSS classes must be prefixed with 'ldr-'")
        print(
            "  This prevents conflicts when Vite bundles CSS from dependencies"
        )
        print("  Example: .custom-button → .ldr-custom-button")
        print("=" * 60)
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())
