"""Plugin capability/permission model.

A plugin declares, in its ``plugin.json``, the sensitive capabilities it needs::

    "permissions": ["network:api.govee.com", "input"]

The desktop app enforces this as a **least-privilege contract**: anything the
plugin tries that isn't declared is denied (not merely prompted). The SDK's
static scanner (:mod:`mixlar.scan`) infers the capabilities a plugin's code
actually touches, so ``validate`` can warn when code uses a capability the
manifest didn't declare (or declares one it never uses).

Capabilities are coarse on purpose — they map to what the app's ``plugin_guard``
audit hook can actually gate. Some support a scope after a colon:

  * ``network[:host]``   — outbound HTTP/socket; optional host allowlist
  * ``subprocess``       — launch external programs / shell
  * ``filesystem[:ro]``  — write files outside the plugin folder (``:ro`` = read-only)
  * ``native``           — ctypes / win32 / COM / registry (very powerful)
  * ``input``            — synthesize keystrokes / mouse (pynput etc.)
  * ``gui``              — create its own windows/dialogs
"""
from __future__ import annotations

from typing import Dict, List, Set, Tuple

CAP_NETWORK = "network"
CAP_SUBPROCESS = "subprocess"
CAP_FS = "filesystem"
CAP_NATIVE = "native"
CAP_INPUT = "input"
CAP_GUI = "gui"

#: Capability -> human description (also the app's consent-prompt copy).
CAPABILITIES: Dict[str, str] = {
    CAP_NETWORK: "Make network requests (talk to web APIs / devices over IP)",
    CAP_SUBPROCESS: "Launch external programs or shell commands",
    CAP_FS: "Read and write files outside its own folder",
    CAP_NATIVE: "Call native OS APIs (ctypes, Win32, COM, registry)",
    CAP_INPUT: "Synthesize keyboard and mouse input",
    CAP_GUI: "Open its own windows and dialogs",
}

#: Capabilities considered high-privilege — worth extra scrutiny in review.
HIGH_PRIVILEGE = {CAP_SUBPROCESS, CAP_NATIVE, CAP_INPUT}


def base_cap(entry: str) -> str:
    """Return the capability name without its scope, e.g. ``network:x`` -> ``network``."""
    return str(entry).split(":", 1)[0].strip().lower()


def scope_of(entry: str) -> str:
    """Return the scope after the colon, or ``""``."""
    parts = str(entry).split(":", 1)
    return parts[1].strip() if len(parts) == 2 else ""


def parse(manifest: dict) -> List[str]:
    """Return the raw declared permission entries from a manifest (may be scoped)."""
    perms = manifest.get("permissions") or []
    if not isinstance(perms, list):
        return []
    return [str(p).strip() for p in perms if str(p).strip()]


def declared_caps(manifest: dict) -> Set[str]:
    """The set of base capabilities a manifest declares."""
    return {base_cap(p) for p in parse(manifest)}


def unknown_entries(manifest: dict) -> List[str]:
    """Declared entries whose base capability isn't a known capability."""
    return [p for p in parse(manifest) if base_cap(p) not in CAPABILITIES]


def diff(declared: Set[str], detected: Set[str]) -> Tuple[Set[str], Set[str]]:
    """Return ``(undeclared, unused)`` capabilities.

    * ``undeclared`` — the code uses it but the manifest didn't declare it
      (a hard problem: the app will block it at runtime).
    * ``unused`` — declared but the scanner saw no use (over-broad; tighten it).
    """
    undeclared = {c for c in detected if c and c not in declared}
    unused = {c for c in declared if c in CAPABILITIES and c not in detected}
    return undeclared, unused


__all__ = [
    "CAPABILITIES", "HIGH_PRIVILEGE",
    "CAP_NETWORK", "CAP_SUBPROCESS", "CAP_FS", "CAP_NATIVE", "CAP_INPUT", "CAP_GUI",
    "base_cap", "scope_of", "parse", "declared_caps", "unknown_entries", "diff",
]
