import { assertUniqueOrganizationStorageKeys, normalizeOrganizationStoragePathSegment, resolveOrganizationLegacyStorageKey, resolveOrganizationStorageKey, } from "@rudderhq/agent-runtime-utils";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { resolveStoredOrDerivedAgentWorkspaceKey } from "./agent-workspace-key.js";
const DEFAULT_INSTANCE_ID = "default";
const INSTANCE_ID_RE = /^[a-zA-Z0-9_-]+$/;
const FRIENDLY_PATH_SEGMENT_RE = /[^a-zA-Z0-9._-]+/g;
const WORKSPACE_PERMISSION_ERROR_CODES = new Set(["EACCES", "EPERM", "EROFS"]);
function expandHomePrefix(value) {
    if (value === "~")
        return os.homedir();
    if (value.startsWith("~/"))
        return path.resolve(os.homedir(), value.slice(2));
    return value;
}
export function resolveRudderHomeDir() {
    const envHome = process.env.RUDDER_HOME?.trim();
    if (envHome)
        return path.resolve(expandHomePrefix(envHome));
    return path.resolve(os.homedir(), ".rudder");
}
export function resolveRudderInstanceId() {
    const raw = process.env.RUDDER_INSTANCE_ID?.trim() || DEFAULT_INSTANCE_ID;
    if (!INSTANCE_ID_RE.test(raw)) {
        throw new Error(`Invalid RUDDER_INSTANCE_ID '${raw}'.`);
    }
    return raw;
}
export function resolveRudderInstanceRoot() {
    return path.resolve(resolveRudderHomeDir(), "instances", resolveRudderInstanceId());
}
export function resolveDefaultConfigPath() {
    return path.resolve(resolveRudderInstanceRoot(), "config.json");
}
export function resolveDefaultEmbeddedPostgresDir() {
    return path.resolve(resolveRudderInstanceRoot(), "db");
}
export function resolveDefaultLogsDir() {
    return path.resolve(resolveRudderInstanceRoot(), "logs");
}
export function resolveDefaultSecretsKeyFilePath() {
    return path.resolve(resolveRudderInstanceRoot(), "secrets", "master.key");
}
export function resolveDefaultStorageDir() {
    return path.resolve(resolveRudderInstanceRoot(), "data", "storage");
}
export function resolveDefaultBackupDir() {
    return path.resolve(resolveRudderInstanceRoot(), "data", "backups");
}
export function resolveOrganizationRoot(orgId) {
    const normalizedOrgId = resolveOrganizationStorageKey(orgId);
    return path.resolve(resolveRudderInstanceRoot(), "organizations", normalizedOrgId);
}
export function resolveLegacyOrganizationRoot(orgId) {
    const legacyOrgId = resolveOrganizationLegacyStorageKey(orgId);
    return path.resolve(resolveRudderInstanceRoot(), "organizations", legacyOrgId);
}
function validatePathSegment(value, label) {
    return normalizeOrganizationStoragePathSegment(value, label);
}
function resolveAgentWorkspacePathSegment(agent) {
    if (typeof agent === "string") {
        return validatePathSegment(agent, "agent workspace key");
    }
    return validatePathSegment(resolveStoredOrDerivedAgentWorkspaceKey(agent), "agent workspace key");
}
export function resolveOrganizationWorkspaceRoot(orgId) {
    const normalizedOrgId = resolveOrganizationStorageKey(orgId);
    return path.resolve(resolveOrganizationWorkspaceHomeDir(), normalizedOrgId, "workspaces");
}
export function resolveLegacyOrganizationWorkspaceRoot(orgId) {
    return path.resolve(resolveOrganizationRoot(orgId), "workspaces");
}
export function resolveOrganizationWorkspaceHomeDir() {
    const envHome = process.env.RUDDER_ORGANIZATION_WORKSPACE_HOME?.trim();
    if (envHome)
        return path.resolve(expandHomePrefix(envHome));
    if (process.env.RUDDER_HOME?.trim()) {
        return path.resolve(resolveRudderInstanceRoot(), "organizations");
    }
    return path.resolve(os.homedir(), "Documents", "Rudder", "instances", resolveRudderInstanceId(), "organizations");
}
export function resolveDefaultAgentWorkspaceDir(orgId, agent) {
    const normalizedWorkspaceKey = resolveAgentWorkspacePathSegment(agent);
    return path.resolve(resolveOrganizationWorkspaceRoot(orgId), "agents", normalizedWorkspaceKey);
}
export function resolveAgentInstructionsDir(orgId, agent) {
    return path.resolve(resolveDefaultAgentWorkspaceDir(orgId, agent), "instructions");
}
export function resolveAgentMemoryDir(orgId, agent) {
    return path.resolve(resolveDefaultAgentWorkspaceDir(orgId, agent), "memory");
}
export function resolveAgentLifeDir(orgId, agent) {
    return path.resolve(resolveDefaultAgentWorkspaceDir(orgId, agent), "life");
}
export function resolveAgentSkillsDir(orgId, agent) {
    return path.resolve(resolveDefaultAgentWorkspaceDir(orgId, agent), "skills");
}
export function resolveOrganizationSkillsDir(orgId) {
    return path.resolve(resolveOrganizationWorkspaceRoot(orgId), "skills");
}
export function resolveOrganizationAgentsDir(orgId) {
    return path.resolve(resolveOrganizationWorkspaceRoot(orgId), "agents");
}
export function resolveOrganizationProjectsDir(orgId) {
    return path.resolve(resolveOrganizationWorkspaceRoot(orgId), "projects");
}
function deriveProjectLibraryKey(input) {
    const explicitKey = sanitizeFriendlyPathSegment(input.projectUrlKey, "").toLowerCase();
    if (explicitKey)
        return explicitKey;
    const nameKey = sanitizeFriendlyPathSegment(input.projectName, "").toLowerCase();
    if (nameKey)
        return nameKey;
    return validatePathSegment(input.projectId, "project id");
}
export function resolveProjectLibraryRelativePath(input) {
    return path.join("projects", deriveProjectLibraryKey(input));
}
export function resolveProjectLibraryDir(input) {
    return path.resolve(resolveOrganizationWorkspaceRoot(input.orgId), resolveProjectLibraryRelativePath(input));
}
export function resolveManagedOrganizationCodebaseDir(input) {
    return path.resolve(resolveOrganizationWorkspaceRoot(input.orgId), "codebase", sanitizeFriendlyPathSegment(input.repoName, "_default"));
}
export async function ensureOrganizationWorkspaceLayout(orgId) {
    await migrateOrganizationStorageRoot(orgId);
    await migrateOrganizationWorkspaceRoot(orgId);
    const root = resolveOrganizationWorkspaceRoot(orgId);
    const agentsDir = resolveOrganizationAgentsDir(orgId);
    const skillsDir = resolveOrganizationSkillsDir(orgId);
    const projectsDir = resolveOrganizationProjectsDir(orgId);
    try {
        await Promise.all([
            fs.mkdir(root, { recursive: true }),
            fs.mkdir(agentsDir, { recursive: true }),
            fs.mkdir(skillsDir, { recursive: true }),
            fs.mkdir(projectsDir, { recursive: true }),
        ]);
    }
    catch (error) {
        if (isPermissionError(error)) {
            throw new Error(formatOrganizationWorkspacePermissionMessage({
                operation: "create",
                code: errorCode(error),
                legacyRootPath: resolveLegacyOrganizationWorkspaceRoot(orgId),
                canonicalRootPath: root,
            }), { cause: error });
        }
        throw error;
    }
    return { root, agentsDir, skillsDir, projectsDir };
}
export async function migrateOrganizationStorageRoot(orgId) {
    const canonicalRootPath = resolveOrganizationRoot(orgId);
    const legacyRootPath = resolveLegacyOrganizationRoot(orgId);
    if (canonicalRootPath === legacyRootPath) {
        return {
            canonicalRootPath,
            legacyRootPath,
            migrated: false,
            mergedIntoExistingTarget: false,
            skippedBecauseTargetExists: false,
        };
    }
    const legacyExists = await directoryExists(legacyRootPath);
    if (!legacyExists) {
        return {
            canonicalRootPath,
            legacyRootPath,
            migrated: false,
            mergedIntoExistingTarget: false,
            skippedBecauseTargetExists: false,
        };
    }
    const canonicalExists = await directoryExists(canonicalRootPath);
    if (canonicalExists) {
        await assertCanMergeDirectoryContents(legacyRootPath, canonicalRootPath);
        await mergeDirectoryContents(legacyRootPath, canonicalRootPath);
        await fs.rmdir(legacyRootPath);
        return {
            canonicalRootPath,
            legacyRootPath,
            migrated: true,
            mergedIntoExistingTarget: true,
            skippedBecauseTargetExists: false,
        };
    }
    await fs.mkdir(path.dirname(canonicalRootPath), { recursive: true });
    try {
        await fs.rename(legacyRootPath, canonicalRootPath);
    }
    catch (error) {
        const code = typeof error === "object" && error !== null && "code" in error
            ? error.code
            : undefined;
        if (code === "ENOENT") {
            return {
                canonicalRootPath,
                legacyRootPath,
                migrated: false,
                mergedIntoExistingTarget: false,
                skippedBecauseTargetExists: false,
            };
        }
        if (code === "EEXIST") {
            return {
                canonicalRootPath,
                legacyRootPath,
                migrated: false,
                mergedIntoExistingTarget: false,
                skippedBecauseTargetExists: true,
            };
        }
        throw error;
    }
    return {
        canonicalRootPath,
        legacyRootPath,
        migrated: true,
        mergedIntoExistingTarget: false,
        skippedBecauseTargetExists: false,
    };
}
export async function migrateOrganizationWorkspaceRoot(orgId) {
    const canonicalRootPath = resolveOrganizationWorkspaceRoot(orgId);
    const legacyRootPath = resolveLegacyOrganizationWorkspaceRoot(orgId);
    if (canonicalRootPath === legacyRootPath) {
        return {
            canonicalRootPath,
            legacyRootPath,
            migrated: false,
            mergedIntoExistingTarget: false,
            skippedBecauseTargetExists: false,
        };
    }
    const legacyExists = await directoryExists(legacyRootPath);
    if (!legacyExists) {
        return {
            canonicalRootPath,
            legacyRootPath,
            migrated: false,
            mergedIntoExistingTarget: false,
            skippedBecauseTargetExists: false,
        };
    }
    const canonicalExists = await directoryExists(canonicalRootPath);
    try {
        if (canonicalExists) {
            await assertCanMergeDirectoryContents(legacyRootPath, canonicalRootPath);
            await mergeDirectoryContents(legacyRootPath, canonicalRootPath);
            await fs.rmdir(legacyRootPath);
            return {
                canonicalRootPath,
                legacyRootPath,
                migrated: true,
                mergedIntoExistingTarget: true,
                skippedBecauseTargetExists: false,
            };
        }
        await fs.mkdir(path.dirname(canonicalRootPath), { recursive: true });
        await movePath(legacyRootPath, canonicalRootPath);
    }
    catch (error) {
        if (isPermissionError(error)) {
            throw new Error(formatOrganizationWorkspacePermissionMessage({
                operation: "migrate",
                code: errorCode(error),
                legacyRootPath,
                canonicalRootPath,
            }), { cause: error });
        }
        const code = errorCode(error);
        if (code === "ENOENT") {
            return {
                canonicalRootPath,
                legacyRootPath,
                migrated: false,
                mergedIntoExistingTarget: false,
                skippedBecauseTargetExists: false,
            };
        }
        if (code === "EEXIST") {
            return {
                canonicalRootPath,
                legacyRootPath,
                migrated: false,
                mergedIntoExistingTarget: false,
                skippedBecauseTargetExists: true,
            };
        }
        throw error;
    }
    return {
        canonicalRootPath,
        legacyRootPath,
        migrated: true,
        mergedIntoExistingTarget: false,
        skippedBecauseTargetExists: false,
    };
}
export async function reconcileOrganizationStorageRoots(liveOrgIds) {
    assertUniqueOrganizationStorageKeys(liveOrgIds);
    const migrations = await Promise.all(liveOrgIds.map((orgId) => migrateOrganizationStorageRoot(orgId)));
    const pruned = await pruneOrphanedOrganizationStorage(liveOrgIds);
    return { migrations, pruned };
}
export async function ensureProjectLibraryLayout(input) {
    await ensureOrganizationWorkspaceLayout(input.orgId);
    const relativePath = resolveProjectLibraryRelativePath(input);
    const root = resolveProjectLibraryDir(input);
    await fs.mkdir(root, { recursive: true });
    const readmePath = path.join(root, "README.md");
    try {
        await fs.writeFile(readmePath, [
            `# ${input.projectName?.trim() || "Project"}`,
            "",
            "Agents should keep durable project work files inside this folder.",
            "Attached Project Resources are surfaced in the Library tree under `resources/` as virtual references; external resources are not copied into this folder.",
            "",
        ].join("\n"), { flag: "wx" });
    }
    catch (error) {
        const code = typeof error === "object" && error !== null && "code" in error
            ? error.code
            : undefined;
        if (code !== "EEXIST") {
            throw error;
        }
    }
    return { root, relativePath, readmePath };
}
export async function ensureAgentWorkspaceLayout(agent) {
    await ensureOrganizationWorkspaceLayout(agent.orgId);
    const workspaceKey = resolveStoredOrDerivedAgentWorkspaceKey(agent);
    const root = resolveDefaultAgentWorkspaceDir(agent.orgId, workspaceKey);
    const instructionsDir = resolveAgentInstructionsDir(agent.orgId, workspaceKey);
    const memoryDir = resolveAgentMemoryDir(agent.orgId, workspaceKey);
    const lifeDir = resolveAgentLifeDir(agent.orgId, workspaceKey);
    const skillsDir = resolveAgentSkillsDir(agent.orgId, workspaceKey);
    await fs.mkdir(root, { recursive: true });
    await Promise.all([
        fs.mkdir(instructionsDir, { recursive: true }),
        fs.mkdir(memoryDir, { recursive: true }),
        fs.mkdir(lifeDir, { recursive: true }),
        fs.mkdir(skillsDir, { recursive: true }),
    ]);
    return {
        root,
        instructionsDir,
        memoryDir,
        lifeDir,
        skillsDir,
    };
}
function sanitizeFriendlyPathSegment(value, fallback = "_default") {
    const trimmed = value?.trim() ?? "";
    if (!trimmed)
        return fallback;
    const sanitized = trimmed
        .replace(FRIENDLY_PATH_SEGMENT_RE, "-")
        .replace(/^-+|-+$/g, "");
    return sanitized || fallback;
}
export async function removeOrganizationStorage(orgId) {
    const normalizedOrgId = validatePathSegment(orgId, "org id");
    const organizationRootPath = resolveOrganizationRoot(normalizedOrgId);
    const legacyOrganizationRootPath = resolveLegacyOrganizationRoot(normalizedOrgId);
    const workspaceRootPath = resolveOrganizationWorkspaceRoot(normalizedOrgId);
    const legacyProjectsRootPath = path.resolve(resolveRudderInstanceRoot(), "projects", normalizedOrgId);
    const removeLegacyOrganizationRoot = legacyOrganizationRootPath === organizationRootPath
        ? []
        : [fs.rm(legacyOrganizationRootPath, { recursive: true, force: true })];
    await Promise.all([
        fs.rm(organizationRootPath, { recursive: true, force: true }),
        fs.rm(workspaceRootPath, { recursive: true, force: true }),
        ...removeLegacyOrganizationRoot,
        // Best-effort cleanup for legacy pre-org-workspace managed project paths.
        fs.rm(legacyProjectsRootPath, { recursive: true, force: true }),
    ]);
    return { organizationRootPath, legacyOrganizationRootPath, workspaceRootPath, legacyProjectsRootPath };
}
async function listDirectoryNames(rootPath) {
    try {
        const entries = await fs.readdir(rootPath, { withFileTypes: true });
        return entries
            .filter((entry) => entry.isDirectory())
            .map((entry) => entry.name)
            .sort();
    }
    catch (error) {
        if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
            return [];
        }
        throw error;
    }
}
async function directoryExists(rootPath) {
    try {
        return (await fs.stat(rootPath)).isDirectory();
    }
    catch (error) {
        if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
            return false;
        }
        throw error;
    }
}
function errorCode(error) {
    const code = error?.code;
    return typeof code === "string" && code.trim().length > 0 ? code : null;
}
function isPermissionError(error) {
    const code = errorCode(error);
    return code !== null && WORKSPACE_PERMISSION_ERROR_CODES.has(code);
}
function formatOrganizationWorkspacePermissionMessage(input) {
    const codeSuffix = input.code ? ` (${input.code})` : "";
    return [
        `Rudder could not ${input.operation} the organization workspace to the Documents workspace location${codeSuffix}.`,
        `Source: ${input.legacyRootPath}.`,
        `Target: ${input.canonicalRootPath}.`,
        "This usually means the target folder is not writable or the operating system blocked access.",
        "Grant Rudder permission to access Documents, choose a writable folder with RUDDER_ORGANIZATION_WORKSPACE_HOME, or on Windows try running Rudder as administrator if the folder policy requires it.",
    ].join(" ");
}
async function lstatIfExists(targetPath) {
    try {
        return await fs.lstat(targetPath);
    }
    catch (error) {
        if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
            return null;
        }
        throw error;
    }
}
async function movePath(sourcePath, targetPath) {
    try {
        await fs.rename(sourcePath, targetPath);
        return;
    }
    catch (error) {
        if (errorCode(error) !== "EXDEV")
            throw error;
    }
    await fs.cp(sourcePath, targetPath, {
        recursive: true,
        errorOnExist: true,
        force: false,
        preserveTimestamps: true,
    });
    await fs.rm(sourcePath, { recursive: true, force: false });
}
async function mergeDirectoryContents(sourceRoot, targetRoot) {
    await fs.mkdir(targetRoot, { recursive: true });
    const entries = await fs.readdir(sourceRoot, { withFileTypes: true });
    for (const entry of entries) {
        const sourcePath = path.join(sourceRoot, entry.name);
        const targetPath = path.join(targetRoot, entry.name);
        const targetStat = await lstatIfExists(targetPath);
        if (!targetStat) {
            await movePath(sourcePath, targetPath);
            continue;
        }
        if (entry.isDirectory() && targetStat.isDirectory()) {
            await mergeDirectoryContents(sourcePath, targetPath);
            await fs.rmdir(sourcePath);
            continue;
        }
        throw new Error(`Cannot migrate organization storage root because '${targetPath}' already exists.`);
    }
}
async function assertCanMergeDirectoryContents(sourceRoot, targetRoot) {
    const entries = await fs.readdir(sourceRoot, { withFileTypes: true });
    for (const entry of entries) {
        const sourcePath = path.join(sourceRoot, entry.name);
        const targetPath = path.join(targetRoot, entry.name);
        const targetStat = await lstatIfExists(targetPath);
        if (!targetStat)
            continue;
        if (entry.isDirectory() && targetStat.isDirectory()) {
            await assertCanMergeDirectoryContents(sourcePath, targetPath);
            continue;
        }
        throw new Error(`Cannot migrate organization storage root because '${targetPath}' already exists.`);
    }
}
export async function pruneOrphanedOrganizationStorage(liveOrgIds) {
    assertUniqueOrganizationStorageKeys(liveOrgIds);
    const liveOrgIdSet = new Set(liveOrgIds.flatMap((orgId) => [
        resolveOrganizationStorageKey(orgId),
        resolveOrganizationLegacyStorageKey(orgId),
    ]));
    const organizationRoot = path.resolve(resolveRudderInstanceRoot(), "organizations");
    const legacyProjectsRoot = path.resolve(resolveRudderInstanceRoot(), "projects");
    const organizationDirNames = await listDirectoryNames(organizationRoot);
    const legacyProjectDirNames = await listDirectoryNames(legacyProjectsRoot);
    const legacyProjectsRootExists = await directoryExists(legacyProjectsRoot);
    const removedOrganizationDirNames = organizationDirNames.filter((dirName) => !liveOrgIdSet.has(dirName));
    const removedLegacyProjectDirNames = legacyProjectDirNames;
    await Promise.all([
        ...removedOrganizationDirNames.map((dirName) => fs.rm(path.resolve(organizationRoot, dirName), { recursive: true, force: true })),
        ...(legacyProjectsRootExists
            ? [fs.rm(legacyProjectsRoot, { recursive: true, force: true })]
            : []),
    ]);
    return {
        removedOrganizationDirNames,
        removedWorkspaceDirNames: [],
        removedLegacyProjectDirNames,
        removedLegacyProjectsRoot: legacyProjectsRootExists,
    };
}
export function resolveHomeAwarePath(value) {
    return path.resolve(expandHomePrefix(value));
}
//# sourceMappingURL=home-paths.js.map