"""runner — the CLI entry point.

Top-level: a handful of client commands (signin, whoami, workspaces, docs, dev,
usage, tokens). Everything else is an authoring verb that serializes to {op, args}
and POSTs to /api/v1/op. JSON to stdout; any rejection to stderr + exit 1, so an
agent loop can branch on the exit code exactly like it does with `./r`.
"""
import json
import sys

from . import __version__, api, config, docs as docs_mod, help_text, signin as signin_mod
from . import dev as dev_mod
from .verbs import VerbError, to_op


def emit(result):
    # peel the /op envelope {ok, result}, then the dispatch envelope {ok, op, scope,
    # result}, so the CLI prints the actual payload — not nested confirmation wrappers.
    if isinstance(result, dict) and "result" in result and set(result.keys()) <= {"ok", "result"}:
        result = result["result"]
    if isinstance(result, dict) and "result" in result and set(result.keys()) <= {"ok", "op", "scope", "result"}:
        result = result["result"]
    print(json.dumps(result, indent=2, default=str, ensure_ascii=False))


def fail(msg, code=1):
    print(f"REJECTED: {msg}", file=sys.stderr)
    sys.exit(code)


def main(argv=None):
    argv = list(sys.argv[1:] if argv is None else argv)
    if not argv or argv[0] in ("-h", "--help"):
        print(help_text.render_top()); return 0
    cmd, rest = argv[0], argv[1:]

    if cmd in ("version", "--version", "-V"):
        print(f"runner (acsetra) {__version__}"); return 0

    # help — top-level, `help <topic>`, and per-verb `<cmd> … --help` (lexical, runs
    # before to_op so it never trips _walk's "missing value for --help").
    if cmd == "help":
        out = help_text.dispatch_help(rest[0], rest[1:]) if rest else None
        print(out if out is not None else help_text.render_top()); return 0
    if "--help" in rest or "-h" in rest:
        out = help_text.dispatch_help(cmd, rest)
        if out is not None:
            print(out); return 0

    # --- client-only commands ---------------------------------------------
    if cmd in ("signin", "login"):
        api_base = _opt(rest, "--api")
        return signin_mod.signin(api_base=api_base, open_browser="--no-browser" not in rest)
    if cmd in ("logout", "signout"):
        cred = config.load_user(); cred.pop("token", None); config.save_user(cred)
        print("logged out", file=sys.stderr); return 0
    if cmd == "use":
        if not rest:
            fail("usage: runner use <workspace-slug>")
        config.set_project(workspace=rest[0]); print(f"workspace -> {rest[0]}", file=sys.stderr); return 0
    if cmd == "dev":
        app = rest[0] if rest and not rest[0].startswith("-") else None
        return dev_mod.dev(app, open_browser="--no-browser" not in rest)
    if cmd == "docs":
        if rest and rest[0] == "pull":
            return docs_mod.pull(scope=_opt(rest, "-S") or _opt(rest, "--scope"))
        fail("usage: runner docs pull [-S scope]")
    if cmd in ("checkout", "co"):
        from . import checkout as checkout_mod
        app = rest[0] if rest and not rest[0].startswith("-") else None
        return checkout_mod.checkout(scope=app, out=_opt(rest, "-o") or _opt(rest, "--out"),
                                     force="--force" in rest)

    # --- everything below needs the API; wrap API errors uniformly --------
    try:
        if cmd == "whoami":
            return emit(api.get("/api/v1/whoami")) or 0
        if cmd == "workspaces":
            use = _opt(rest, "--use")
            if use:
                config.set_project(workspace=use)
            return emit(api.get("/api/v1/workspaces")) or 0
        if cmd == "usage":
            days = _opt(rest, "--days") or "30"
            return emit(api.get("/api/v1/usage", {"days": days})) or 0
        if cmd == "tokens":
            return _tokens(rest)

        # --- authoring verbs ---------------------------------------------
        op, args = to_op(cmd, rest)
        result = api.post_op(op, args)
        emit(result)
        return 0
    except VerbError as e:
        fail(str(e), 2)
    except api.ApiError as e:
        detail = ""
        if e.detail and isinstance(e.detail, dict):
            extra = {k: v for k, v in e.detail.items() if k != "error"}
            if extra:
                detail = " " + json.dumps(extra, default=str)
        fail(f"{e}{detail}", 1)


def _tokens(rest):
    if rest and rest[0] == "new":
        out = api.post("/api/v1/tokens", {"name": _opt(rest, "--name") or "cli"})
        print(json.dumps(out, indent=2)); return 0
    if rest and rest[0] == "revoke" and len(rest) > 1:
        return emit(api.delete(f"/api/v1/tokens/{rest[1]}")) or 0
    return emit(api.get("/api/v1/tokens")) or 0


def _opt(rest, flag):
    """Pull `--flag value` out of a flat arg list (for client-only commands)."""
    if flag in rest:
        i = rest.index(flag)
        if i + 1 < len(rest):
            return rest[i + 1]
    return None


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