import { z } from 'zod';

/**
 * Auditor T1D (2026-06-01): /api/chat body validation.
 *
 * The chat route uses parseJsonBounded (2 MB cap), so body size is
 * already gated — but the SHAPE of the message array was not
 * validated. Without Zod, the OpenAI client receives whatever shape
 * the client sent, and any thrown error from the SDK leaks into the
 * 500 path with no clean 400 boundary.
 *
 * Schema is intentionally permissive on role types so the upstream
 * Chat Completions API can still validate further — but every
 * message must have a role + content + (optional) name/tool fields.
 */
const noControlChars = (val: string) => !/[\r\n\t\0]/.test(val);

// Roles per OpenAI Chat Completions API. Includes the function-calling
// roles even though our app doesn't construct them client-side; if the
// server later echoes them back, validation must pass.
const messageRoleEnum = z.enum(['system', 'user', 'assistant', 'tool', 'function']);

// Content can be a plain string OR an array of multi-modal parts
// (OpenAI's content-parts format). We accept both to stay forward-
// compatible without enumerating every part shape.
const messageContent = z.union([
    z.string().max(64 * 1024), // 64KB per message
    z.array(z.unknown()).max(50),
    z.null(),
]);

export const chatMessageSchema = z.object({
    role: messageRoleEnum,
    content: messageContent,
    name: z.string().max(200).refine(noControlChars).optional(),
    // Pass-through for tool_call_id / function_call / tool_calls — the
    // upstream client validates strict shapes; we just bound the field.
}).passthrough();

export const chatRequestSchema = z.object({
    messages: z.array(chatMessageSchema).min(1).max(200),
    chatId: z.union([z.string(), z.number()]).optional().nullable(),
    title: z.string().max(200).refine(noControlChars).optional().nullable(),
});

export type ChatRequestInput = z.infer<typeof chatRequestSchema>;
