"use strict";
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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultConfig = exports.Config = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const crypto = __importStar(require("crypto"));
const jsonc = __importStar(require("jsonc-parser"));
const write_file_atomic_1 = __importDefault(require("write-file-atomic"));
/**
 * Config - Centralized configuration management
 *
 * Encapsulates all config paths and operations, making them dependency-injectable
 * and testable. Pass a custom rootDir for tests to avoid polluting ~/.cmux
 */
class Config {
    rootDir;
    sessionsDir;
    srcDir;
    configFile;
    providersFile;
    secretsFile;
    constructor(rootDir) {
        const envRoot = process.env.CMUX_TEST_ROOT;
        this.rootDir = rootDir ?? envRoot ?? path.join(os.homedir(), ".cmux");
        this.sessionsDir = path.join(this.rootDir, "sessions");
        this.srcDir = path.join(this.rootDir, "src");
        this.configFile = path.join(this.rootDir, "config.json");
        this.providersFile = path.join(this.rootDir, "providers.jsonc");
        this.secretsFile = path.join(this.rootDir, "secrets.json");
    }
    loadConfigOrDefault() {
        try {
            if (fs.existsSync(this.configFile)) {
                const data = fs.readFileSync(this.configFile, "utf-8");
                const parsed = JSON.parse(data);
                // Config is stored as array of [path, config] pairs
                if (parsed.projects && Array.isArray(parsed.projects)) {
                    const projectsMap = new Map(parsed.projects);
                    return {
                        projects: projectsMap,
                    };
                }
            }
        }
        catch (error) {
            console.error("Error loading config:", error);
        }
        // Return default config
        return {
            projects: new Map(),
        };
    }
    saveConfig(config) {
        try {
            if (!fs.existsSync(this.rootDir)) {
                fs.mkdirSync(this.rootDir, { recursive: true });
            }
            const data = {
                projects: Array.from(config.projects.entries()),
            };
            write_file_atomic_1.default.sync(this.configFile, JSON.stringify(data, null, 2));
        }
        catch (error) {
            console.error("Error saving config:", error);
        }
    }
    /**
     * Edit config atomically using a transformation function
     * @param fn Function that takes current config and returns modified config
     */
    editConfig(fn) {
        const config = this.loadConfigOrDefault();
        const newConfig = fn(config);
        this.saveConfig(newConfig);
    }
    getProjectName(projectPath) {
        return projectPath.split("/").pop() ?? projectPath.split("\\").pop() ?? "unknown";
    }
    /**
     * Generate a stable unique workspace ID.
     * Uses 10 random hex characters for readability while maintaining uniqueness.
     *
     * Example: "a1b2c3d4e5"
     */
    generateStableId() {
        // Generate 5 random bytes and convert to 10 hex chars
        return crypto.randomBytes(5).toString("hex");
    }
    /**
     * DEPRECATED: Generate legacy workspace ID from project and workspace paths.
     * This method is used only for legacy workspace migration to look up old workspaces.
     * New workspaces use generateStableId() which returns a random stable ID.
     *
     * DO NOT use this method or its format to construct workspace IDs anywhere in the codebase.
     * Workspace IDs are backend implementation details and must only come from backend operations.
     */
    generateLegacyId(projectPath, workspacePath) {
        const projectBasename = this.getProjectName(projectPath);
        const workspaceBasename = workspacePath.split("/").pop() ?? workspacePath.split("\\").pop() ?? "unknown";
        return `${projectBasename}-${workspaceBasename}`;
    }
    /**
     * Get the workspace directory path for a given directory name.
     * The directory name is the workspace name (branch name).
     */
    /**
     * Add paths to WorkspaceMetadata to create FrontendWorkspaceMetadata.
     * Helper to avoid duplicating path computation logic.
     */
    addPathsToMetadata(metadata, workspacePath, _projectPath) {
        return {
            ...metadata,
            namedWorkspacePath: workspacePath,
        };
    }
    /**
     * Find a workspace path and project path by workspace ID
     * @returns Object with workspace and project paths, or null if not found
     */
    findWorkspace(workspaceId) {
        const config = this.loadConfigOrDefault();
        for (const [projectPath, project] of config.projects) {
            for (const workspace of project.workspaces) {
                // NEW FORMAT: Check config first (primary source of truth after migration)
                if (workspace.id === workspaceId) {
                    return { workspacePath: workspace.path, projectPath };
                }
                // LEGACY FORMAT: Fall back to metadata.json and legacy ID for unmigrated workspaces
                if (!workspace.id) {
                    // Extract workspace basename (could be stable ID or legacy name)
                    const workspaceBasename = workspace.path.split("/").pop() ?? workspace.path.split("\\").pop() ?? "unknown";
                    // Try loading metadata with basename as ID (works for old workspaces)
                    const metadataPath = path.join(this.getSessionDir(workspaceBasename), "metadata.json");
                    if (fs.existsSync(metadataPath)) {
                        try {
                            const data = fs.readFileSync(metadataPath, "utf-8");
                            const metadata = JSON.parse(data);
                            if (metadata.id === workspaceId) {
                                return { workspacePath: workspace.path, projectPath };
                            }
                        }
                        catch {
                            // Ignore parse errors, try legacy ID
                        }
                    }
                    // Try legacy ID format as last resort
                    const legacyId = this.generateLegacyId(projectPath, workspace.path);
                    if (legacyId === workspaceId) {
                        return { workspacePath: workspace.path, projectPath };
                    }
                }
            }
        }
        return null;
    }
    /**
     * Workspace Path Architecture:
     *
     * Workspace paths are computed on-demand from projectPath + workspace name using
     * config.getWorkspacePath(projectPath, directoryName). This ensures a single source of truth.
     *
     * - Worktree directory name: uses workspace.name (the branch name)
     * - Workspace ID: stable random identifier for identity and sessions (not used for directories)
     *
     * Backend: Uses getWorkspacePath(metadata.projectPath, metadata.name) for workspace directory paths
     * Frontend: Gets enriched metadata with paths via IPC (FrontendWorkspaceMetadata)
     *
     * WorkspaceMetadata.workspacePath is deprecated and will be removed. Use computed
     * paths from getWorkspacePath() or getWorkspacePaths() instead.
     */
    /**
     * Get the session directory for a specific workspace
     */
    getSessionDir(workspaceId) {
        return path.join(this.sessionsDir, workspaceId);
    }
    /**
     * Get all workspace metadata by loading config and metadata files.
     *
     * Returns FrontendWorkspaceMetadata with paths already computed.
     * This eliminates the need for separate "enrichment" - paths are computed
     * once during the loop when we already have all the necessary data.
     *
     * NEW BEHAVIOR: Config is the primary source of truth
     * - If workspace has id/name/createdAt in config, use those directly
     * - If workspace only has path, fall back to reading metadata.json
     * - Migrate old workspaces by copying metadata from files to config
     *
     * This centralizes workspace metadata in config.json and eliminates the need
     * for scattered metadata.json files (kept for backward compat with older versions).
     *
     * GUARANTEE: Every workspace returned will have a createdAt timestamp.
     * If missing from config or legacy metadata, a new timestamp is assigned and
     * saved to config for subsequent loads.
     */
    getAllWorkspaceMetadata() {
        const config = this.loadConfigOrDefault();
        const workspaceMetadata = [];
        let configModified = false;
        for (const [projectPath, projectConfig] of config.projects) {
            const projectName = this.getProjectName(projectPath);
            for (const workspace of projectConfig.workspaces) {
                // Extract workspace basename from path (could be stable ID or legacy name)
                const workspaceBasename = workspace.path.split("/").pop() ?? workspace.path.split("\\").pop() ?? "unknown";
                try {
                    // NEW FORMAT: If workspace has metadata in config, use it directly
                    if (workspace.id && workspace.name) {
                        const metadata = {
                            id: workspace.id,
                            name: workspace.name,
                            projectName,
                            projectPath,
                            // GUARANTEE: All workspaces must have createdAt (assign now if missing)
                            createdAt: workspace.createdAt ?? new Date().toISOString(),
                            // Include runtime config if present (for SSH workspaces)
                            runtimeConfig: workspace.runtimeConfig,
                        };
                        // Migrate missing createdAt to config for next load
                        if (!workspace.createdAt) {
                            workspace.createdAt = metadata.createdAt;
                            configModified = true;
                        }
                        workspaceMetadata.push(this.addPathsToMetadata(metadata, workspace.path, projectPath));
                        continue; // Skip metadata file lookup
                    }
                    // LEGACY FORMAT: Fall back to reading metadata.json
                    // Try legacy ID format first (project-workspace) - used by E2E tests and old workspaces
                    const legacyId = this.generateLegacyId(projectPath, workspace.path);
                    const metadataPath = path.join(this.getSessionDir(legacyId), "metadata.json");
                    let metadataFound = false;
                    if (fs.existsSync(metadataPath)) {
                        const data = fs.readFileSync(metadataPath, "utf-8");
                        let metadata = JSON.parse(data);
                        // Ensure required fields are present
                        if (!metadata.name || !metadata.projectPath) {
                            metadata = {
                                ...metadata,
                                name: metadata.name ?? workspaceBasename,
                                projectPath: metadata.projectPath ?? projectPath,
                                projectName: metadata.projectName ?? projectName,
                            };
                        }
                        // GUARANTEE: All workspaces must have createdAt
                        metadata.createdAt ?? (metadata.createdAt = new Date().toISOString());
                        // Migrate to config for next load
                        workspace.id = metadata.id;
                        workspace.name = metadata.name;
                        workspace.createdAt = metadata.createdAt;
                        configModified = true;
                        workspaceMetadata.push(this.addPathsToMetadata(metadata, workspace.path, projectPath));
                        metadataFound = true;
                    }
                    // No metadata found anywhere - create basic metadata
                    if (!metadataFound) {
                        const legacyId = this.generateLegacyId(projectPath, workspace.path);
                        const metadata = {
                            id: legacyId,
                            name: workspaceBasename,
                            projectName,
                            projectPath,
                            // GUARANTEE: All workspaces must have createdAt
                            createdAt: new Date().toISOString(),
                        };
                        // Save to config for next load
                        workspace.id = metadata.id;
                        workspace.name = metadata.name;
                        workspace.createdAt = metadata.createdAt;
                        configModified = true;
                        workspaceMetadata.push(this.addPathsToMetadata(metadata, workspace.path, projectPath));
                    }
                }
                catch (error) {
                    console.error(`Failed to load/migrate workspace metadata:`, error);
                    // Fallback to basic metadata if migration fails
                    const legacyId = this.generateLegacyId(projectPath, workspace.path);
                    const metadata = {
                        id: legacyId,
                        name: workspaceBasename,
                        projectName,
                        projectPath,
                        // GUARANTEE: All workspaces must have createdAt (even in error cases)
                        createdAt: new Date().toISOString(),
                    };
                    workspaceMetadata.push(this.addPathsToMetadata(metadata, workspace.path, projectPath));
                }
            }
        }
        // Save config if we migrated any workspaces
        if (configModified) {
            this.saveConfig(config);
        }
        return workspaceMetadata;
    }
    /**
     * Add a workspace to config.json (single source of truth for workspace metadata).
     * Creates project entry if it doesn't exist.
     *
     * @param projectPath Absolute path to the project
     * @param metadata Workspace metadata to save
     */
    addWorkspace(projectPath, metadata) {
        this.editConfig((config) => {
            let project = config.projects.get(projectPath);
            if (!project) {
                project = { workspaces: [] };
                config.projects.set(projectPath, project);
            }
            // Check if workspace already exists (by ID)
            const existingIndex = project.workspaces.findIndex((w) => w.id === metadata.id);
            // Compute workspace path - this is only for legacy config migration
            // New code should use Runtime.getWorkspacePath() directly
            const projectName = this.getProjectName(projectPath);
            const workspacePath = path.join(this.srcDir, projectName, metadata.name);
            const workspaceEntry = {
                path: workspacePath,
                id: metadata.id,
                name: metadata.name,
                createdAt: metadata.createdAt,
            };
            if (existingIndex >= 0) {
                // Update existing workspace
                project.workspaces[existingIndex] = workspaceEntry;
            }
            else {
                // Add new workspace
                project.workspaces.push(workspaceEntry);
            }
            return config;
        });
    }
    /**
     * Load providers configuration from JSONC file
     * Supports comments in JSONC format
     */
    loadProvidersConfig() {
        try {
            if (fs.existsSync(this.providersFile)) {
                const data = fs.readFileSync(this.providersFile, "utf-8");
                return jsonc.parse(data);
            }
        }
        catch (error) {
            console.error("Error loading providers config:", error);
        }
        return null;
    }
    /**
     * Save providers configuration to JSONC file
     * @param config The providers configuration to save
     */
    saveProvidersConfig(config) {
        try {
            if (!fs.existsSync(this.rootDir)) {
                fs.mkdirSync(this.rootDir, { recursive: true });
            }
            // Format with 2-space indentation for readability
            const jsonString = JSON.stringify(config, null, 2);
            // Add a comment header to the file
            const contentWithComments = `// Providers configuration for cmux
// Configure your AI providers here
// Example:
// {
//   "anthropic": {
//     "apiKey": "sk-...",
//     "baseUrl": "https://api.anthropic.com"
//   }
// }
${jsonString}`;
            fs.writeFileSync(this.providersFile, contentWithComments);
        }
        catch (error) {
            console.error("Error saving providers config:", error);
            throw error; // Re-throw to let caller handle
        }
    }
    /**
     * Load secrets configuration from JSON file
     * Returns empty config if file doesn't exist
     */
    loadSecretsConfig() {
        try {
            if (fs.existsSync(this.secretsFile)) {
                const data = fs.readFileSync(this.secretsFile, "utf-8");
                return JSON.parse(data);
            }
        }
        catch (error) {
            console.error("Error loading secrets config:", error);
        }
        return {};
    }
    /**
     * Save secrets configuration to JSON file
     * @param config The secrets configuration to save
     */
    saveSecretsConfig(config) {
        try {
            if (!fs.existsSync(this.rootDir)) {
                fs.mkdirSync(this.rootDir, { recursive: true });
            }
            write_file_atomic_1.default.sync(this.secretsFile, JSON.stringify(config, null, 2));
        }
        catch (error) {
            console.error("Error saving secrets config:", error);
            throw error;
        }
    }
    /**
     * Get secrets for a specific project
     * @param projectPath The path to the project
     * @returns Array of secrets for the project, or empty array if none
     */
    getProjectSecrets(projectPath) {
        const config = this.loadSecretsConfig();
        return config[projectPath] ?? [];
    }
    /**
     * Update secrets for a specific project
     * @param projectPath The path to the project
     * @param secrets The secrets to save for the project
     */
    updateProjectSecrets(projectPath, secrets) {
        const config = this.loadSecretsConfig();
        config[projectPath] = secrets;
        this.saveSecretsConfig(config);
    }
}
exports.Config = Config;
// Default instance for application use
exports.defaultConfig = new Config();
//# sourceMappingURL=config.js.map