import { z } from 'zod';
import { ValidationError } from '../errors.js';
/**
 * Parse `value` with `schema`. On success returns the typed value.
 * On failure throws ValidationError(message, details) so src/errors.ts maps it
 * to HTTP 400 / JSON-RPC -32602 exactly as the old hand-rolled validators did.
 */
export function parseOrThrow(schema, value, label) {
    const result = schema.safeParse(value);
    if (result.success) {
        return result.data;
    }
    const details = result.error.issues.map((issue) => ({
        path: [label, ...issue.path.map(String)].filter(Boolean).join('.'),
        message: issue.message,
    }));
    const first = details[0];
    const message = first ? `${first.path}: ${first.message}` : `${label}: invalid input.`;
    throw new ValidationError(message, details);
}
// Per-field hard caps to prevent DoS via giant inputs (ReDoS / OOM) at the MCP & HTTP boundary.
// Mirror the original hand-rolled enforceStringLength / readOptionalStringArray limits.
const MAX_STRING_LENGTH = 2_000_000;
const MAX_STRING_ARRAY_LENGTH = 4096;
/** Non-blank, length-capped string (rejects empty/whitespace-only); value is not mutated. */
export const zRequiredString = z
    .string()
    .max(MAX_STRING_LENGTH, { message: `must be ${MAX_STRING_LENGTH} characters or fewer.` })
    .refine((s) => s.trim().length > 0, {
    message: 'must be a non-empty string.',
});
/** Optional string; absent stays absent (matches readOptionalString). */
export const zOptionalString = zRequiredString.optional();
/** Length-capped array of non-blank strings. */
export const zStringArray = z
    .array(zRequiredString)
    .max(MAX_STRING_ARRAY_LENGTH, { message: `must contain ${MAX_STRING_ARRAY_LENGTH} or fewer entries.` });
/** Strictly positive, finite number (matches readOptionalPositiveNumber). */
export const zPositiveNumber = z.number().finite().positive();
/** Finite number; rejects NaN/Infinity and non-numbers (matches readOptionalNumber). */
export const zFiniteNumber = z.number().finite();
/** Finite number in [0, 1] (matches readOptional*Confidence). */
export const zConfidence = z
    .number()
    .finite()
    .min(0, { message: 'must be between 0 and 1.' })
    .max(1, { message: 'must be between 0 and 1.' });
/** Integer >= 1 (matches readOptionalPositiveInteger). */
export const zPositiveInteger = z.number().int({ message: 'must be a positive integer.' }).min(1, { message: 'must be a positive integer.' });
/** Integer >= 0 (matches readOptionalNonNegativeInteger). */
export const zNonNegativeInteger = z.number().int({ message: 'must be a non-negative integer.' }).min(0, { message: 'must be a non-negative integer.' });
/** Plain object (rejects null/array/non-object), matches expectObject / readOptionalObject. */
export const zRecord = z.record(z.string(), z.unknown());
/**
 * Nullable, non-blank, length-capped string (matches readOptionalNullableString):
 * absent stays absent, explicit null is preserved, otherwise must be non-blank.
 */
export const zNullableString = z.union([zRequiredString, z.null()]).optional();
/** ISO date string (matches readOptionalIsoDate): non-blank and Date.parse-able. */
export const zIsoDate = zRequiredString.refine((s) => !Number.isNaN(Date.parse(s)), {
    message: 'must be an ISO date string.',
});
