import { parseMessage } from "tiny-stdio-mcp-server/jsonrpc";
function isObjectRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function hasOwn(value, property) {
    return Object.prototype.hasOwnProperty.call(value, property);
}
function isValidResponse(value) {
    if (!isObjectRecord(value) || value.jsonrpc !== "2.0" || hasOwn(value, "method")) {
        return false;
    }
    if (!hasOwn(value, "id")) {
        return false;
    }
    if (value.id !== null &&
        typeof value.id !== "string" &&
        typeof value.id !== "number") {
        return false;
    }
    const hasResult = hasOwn(value, "result");
    const hasError = hasOwn(value, "error");
    if (hasResult === hasError) {
        return false;
    }
    if (!hasError) {
        return true;
    }
    return (isObjectRecord(value.error) &&
        typeof value.error.code === "number" &&
        typeof value.error.message === "string");
}
function hasResponseFields(value) {
    return isObjectRecord(value) && (hasOwn(value, "result") || hasOwn(value, "error"));
}
async function readStreamBody(req, maxBytes) {
    const chunks = [];
    let totalBytes = 0;
    for await (const chunk of req) {
        let bytes;
        if (typeof chunk === "string") {
            bytes = Buffer.from(chunk);
        }
        else if (chunk instanceof Uint8Array) {
            bytes = chunk;
        }
        else {
            bytes = Buffer.from(String(chunk));
        }
        totalBytes += bytes.byteLength;
        if (maxBytes !== undefined && totalBytes > maxBytes) {
            throw new Error("Payload too large");
        }
        chunks.push(bytes);
    }
    return Buffer.concat(chunks).toString("utf8");
}
async function readRawBody(req, preParsed, options) {
    if (preParsed !== undefined) {
        return preParsed;
    }
    const reqWithBody = req;
    if (reqWithBody.body !== undefined) {
        return reqWithBody.body;
    }
    const raw = await readStreamBody(req, options.maxBytes);
    try {
        return JSON.parse(raw);
    }
    catch {
        throw new Error("Parse error");
    }
}
function toMessageList(body, maxBatchSize) {
    if (Array.isArray(body)) {
        if (body.length === 0) {
            throw new Error("Invalid Request");
        }
        if (maxBatchSize !== undefined && body.length > maxBatchSize) {
            throw new Error("Batch size exceeds configured limit");
        }
        return body;
    }
    if (isObjectRecord(body)) {
        return [body];
    }
    throw new Error("Invalid Request");
}
export async function readAndClassifyBody(req, preParsed, options = {}) {
    const body = await readRawBody(req, preParsed, options);
    const inputMessages = toMessageList(body, options.maxBatchSize);
    const isBatch = Array.isArray(body);
    const entries = [];
    const requests = [];
    const notifications = [];
    const responses = [];
    const messages = [];
    for (const message of inputMessages) {
        if (isValidResponse(message)) {
            responses.push(message);
            messages.push(message);
            entries.push(message);
            continue;
        }
        if (hasResponseFields(message)) {
            if (!isBatch) {
                throw new Error("Invalid Request");
            }
            entries.push(null);
            continue;
        }
        const parsed = parseMessage(JSON.stringify(message));
        if (!parsed.success) {
            if (!isBatch) {
                throw new Error(parsed.error.message);
            }
            entries.push(null);
            continue;
        }
        if (parsed.isNotification) {
            const notification = parsed.request;
            notifications.push(notification);
            messages.push(notification);
            entries.push(notification);
            continue;
        }
        const request = parsed.request;
        requests.push(request);
        messages.push(request);
        entries.push(request);
    }
    return {
        entries,
        messages,
        hasRequests: requests.length > 0,
        hasNotifications: notifications.length > 0,
        hasResponses: responses.length > 0,
        requests,
        notifications,
        responses,
    };
}
