#!/usr/bin/env python3
"""
Wrapper for official @salesforce-ux/slds-linter npm package.

Provides integration with SLDS Linter for:
- HTML template validation
- CSS styling hooks validation
- Automated linting with structured output

The SLDS Linter is optional - if not installed, validation gracefully
degrades to custom Python-based validators.

Installation:
    npm install -g @salesforce-ux/slds-linter
"""

import subprocess
import json
import os
from typing import Any


class SLDSLinterWrapper:
    """Wrapper for npm-based SLDS Linter."""

    def __init__(self, project_root: str | None = None):
        """
        Initialize the SLDS Linter wrapper.

        Args:
            project_root: Root directory for linting context (optional)
        """
        self.project_root = project_root or os.getcwd()
        self._available: bool | None = None

    def is_available(self) -> bool:
        """
        Check if slds-linter is installed and available.

        Returns:
            True if linter is available, False otherwise
        """
        if self._available is not None:
            return self._available

        try:
            result = subprocess.run(
                ["npx", "@salesforce-ux/slds-linter", "--version"],
                capture_output=True,
                text=True,
                timeout=10,
            )
            self._available = result.returncode == 0
        except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
            self._available = False

        return self._available

    def lint_file(self, file_path: str) -> dict[str, Any]:
        """
        Lint a single file using SLDS Linter.

        Args:
            file_path: Path to HTML or CSS file to lint

        Returns:
            dict with success status, violations list, and any errors
        """
        if not self.is_available():
            return {
                "success": False,
                "error": "slds-linter not installed. Install with: npm i -g @salesforce-ux/slds-linter",
                "violations": [],
            }

        try:
            # Run SLDS Linter with JSON output
            result = subprocess.run(
                ["npx", "@salesforce-ux/slds-linter", "lint", file_path, "--format", "json"],
                capture_output=True,
                text=True,
                timeout=30,
                cwd=self.project_root,
            )

            violations = self._parse_output(result.stdout, result.stderr)

            return {"success": True, "violations": violations, "exit_code": result.returncode}

        except subprocess.TimeoutExpired:
            return {
                "success": False,
                "error": "slds-linter timed out after 30 seconds",
                "violations": [],
            }
        except FileNotFoundError:
            return {
                "success": False,
                "error": "npx not found - ensure Node.js is installed",
                "violations": [],
            }
        except Exception as e:
            return {"success": False, "error": str(e), "violations": []}

    def lint_directory(self, dir_path: str, extensions: list[str] = None) -> dict[str, Any]:
        """
        Lint all matching files in a directory.

        Args:
            dir_path: Directory path to lint
            extensions: File extensions to lint (default: ['.html', '.css'])

        Returns:
            dict with success status, file_results, and total violations
        """
        if extensions is None:
            extensions = [".html", ".css"]

        if not self.is_available():
            return {
                "success": False,
                "error": "slds-linter not installed",
                "file_results": {},
                "total_violations": 0,
            }

        file_results = {}
        total_violations = 0

        for root, _dirs, files in os.walk(dir_path):
            for file in files:
                if any(file.endswith(ext) for ext in extensions):
                    file_path = os.path.join(root, file)
                    result = self.lint_file(file_path)
                    file_results[file_path] = result
                    total_violations += len(result.get("violations", []))

        return {"success": True, "file_results": file_results, "total_violations": total_violations}

    def _parse_output(self, stdout: str, stderr: str) -> list[dict]:
        """
        Parse SLDS Linter JSON output into structured violations.

        Args:
            stdout: Standard output from linter
            stderr: Standard error from linter

        Returns:
            List of violation dictionaries
        """
        violations = []

        # Try to parse JSON output
        try:
            if stdout.strip():
                data = json.loads(stdout)

                # Handle ESLint-style JSON output
                if isinstance(data, list):
                    for file_result in data:
                        for message in file_result.get("messages", []):
                            violations.append(
                                {
                                    "rule": message.get("ruleId", "unknown"),
                                    "message": message.get("message", ""),
                                    "line": message.get("line", 0),
                                    "column": message.get("column", 0),
                                    "severity": self._map_severity(message.get("severity", 1)),
                                    "source": "slds-linter",
                                    "file": file_result.get("filePath", ""),
                                }
                            )

                # Handle single object output
                elif isinstance(data, dict):
                    for message in data.get("messages", []):
                        violations.append(
                            {
                                "rule": message.get("ruleId", "unknown"),
                                "message": message.get("message", ""),
                                "line": message.get("line", 0),
                                "column": message.get("column", 0),
                                "severity": self._map_severity(message.get("severity", 1)),
                                "source": "slds-linter",
                            }
                        )

        except json.JSONDecodeError:
            # If not valid JSON, try to extract violations from text output
            violations.extend(self._parse_text_output(stdout))
            violations.extend(self._parse_text_output(stderr))

        return violations

    def _parse_text_output(self, output: str) -> list[dict]:
        """
        Parse plain text linter output for violations.

        Args:
            output: Text output from linter

        Returns:
            List of violation dictionaries
        """
        violations = []

        if not output:
            return violations

        # Common patterns for linter output
        # Example: "filename.html:10:5: error - message"
        import re

        pattern = r"(\S+):(\d+):(\d+):\s*(error|warning|info)\s*[-:]\s*(.+)"

        for line in output.splitlines():
            match = re.match(pattern, line, re.IGNORECASE)
            if match:
                violations.append(
                    {
                        "file": match.group(1),
                        "line": int(match.group(2)),
                        "column": int(match.group(3)),
                        "severity": match.group(4).upper(),
                        "message": match.group(5).strip(),
                        "rule": "slds",
                        "source": "slds-linter",
                    }
                )

        return violations

    def _map_severity(self, level: int) -> str:
        """
        Map ESLint severity levels to our labels.

        Args:
            level: ESLint severity (1=warning, 2=error)

        Returns:
            Severity label string
        """
        return {0: "INFO", 1: "WARNING", 2: "HIGH"}.get(level, "INFO")


def is_slds_linter_available() -> bool:
    """
    Convenience function to check if SLDS Linter is available.

    Returns:
        True if available, False otherwise
    """
    wrapper = SLDSLinterWrapper()
    return wrapper.is_available()


def lint_lwc_file(file_path: str) -> dict[str, Any]:
    """
    Convenience function to lint a single LWC file.

    Args:
        file_path: Path to file to lint

    Returns:
        Linting results dictionary
    """
    wrapper = SLDSLinterWrapper()
    return wrapper.lint_file(file_path)


if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("Usage: python slds_linter_wrapper.py <file.html|file.css>")
        print("\nChecking SLDS Linter availability...")
        print(f"Available: {is_slds_linter_available()}")
        sys.exit(0)

    result = lint_lwc_file(sys.argv[1])
    print(json.dumps(result, indent=2))
