#!/usr/bin/env python3
"""
check-tags.py — Tag similarity checker for Hugo content

Compares tags in staged files against all existing tags in the site.
Warns and blocks commit when a new tag looks similar to an existing one.

Similarity checks (via difflib.SequenceMatcher):
  - Ratio >= 0.6 (catches typos, reordered chars, partial matches)
  - One tag is a substring of the other

Skip with: SKIP_TAG_CHECK=1 git commit

Usage: check-tags.py <file1.md> [file2.md ...]
"""

import os
import re
import sys
from difflib import SequenceMatcher
from pathlib import Path

# --- Colors ---
RED = "\033[0;31m"
YELLOW = "\033[0;33m"
GREEN = "\033[0;32m"
CYAN = "\033[0;36m"
BOLD = "\033[1m"
NC = "\033[0m"

SIMILARITY_THRESHOLD = 0.6  # SequenceMatcher ratio (0-1)


def extract_tags(filepath: Path, *, keep_blanks: bool = False) -> list[str]:
    """Extract tags from YAML front matter of a markdown file.

    If keep_blanks is True, empty strings from blank tag entries like
    tags: ["", "foo"] are preserved so the caller can detect them.
    """
    try:
        text = filepath.read_text(encoding="utf-8")
    except (OSError, UnicodeDecodeError):
        return []

    # Match front matter between --- delimiters
    fm_match = re.match(r"^---\s*\n(.*?)\n---\s*\n", text, re.DOTALL)
    if not fm_match:
        return []

    frontmatter = fm_match.group(1)

    tags: list[str] = []

    # Match inline array: tags: ["foo", "bar"] or tags: ['foo', 'bar'] or tags: [foo, bar]
    inline = re.search(r"^tags:\s*\[([^\]]*)\]", frontmatter, re.MULTILINE)
    if inline:
        raw = inline.group(1).strip()
        if raw:  # non-empty array contents (skip tags: [] and tags: [ ])
            tags = [t.strip().strip("\"'").lower() for t in raw.split(",")]
    else:
        # Match YAML list format:
        # tags:
        #   - foo
        #   - bar
        list_match = re.search(
            r"^tags:\s*\n((?:\s+-\s+.+\n?)+)", frontmatter, re.MULTILINE
        )
        if list_match:
            items = re.findall(r"^\s+-\s+(.*)", list_match.group(1), re.MULTILINE)
            tags = [t.strip().strip("\"'").lower() for t in items]

    if keep_blanks:
        return tags
    return [t for t in tags if t]


def find_similar(new_tag: str, existing_tags: set[str]) -> list[tuple[str, str]]:
    """Find existing tags similar to a new tag.

    Returns list of (existing_tag, reason) tuples.
    """
    similar = []

    for existing in sorted(existing_tags):
        if existing == new_tag:
            continue

        # Check substring match
        if existing in new_tag or new_tag in existing:
            similar.append((existing, "substring match"))
            continue

        # Check similarity ratio
        ratio = SequenceMatcher(None, new_tag, existing).ratio()
        if ratio >= SIMILARITY_THRESHOLD:
            similar.append((existing, f"similarity: {ratio:.0%}"))

    return similar


def main() -> int:
    if os.environ.get("SKIP_TAG_CHECK") == "1":
        print(f"{YELLOW}Skipping tag similarity check (SKIP_TAG_CHECK=1){NC}")
        return 0

    if len(sys.argv) < 2:
        print("Usage: check-tags.py <file1.md> [file2.md ...]", file=sys.stderr)
        return 1

    staged_files = sys.argv[1:]

    # Find repo root
    repo_root = Path.cwd()
    content_dir = repo_root / "content"
    if not content_dir.is_dir():
        print(f"{YELLOW}No content/ directory found, skipping tag check{NC}")
        return 0

    # Resolve staged files to absolute paths for exclusion
    staged_abs = {(repo_root / f).resolve() for f in staged_files}

    # Build tag registry from all content files (excluding staged files)
    all_tags: set[str] = set()
    for md_file in content_dir.rglob("*.md"):
        if md_file.resolve() in staged_abs:
            continue
        all_tags.update(extract_tags(md_file))

    if not all_tags:
        print(f"{GREEN}No existing tags found, nothing to compare against.{NC}")
        return 0

    # Check staged files for similar tags
    found_issues = False

    for staged_file in staged_files:
        filepath = repo_root / staged_file
        if not filepath.is_file():
            continue
        # Only check content files
        if not staged_file.startswith("content/"):
            continue

        file_tags_raw = extract_tags(filepath, keep_blanks=True)

        # Check for blank/empty tags
        blank_count = sum(1 for t in file_tags_raw if not t)
        if blank_count:
            found_issues = True
            print()
            print(
                f"{YELLOW}{BOLD}Found {blank_count} blank tag(s) "
                f"in {staged_file}{NC}"
            )
            print(f"{YELLOW}  Remove empty strings from the tags array{NC}")

        file_tags = [t for t in file_tags_raw if t]

        for tag in file_tags:
            # If the tag already exists exactly, it's fine
            if tag in all_tags:
                continue

            # New tag — check for similarity
            similar = find_similar(tag, all_tags)

            if similar:
                found_issues = True
                print()
                print(f"{YELLOW}{BOLD}New tag '{tag}' in {staged_file}{NC}")
                print(f"{YELLOW}  Similar existing tags:{NC}")
                for existing, reason in similar:
                    print(f"    {CYAN}\u2192 {existing} ({reason}){NC}")

    if found_issues:
        print()
        print(f"{RED}{BOLD}Tag similarity check failed.{NC}")
        print(f"{RED}Consider using an existing tag, or skip with:{NC}")
        print(f"{RED}  SKIP_TAG_CHECK=1 git commit{NC}")
        print()
        return 1

    print(f"{GREEN}Tag check passed \u2014 no similar tags found.{NC}")
    return 0


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