"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigIO = void 0;
exports.getSchemaUrlForVersion = getSchemaUrlForVersion;
exports.createConfigIO = createConfigIO;
const schema_1 = require("../../../schema");
const errors_1 = require("../../errors");
const utils_1 = require("../../utils");
const path_resolver_1 = require("./path-resolver");
const shared_ini_file_loader_1 = require("@smithy/shared-ini-file-loader");
const fs_1 = require("fs");
const promises_1 = require("fs/promises");
const path_1 = require("path");
function getSchemaUrlForVersion(version) {
    return `https://schema.agentcore.aws.dev/v${version}/agentcore.json`;
}
/**
 * Manages reading, writing, and validation of AgentCore configuration files
 */
class ConfigIO {
    pathResolver;
    projectDiscovered;
    /**
     * Create a ConfigIO instance.
     * If no baseDir is provided, automatically discovers the project using findConfigRoot().
     */
    constructor(pathConfig) {
        // Track if baseDir was explicitly provided
        const baseDirProvided = !!pathConfig?.baseDir;
        // Auto-discover config root if no baseDir provided
        if (!baseDirProvided) {
            const discoveredRoot = (0, path_resolver_1.findConfigRoot)();
            if (discoveredRoot) {
                pathConfig = { ...pathConfig, baseDir: discoveredRoot };
                this.projectDiscovered = true;
            }
            else {
                // No project found and no explicit baseDir - mark as not discovered
                this.projectDiscovered = false;
            }
        }
        else {
            // baseDir was explicitly provided (e.g., during project creation)
            this.projectDiscovered = true;
        }
        this.pathResolver = new path_resolver_1.PathResolver(pathConfig);
    }
    /**
     * Check if this ConfigIO is associated with a discovered or explicitly configured project.
     * Returns false if no baseDir was provided and no project was found via auto-discovery.
     */
    hasProject() {
        return this.projectDiscovered;
    }
    /**
     * Get the current path resolver
     */
    getPathResolver() {
        return this.pathResolver;
    }
    /**
     * Update the base directory for config files
     */
    setBaseDir(baseDir) {
        this.pathResolver.setBaseDir(baseDir);
    }
    /**
     * Get the project root directory (parent of agentcore/)
     */
    getProjectRoot() {
        return this.pathResolver.getProjectRoot();
    }
    /**
     * Get the config root directory (the agentcore/ directory)
     */
    getConfigRoot() {
        return this.pathResolver.getBaseDir();
    }
    /**
     * Read and validate the project configuration.
     */
    async readProjectSpec() {
        const filePath = this.pathResolver.getAgentConfigPath();
        return this.readAndValidate(filePath, 'AgentCore Project Config', schema_1.AgentCoreProjectSpecSchema);
    }
    /**
     * Write and validate the project configuration file.
     */
    async writeProjectSpec(data) {
        const filePath = this.pathResolver.getAgentConfigPath();
        // TODO: extend this to all resource arrays so empty defaults never pollute agentcore.json
        const cleaned = { ...data };
        if (cleaned.configBundles?.length === 0)
            delete cleaned.configBundles;
        if (cleaned.abTests?.length === 0)
            delete cleaned.abTests;
        if (cleaned.httpGateways?.length === 0)
            delete cleaned.httpGateways;
        await this.validateAndWrite(filePath, 'AgentCore Project Config', schema_1.AgentCoreProjectSpecSchema, cleaned);
    }
    /**
     * Read and validate the AWS configuration file.
     * Region is preserved as saved. Use resolveAWSDeploymentTargets() for environment/profile overrides.
     * TODO: Account is still overridden via AWS_PROFILE — consider moving to resolveAWSDeploymentTargets() for consistency.
     */
    async readAWSDeploymentTargets() {
        const filePath = this.pathResolver.getAWSTargetsConfigPath();
        let targets = await this.readAndValidate(filePath, 'AWS Targets', schema_1.AwsDeploymentTargetsSchema);
        // Override account from credentials if AWS_PROFILE is set
        if (process.env.AWS_PROFILE) {
            const account = await (0, utils_1.detectAwsAccount)();
            if (account) {
                targets = targets.map(t => ({ ...t, account }));
            }
        }
        return targets;
    }
    /**
     * Read AWS deployment targets with region fallback from environment/profile.
     * Region precedence: saved value > AWS_REGION > AWS_DEFAULT_REGION > profile config.
     * Environment/profile region is only used as a fallback when no region is saved in aws-targets.json.
     */
    async resolveAWSDeploymentTargets() {
        const targets = await this.readAWSDeploymentTargets();
        // Only resolve region for targets that don't already have one saved
        const fallbackRegion = await this.resolveRegionFallback();
        if (!fallbackRegion) {
            return targets;
        }
        return targets.map(t => (t.region ? t : { ...t, region: fallbackRegion }));
    }
    /**
     * Resolve a fallback region from environment variables or AWS profile config.
     */
    async resolveRegionFallback() {
        // Check env vars first
        const envRegion = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
        if (envRegion && schema_1.AgentCoreRegionSchema.safeParse(envRegion).success) {
            return envRegion;
        }
        // Check profile config
        try {
            const profile = process.env.AWS_PROFILE ?? 'default';
            const config = await (0, shared_ini_file_loader_1.loadSharedConfigFiles)();
            const profileRegion = config.configFile?.[profile]?.region;
            if (profileRegion && schema_1.AgentCoreRegionSchema.safeParse(profileRegion).success) {
                return profileRegion;
            }
        }
        catch {
            // Config file not available
        }
        return undefined;
    }
    /**
     * Write and validate the AWS configuration file
     */
    async writeAWSDeploymentTargets(data) {
        const filePath = this.pathResolver.getAWSTargetsConfigPath();
        await this.validateAndWrite(filePath, 'AWS Targets', schema_1.AwsDeploymentTargetsSchema, data);
    }
    /**
     * Read and validate the deployed state file.
     * Validates that all target keys exist in aws-targets.
     */
    async readDeployedState() {
        const targets = await this.readAWSDeploymentTargets();
        const targetNames = targets.map(t => t.name);
        const schema = (0, schema_1.createValidatedDeployedStateSchema)(targetNames);
        const filePath = this.pathResolver.getStatePath();
        return this.readAndValidate(filePath, 'State', schema);
    }
    /**
     * Write and validate the deployed state file.
     * Validates that all target keys exist in aws-targets.
     */
    async writeDeployedState(data) {
        const targets = await this.readAWSDeploymentTargets();
        const targetNames = targets.map(t => t.name);
        const schema = (0, schema_1.createValidatedDeployedStateSchema)(targetNames);
        const filePath = this.pathResolver.getStatePath();
        await this.validateAndWrite(filePath, 'State', schema, data);
    }
    /**
     * Read and validate the MCP definitions file
     */
    async readMcpDefs() {
        const filePath = this.pathResolver.getMcpDefsPath();
        return this.readAndValidate(filePath, 'MCP Definitions', schema_1.AgentCoreCliMcpDefsSchema);
    }
    /**
     * Write and validate the MCP definitions file
     */
    async writeMcpDefs(data) {
        const filePath = this.pathResolver.getMcpDefsPath();
        await this.validateAndWrite(filePath, 'MCP Definitions', schema_1.AgentCoreCliMcpDefsSchema, data);
    }
    /**
     * Check if the base directory exists
     */
    baseDirExists() {
        return (0, fs_1.existsSync)(this.pathResolver.getBaseDir());
    }
    /**
     * Check if a specific config file exists
     */
    configExists(type) {
        const pathMap = {
            project: this.pathResolver.getAgentConfigPath(),
            awsTargets: this.pathResolver.getAWSTargetsConfigPath(),
            state: this.pathResolver.getStatePath(),
            mcpDefs: this.pathResolver.getMcpDefsPath(),
        };
        return (0, fs_1.existsSync)(pathMap[type]);
    }
    /**
     * Initialize the base directory and CLI system subdirectory.
     * Requires that a baseDir was explicitly provided or a project was discovered.
     */
    async initializeBaseDir() {
        // Prevent creating directories when no project was configured
        if (!this.projectDiscovered) {
            throw new errors_1.NoProjectError();
        }
        const baseDir = this.pathResolver.getBaseDir();
        const cliSystemDir = this.pathResolver.getCliSystemDir();
        try {
            await (0, promises_1.mkdir)(baseDir, { recursive: true });
            await (0, promises_1.mkdir)(cliSystemDir, { recursive: true });
        }
        catch (err) {
            const normalizedError = err instanceof Error ? err : new Error('Unknown error');
            throw new errors_1.ConfigWriteError(baseDir, normalizedError);
        }
    }
    /**
     * Generic read and validate method
     */
    async readAndValidate(filePath, fileType, schema) {
        // Check if file exists
        if (!(0, fs_1.existsSync)(filePath)) {
            throw new errors_1.ConfigNotFoundError(filePath, fileType);
        }
        // Read file
        let fileContent;
        try {
            fileContent = await (0, promises_1.readFile)(filePath, 'utf-8');
        }
        catch (err) {
            const normalizedError = err instanceof Error ? err : new Error('Unknown error');
            throw new errors_1.ConfigReadError(filePath, normalizedError);
        }
        // Parse JSON
        let parsed;
        try {
            parsed = JSON.parse(fileContent);
        }
        catch (err) {
            const normalizedError = err instanceof Error ? err : new Error('Invalid JSON');
            throw new errors_1.ConfigParseError(filePath, normalizedError);
        }
        // Validate with Zod schema
        const result = schema.safeParse(parsed);
        if (!result.success) {
            throw new errors_1.ConfigValidationError(filePath, fileType, result.error);
        }
        return result.data;
    }
    /**
     * Generic validate and write method
     */
    async validateAndWrite(filePath, fileType, schema, data) {
        // Prevent writing to non-existent projects
        if (!this.projectDiscovered) {
            throw new errors_1.NoProjectError();
        }
        // Validate data with Zod schema
        const result = schema.safeParse(data);
        if (!result.success) {
            throw new errors_1.ConfigValidationError(filePath, fileType, result.error);
        }
        // Ensure directory exists
        const dir = (0, path_1.dirname)(filePath);
        try {
            await (0, promises_1.mkdir)(dir, { recursive: true });
        }
        catch (err) {
            const normalizedError = err instanceof Error ? err : new Error('Unknown error');
            throw new errors_1.ConfigWriteError(filePath, normalizedError);
        }
        // Write file with pretty formatting
        try {
            const jsonContent = JSON.stringify(result.data, null, 2);
            await (0, promises_1.writeFile)(filePath, jsonContent, 'utf-8');
        }
        catch (err) {
            const normalizedError = err instanceof Error ? err : new Error('Unknown error');
            throw new errors_1.ConfigWriteError(filePath, normalizedError);
        }
    }
}
exports.ConfigIO = ConfigIO;
/**
 * Create a new ConfigIO instance
 */
function createConfigIO(pathConfig) {
    return new ConfigIO(pathConfig);
}
//# sourceMappingURL=config-io.js.map