#!/usr/bin/env python3
"""
Pre-commit hook to detect double-escaping in JavaScript files.

Catches patterns where escapeHtml() is called on values that are then
passed to functions which already escape internally (showError, showSuccess,
showInfo, showAlert).

Known limitations:
- Multi-line function calls are not detected (line-by-line processing).
- Variable indirection bypasses detection
  (e.g., ``const safe = escapeHtml(msg); showError(safe);``).
"""

import re
import sys


# Functions that escape their arguments internally
ESCAPING_FUNCTIONS = ["showError", "showSuccess", "showInfo", "showAlert"]

# Build pattern: showError(...escapeHtml(...)...)
PATTERN = re.compile(
    r"\b(" + "|".join(ESCAPING_FUNCTIONS) + r")\s*\("
    r".*?"
    r"(?:escapeHtml|XSSProtection\.escapeHtml)\s*\("
)


def strip_js_comments(line, in_block_comment):
    """Strip JavaScript comments from a line, respecting string literals.

    Handles ``//`` line comments, ``/* */`` block comments (including
    multi-line), and correctly ignores comment-like sequences inside
    single-quoted, double-quoted, and template-literal strings.

    Returns ``(code_without_comments, still_in_block_comment)``.
    """
    result = []
    i = 0
    in_single_quote = False
    in_double_quote = False
    in_template = False

    while i < len(line):
        ch = line[i]

        # Inside a block comment — scan for */
        if in_block_comment:
            if ch == "*" and i + 1 < len(line) and line[i + 1] == "/":
                in_block_comment = False
                i += 2
            else:
                i += 1
            continue

        # Handle escape sequences inside strings
        if (in_single_quote or in_double_quote or in_template) and ch == "\\":
            result.append(ch)
            if i + 1 < len(line):
                result.append(line[i + 1])
                i += 2
            else:
                i += 1
            continue

        # Track string state
        if ch == "'" and not in_double_quote and not in_template:
            in_single_quote = not in_single_quote
        elif ch == '"' and not in_single_quote and not in_template:
            in_double_quote = not in_double_quote
        elif ch == "`" and not in_single_quote and not in_double_quote:
            in_template = not in_template
        elif not in_single_quote and not in_double_quote and not in_template:
            # Outside strings — check for comments
            if ch == "/" and i + 1 < len(line):
                if line[i + 1] == "/":
                    break  # Line comment — rest of line is comment
                if line[i + 1] == "*":
                    in_block_comment = True
                    i += 2
                    continue

        result.append(ch)
        i += 1

    return "".join(result), in_block_comment


def check_file(file_path):
    """Check a JavaScript file for double-escaping patterns."""
    issues = []

    try:
        with open(file_path, "r", encoding="utf-8") as f:
            lines = f.readlines()
    except (UnicodeDecodeError, IOError):
        # Skip binary files or files we can't read
        return []

    in_block_comment = False
    for line_num, line in enumerate(lines, 1):
        code, in_block_comment = strip_js_comments(line, in_block_comment)

        if PATTERN.search(code):
            issues.append(
                f"{file_path}:{line_num}: Possible double-escaping — "
                f"escapeHtml() inside a function that already escapes: "
                f"{line.strip()}"
            )

    return issues


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

    all_issues = []

    for file_path in sys.argv[1:]:
        if not file_path.endswith(".js"):
            continue

        try:
            issues = check_file(file_path)
            all_issues.extend(issues)
        except Exception as e:
            print(f"Error checking {file_path}: {e}")
            continue

    if all_issues:
        print("Double-escaping detected:")
        print("-" * 60)
        for issue in all_issues:
            print(f"  {issue}")
        print("-" * 60)
        print("\nThese functions already escape their arguments internally.")
        print("Pass raw text instead of pre-escaping with escapeHtml().")
        return 1

    return 0


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