#!/usr/bin/env python3
"""Lint check for hardcoded DSL symbols in module wrapper consumers.

Scans files that use the adapters/module_wrapper system for Unicode
characters from the DSL symbol pools (LETTER_SYMBOLS, FALLBACK_SYMBOLS).
These files should obtain symbols dynamically from the wrapper, never
hardcode them.

Usage:
    python scripts/lint_no_hardcoded_symbols.py
    python scripts/lint_no_hardcoded_symbols.py --verbose

Exit codes:
    0 - No violations found
    1 - Violations found
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

# ---------------------------------------------------------------------------
# Resolve project root (parent of scripts/)
# ---------------------------------------------------------------------------
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

from adapters.module_wrapper.symbol_generator import (  # noqa: E402
    FALLBACK_SYMBOLS,
    LETTER_SYMBOLS,
)

# ---------------------------------------------------------------------------
# Build banned-character set + reverse lookup from the SSoT
# ---------------------------------------------------------------------------

# char -> list of letter labels (e.g. 'ρ' appears under both R and P)
_CHAR_TO_LETTERS: dict[str, list[str]] = {}
for _letter, _syms in LETTER_SYMBOLS.items():
    for _sym in _syms:
        _CHAR_TO_LETTERS.setdefault(_sym, []).append(_letter)
for _sym in FALLBACK_SYMBOLS:
    _CHAR_TO_LETTERS.setdefault(_sym, []).append("FALLBACK")

BANNED_CHARS: set[str] = set(_CHAR_TO_LETTERS.keys())

# Symbols with common non-DSL uses that should not trigger violations.
EXCLUDED_SYMBOLS: set[str] = {
    "©",  # copyright notices in license headers
}

BANNED_CHARS -= EXCLUDED_SYMBOLS

# ---------------------------------------------------------------------------
# Target files — module wrapper consumers / facades
#
# These files use the adapter/module_wrapper system and should get symbols
# dynamically, never hardcode them.  Add new wrapper facades here.
# ---------------------------------------------------------------------------
TARGET_FILES: list[str] = [
    "gchat/card_framework_wrapper.py",
    "middleware/qdrant_core/qdrant_models_wrapper.py",
]


def _label_for_char(char: str) -> str:
    """Return a human-readable label for a banned character."""
    letters = _CHAR_TO_LETTERS.get(char, ["?"])
    return "/".join(letters)


# ---------------------------------------------------------------------------
# Scanner
# ---------------------------------------------------------------------------


def scan_file(rel_path: Path) -> list[tuple[int, int, str, str]]:
    """Scan a single file for banned characters.

    Returns list of (line_number, column, char, label).
    """
    abs_path = PROJECT_ROOT / rel_path
    violations: list[tuple[int, int, str, str]] = []
    try:
        text = abs_path.read_text(encoding="utf-8")
    except (OSError, UnicodeDecodeError):
        return violations

    for lineno, line in enumerate(text.splitlines(), start=1):
        for col, ch in enumerate(line, start=1):
            if ch in BANNED_CHARS:
                violations.append((lineno, col, ch, _label_for_char(ch)))
    return violations


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--verbose", "-v", action="store_true", help="Show scanned file list"
    )
    args = parser.parse_args()

    targets = [Path(p) for p in TARGET_FILES]
    missing = [p for p in targets if not (PROJECT_ROOT / p).exists()]
    if missing:
        for p in missing:
            print(f"WARNING: target file not found: {p}")

    scannable = [p for p in targets if (PROJECT_ROOT / p).exists()]

    if args.verbose:
        print(f"Scanning {len(scannable)} target file(s):")
        for p in scannable:
            print(f"  {p}")

    total_violations = 0
    for rel_path in scannable:
        hits = scan_file(rel_path)
        for lineno, col, char, label in hits:
            print(
                f"{rel_path}:{lineno}:{col}: DSL001 Hardcoded DSL symbol "
                f"'{char}' ({label}_LETTER) found "
                f"— use dynamic symbol lookup instead"
            )
            total_violations += 1

    if total_violations:
        print(f"\nFound {total_violations} violation(s).")
        return 1

    if args.verbose:
        print("No hardcoded DSL symbols found.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
