import { z } from 'zod';
import { defineTool, } from '../toolDefinition.js';
import { AgentToolNames } from '../shared/agentToolNames.js';
const DATABASE_QUERY_ASSERTION_TYPES = [
    'equals',
    'does_not_equal',
    'contains',
    'does_not_contain',
    'starts_with',
    'starts_without',
    'ends_with',
    'ends_without',
    'greater_than',
    'less_than',
    'greater_than_or_equals',
    'less_than_or_equals',
    'matches_regex',
];
const DATABASE_QUERY_SOURCE = ['metadata', 'result'];
const databaseQueryAssertionSchema = z.object({
    source: z.enum(DATABASE_QUERY_SOURCE).meta({
        description: 'Where to read the value from. Use \'metadata\' for things about the query itself (columns, row count) — valid targets are "columns" and "rowCount". Use \'result\' for values in the returned rows — targets are dot-separated row-index.column like "0.user_id"; quote the column when it contains a dot, e.g. "3.\'customer.email\'".',
    }),
    target: z.string().optional().meta({
        description: 'The field to read. For source=\'metadata\' use "columns" or "rowCount". For source=\'result\' use <rowIndex>.<columnName> (e.g. "0.user_id"); wrap the column in single quotes if it contains a dot (e.g. "3.\'customer.email\'"). Omit target when asserting directly on \'rowCount\' metadata with no further path.',
    }),
    assertion: z.enum(DATABASE_QUERY_ASSERTION_TYPES).meta({
        description: 'The comparison to apply between the value at target and the expected value.',
    }),
    value: z.string().meta({
        description: 'The expected value. Reference other variables with {{@variableName}} syntax (e.g. {{@userCount}}). Numeric comparisons (greater_than etc.) are performed numerically when both sides parse as numbers.',
    }),
});
const databaseQueryVariableSchema = z.object({
    source: z.enum(DATABASE_QUERY_SOURCE).meta({
        description: 'Where to read the value from. \'metadata\' for query-level fields ("columns", "rowCount"); \'result\' for row values (target like "0.user_id").',
    }),
    target: z.string().optional().meta({
        description: 'The field to read. Same format as assertions: "columns"/"rowCount" for metadata, <rowIndex>.<columnName> for result.',
    }),
    name: z.string().meta({
        description: 'The name under which to save the extracted value. Other steps can reference it with {{@name}} syntax. Alphanumeric and underscores only; do not include the user. prefix.',
    }),
});
// NOTE on schema shape: Gemini's FunctionDeclaration.parametersJsonSchema rejects
// root-level `anyOf` / `oneOf`, so this MUST stay a flat z.object — not a
// z.discriminatedUnion. Per-branch field requirements (database_connection_id +
// query when type='inline'; snippet_id when type='reusable') are enforced via
// .refine() (which Zod omits from the JSON Schema) and re-checked defensively in
// the client executor (mabl-cli/runtime/.../DatabaseQueryExecutor.ts).
const executeDatabaseQueryFlatObjectSchema = z.object({
    type: z.enum(['inline', 'reusable']).meta({
        description: "Discriminator: 'inline' to author a one-shot SQL query, or 'reusable' to reference an existing workspace SQL snippet by id. Prefer 'reusable' — discover candidates first via request_snippet_tools with snippet_types: ['sql'].",
    }),
    // Inline-only fields. Required when type='inline'; ignored otherwise.
    database_connection_id: z.string().optional().meta({
        description: 'INLINE ONLY (required when type=\'inline\'). The id of the workspace database connection to run the query against (e.g. "abc123-dc"). The user must choose a connection that exists in the workspace — do not invent one.',
    }),
    query: z.string().optional().meta({
        description: "INLINE ONLY (required when type='inline'). The SQL to execute. Reference existing variables with {{@variableName}} syntax (e.g. \"SELECT count(*) FROM users WHERE email = '{{@userEmail}}'\"). Keep it read-only by default; only run destructive SQL if the user explicitly asked for it.",
    }),
    snippet_name: z.string().optional().meta({
        description: 'INLINE ONLY (optional). Human-readable name for the inline snippet (shown in the editor and logs). If omitted, a name is derived from the query.',
    }),
    // Reusable-only fields. Required when type='reusable'; ignored otherwise.
    snippet_id: z.string().optional().meta({
        description: 'REUSABLE ONLY (required when type=\'reusable\'). The snippet_id returned by request_snippet_tools for a SQL snippet (e.g. "abc123-sn"). Pass exactly as returned; do not add any prefix.',
    }),
    parameter_overrides: z.record(z.string(), z.string()).optional().meta({
        description: "REUSABLE ONLY (optional). Parameter overrides for the reusable snippet. Keys are the snippet's parameter names (as shown in the search results). Values can be literals or variable references ({{@variableName}}). Unrecognised parameter names are ignored with a warning.",
    }),
    // Shared (optional on both branches).
    assertions: z.array(databaseQueryAssertionSchema).optional().meta({
        description: 'Assertions to apply to the query metadata and/or result. Multiple assertions may be provided on the same step.',
    }),
    variables: z.array(databaseQueryVariableSchema).optional().meta({
        description: 'Variables to extract from the query metadata and/or result for use by later steps.',
    }),
});
export const executeDatabaseQueryArgumentsSchema = executeDatabaseQueryFlatObjectSchema.refine((args) => {
    if (args.type === 'inline') {
        return typeof args.database_connection_id === 'string' && typeof args.query === 'string';
    }
    return typeof args.snippet_id === 'string';
}, {
    message: "When type='inline', both database_connection_id and query are required. When type='reusable', snippet_id is required.",
});
/**
 * Response schema for execute_database_query tool.
 * The client should report any variables created by the query so the agent
 * has context for the next decision.
 */
