"""Source code scanner for TransDuck ait() and ait_plural() calls."""

import os
import re
import sys
from pathlib import Path

# Regex patterns

# ait("text") or ait("text", context="ctx") — Python/template keyword style
_AIT_KEYWORD_CTX = re.compile(
    r'''ait\s*\(\s*(['"])(.*?)\1(?:\s*,\s*context\s*=\s*(['"])(.*?)\3)?''',
)

# ait("text") or ait("text", "ctx") — JS positional style
_AIT_POSITIONAL_CTX = re.compile(
    r'''ait\s*\(\s*(['"])(.*?)\1(?:\s*,\s*(['"])(.*?)\3)?''',
)

# ait_plural("one", "other") or aitPlural("one", "other")
_AIT_PLURAL = re.compile(
    r'''ait(?:_p|P)lural\s*\(\s*(['"])(.*?)\1\s*,\s*(['"])(.*?)\3''',
)

# {% ait "text" %} or {% ait "text" context="ctx" %}
_DJANGO_TAG = re.compile(
    r'''\{%\s*ait\s+(['"])(.*?)\1(?:\s+context=(['"])(.*?)\3)?\s*%\}''',
)

# {% ait_plural "one" "other" %} or {% ait_plural "one" "other" context="ctx" %}
_DJANGO_PLURAL_TAG = re.compile(
    r'''\{%\s*ait_plural\s+(['"])(.*?)\1\s+(['"])(.*?)\3(?:\s+context=(['"])(.*?)\5)?\s*%\}''',
)

# t("text") or t("text", "ctx") — only in files with transduck/react import
_T_POSITIONAL = re.compile(r'''(?<![a-zA-Z_.$])t\s*\(\s*(['"])(.*?)\1(?:\s*,\s*(['"])(.*?)\3)?''')

# tPlural("one", "other") — only in files with transduck/react import
_T_PLURAL = re.compile(r'''(?<![a-zA-Z_.$])tPlural\s*\(\s*(['"])(.*?)\1\s*,\s*(['"])(.*?)\3''')

_HAS_TRANSDUCK_REACT = re.compile(r'''from\s+['"]transduck/react['"]''')

# File extensions that use JS-style positional context
_JS_EXTENSIONS = {".js", ".ts", ".tsx", ".jsx"}

# File extensions that may contain Django template tags
_TEMPLATE_EXTENSIONS = {".html", ".jinja", ".jinja2"}


def _normalize_multiline_strings(content: str) -> str:
    """Join adjacent string literals so the regex can match multi-line strings.

    Handles:
    - Python implicit concatenation: "foo" \\n "bar" -> "foobar"
    - JS + concatenation: "foo" + \\n "bar" -> "foobar"
    - Both single and double quotes
    """
    # Join adjacent string literals: "..." \s+ "..." or '...' \s+ '...'
    # Also handles "..." + \s+ "..." for JS
    content = re.sub(
        r'''(['"])\s*\+?\s*\n\s*\1''',
        '',
        content,
    )
    return content


