#!/usr/bin/env python3
"""maketool-refscan

Option B: String reference scan

- Scans a project directory (derived from Sublime's {file} or current directory)
- Shows ONLY unused candidates
- Groups UNUSED files by extension, separated by a blank line
- No group headers

Expected input:
  refscan.py "C:\\full\\path\\to\\current_file.py"
  (Sublime typically passes {file})

If no argument is provided, scans the current working directory.

This is intentionally heuristic: it searches for filename/path *tokens* inside
project text sources. It's best for catching unused assets (.ui/.ico/.png/.qss/etc.)
and "orphan" files. For definitive Python reachability, use an import/AST walk.
"""

from __future__ import annotations

import sys
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Iterable, List, Optional, Set, Tuple


DEFAULT_IGNORE_DIRS = {
    ".git", ".hg", ".svn",
    "__pycache__", ".mypy_cache", ".pytest_cache",
    ".venv", "venv", "env",
    "build", "dist", ".tox",
}

DEFAULT_SCAN_EXTS = {
    ".py", ".ui", ".qrc", ".qss",
    ".bat", ".cmd", ".ps1",
    ".txt", ".md", ".rst",
    ".ini", ".cfg", ".toml",
    ".json", ".yaml", ".yml",
    ".xml", ".html", ".htm", ".css",
}

DEFAULT_SKIP_CONTENT_EXTS = {
    ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".svg",
    ".ico", ".icns",
    ".pdf", ".zip", ".7z", ".rar",
    ".db", ".sqlite", ".dll", ".exe", ".pyd",
}


@dataclass(frozen=True)
class Candidate:
    path: Path
    rel: str
    tokens: Tuple[str, ...]


def iter_files(root: Path, ignore_dirs: Set[str]) -> Iterable[Path]:
    for p in root.rglob("*"):
        if p.is_dir():
            continue
        if any(part in ignore_dirs for part in p.parts):
            continue
        yield p


def safe_read_text(p: Path) -> Optional[str]:
    try:
        return p.read_text(encoding="utf-8", errors="ignore")
    except Exception:
        return None


def norm(s: str) -> str:
    return s.replace("\\", "/").lower()


def build_candidates(root: Path, files: List[Path]) -> List[Candidate]:
    out: List[Candidate] = []
    for p in files:
        rel = str(p.relative_to(root))
        rel_norm = norm(rel)
        base = p.name.lower()
        stem = p.stem.lower()

        tokens = {
            base,
            stem,
            rel_norm,
            rel_norm.replace("/", "\\"),
        }

        out.append(Candidate(p, rel, tuple(t for t in tokens if t)))
    return out


def is_text_source(p: Path) -> bool:
    ext = p.suffix.lower()
    if ext in DEFAULT_SKIP_CONTENT_EXTS:
        return False
    return ext in DEFAULT_SCAN_EXTS


def choose_root_from_argv(argv: List[str]) -> Path:
    """
    If Sublime passes {file}, argv[1] is the absolute file path.
    Scan that file's parent directory.
    """
    if len(argv) >= 2:
        p = Path(argv[1]).expanduser()
        try:
            p = p.resolve()
        except Exception:
            pass

        if p.suffix:
            return p.parent
        return p

    return Path.cwd().resolve()


def print_unused_grouped(unused: List[Candidate]) -> None:
    groups: Dict[str, List[str]] = defaultdict(list)

    for c in unused:
        ext = c.path.suffix.lower() or ""
        groups[ext].append(c.rel)

    title = "UNUSED files (no filename/path tokens found)"
    print(title)
    print("-" * len(title))

    first = True
    for ext in sorted(groups):
        if not first:
            print()
        first = False

        for rel in sorted(groups[ext]):
            print(rel)
    print()


def main() -> int:
    root = choose_root_from_argv(sys.argv)

    if not root.exists() or not root.is_dir():
        title = "UNUSED files (no filename/path tokens found)"
        print(title)
        print("-" * len(title))
        print(f"(skipped: root is not a directory: {root})")
        print()
        return 2

    files = sorted(iter_files(root, DEFAULT_IGNORE_DIRS))
    candidates = build_candidates(root, files)
    text_sources = [p for p in files if is_text_source(p)]

    references: Dict[str, List[str]] = {c.rel: [] for c in candidates}

    for src in text_sources:
        text = safe_read_text(src)
        if not text:
            continue
        hay = text.lower()
        src_rel = str(src.relative_to(root))

        for c in candidates:
            if src == c.path:
                continue
            if any(tok in hay for tok in c.tokens):
                references[c.rel].append(src_rel)

    unused = [c for c in candidates if not references[c.rel]]
    print_unused_grouped(unused)

    return 0


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