/**
 * Validation + normalization for the `search_emails` tool args. Extracted from
 * the handler so the ~110 LOC of input checks (type/length/range; VALID-002
 * length caps; TOOL-016 byte predicates; TOOL-017 parseable dates) live in one
 * testable place instead of crowding the handler. Throws McpError(InvalidParams)
 * on any malformed input; returns a ready-to-use SearchEmailOptions.
 */
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { validateTargetFolder } from "../utils/helpers.js";
const MAX_SEARCH_TEXT = 500;
export function validateSearchInput(args, maxEmailListResults) {
    const folder = args.folder || "INBOX";
    // Runtime-guard the array cast: a client passing folders:"INBOX" (string)
    // would otherwise iterate per-character into validateTargetFolder; folders:{}
    // would silently fall through with folders.length === undefined (TOOL-001).
    if (args.folders !== undefined && !Array.isArray(args.folders)) {
        throw new McpError(ErrorCode.InvalidParams, "'folders' must be an array of folder paths when provided.");
    }
    const folders = args.folders;
    if (!folders) {
        const seFolderErr = validateTargetFolder(folder);
        if (seFolderErr)
            throw new McpError(ErrorCode.InvalidParams, `folder: ${seFolderErr}`);
    }
    if (folders && !(folders.length === 1 && folders[0] === "*")) {
        for (let i = 0; i < folders.length; i++) {
            const fErr = validateTargetFolder(folders[i]);
            if (fErr)
                throw new McpError(ErrorCode.InvalidParams, `folders[${i}]: ${fErr}`);
        }
    }
    if (args.from !== undefined && typeof args.from !== "string") {
        throw new McpError(ErrorCode.InvalidParams, "'from' filter must be a string when provided.");
    }
    if (args.from && args.from.length > MAX_SEARCH_TEXT) {
        throw new McpError(ErrorCode.InvalidParams, `'from' filter must not exceed ${MAX_SEARCH_TEXT} characters.`);
    }
    if (args.to !== undefined && typeof args.to !== "string") {
        throw new McpError(ErrorCode.InvalidParams, "'to' filter must be a string when provided.");
    }
    if (args.to && args.to.length > MAX_SEARCH_TEXT) {
        throw new McpError(ErrorCode.InvalidParams, `'to' filter must not exceed ${MAX_SEARCH_TEXT} characters.`);
    }
    if (args.subject !== undefined && typeof args.subject !== "string") {
        throw new McpError(ErrorCode.InvalidParams, "'subject' filter must be a string when provided.");
    }
    if (args.subject && args.subject.length > MAX_SEARCH_TEXT) {
        throw new McpError(ErrorCode.InvalidParams, `'subject' filter must not exceed ${MAX_SEARCH_TEXT} characters.`);
    }
    if (args.hasAttachment !== undefined && typeof args.hasAttachment !== "boolean") {
        throw new McpError(ErrorCode.InvalidParams, "'hasAttachment' must be a boolean when provided.");
    }
    if (args.isRead !== undefined && typeof args.isRead !== "boolean") {
        throw new McpError(ErrorCode.InvalidParams, "'isRead' must be a boolean when provided.");
    }
    if (args.isStarred !== undefined && typeof args.isStarred !== "boolean") {
        throw new McpError(ErrorCode.InvalidParams, "'isStarred' must be a boolean when provided.");
    }
    if (args.limit !== undefined && typeof args.limit !== "number") {
        throw new McpError(ErrorCode.InvalidParams, "'limit' must be a number.");
    }
    // TOOL-017: dateFrom/dateTo must be a string AND parseable when provided — same
    // contract as sentBefore/sentSince below. The old type-only check let
    // {dateFrom:"not-a-date"} pass through and be silently dropped downstream.
    if (args.dateFrom !== undefined &&
        (typeof args.dateFrom !== "string" || Number.isNaN(Date.parse(args.dateFrom)))) {
        throw new McpError(ErrorCode.InvalidParams, "'dateFrom' must be a parseable date string when provided.");
    }
    if (args.dateTo !== undefined &&
        (typeof args.dateTo !== "string" || Number.isNaN(Date.parse(args.dateTo)))) {
        throw new McpError(ErrorCode.InvalidParams, "'dateTo' must be a parseable date string when provided.");
    }
    if (args.dateFrom && args.dateTo) {
        const dfTs = Date.parse(args.dateFrom);
        const dtTs = Date.parse(args.dateTo);
        if (dfTs > dtTs) {
            throw new McpError(ErrorCode.InvalidParams, "'dateFrom' must not be later than 'dateTo'.");
        }
    }
    // VALID-002: body/text/bcc are string filters, length-capped at MAX_SEARCH_TEXT
    // like from/to/subject. Reject a non-string (rather than silently dropping it)
    // so the contract — throw on malformed input — holds for every field.
    if (args.body !== undefined && typeof args.body !== "string") {
        throw new McpError(ErrorCode.InvalidParams, "'body' filter must be a string when provided.");
    }
    if (args.text !== undefined && typeof args.text !== "string") {
        throw new McpError(ErrorCode.InvalidParams, "'text' filter must be a string when provided.");
    }
    if (args.bcc !== undefined && typeof args.bcc !== "string") {
        throw new McpError(ErrorCode.InvalidParams, "'bcc' filter must be a string when provided.");
    }
    const body = args.body;
    const text = args.text;
    const bcc = args.bcc;
    if (body !== undefined && body.length > MAX_SEARCH_TEXT) {
        throw new McpError(ErrorCode.InvalidParams, `'body' filter must not exceed ${MAX_SEARCH_TEXT} characters.`);
    }
    if (text !== undefined && text.length > MAX_SEARCH_TEXT) {
        throw new McpError(ErrorCode.InvalidParams, `'text' filter must not exceed ${MAX_SEARCH_TEXT} characters.`);
    }
    if (bcc !== undefined && bcc.length > MAX_SEARCH_TEXT) {
        throw new McpError(ErrorCode.InvalidParams, `'bcc' filter must not exceed ${MAX_SEARCH_TEXT} characters.`);
    }
    const answered = typeof args.answered === "boolean" ? args.answered : undefined;
    const isDraft = typeof args.isDraft === "boolean" ? args.isDraft : undefined;
    // TOOL-016: byte-size predicates are non-negative integers; reject negatives
    // and non-finite (NaN/Infinity) instead of forwarding them to the IMAP wire.
    if (args.larger !== undefined &&
        (typeof args.larger !== "number" || !Number.isFinite(args.larger) || args.larger < 0)) {
        throw new McpError(ErrorCode.InvalidParams, "'larger' must be a non-negative finite number (bytes) when provided.");
    }
    if (args.smaller !== undefined &&
        (typeof args.smaller !== "number" || !Number.isFinite(args.smaller) || args.smaller < 0)) {
        throw new McpError(ErrorCode.InvalidParams, "'smaller' must be a non-negative finite number (bytes) when provided.");
    }
    const larger = typeof args.larger === "number" ? args.larger : undefined;
    const smaller = typeof args.smaller === "number" ? args.smaller : undefined;
    // TOOL-017: require a string + a parseable date. The old truthy-check let
    // numbers/objects through — `new Date(null)` → 1970, `new Date({})` →
    // Invalid Date — silently corrupting the search window.
    const parseSentDate = (raw, field) => {
        if (raw === undefined || raw === null)
            return undefined;
        if (typeof raw !== "string" || Number.isNaN(Date.parse(raw))) {
            throw new McpError(ErrorCode.InvalidParams, `'${field}' must be a parseable date string when provided.`);
        }
        return new Date(raw);
    };
    const sentBefore = parseSentDate(args.sentBefore, "sentBefore");
    const sentSince = parseSentDate(args.sentSince, "sentSince");
    return {
        folder: folders ? undefined : folder,
        folders,
        from: args.from,
        to: args.to,
        subject: args.subject,
        hasAttachment: args.hasAttachment,
        isRead: args.isRead,
        isStarred: args.isStarred,
        dateFrom: args.dateFrom,
        dateTo: args.dateTo,
        limit: Math.min(Math.max(1, args.limit || 50), 200, maxEmailListResults),
        body,
        text,
        bcc,
        answered,
        isDraft,
        larger,
        smaller,
        sentBefore,
        sentSince,
    };
}
//# sourceMappingURL=search-input.js.map