// mcp-proxy - stable forwarder for the mcp-wrapper.
//
// Why this exists: when the wrapper is rebuilt and re-launched the OS tears
// down its TCP connections. Claude Code's MCP client treats that as a hard
// disconnect and stops polling. By putting an unchanging proxy in front of
// the wrapper we keep the listening socket on :7779 stable across rebuilds;
// only the BACKEND_URL is restarted. Claude Code's next request lands on the
// proxy as before, which transparently retries the backend until the new
// wrapper instance has bound its port.
//
// Listens on:        MCP_PORT       default 7779
// Forwards to:       BACKEND_URL    default http://localhost:17779
// Retry budget:      RETRY_MS       default 3000  (total wall time across
//                                                  per-attempt 100ms backoffs)

fn parse_port(bind: String) -> Int {
    let colon: Int = str_index_of(bind, ":")
    if colon < 0 { return str_to_int(bind) }
    let after: String = str_slice(bind, colon + 1, str_len(bind))
    return str_to_int(after)
}

fn backend_url() -> String {
    let u: String = env("BACKEND_URL")
    if str_eq(u, "") { return "http://localhost:17779" }
    return u
}

fn retry_budget_ms() -> Int {
    let v: String = env("RETRY_MS")
    if str_eq(v, "") { return 3000 }
    return str_to_int(v)
}

// Forward with retry. Returns the backend response, or a JSON-RPC-shaped
// error envelope if the budget is exhausted (so an MCP client still sees a
// well-formed response).
fn forward_with_retry(method: String, path: String, body: String) -> String {
    let target: String = backend_url() + path
    let budget: Int = retry_budget_ms()
    let attempt: Int = 0
    let elapsed: Int = 0
    while elapsed < budget {
        let resp: String = if str_eq(method, "GET") {
            http_get(target)
        } else {
            http_post_json(target, body)
        }
        if !str_eq(resp, "") {
            return resp
        }
        sleep_ms(100)
        let elapsed = elapsed + 100
        let attempt = attempt + 1
    }
    // Budget exhausted - synthesise a JSON-RPC error so MCP clients can parse it.
    return "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32000,\"message\":\"backend unreachable after " + int_to_str(budget) + "ms\"}}"
}

fn handle_request(method: String, path: String, body: String) -> String {
    if str_eq(method, "GET") && (str_eq(path, "/health") || str_eq(path, "/proxy/health")) {
        return "{\"status\":\"ok\",\"service\":\"neuron-mcp-proxy\",\"backend\":\"" + backend_url() + "\"}"
    }
    return forward_with_retry(method, path, body)
}

let bind_str: String = env("MCP_PORT")
if str_eq(bind_str, "") { let bind_str = "7779" }
let port: Int = parse_port(bind_str)

println("[mcp-proxy] listening on :" + int_to_str(port))
println("[mcp-proxy] backend=" + backend_url())

http_serve(port, "handle_request")
