"""Sandbox child entry point (runs in an isolated subprocess).

Invoked as ``python -m mixlar._sandbox_child <pkg_dir> <caps>``. It installs the
global capability enforcer for the declared capabilities *before* importing the
plugin, then loads the plugin's entry module and tries to instantiate its
``MixlarPlugin`` subclass. Anything the plugin does that exceeds its declared
capabilities raises :class:`mixlar.enforce.CapabilityViolation` and is reported.

The result is printed to stdout as a single JSON line prefixed with
``__MIXLAR_SANDBOX__`` so the parent (:mod:`mixlar.sandbox`) can find it amid any
plugin output. This module executes plugin code — it is meant to run only in a
throwaway child process, never imported into the host app.
"""
from __future__ import annotations

import json
import os
import sys


def _emit(result: dict) -> None:
    sys.stdout.write("\n__MIXLAR_SANDBOX__" + json.dumps(result) + "\n")
    sys.stdout.flush()


def main() -> int:
    if len(sys.argv) < 2:
        _emit({"ok": False, "error": "usage: _sandbox_child <pkg_dir> [caps]"})
        return 2
    pkg_dir = sys.argv[1]
    caps = [c for c in (sys.argv[2].split(",") if len(sys.argv) > 2 else []) if c]

    # Import everything the loader needs BEFORE the hook goes up, so our own
    # infrastructure isn't gated — only the plugin's subsequent actions are.
    from mixlar import enforce, manifest as _manifest

    man, err = _manifest.read_manifest(pkg_dir)
    if err:
        _emit({"ok": False, "error": f"invalid manifest: {err}"})
        return 1

    violations = []

    def _record(cap, event):
        violations.append({"capability": cap, "event": event})

    # From here on, the plugin is confined to its declared capabilities.
    enforce.install(caps, on_violation=_record)

    entry = os.path.join(pkg_dir, man["entry"])
    # Minimal app-like namespace so entry modules that reference injected
    # globals still import (the real app injects the same names).
    ns = {
        "__name__": "mixlar_sandbox_plugin",
        "__file__": entry,
        "MixlarPlugin": _make_stub_base(),
    }
    ns.update(_stub_globals())

    try:
        with open(entry, "r", encoding="utf-8") as fh:
            src = fh.read()
        code = compile(src, entry, "exec")
        exec(code, ns)  # noqa: S102 — this is the whole point, under enforcement
    except enforce.CapabilityViolation as e:
        _emit({"ok": False, "error": str(e), "capability": e.capability,
               "event": e.event, "violations": violations, "phase": "import"})
        return 1
    except Exception as e:  # noqa: BLE001
        _emit({"ok": True, "loaded": True, "instantiated": False,
               "note": f"imported under enforcement; load-time error: {type(e).__name__}: {e}",
               "violations": violations})
        return 0

    # Try to find and instantiate the plugin class.
    inst_ok, inst_note = _try_instantiate(ns, enforce)
    _emit({"ok": True, "loaded": True, "instantiated": inst_ok,
           "note": inst_note, "capabilities_declared": caps,
           "violations": violations})
    return 0


def _try_instantiate(ns, enforce):
    from mixlar.plugin import MixlarPlugin as RealBase  # for isinstance-ish check
    for val in list(ns.values()):
        if isinstance(val, type) and val.__name__ != "MixlarPlugin" \
                and any(getattr(b, "__name__", "") == "MixlarPlugin"
                        for b in getattr(val, "__mro__", [])):
            try:
                val()
                return True, f"instantiated {val.__name__}"
            except enforce.CapabilityViolation as e:
                return False, str(e)
            except Exception as e:  # noqa: BLE001
                return False, f"{val.__name__}() raised {type(e).__name__}: {e}"
    return False, "no MixlarPlugin subclass found"


def _make_stub_base():
    class MixlarPlugin:  # minimal stand-in for isolated loading
        plugin_id = ""
        def _pget(self, k, d=None): return d
        def _pset(self, k, v): pass
        def push_widget_data(self, *a, **k): pass
        def push_widget_image(self, *a, **k): pass
    return MixlarPlugin


def _stub_globals():
    # The app injects these names into plugin modules; provide inert stand-ins.
    g = {name: "" for name in ("ACCENT", "CARD2", "SEP", "WHITE", "GRAY",
                               "GREEN", "RED", "ORANGE", "FONT")}
    g["_fa"] = lambda *a, **k: None
    g["_EDITOR_COMBO_STYLE"] = lambda *a, **k: ""
    g["_style_combo_view"] = lambda *a, **k: None
    return g


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