"use strict";
/**
 * Types and logic for the wizard's project-level Amplitude binding.
 *
 * The canonical on-disk location is `<installDir>/.amplitude/project-binding.json`.
 * Legacy `ampli.json` in the project root remains readable (and is written
 * during transition) for older workflows.
 *
 * UNIT-TESTABLE SURFACE (pure, no I/O):
 *   parseAmpliConfig, validateAmpliConfig, mergeAmpliConfig,
 *   isConfigured, isMinimllyConfigured
 *
 * I/O SURFACE:
 *   readAmpliConfig, writeAmpliConfig, ampliConfigExists
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.AMPLI_CONFIG_FILENAME = void 0;
exports.parseAmpliConfig = parseAmpliConfig;
exports.isMinimallyConfigured = isMinimallyConfigured;
exports.isConfigured = isConfigured;
exports.mergeAmpliConfig = mergeAmpliConfig;
exports.hasMergeConflicts = hasMergeConflicts;
exports.ampliConfigPath = ampliConfigPath;
exports.ampliConfigExists = ampliConfigExists;
exports.readAmpliConfig = readAmpliConfig;
exports.writeAmpliConfig = writeAmpliConfig;
exports.clearAuthFieldsInAmpliConfig = clearAuthFieldsInAmpliConfig;
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const zod_1 = require("zod");
const atomic_write_js_1 = require("../utils/atomic-write.js");
const storage_paths_js_1 = require("../utils/storage-paths.js");
const logger_js_1 = require("./observability/logger.js");
const log = (0, logger_js_1.createLogger)('ampli-config');
exports.AMPLI_CONFIG_FILENAME = 'ampli.json';
// ── Pure logic (unit-testable) ────────────────────────────────────────────────
const AmpliConfigSchema = zod_1.z
    .object({
    OrgId: zod_1.z.string().optional(),
    ProjectId: zod_1.z.string().optional(),
    // Kept readable for back-compat with ampli.json files written before the
    // workspace → project rename. parseAmpliConfig migrates it to ProjectId
    // at the read boundary; everything downstream only sees ProjectId.
    WorkspaceId: zod_1.z.string().optional(),
    SourceId: zod_1.z.string().optional(),
    Branch: zod_1.z.string().optional(),
    Path: zod_1.z.string().optional(),
    Version: zod_1.z.string().optional(),
    VersionId: zod_1.z.string().optional(),
    Zone: zod_1.z.string().optional(),
    Runtime: zod_1.z.string().optional(),
    Platform: zod_1.z.string().optional(),
    Language: zod_1.z.string().optional(),
    SDK: zod_1.z.string().optional(),
    OmitApiKeys: zod_1.z.boolean().optional(),
    SourceDirs: zod_1.z.array(zod_1.z.string()).optional(),
    InstanceNames: zod_1.z.array(zod_1.z.string()).optional(),
    AppId: zod_1.z.string().optional(),
    AppName: zod_1.z.string().optional(),
    EnvName: zod_1.z.string().optional(),
    DashboardUrl: zod_1.z.string().optional(),
    DashboardId: zod_1.z.string().optional(),
})
    .passthrough();
/**
 * Parse a raw JSON string into an AmpliConfig.
 * Returns a typed result rather than throwing.
 *
 * Read-time migration: if a legacy `WorkspaceId` field is present and the new
 * `ProjectId` is absent, the value is copied to `ProjectId` and `WorkspaceId`
 * is dropped from the returned object. When both are present, `ProjectId`
 * wins. This keeps the rest of the codebase unaware of the legacy key;
 * `writeAmpliConfig` only ever emits `ProjectId`, so files auto-migrate on
 * the user's next save.
 */
function parseAmpliConfig(raw) {
    if (hasMergeConflicts(raw)) {
        return { ok: false, error: 'merge_conflicts' };
    }
    try {
        const result = AmpliConfigSchema.safeParse(JSON.parse(raw));
        if (!result.success) {
            return { ok: false, error: 'invalid_json' };
        }
        const config = { ...result.data };
        if (config.WorkspaceId && !config.ProjectId) {
            config.ProjectId = config.WorkspaceId;
        }
        delete config.WorkspaceId;
        return { ok: true, config };
    }
    catch {
        return { ok: false, error: 'invalid_json' };
    }
}
/**
 * Returns true when the config contains the fields required to query Amplitude
 * for event data (i.e. the project has been linked to an Amplitude source).
 */
function isMinimallyConfigured(config) {
    return Boolean(config.SourceId);
}
/**
 * Returns true when the config is considered fully configured: it has an org,
 * a project, and a source linked.
 */
function isConfigured(config) {
    return Boolean(config.OrgId && config.ProjectId && config.SourceId);
}
/**
 * Shallow-merge an update into an existing config. Undefined update values are
 * ignored so callers can safely spread partial objects.
 */
function mergeAmpliConfig(existing, updates) {
    const result = { ...existing };
    for (const [key, value] of Object.entries(updates)) {
        if (value !== undefined) {
            // @ts-expect-error – dynamic key assignment
            result[key] = value;
        }
    }
    return result;
}
/**
 * Detect git merge conflict markers in a file's content.
 * Mirrors the check in the ampli CLI (ampli/src/util/git/mergeConflicts.ts).
 */
