/**
 * Shared helper functions for error handling.
 *
 * These functions are used to parse error responses and raise appropriate
 * exceptions. They contain no I/O operations.
 */
import { LangSmithQuotaExceededError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithSandboxAPIError, LangSmithSandboxAuthenticationError, LangSmithSandboxError, LangSmithSandboxConnectionError, LangSmithSandboxCreationError, LangSmithSandboxNotReadyError, LangSmithSandboxOperationError, LangSmithValidationError, } from "./errors.js";
// =============================================================================
// Input validation
// =============================================================================
/**
 * Validate TTL values for sandbox create/update (minute resolution).
 *
 * @param value - TTL in seconds (`undefined` means unset; `0` disables).
 * @param name - Parameter name for error messages.
 * @throws LangSmithValidationError if negative or not a multiple of 60 (when non-zero).
 */
export function validateTtl(value, name) {
    if (value === undefined) {
        return;
    }
    if (value < 0) {
        throw new LangSmithValidationError(`${name} must be >= 0, got ${value}`, name);
    }
    if (value !== 0 && value % 60 !== 0) {
        throw new LangSmithValidationError(`${name} must be a multiple of 60 seconds, got ${value}`, name);
    }
}
/**
 * Parse standardized error response.
 *
 * Expected format: {"detail": {"error": "...", "message": "..."}}
 */
export async function parseErrorResponse(response) {
    try {
        const data = await response.json();
        const detail = data?.detail;
        // Standardized format: {"detail": {"error": "...", "message": "..."}}
        if (detail && typeof detail === "object" && !Array.isArray(detail)) {
            return {
                errorType: detail.error,
                message: detail.message || `HTTP ${response.status}: ${response.statusText}`,
            };
        }
        // Pydantic validation error format: {"detail": [{"loc": [...], "msg": "..."}]}
        if (Array.isArray(detail) && detail.length > 0) {
            const messages = detail
                .filter((d) => typeof d === "object" && d !== null)
                .map((d) => d.msg || String(d))
                .filter(Boolean);
            return {
                errorType: undefined,
                message: messages.length > 0
                    ? messages.join("; ")
                    : `HTTP ${response.status}: ${response.statusText}`,
            };
        }
        // Fallback for plain string detail
        return {
            errorType: undefined,
            message: detail || `HTTP ${response.status}: ${response.statusText}`,
        };
    }
    catch {
        return {
            errorType: undefined,
            message: `HTTP ${response.status}: ${response.statusText}`,
        };
    }
}
/**
 * Parse Pydantic validation error response.
 *
 * Returns a list of validation error details.
 */
export async function parseValidationError(response) {
    try {
        const data = await response.json();
        const detail = data?.detail;
        if (Array.isArray(detail)) {
            return detail;
        }
        return [];
    }
    catch {
        return [];
    }
}
/**
 * Extract quota type from error message.
 */
export function extractQuotaType(message) {
    const messageLower = message.toLowerCase();
    // Check for sandbox count quota
    if (messageLower.includes("sandbox") &&
        (messageLower.includes("count") || messageLower.includes("limit"))) {
        return "sandbox_count";
    }
    else if (messageLower.includes("cpu")) {
        return "cpu";
    }
    else if (messageLower.includes("memory")) {
        return "memory";
    }
    else if (messageLower.includes("storage")) {
        return "storage";
    }
    return undefined;
}
// =============================================================================
// Client Error Handlers
// =============================================================================
/**
 * Handle HTTP errors specific to sandbox creation.
 *
 * Maps API error responses to specific exception types:
 * - 408: LangSmithResourceTimeoutError (sandbox didn't become ready in time)
 * - 422: LangSmithValidationError (bad input) or LangSmithSandboxCreationError (runtime)
 * - 429: LangSmithQuotaExceededError (org limits exceeded)
 * - 503: LangSmithSandboxCreationError (no resources available)
 * - Other: Falls through to generic error handling
 */
