"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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeJsonObjectFileAtomic = exports.parseJsonObjectText = exports.probeJsonObjectFile = exports.JsonFileConflictError = exports.JsonFileValidationError = void 0;
const fs_1 = require("fs");
const path = __importStar(require("path"));
class JsonFileValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = 'JsonFileValidationError';
    }
}
exports.JsonFileValidationError = JsonFileValidationError;
class JsonFileConflictError extends Error {
    constructor(message, mtime) {
        super(message);
        this.code = 'CONFLICT';
        this.name = 'JsonFileConflictError';
        this.mtime = mtime;
    }
}
exports.JsonFileConflictError = JsonFileConflictError;
function isObject(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
async function statPath(filePath) {
    try {
        return await fs_1.promises.lstat(filePath);
    }
    catch (error) {
        if (error.code === 'ENOENT') {
            return null;
        }
        throw error;
    }
}
async function probeJsonObjectFile(filePath, label, displayPath) {
    const stat = await statPath(filePath);
    if (!stat) {
        return {
            diagnostics: {
                label,
                path: displayPath,
                resolvedPath: filePath,
                exists: false,
                isSymlink: false,
                isRegularFile: false,
                sizeBytes: null,
                mtimeMs: null,
                parseError: null,
                readError: null,
            },
            json: null,
            rawText: '{}',
        };
    }
    const diagnostics = {
        label,
        path: displayPath,
        resolvedPath: filePath,
        exists: true,
        isSymlink: stat.isSymbolicLink(),
        isRegularFile: stat.isFile(),
        sizeBytes: stat.size,
        mtimeMs: stat.mtimeMs,
        parseError: null,
        readError: null,
    };
    if (diagnostics.isSymlink) {
        diagnostics.readError = 'Refusing symlink file for safety.';
        return { diagnostics, json: null, rawText: '{}' };
    }
    if (!diagnostics.isRegularFile) {
        diagnostics.readError = 'Target is not a regular file.';
        return { diagnostics, json: null, rawText: '{}' };
    }
    try {
        const rawText = await fs_1.promises.readFile(filePath, 'utf8');
        try {
            const parsed = JSON.parse(rawText);
            if (!isObject(parsed)) {
                diagnostics.parseError = 'JSON root must be an object.';
                return { diagnostics, json: null, rawText };
            }
            return { diagnostics, json: parsed, rawText };
        }
        catch (error) {
            diagnostics.parseError = error.message;
            return { diagnostics, json: null, rawText };
        }
    }
    catch (error) {
        diagnostics.readError = error.message;
        return { diagnostics, json: null, rawText: '{}' };
    }
}
exports.probeJsonObjectFile = probeJsonObjectFile;
function parseJsonObjectText(rawText, fieldName = 'rawText') {
    let parsed;
    try {
        parsed = JSON.parse(rawText);
    }
    catch (error) {
        throw new JsonFileValidationError(`Invalid JSON in ${fieldName}: ${error.message}`);
    }
    if (!isObject(parsed)) {
        throw new JsonFileValidationError(`${fieldName} JSON root must be an object.`);
    }
    return parsed;
}
exports.parseJsonObjectText = parseJsonObjectText;
async function writeJsonObjectFileAtomic(input) {
    const fileLabel = input.fileLabel || path.basename(input.filePath);
    const parsed = parseJsonObjectText(input.rawText, fileLabel);
    const targetPath = input.filePath;
    const targetDir = path.dirname(targetPath);
    const tempPath = targetPath + '.tmp';
    const dirMode = input.dirMode ?? 0o700;
    const fileMode = input.fileMode ?? 0o600;
    await fs_1.promises.mkdir(targetDir, { recursive: true, mode: dirMode });
    const targetStat = await statPath(targetPath);
    if (targetStat) {
        const stat = targetStat;
        if (stat.isSymbolicLink()) {
            throw new Error(`Refusing to write: ${fileLabel} is a symlink.`);
        }
        if (!stat.isFile()) {
            throw new Error(`Refusing to write: ${fileLabel} is not a regular file.`);
        }
        if (typeof input.expectedMtime !== 'number' || !Number.isFinite(input.expectedMtime)) {
            throw new JsonFileConflictError('File metadata not loaded. Refresh and retry.', stat.mtimeMs);
        }
        if (Math.abs(stat.mtimeMs - input.expectedMtime) > 1000) {
            throw new JsonFileConflictError('File modified externally.', stat.mtimeMs);
        }
    }
    let wroteTemp = false;
    try {
        const existingTempStat = await statPath(tempPath);
        if (existingTempStat) {
            const tempStat = existingTempStat;
            if (tempStat.isSymbolicLink()) {
                throw new Error(`Refusing to write: ${fileLabel}.tmp is a symlink.`);
            }
            if (!tempStat.isFile()) {
                throw new Error(`Refusing to write: ${fileLabel}.tmp is not a regular file.`);
            }
        }
        await fs_1.promises.writeFile(tempPath, JSON.stringify(parsed, null, 2) + '\n', { mode: fileMode });
        wroteTemp = true;
        const tempStat = await fs_1.promises.lstat(tempPath);
        if (tempStat.isSymbolicLink()) {
            throw new Error(`Refusing to write: ${fileLabel}.tmp is a symlink.`);
        }
        if (!tempStat.isFile()) {
            throw new Error(`Refusing to write: ${fileLabel}.tmp is not a regular file.`);
        }
        await fs_1.promises.rename(tempPath, targetPath);
        wroteTemp = false;
        try {
            await fs_1.promises.chmod(targetPath, fileMode);
        }
        catch {
            // Best-effort permission hardening.
        }
        const stat = await fs_1.promises.stat(targetPath);
        return { mtime: stat.mtimeMs };
    }
    finally {
        if (wroteTemp) {
            try {
                await fs_1.promises.unlink(tempPath);
            }
            catch (error) {
                if (error.code !== 'ENOENT') {
                    throw error;
                }
            }
        }
    }
}
exports.writeJsonObjectFileAtomic = writeJsonObjectFileAtomic;
//# sourceMappingURL=compatible-cli-json-file-service.js.map