export const executeDatabaseQueryResponseSchema = z.object({
    success: z.boolean(),
    message: z.string().optional(),
    created_variables: z.record(z.string(), z.string()).optional().meta({
        description: 'Variables created by this step, as a map of variable name to stored value. Present only when the query ran during authoring and produced values.',
    }),
});
export const ExecuteDatabaseQueryToolDefinition = defineTool({
    name: AgentToolNames.ExecuteDatabaseQuery,
    description: `Executes a database query step and records it into the test. Produces a \`database_query\` step that runs a SQL statement against one of the workspace's database connections at replay time. Pick a mode via the \`type\` field:

- **type: 'inline'** — author a one-shot SQL statement. **Required:** \`database_connection_id\`, \`query\`. Use \`{{@variableName}}\` to interpolate prior variables into the SQL.
- **type: 'reusable'** — reference an existing SQL snippet in the workspace. **Required:** \`snippet_id\`. First call \`request_snippet_tools\` with \`snippet_types: ['sql']\` to discover candidates; pass any overrides via \`parameter_overrides\` keyed by the snippet's parameter names.

Fields not relevant to the chosen mode are ignored.

**Assertions and variables** (optional on both modes) use a \`source\` of \`'metadata'\` or \`'result'\`:
- \`source: 'metadata'\` — targets \`"columns"\` or \`"rowCount"\`.
- \`source: 'result'\` — target is \`<rowIndex>.<columnName>\`, e.g. \`"0.user_id"\`. Wrap the column in single quotes when it contains a dot, e.g. \`"3.'customer.email'"\`.

**Examples**

Inline with a result assertion:
\`\`\`json
{
  "type": "inline",
  "database_connection_id": "abc123-dc",
  "query": "SELECT count(*) AS total FROM users",
  "assertions": [
    {"source": "result", "target": "0.total", "assertion": "greater_than", "value": "0"}
  ]
}
\`\`\`

Reusable with a parameter override and a captured variable:
\`\`\`json
{
  "type": "reusable",
  "snippet_id": "xyz789-sn",
  "parameter_overrides": {"status": "active"},
  "variables": [
    {"source": "metadata", "target": "rowCount", "name": "activeUserCount"}
  ]
}
\`\`\`

**Guidelines**

- Prefer reusable snippets when a matching SQL snippet exists in the workspace.
- Never invent a \`database_connection_id\` or \`snippet_id\` — use values returned by workspace-context or \`request_snippet_tools\`.
- For read-only verification (the common case), keep the SQL a \`SELECT\`.`,
    argumentsSchema: executeDatabaseQueryArgumentsSchema,
    responseSchema: executeDatabaseQueryResponseSchema,
});
//# sourceMappingURL=ExecuteDatabaseQueryDefinition.js.map