import { z } from 'zod';
import { defineTool, } from '../toolDefinition.js';
import { AgentToolNames } from '../shared/agentToolNames.js';
import { comparisonTypeEnum } from '../shared/schemas.js';
const HTTP_METHODS = ['GET', 'DELETE', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'LINK', 'UNLINK'];
const RESPONSE_SOURCES = ['body', 'header', 'json', 'response', 'status'];
const refineSourceTarget = ({ source, target }, ctx) => {
    const needsTarget = source === 'header' || source === 'json';
    const hasTarget = target !== undefined && target.trim() !== '';
    if (needsTarget && !hasTarget) {
        const example = source === 'header' ? 'a header name (e.g. "Content-Type")' : 'a JSON path (e.g. "data.users[0].id")';
        ctx.addIssue({
            code: 'custom',
            path: ['target'],
            message: `target is required when source is "${source}" — provide ${example}.`,
        });
    }
    if (!needsTarget && hasTarget) {
        ctx.addIssue({
            code: 'custom',
            path: ['target'],
            message: `target must be omitted when source is "${source}" — only "header" and "json" sources use target.`,
        });
    }
};
const httpHeaderSchema = z.object({
    name: z.string().meta({ description: 'Header name, e.g. "Content-Type" or "X-Custom-Header".' }),
    value: z.string().meta({
        description: 'Header value. Supports {{@variable}} syntax, which is the preferred way to pass dynamic values (e.g. CSRF tokens captured in a previous step).',
    }),
});
// NOTE on schema shape: Gemini's FunctionDeclaration.parametersJsonSchema
// rejects `anyOf`/`oneOf` at ANY depth, not just the root. So this MUST stay a
// flat z.object — not a z.discriminatedUnion — even though it's nested inside
// httpRequestSchema. Per-branch field requirements (username+password when
// type='basic'; token when type='bearer') are enforced via .refine() (which
// Zod omits from the JSON Schema) and re-checked defensively in the client
// executor (mabl-cli/runtime/.../SendHttpRequestExecutor.ts).
const httpAuthorizationFlatObjectSchema = z.object({
    type: z.enum(['basic', 'bearer']).meta({
        description: "Auth type. 'basic' requires properties.username + properties.password. 'bearer' requires properties.token.",
    }),
    properties: z
        .object({
        username: z.string().optional().meta({
            description: "BASIC ONLY (required when type='basic'). HTTP Basic username. Prefer {{@variable}} refs over literals for any real credential.",
        }),
        password: z.string().optional().meta({
            description: 'BASIC ONLY (required when type=\'basic\'). HTTP Basic password. You MUST prefer {{@variable}} refs (e.g. "{{@basicPassword}}") over literal values — saving a literal password into the test is a security leak.',
        }),
        token: z.string().optional().meta({
            description: 'BEARER ONLY (required when type=\'bearer\'). Bearer token. You MUST prefer {{@variable}} refs (e.g. "{{@apiToken}}") over literal values — saving a literal token into the test is a security leak.',
        }),
    })
        .meta({
        description: "Auth-specific fields. For type='basic' provide username + password. For type='bearer' provide token.",
    }),
});
const httpAuthorizationSchema = httpAuthorizationFlatObjectSchema.refine((auth) => {
    if (auth.type === 'basic') {
        return typeof auth.properties.username === 'string' && typeof auth.properties.password === 'string';
    }
    return typeof auth.properties.token === 'string';
}, {
    message: "When type='basic', properties.username and properties.password are required. When type='bearer', properties.token is required.",
});
const httpRequestSchema = z.object({
    url: z.string().meta({
        description: 'The URL to send the request to. Supports {{@variable}} syntax for dynamic pieces (base URL, path IDs, query params) — prefer variables over literals when the URL contains data captured or generated earlier in the test.',
    }),
    method: z.enum(HTTP_METHODS).meta({
        description: 'HTTP method. Most API steps use GET, POST, PUT, PATCH, or DELETE.',
    }),
    headers: z.array(httpHeaderSchema).optional().meta({
        description: 'Optional request headers. Use for "Content-Type", "Accept", or custom headers.',
    }),
    authorization: httpAuthorizationSchema.optional().meta({
        description: 'Optional authentication. Choose "basic" for username+password or "bearer" for a token. For real credentials always pass {{@variable}} refs in the properties rather than literal strings.',
    }),
    body: z.string().optional().meta({
        description: 'Optional request body, typically a JSON string for POST/PUT/PATCH. Supports {{@variable}} syntax inside the body string.',
    }),
});
const sendHttpRequestOptionsSchema = z.object({
    loadResponseCookies: z.boolean().optional().meta({
        description: 'If true, cookies returned in the response are loaded into the browser session so subsequent browser steps use them. Leave unset (false) for plain API validation; set true only when the API call establishes browser auth for later steps.',
    }),
});
const sendHttpRequestAssertionSchema = z
    .object({
    source: z.enum(RESPONSE_SOURCES).meta({
        description: 'What to assert against. "status" = HTTP status code. "header" = a specific response header value (requires target). "json" = a value inside the JSON-parsed body at a JSON path (requires target, e.g. "data.users[0].id"). "body" = the raw response body as text. "response" = the full response object serialized (rarely needed).',
    }),
    target: z.string().optional().meta({
        description: 'Required when source is "header" (provide the header name, e.g. "Content-Type") or "json" (provide a dotted JSON path, e.g. "data.users[0].id"). Omit for "status", "body", "response".',
    }),
    assertion: comparisonTypeEnum.meta({
        description: 'Comparison type. Typical choices: "equals" for exact status codes (e.g. status equals "200"), "contains" for substring checks on body/header values, "matches_regex" for patterns. "greater_than"/"less_than" for numeric comparisons.',
    }),
    value: z.string().meta({
        description: 'Expected value, as a string. Numeric statuses like 200 should be passed as "200". Use {{@variable}} syntax to compare against a value captured earlier in the test.',
    }),
})
    .superRefine(refineSourceTarget);
