/**
 * Path encoding utility for converting working directory paths to filesystem-safe names
 * Handles cross-platform directory name encoding for project-based session organization
 */
import { resolve, join } from "path";
import { createHash } from "crypto";
import { realpath, mkdir } from "fs/promises";
import { homedir, platform } from "os";
/**
 * PathEncoder class for converting working directory paths to filesystem-safe names
 */
export class PathEncoder {
    constructor(options = {}) {
        this.options = {
            maxLength: options.maxLength ?? 200,
            pathSeparatorReplacement: options.pathSeparatorReplacement ?? "-",
            spaceReplacement: options.spaceReplacement ?? "_",
            invalidCharReplacement: options.invalidCharReplacement ?? "_",
            hashLength: options.hashLength ?? 8,
        };
        this.constraints = this.getFilesystemConstraints();
    }
    /**
     * Encode a working directory path to a filesystem-safe directory name
     */
    async encode(originalPath) {
        // Resolve symbolic links and normalize path
        const resolvedPath = await this.resolvePath(originalPath);
        return this.encodeSync(resolvedPath);
    }
    /**
     * Synchronously encode a path to a filesystem-safe directory name
     * Note: Does not resolve symbolic links - use encode() for full path resolution
     */
    encodeSync(pathToEncode) {
        // Convert to safe directory name
        let encoded = pathToEncode;
        // Remove leading slash to avoid empty directory names
        if (encoded.startsWith("/")) {
            encoded = encoded.substring(1);
        }
        // Replace path separators with hyphens
        encoded = encoded.replace(/[/\\]/g, this.options.pathSeparatorReplacement);
        // Replace spaces with underscores
        encoded = encoded.replace(/\s+/g, this.options.spaceReplacement);
        // Replace invalid characters with underscores
        const escapedChars = this.constraints.invalidCharacters
            .map((c) => `\\${c}`)
            .join("");
        const invalidChars = new RegExp(`[${escapedChars}]`, "g");
        encoded = encoded.replace(invalidChars, this.options.invalidCharReplacement);
        // Case is preserved
        // Handle length limit with hash
        if (encoded.length > this.options.maxLength) {
            const hash = this.generateHash(pathToEncode, this.options.hashLength);
            const maxBaseLength = this.options.maxLength - this.options.hashLength - 1; // -1 for separator
            encoded = `${encoded.substring(0, maxBaseLength)}-${hash}`;
        }
        return encoded;
    }
    /**
     * Decode an encoded directory name back to original path (limited functionality)
     * Note: This is best-effort as encoding is lossy
     */
    async decode(encodedName) {
        return this.decodeSync(encodedName);
    }
    /**
     * Synchronously decode an encoded directory name back to original path (limited functionality)
     * Note: This is best-effort as encoding is lossy
     */
    decodeSync(encodedName) {
        // This is a simplified version - full reversal is not always possible
        // due to lossy encoding (case changes, character replacements, hashing)
        // Check if this has a hash suffix
        const hashPattern = new RegExp(`-[a-f0-9]{${this.options.hashLength}}$`);
        if (hashPattern.test(encodedName)) {
            // Cannot reliably decode hashed paths
            return null;
        }
        // Attempt basic reversal
        let decoded = encodedName;
        // Reverse path separator replacement
        decoded = decoded.replace(new RegExp(this.options.pathSeparatorReplacement, "g"), "/");
        // Reverse space replacement
        decoded = decoded.replace(new RegExp(this.options.spaceReplacement, "g"), " ");
        // Add leading slash
        decoded = `/${decoded}`;
        return decoded;
    }
    /**
     * Resolve symbolic links and normalize path before encoding
     */
    async resolvePath(path) {
        try {
            // Expand tilde to home directory
            const expandedPath = this.expandTilde(path);
            // Resolve to absolute path
            const absolutePath = resolve(expandedPath);
            // Resolve symbolic links
            const resolvedPath = await realpath(absolutePath);
            return resolvedPath;
        }
        catch (error) {
            throw new Error(`Failed to resolve path "${path}": ${error}`);
        }
    }
    /**
     * Get project directory info without creating the directory
     */
    async getProjectDirectory(originalPath, baseSessionDir) {
        // Resolve the original path and check for symbolic links
        const expandedPath = this.expandTilde(originalPath);
        const absolutePath = resolve(expandedPath);
        let resolvedPath;
        let isSymbolicLink = false;
        try {
            resolvedPath = await realpath(absolutePath);
            isSymbolicLink = resolvedPath !== absolutePath;
        }
        catch {
            // If realpath fails, use the absolute path
            resolvedPath = absolutePath;
        }
        // Encode the resolved path
        const encodedName = await this.encode(resolvedPath);
        const encodedPath = join(baseSessionDir, encodedName);
        // Generate hash if encoding resulted in truncation
        let pathHash;
        if (resolvedPath.length > this.options.maxLength) {
            pathHash = this.generateHash(resolvedPath, this.options.hashLength);
        }
        return {
            originalPath: resolvedPath,
            encodedName,
            encodedPath,
            pathHash,
            isSymbolicLink,
        };
    }
    /**
     * Create project directory entity from original path
     */
    async createProjectDirectory(originalPath, baseSessionDir) {
        const projectDirectory = await this.getProjectDirectory(originalPath, baseSessionDir);
        // Ensure the encoded directory exists
        try {
            await mkdir(projectDirectory.encodedPath, { recursive: true });
        }
        catch {
            // Ignore errors if directory already exists
        }
        return projectDirectory;
    }
    /**
     * Validate that an encoded name is filesystem-safe
     */
    validateEncodedName(encodedName) {
        // Check length
        if (encodedName.length > this.constraints.maxDirectoryNameLength) {
            return false;
        }
        // Check for invalid characters
        for (const char of this.constraints.invalidCharacters) {
            if (encodedName.includes(char)) {
                return false;
            }
        }
        // Check for reserved names
        const lowerName = encodedName.toLowerCase();
        if (this.constraints.reservedNames.some((reserved) => reserved.toLowerCase() === lowerName)) {
            return false;
        }
        // Check for empty or dots-only names
        if (!encodedName.trim() || /^\.+$/.test(encodedName)) {
            return false;
        }
        return true;
    }
    /**
     * Handle encoding collisions by generating unique names
     */
    resolveCollision(baseName, existingNames) {
        if (!existingNames.has(baseName)) {
            return baseName;
        }
        // Try numbered suffixes first
        for (let i = 1; i <= 999; i++) {
            const candidate = `${baseName}-${i}`;
            if (!existingNames.has(candidate)) {
                return candidate;
            }
        }
        // If all numbered suffixes are taken, use hash
        const hash = this.generateHash(baseName + Date.now(), this.options.hashLength);
        return `${baseName}-${hash}`;
    }
    /**
     * Get platform-specific filesystem constraints
     */
    getFilesystemConstraints() {
        const currentPlatform = platform();
        switch (currentPlatform) {
            case "win32":
                return {
                    maxDirectoryNameLength: 255,
                    maxPathLength: 260,
                    invalidCharacters: ["<", ">", ":", '"', "|", "?", "*"],
                    reservedNames: [
                        "CON",
                        "PRN",
                        "AUX",
                        "NUL",
                        "COM1",
                        "COM2",
                        "COM3",
                        "COM4",
                        "COM5",
                        "COM6",
                        "COM7",
                        "COM8",
                        "COM9",
                        "LPT1",
                        "LPT2",
                        "LPT3",
                        "LPT4",
                        "LPT5",
                        "LPT6",
                        "LPT7",
                        "LPT8",
                        "LPT9",
                    ],
                    caseSensitive: false,
                };
            case "darwin":
                return {
                    maxDirectoryNameLength: 255,
                    maxPathLength: 1024,
                    invalidCharacters: [":"],
                    reservedNames: [],
                    caseSensitive: false, // HFS+ is case-insensitive by default
                };
            default: // Linux and other Unix-like systems
                return {
                    maxDirectoryNameLength: 255,
                    maxPathLength: 4096,
                    invalidCharacters: ["\0"],
                    reservedNames: [],
                    caseSensitive: true,
                };
        }
    }
    /**
     * Generate hash for collision resolution
     */
    generateHash(input, length) {
        return createHash("sha256")
            .update(input)
            .digest("hex")
            .substring(0, length);
    }
    /**
     * Expand tilde (~) to home directory
     */
    expandTilde(path) {
        if (path.startsWith("~/") || path === "~") {
            return path.replace(/^~/, homedir());
        }
        return path;
    }
}
/**
 * Default PathEncoder instance
 */
export const pathEncoder = new PathEncoder();