function hasMergeConflicts(text) {
    return (text.includes('<<<<<<<') &&
        text.includes('=======') &&
        text.includes('>>>>>>>'));
}
// ── I/O (not unit-testable in isolation) ────────────────────────────────────
/**
 * Returns the full path to ampli.json in the given directory.
 */
function ampliConfigPath(dir) {
    return path.join(dir, exports.AMPLI_CONFIG_FILENAME);
}
/**
 * Returns true if ampli.json exists in the given directory.
 */
function ampliConfigExists(dir) {
    return (fs.existsSync((0, storage_paths_js_1.getProjectBindingFile)(dir)) ||
        fs.existsSync(ampliConfigPath(dir)));
}
/**
 * Read and parse a single binding JSON file.
 * Returns a typed result; never throws.
 */
function readAmpliConfigFile(filePath) {
    let raw;
    try {
        raw = fs.readFileSync(filePath, 'utf-8');
    }
    catch (err) {
        const code = err.code;
        if (code === 'ENOENT') {
            return { ok: false, error: 'not_found' };
        }
        log.debug('readAmpliConfigFile: read failed', {
            path: filePath,
            'error message': err instanceof Error ? err.message : String(err),
        });
        return { ok: false, error: 'read_error' };
    }
    return parseAmpliConfig(raw);
}
/**
 * Read project binding from `.amplitude/project-binding.json`, falling back to
 * legacy root `ampli.json`, merging when both exist (binding overrides).
 */
function readAmpliConfig(dir) {
    const bindingPath = (0, storage_paths_js_1.getProjectBindingFile)(dir);
    const legacyPath = ampliConfigPath(dir);
    const bindingResult = readAmpliConfigFile(bindingPath);
    const legacyResult = readAmpliConfigFile(legacyPath);
    let merged;
    if (legacyResult.ok) {
        merged = legacyResult.config;
    }
    if (bindingResult.ok) {
        merged = merged
            ? mergeAmpliConfig(merged, bindingResult.config)
            : bindingResult.config;
    }
    if (merged !== undefined) {
        if (legacyResult.ok &&
            !bindingResult.ok &&
            bindingResult.error === 'not_found') {
            try {
                (0, storage_paths_js_1.ensureDir)((0, storage_paths_js_1.getProjectMetaDir)(dir));
                (0, atomic_write_js_1.atomicWriteJSON)(bindingPath, merged, 0o644);
            }
            catch (err) {
                log.debug('readAmpliConfig: could not migrate binding file forward', {
                    'error message': err instanceof Error ? err.message : String(err),
                });
            }
        }
        return { ok: true, config: merged };
    }
    if (!bindingResult.ok &&
        bindingResult.error === 'not_found' &&
        !legacyResult.ok &&
        legacyResult.error === 'not_found') {
        return { ok: false, error: 'not_found' };
    }
    if (!legacyResult.ok && legacyResult.error !== 'not_found') {
        return legacyResult;
    }
    if (!bindingResult.ok && bindingResult.error !== 'not_found') {
        return bindingResult;
    }
    return { ok: false, error: 'not_found' };
}
/**
 * Write an AmpliConfig to the canonical binding path and mirror to legacy
 * `ampli.json` for transition compatibility.
 *
 * @returns true if at least one destination was written successfully.
 */
function writeAmpliConfig(dir, config) {
    const bindingPath = (0, storage_paths_js_1.getProjectBindingFile)(dir);
    let anyOk = false;
    try {
        (0, storage_paths_js_1.ensureDir)((0, storage_paths_js_1.getProjectMetaDir)(dir));
        (0, atomic_write_js_1.atomicWriteJSON)(bindingPath, config, 0o644);
        anyOk = true;
    }
    catch (err) {
        log.warn('writeAmpliConfig: canonical binding write failed', {
            'error message': err instanceof Error ? err.message : String(err),
        });
    }
    try {
        fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(ampliConfigPath(dir), JSON.stringify(config, null, 2), 'utf-8');
        anyOk = true;
    }
    catch (err) {
        log.warn('writeAmpliConfig: legacy ampli.json write failed', {
            'error message': err instanceof Error ? err.message : String(err),
        });
    }
    return anyOk;
}
/**
 * Remove org/project/zone bindings from a project's ampli.json. Called on
 * logout so a subsequent login doesn't auto-select the previous user's org
 * and project. Tracking-plan fields (SourceId, Branch, Version, etc.) are
 * preserved — they're not auth state. No-op if ampli.json is missing or
 * malformed.
 *
 * Also deletes the legacy `WorkspaceId` field as a belt-and-suspenders
 * cleanup — parseAmpliConfig normalizes it away, but if the raw file still
 * carries it (e.g. someone edited it by hand), we strip it here too.
 */
function clearAuthFieldsInAmpliConfig(dir) {
    const result = readAmpliConfig(dir);
    if (!result.ok)
        return;
    const next = { ...result.config };
    delete next.OrgId;
    delete next.ProjectId;
    delete next.WorkspaceId;
    delete next.Zone;
    // The setup_complete fields below are also auth-scoped — they bind
    // the project to a specific Amplitude app/env that only exists in
    // the previously authenticated org. On logout, drop them so the
    // next sign-in starts from a clean scope. Tracking-plan fields
    // (SourceId, Branch, Version) survive — they belong to the project,
    // not the user.
    delete next.AppId;
    delete next.AppName;
    delete next.EnvName;
    delete next.DashboardUrl;
    delete next.DashboardId;
    writeAmpliConfig(dir, next);
}
