/**
 * @fileoverview Resource-specific lint rules.
 * Validates resource definitions against MCP spec and framework conventions.
 * @module src/linter/rules/resource-rules
 */
import { lintErrorContract, lintErrorContractConformance } from './error-contract-rules.js';
import { lintHandlerBody } from './handler-body-rules.js';
import { checkNameRequired } from './name-rules.js';
import { lintSchemaPortability } from './portability-rules.js';
import { checkFieldDescriptions, checkIsZodObject, checkSchemaSerializable, } from './schema-rules.js';
import { lintAuthScopes } from './tool-rules.js';
/**
 * Runs all lint rules against a single resource definition.
 */
export function lintResourceDefinition(def, portability) {
    const diagnostics = [];
    const d = def;
    const uriTemplate = typeof d?.uriTemplate === 'string' ? d.uriTemplate : '';
    const name = typeof d?.name === 'string' ? d.name : uriTemplate;
    const displayName = name || '<unnamed>';
    // URI template is required
    if (!uriTemplate) {
        diagnostics.push({
            rule: 'uri-template-required',
            severity: 'error',
            message: `Resource '${displayName}' is missing a uriTemplate.`,
            definitionType: 'resource',
            definitionName: displayName,
        });
    }
    else {
        // Validate URI template format (basic RFC 6570 check)
        const templateCheck = checkUriTemplate(uriTemplate, name);
        if (templateCheck)
            diagnostics.push(templateCheck);
    }
    // Name fallback warning
    if (!d?.name && uriTemplate) {
        diagnostics.push({
            rule: 'resource-name-not-uri',
            severity: 'warning',
            message: `Resource '${uriTemplate}' has no explicit name and will use the URI template as its display name. ` +
                'Consider adding a name for cleaner resources/list output.',
            definitionType: 'resource',
            definitionName: uriTemplate,
        });
    }
    // Name validation (when explicitly provided)
    if (d?.name !== undefined) {
        const nameReq = checkNameRequired(d.name, 'resource', name);
        if (nameReq)
            diagnostics.push(nameReq);
    }
    // Description
    if (typeof d?.description !== 'string' || d.description.length === 0) {
        diagnostics.push({
            rule: 'description-required',
            severity: 'warning',
            message: `Resource '${displayName}' has no description.`,
            definitionType: 'resource',
            definitionName: displayName,
        });
    }
    // Handler
    if (typeof d?.handler !== 'function') {
        diagnostics.push({
            rule: 'handler-required',
            severity: 'error',
            message: `Resource '${displayName}' is missing a handler function.`,
            definitionType: 'resource',
            definitionName: displayName,
        });
    }
    // Params schema (optional, but must be ZodObject when present)
    if (d?.params !== undefined) {
        const paramsCheck = checkIsZodObject(d.params, 'params', 'resource', displayName);
        if (paramsCheck) {
            diagnostics.push(paramsCheck);
        }
        else {
            diagnostics.push(...checkFieldDescriptions(d.params, 'params', 'resource', displayName));
            const paramsSerial = checkSchemaSerializable(d.params, 'params', 'resource', displayName);
            if (paramsSerial) {
                diagnostics.push(paramsSerial);
            }
            else if (portability) {
                diagnostics.push(...lintSchemaPortability(d.params, 'params', 'resource', displayName, portability));
            }
            // Cross-reference: template variables must match params schema keys
            if (uriTemplate) {
                diagnostics.push(...checkTemplateParamsAlignment(uriTemplate, d.params, displayName));
            }
        }
    }
    // Auth scopes validation
    if (d?.auth !== undefined) {
        diagnostics.push(...lintAuthScopes(d.auth, 'resource', displayName));
    }
    // Output schema (optional, but must be ZodObject when present)
    if (d?.output !== undefined) {
        const outputCheck = checkIsZodObject(d.output, 'output', 'resource', displayName);
        if (outputCheck) {
            diagnostics.push(outputCheck);
        }
        else {
            diagnostics.push(...checkFieldDescriptions(d.output, 'output', 'resource', displayName));
            const outputSerial = checkSchemaSerializable(d.output, 'output', 'resource', displayName);
            if (outputSerial) {
                diagnostics.push(outputSerial);
            }
            else if (portability) {
                diagnostics.push(...lintSchemaPortability(d.output, 'output', 'resource', displayName, portability));
            }
        }
    }
    // Handler body heuristic checks (error-handling anti-patterns)
    diagnostics.push(...lintHandlerBody(d, 'resource'));
    // Declarative error contract validation
    if (d?.errors !== undefined) {
        diagnostics.push(...lintErrorContract(d.errors, 'resource', displayName));
        diagnostics.push(...lintErrorContractConformance(d, 'resource', displayName));
    }
    return diagnostics;
}
/** Extracts variable names from an RFC 6570 URI template (strips operators like +, #, ?, &, etc.). */
function extractTemplateVariables(template) {
    const vars = [];
    for (const match of template.matchAll(/\{(?:[+#./;?&]?)([^}]+)\}/g)) {
        // match[1] contains comma-separated variable names, each optionally with :maxLength or *
        const varList = match[1] ?? '';
        for (const part of varList.split(',')) {
            const name = part.replace(/:[0-9]+$|\*$/g, '').trim();
            if (name)
                vars.push(name);
        }
    }
    return vars;
}
/**
 * Checks that URI template variables align with params schema keys.
 * A mismatch causes hard failures: the SDK extracts variables from the URI
 * and passes them to the params schema for validation.
 */
function checkTemplateParamsAlignment(template, params, resourceName) {
    const templateVars = extractTemplateVariables(template);
    if (templateVars.length === 0)
        return [];
    const shape = params.shape;
    const schemaKeys = new Set(Object.keys(shape));
    const diagnostics = [];
    for (const varName of templateVars) {
        if (!schemaKeys.has(varName)) {
            diagnostics.push({
                rule: 'template-params-align',
                severity: 'error',
                message: `Resource '${resourceName}' URI template variable '{${varName}}' has no matching key in params schema. ` +
                    `Params schema has: [${[...schemaKeys].join(', ')}]. This will cause every resource read to fail.`,
                definitionType: 'resource',
                definitionName: resourceName,
            });
        }
    }
    return diagnostics;
}
/**
 * Basic URI template validation.
 * Checks for balanced braces and non-empty variable names (RFC 6570 subset).
 */
function checkUriTemplate(template, name) {
    // Check for unbalanced braces
    let depth = 0;
    for (const char of template) {
        if (char === '{')
            depth++;
        if (char === '}')
            depth--;
        if (depth < 0) {
            return {
                rule: 'uri-template-valid',
                severity: 'error',
                message: `Resource '${name}' has an invalid URI template: unbalanced braces in '${template}'.`,
                definitionType: 'resource',
                definitionName: name,
            };
        }
    }
    if (depth !== 0) {
        return {
            rule: 'uri-template-valid',
            severity: 'error',
            message: `Resource '${name}' has an invalid URI template: unclosed brace in '${template}'.`,
            definitionType: 'resource',
            definitionName: name,
        };
    }
    // Check for empty variable names like {}
    if (/\{\s*\}/.test(template)) {
        return {
            rule: 'uri-template-valid',
            severity: 'error',
            message: `Resource '${name}' has an invalid URI template: empty variable name in '${template}'.`,
            definitionType: 'resource',
            definitionName: name,
        };
    }
    return null;
}
//# sourceMappingURL=resource-rules.js.map