#!/usr/bin/env python3

"""Report installed cursor and icon themes from the standard XDG roots.

This helper is intentionally read-only and argument-free. Theme paths come
only from XDG_DATA_HOME and XDG_DATA_DIRS; directory symlinks are not followed.
"""

from __future__ import annotations

import json
import os
import stat
import sys
from pathlib import Path


def icon_roots() -> list[Path]:
    home = Path(os.environ.get("HOME") or "/nonexistent")
    data_home = Path(os.environ.get("XDG_DATA_HOME") or home / ".local/share")
    data_dirs = os.environ.get("XDG_DATA_DIRS") or "/usr/local/share:/usr/share"

    roots = [data_home / "icons"]
    roots.extend(Path(directory) / "icons" for directory in data_dirs.split(":") if directory)
    # XDG paths are required to be absolute. Ignoring malformed relative
    # entries also prevents the helper's working directory becoming an
    # accidental caller-controlled search root.
    return [root for root in roots if root.is_absolute()]


def is_real_directory(path: Path) -> bool:
    try:
        return stat.S_ISDIR(path.lstat().st_mode)
    except OSError:
        return False


def is_real_file(path: Path) -> bool:
    try:
        return stat.S_ISREG(path.lstat().st_mode)
    except OSError:
        return False


def has_icon_directories(index_path: Path) -> bool:
    try:
        with index_path.open(encoding="utf-8", errors="replace") as handle:
            for raw_line in handle:
                line = raw_line.strip()
                if line.startswith(("#", ";")) or "=" not in line:
                    continue
                key, value = line.split("=", 1)
                if key.strip() == "Directories":
                    return bool(value.strip())
    except OSError:
        return False
    return False


def catalog() -> dict[str, list[str]]:
    cursor_themes: set[str] = set()
    icon_themes: set[str] = set()

    for root in icon_roots():
        if not is_real_directory(root):
            continue
        try:
            entries = list(os.scandir(root))
        except OSError:
            continue

        for entry in entries:
            if not entry.is_dir(follow_symlinks=False):
                continue
            theme = Path(entry.path)
            if is_real_directory(theme / "cursors"):
                cursor_themes.add(entry.name)

            index_path = theme / "index.theme"
            if is_real_file(index_path) and has_icon_directories(index_path):
                icon_themes.add(entry.name)

    return {
        "cursorThemes": sorted(cursor_themes, key=str.casefold),
        "iconThemes": sorted(icon_themes, key=str.casefold),
    }


def main() -> int:
    if len(sys.argv) != 1:
        print("panama-desktop-style takes no arguments", file=sys.stderr)
        return 2
    print(json.dumps(catalog(), ensure_ascii=False, separators=(",", ":")))
    return 0


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