const sendHttpRequestVariableSchema = z
    .object({
    source: z.enum(RESPONSE_SOURCES).meta({
        description: 'Where to extract the variable value from. Same enum as assertions: "status" (HTTP status code), "header" (requires target = header name), "json" (requires target = JSON path inside body), "body" (raw body text), "response" (full response object).',
    }),
    target: z.string().optional().meta({
        description: 'Required when source is "header" or "json"; same semantics as the assertion target. Omit for "status", "body", "response".',
    }),
    name: z
        .string()
        .regex(/^[A-Za-z_][A-Za-z0-9_]*$/)
        .meta({
        description: 'The variable name to create. Must be alphanumeric plus underscores (no spaces, no hyphens). Reference it in later steps as {{@name}}.',
    }),
})
    .superRefine(refineSourceTarget);
export const executeSendHttpRequestArgumentsSchema = z.object({
    request: httpRequestSchema.meta({
        description: 'The HTTP request to record as part of the test.',
    }),
    options: sendHttpRequestOptionsSchema.optional().meta({
        description: 'Optional advanced options. Omit when no advanced behavior is needed.',
    }),
    assertions: z.array(sendHttpRequestAssertionSchema).optional().meta({
        description: 'Response assertions to verify at replay time. Example: assert status equals "200", or assert json path "data.users[0].id" contains "user-". Each assertion runs against the response of this same step.',
    }),
    variables: z.array(sendHttpRequestVariableSchema).optional().meta({
        description: 'Variables to extract from the response for use in later steps. Example: source "json", target "data.token", name "authToken" — later steps can reference it as {{@authToken}}.',
    }),
});
export const executeSendHttpRequestResponseSchema = z.object({
    success: z.boolean(),
    message: z.string().optional(),
});
export const ExecuteSendHttpRequestToolDefinition = defineTool({
    name: AgentToolNames.ExecuteSendHttpRequest,
    description: `Records and dispatches an HTTP API request step. Use this for REST/HTTP traffic alongside browser steps — calling an endpoint, asserting on the response, and optionally extracting response values into variables. Do NOT use it to drive the UI (forms, clicks, inputs); those have dedicated tools.

Authoring-time contract:
- The request fires when the step is recorded so that captured response variables get populated for use by subsequent steps in the same session.
- Side-effecting verbs (POST/PUT/PATCH/DELETE) hit real systems on each authoring iteration. Ask the user about test data and cleanup before authoring such a step.
- Every {{@variable}} reference (URL, headers, body, auth) must already exist or the runtime fails with "Cannot resolve variable" before any network call. If a needed variable does not exist, call ${AgentToolNames.ExecuteAskForHelp} — never fabricate one via ${AgentToolNames.ExecuteCreateOrSetVariable}, especially for credentials.
- The actual response is NOT surfaced back to you. Generate assertions from user intent and prefer high-confidence checks (e.g., status equals "200") over speculative deep-body assertions.

Capabilities:
- Methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.
- Auth: 'basic' (username/password) or 'bearer' (token). For real credentials always pass {{@variable}} refs — literal credentials in a saved test are a leak.
- Body: string in JSON, URL-encoded, plain text, or XML. Set Content-Type explicitly to match.
- Response assertions and variables share the same source enum: "status", "json", "header", "body", "response".

For body-format examples, base-URL conventions ({{@web.defaults.url}} vs {{@api.url}}), the default-Content-Type footgun, the headers-vs-authorization conflict, and full anti-patterns, fetch the api-steps skill.`,
    argumentsSchema: executeSendHttpRequestArgumentsSchema,
    responseSchema: executeSendHttpRequestResponseSchema,
});
//# sourceMappingURL=ExecuteSendHttpRequestDefinition.js.map