"""NDJSON server: one JSON request per line in, one reply per line out.

    python -m zytectx.host

``{"op": "ping"}`` answers ``{"ok": true}``. Every other op is one of the
public ``zytectx`` functions called with named arguments, answering
``{"ok": true, "result": <return value>}``. Failures answer
``{"ok": false, "error": "..."}``; an ``id`` on the request is echoed on the
reply. Nothing reaching this loop can end it except EOF on stdin.
"""

from __future__ import annotations

import inspect
import json
import sys
from typing import Any, Callable, TextIO

import zytectx

OPERATIONS: dict[str, Callable[..., Any]] = {
    "answer_lookup": zytectx.answer_lookup,
    "answer_record": zytectx.answer_record,
    "change_analyze": zytectx.change_analyze,
    "claim_check": zytectx.claim_check,
    "claim_create": zytectx.claim_create,
    "fact": zytectx.fact,
    "knowledge_events": zytectx.knowledge_events,
    "knowledge_query": zytectx.knowledge_query,
    "knowledge_record_answer": zytectx.knowledge_record_answer,
    "scan": zytectx.scan,
    "status": zytectx.status,
    "retrieve": zytectx.retrieve,
    "read": zytectx.read,
    "neighbors": zytectx.neighbors,
    "obligation_list": zytectx.obligation_list,
    "obligation_resolve": zytectx.obligation_resolve,
    "symbol_at": zytectx.symbol_at,
    "register_edits": zytectx.register_edits,
    "register_reads": zytectx.register_reads,
    "remember": zytectx.remember,
    "prime": zytectx.prime,
    "probe_get": zytectx.probe_get,
    "probe_plan": zytectx.probe_plan,
    "probe_run": zytectx.probe_run,
    "snapshot_capture": zytectx.snapshot_capture,
    "typescript_boundary": zytectx.typescript_boundary,
}

CAPABILITIES = {
    "service": "Byte Repository Intelligence",
    "version": 1,
    "operations": sorted(OPERATIONS),
    "claim_statuses": ["supported", "refuted", "unresolved"],
    "applicability": ["current", "needs_recheck", "unknown"],
    "dependency_types": ["exact_blob", "query_set", "negative_query"],
    "trusted_probe_platforms": ["linux", "darwin"],
}


def handle(request: dict[str, Any]) -> dict[str, Any]:
    operation = request.get("op")
    if operation == "ping":
        return {"ok": True}
    if operation == "capabilities":
        return {"ok": True, "result": CAPABILITIES}
    function = OPERATIONS.get(operation) if isinstance(operation, str) else None
    if function is None:
        return {"ok": False, "error": f"unknown op: {operation!r}"}
    parameters = inspect.signature(function).parameters
    arguments = {key: value for key, value in request.items() if key not in ("op", "id")}
    unexpected = sorted(key for key in arguments if key not in parameters)
    if unexpected:
        accepted = ", ".join(parameters)
        return {"ok": False, "error": f"{operation}: unexpected argument(s) {', '.join(unexpected)}; accepts {accepted}"}
    try:
        return {"ok": True, "result": function(**arguments)}
    except TypeError as error:
        return {"ok": False, "error": f"{operation}: {error}"}
    except Exception as error:  # pragma: no cover - the public API already swallows its own failures
        return {"ok": False, "error": f"{operation}: {type(error).__name__}: {error}"}


def _reply(line: str) -> dict[str, Any]:
    try:
        request = json.loads(line)
    except ValueError as error:
        return {"ok": False, "error": f"invalid json: {error}"}
    if not isinstance(request, dict):
        return {"ok": False, "error": "request must be a JSON object"}
    reply = handle(request)
    identifier = request.get("id")
    if identifier is not None:
        reply["id"] = identifier
    return reply


def serve(stdin: TextIO | None = None, stdout: TextIO | None = None) -> None:
    source = stdin if stdin is not None else sys.stdin
    sink = stdout if stdout is not None else sys.stdout
    for line in source:
        stripped = line.strip()
        if not stripped:
            continue
        try:
            reply = _reply(stripped)
        except Exception as error:
            reply = {"ok": False, "error": f"{type(error).__name__}: {error}"}
        try:
            sink.write(json.dumps(reply, ensure_ascii=False) + "\n")
            sink.flush()
        except Exception:
            return


def main() -> None:
    for stream, options in ((sys.stdin, {}), (sys.stdout, {"newline": "\n"})):
        reconfigure = getattr(stream, "reconfigure", None)
        if reconfigure is not None:
            try:
                reconfigure(encoding="utf-8", **options)
            except (OSError, ValueError):
                pass
    serve()


if __name__ == "__main__":
    main()