export async function handleSandboxCreationError(response) {
    const status = response.status;
    const clonedResponse = response.clone();
    const data = await parseErrorResponse(response);
    if (status === 408) {
        // Timeout - include the message which contains last known status
        throw new LangSmithResourceTimeoutError(data.message, "sandbox");
    }
    else if (status === 422) {
        // Check if this is a Pydantic validation error (bad input) vs creation error
        const details = await parseValidationError(clonedResponse);
        if (details.length > 0 && details.some((d) => d.type === "value_error")) {
            // Pydantic validation error (bad input - exceeds server limits)
            const field = details[0]?.loc?.slice(-1)[0];
            throw new LangSmithValidationError(data.message, field, details);
        }
        else {
            // Sandbox creation failed (runtime error like image pull failure)
            throw new LangSmithSandboxCreationError(data.message, data.errorType);
        }
    }
    else if (status === 429) {
        // Organization quota exceeded - extract type or default to sandbox_count
        const quotaType = extractQuotaType(data.message) ?? "unknown";
        throw new LangSmithQuotaExceededError(data.message, quotaType);
    }
    else if (status === 503) {
        // Service Unavailable - scheduling failed
        throw new LangSmithSandboxCreationError(data.message, data.errorType || "Unschedulable");
    }
    // Fall through to generic handling — pass clone since body is already consumed
    return handleClientHttpError(clonedResponse);
}
/**
 * Handle HTTP errors and raise appropriate exceptions (for client operations).
 */
export async function handleClientHttpError(response) {
    const status = response.status;
    // Only clone when we need to read the body twice (status 422 reads it again
    // for structured validation details after parseErrorResponse consumes it).
    const clonedResponse = status === 422 ? response.clone() : null;
    const data = await parseErrorResponse(response);
    const message = data.message;
    const errorType = data.errorType;
    if (status === 401 || status === 403) {
        throw new LangSmithSandboxAuthenticationError(message);
    }
    if (status === 404) {
        throw new LangSmithResourceNotFoundError(message);
    }
    // Handle validation errors (invalid resource values, formats, etc.)
    if (status === 422 && clonedResponse) {
        const details = await parseValidationError(clonedResponse);
        const field = details[0]?.loc?.slice(-1)[0];
        throw new LangSmithValidationError(message, field, details);
    }
    // Handle quota exceeded errors (org limits)
    if (status === 429) {
        const quotaType = extractQuotaType(message);
        throw new LangSmithQuotaExceededError(message, quotaType);
    }
    if (status === 502 && errorType === "ConnectionError") {
        throw new LangSmithSandboxConnectionError(message);
    }
    if (status === 500) {
        throw new LangSmithSandboxAPIError(message);
    }
    throw new LangSmithSandboxError(message);
}
// =============================================================================
// Sandbox Operation Error Handlers
// =============================================================================
/**
 * Handle HTTP errors for sandbox operations (run, read, write).
 *
 * Maps API error types to specific exceptions:
 * - WriteError -> LangSmithSandboxOperationError (operation="write")
 * - ReadError -> LangSmithSandboxOperationError (operation="read")
 * - CommandError -> LangSmithSandboxOperationError (operation="command")
 * - ConnectionError (502) -> LangSmithSandboxConnectionError
 * - FileNotFound / 404 -> LangSmithResourceNotFoundError (resourceType="file")
 * - NotReady (400) -> LangSmithSandboxNotReadyError
 * - 403 -> LangSmithSandboxOperationError (permission denied)
 */
export async function handleSandboxHttpError(response) {
    const data = await parseErrorResponse(response);
    const message = data.message;
    const errorType = data.errorType;
    const status = response.status;
    // Operation-specific errors (from sandbox runtime)
    if (errorType === "WriteError") {
        throw new LangSmithSandboxOperationError(message, "write", errorType);
    }
    if (errorType === "ReadError") {
        throw new LangSmithSandboxOperationError(message, "read", errorType);
    }
    if (errorType === "CommandError") {
        throw new LangSmithSandboxOperationError(message, "command", errorType);
    }
    // Permission denied
    if (status === 403) {
        throw new LangSmithSandboxOperationError(message, undefined, "PermissionDenied");
    }
    // Connection to sandbox failed
    if (status === 502 && errorType === "ConnectionError") {
        throw new LangSmithSandboxConnectionError(message);
    }
    // Not ready / not found
    if (status === 400 && errorType === "NotReady") {
        throw new LangSmithSandboxNotReadyError(message);
    }
    if (status === 404 || errorType === "FileNotFound") {
        throw new LangSmithResourceNotFoundError(message, "file");
    }
    throw new LangSmithSandboxError(message);
}