def extract_strings(content: str, filename: str) -> list[dict]:
    """Extract translatable strings from file content.

    Returns a list of dicts, each with:
    - text, context (for regular strings)
    - plural, one, other, context (for plural strings)
    - line (1-based line number)
    """
    content = _normalize_multiline_strings(content)
    results = []
    ext = Path(filename).suffix.lower()
    is_js = ext in _JS_EXTENSIONS
    is_template = ext in _TEMPLATE_EXTENSIONS

    lines = content.split("\n")

    # Track positions of plural matches so we don't double-match them as ait()
    plural_spans = set()

    # 1. Find all plural calls
    for match in _AIT_PLURAL.finditer(content):
        one = match.group(2)
        other = match.group(4)
        line_num = content[:match.start()].count("\n") + 1
        results.append({
            "plural": True,
            "one": one,
            "other": other,
            "context": None,
            "line": line_num,
        })
        plural_spans.add((match.start(), match.end()))

    # 2. Find Django template tags (only in template files)
    if is_template:
        for match in _DJANGO_TAG.finditer(content):
            text = match.group(2)
            context = match.group(4) if match.group(4) else None
            line_num = content[:match.start()].count("\n") + 1
            results.append({
                "text": text,
                "context": context,
                "line": line_num,
            })

        for match in _DJANGO_PLURAL_TAG.finditer(content):
            one = match.group(2)
            other = match.group(4)
            context = match.group(6) if match.group(6) else None
            line_num = content[:match.start()].count("\n") + 1
            results.append({
                "plural": True,
                "one": one,
                "other": other,
                "context": context,
                "line": line_num,
            })
            plural_spans.add((match.start(), match.end()))

    # 3. Find ait() calls
    pattern = _AIT_POSITIONAL_CTX if is_js else _AIT_KEYWORD_CTX
    for match in pattern.finditer(content):
        # Skip if this is part of a plural match (ait_plural / aitPlural)
        if any(ps <= match.start() < pe for ps, pe in plural_spans):
            continue

        # Check that this is specifically "ait(" not "ait_plural(" or "aitPlural("
        prefix_start = max(0, match.start() - 10)
        prefix = content[prefix_start:match.start() + 4]
        if "plural" in prefix.lower() or "Plural" in prefix:
            continue

        text = match.group(2)
        context = match.group(4) if match.group(4) else None
        line_num = content[:match.start()].count("\n") + 1
        results.append({
            "text": text,
            "context": context,
            "line": line_num,
        })

    # 4. t() and tPlural() — only in files that import from transduck/react
    if _HAS_TRANSDUCK_REACT.search(content):
        for match in _T_POSITIONAL.finditer(content):
            # Skip if overlaps with plural spans
            if any(ps <= match.start() < pe for ps, pe in plural_spans):
                continue
            text = match.group(2)
            context = match.group(4) if match.group(4) else None
            line_num = content[:match.start()].count("\n") + 1
            results.append({"text": text, "context": context, "line": line_num})

        for match in _T_PLURAL.finditer(content):
            one = match.group(2)
            other = match.group(4)
            line_num = content[:match.start()].count("\n") + 1
            results.append({"plural": True, "one": one, "other": other, "context": None, "line": line_num})

    return results


# Supported file extensions
_SCAN_EXTENSIONS = {".py", ".js", ".ts", ".tsx", ".jsx", ".html", ".jinja", ".jinja2"}

# Directories to skip
_SKIP_DIRS = {"node_modules", ".venv", "venv", "__pycache__", ".git", "dist", "build", ".next"}


def _should_skip_dir(dirname: str) -> bool:
    if dirname in _SKIP_DIRS:
        return True
    if "egg-info" in dirname:
        return True
    return False


def scan_directory(dirs: list[Path]) -> list[dict]:
    """Walk directories and extract all translatable strings.

    Returns deduplicated list of entries with 'files' field listing all locations.
    """
    # Collect all raw matches: (dedup_key, entry_template, file_location)
    raw_matches: dict[tuple, dict] = {}

    for scan_dir in dirs:
        for root, dirnames, filenames in os.walk(scan_dir):
            # Filter out skip dirs (modifying dirnames in-place controls os.walk)
            dirnames[:] = [d for d in dirnames if not _should_skip_dir(d)]

            for fname in filenames:
                ext = Path(fname).suffix.lower()
                if ext not in _SCAN_EXTENSIONS:
                    continue

                filepath = Path(root) / fname
                try:
                    content = filepath.read_text(encoding="utf-8")
                except (PermissionError, UnicodeDecodeError, OSError) as e:
                    print(f"Warning: skipping {filepath}: {e}", file=sys.stderr)
                    continue

                entries = extract_strings(content, fname)
                for entry in entries:
                    # Build dedup key
                    if entry.get("plural"):
                        key = (entry["one"], entry["other"], entry.get("context"))
                    else:
                        key = (entry["text"], entry.get("context"))

                    file_loc = f"{filepath}:{entry['line']}"

                    if key in raw_matches:
                        raw_matches[key]["files"].append(file_loc)
                    else:
                        result_entry = {**entry, "files": [file_loc]}
                        result_entry.pop("line", None)
                        raw_matches[key] = result_entry

    return list(raw_matches.values())
