import * as fsCb from 'node:fs';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { messages } from '../../core/messages/index.js';
const getLog = () => messages.log;
const RAIZ = process.cwd();
const IS_TEST = (process.env.VITEST ?? '') !== '';
function safeGet(obj, key) {
    try {
        return obj[key];
    }
    catch (err) {
        getLog().debug(`Erro em safeGet ao acessar chave ${key}: ${err instanceof Error ? err.message : String(err)}`);
        return undefined;
    }
}
function assertInsideRoot(caminho) {
    if ((process.env.VITEST ?? '') !== '' || process.env.TUTANUS_ALLOW_OUTSIDE_FS === '1')
        return;
    const resolved = path.resolve(caminho);
    if (!resolved.startsWith(path.resolve(RAIZ))) {
        throw new Error(messages.ExcecoesMensagens.persistenciaNegadaForaRaizProjeto(caminho));
    }
}
function sortKeysDeep(valor) {
    if (valor === null || valor === undefined)
        return valor;
    if (Array.isArray(valor))
        return valor.map(item => sortKeysDeep(item));
    if (typeof valor === 'object') {
        const configObject = valor;
        const out = {};
        for (const chave of Object.keys(configObject).sort())
            out[chave] = sortKeysDeep(configObject[chave]);
        return out;
    }
    return valor;
}
function stableStringify(dados) {
    return JSON.stringify(sortKeysDeep(dados), null, 2);
}
export async function lerEstado(caminho, padrao) {
    try {
        const conteudo = await withFsFallback({
            promisifiedKey: 'readFile',
            callbackKey: 'readFile',
            promisifiedFn: (p, enc) => fs.readFile(p, enc),
            callbackFn: (p, enc, cb) => fsCb.readFile(p, enc, cb),
            args: [caminho, 'utf-8']
        });
        try {
            return JSON.parse(conteudo);
        }
        catch (err) {
            getLog().debug(`Erro ao parsear JSON em lerEstado (${caminho}): ${err instanceof Error ? err.message : String(err)}`);
            return padrao !== undefined ? padrao : [];
        }
    }
    catch (err) {
        getLog().debug(`Erro ao ler arquivo em lerEstado (${caminho}): ${err instanceof Error ? err.message : String(err)}`);
        return padrao !== undefined ? padrao : [];
    }
}
async function salvarEstadoImpl(caminho, dados) {
    assertInsideRoot(caminho);
    const dir = path.dirname(caminho);
    await withFsFallback({
        promisifiedKey: 'mkdir',
        callbackKey: 'mkdir',
        promisifiedFn: (p, opts) => fs.mkdir(p, opts),
        callbackFn: (p, opts, cb) => fsCb.mkdir(p, opts, cb),
        args: [dir, { recursive: true, mode: 0o700 }]
    }).catch(err => {
        getLog().debug(`Erro em mkdirSafe em salvarEstadoImpl: ${err instanceof Error ? err.message : String(err)}`);
    });
    const isString = typeof dados === 'string';
    const payload = isString ? dados : stableStringify(dados);
    const tempArquivoCaminho = path.join(dir, `.tmp-bin-${Date.now()}-${Math.random().toString(16).slice(2)}.tutanus`);
    await withFsFallback({
        promisifiedKey: 'writeFile',
        callbackKey: 'writeFile',
        promisifiedFn: (p, data, opts) => fs.writeFile(p, data, opts),
        callbackFn: (p, data, opts, cb) => fsCb.writeFile(p, data, opts, cb),
        args: [tempArquivoCaminho, payload, { encoding: 'utf-8', mode: 0o600 }]
    });
    await withFsFallback({
        promisifiedKey: 'rename',
        callbackKey: 'rename',
        promisifiedFn: (oldP, newP) => fs.rename(oldP, newP),
        callbackFn: (oldP, newP, cb) => fsCb.rename(oldP, newP, cb),
        args: [tempArquivoCaminho, caminho]
    });
}
function wrapVitestSpy(fn) {
    try {
        const maybeVi = globalThis.vi;
        if (IS_TEST && maybeVi && typeof maybeVi.fn === 'function') {
            return maybeVi.fn(fn);
        }
    }
    catch (err) {
        getLog().debug(`Erro ao configurar spy (Vitest): ${err instanceof Error ? err.message : String(err)}`);
    }
    return fn;
}
export let salvarEstado = salvarEstadoImpl;
salvarEstado = wrapVitestSpy(salvarEstadoImpl);
export async function lerArquivoTexto(caminho) {
    try {
        return await withFsFallback({
            promisifiedKey: 'readFile',
            callbackKey: 'readFile',
            promisifiedFn: (p, enc) => fs.readFile(p, enc),
            callbackFn: (p, enc, cb) => fsCb.readFile(p, enc, cb),
            args: [caminho, 'utf-8']
        });
    }
    catch (err) {
        getLog().debug(`Erro em lerArquivoTexto (${caminho}): ${err instanceof Error ? err.message : String(err)}`);
        return '';
    }
}
export async function salvarEstadoAtomico(caminho, dados) {
    assertInsideRoot(caminho);
    const dir = path.dirname(caminho);
    await withFsFallback({
        promisifiedKey: 'mkdir',
        callbackKey: 'mkdir',
        promisifiedFn: (p, opts) => fs.mkdir(p, opts),
        callbackFn: (p, opts, cb) => fsCb.mkdir(p, opts, cb),
        args: [dir, { recursive: true, mode: 0o700 }]
    });
    const tempArquivoCaminho = path.join(dir, `.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}.json`);
    const payload = stableStringify(dados);
    await withFsFallback({
        promisifiedKey: 'writeFile',
        callbackKey: 'writeFile',
        promisifiedFn: (p, data, opts) => fs.writeFile(p, data, opts),
        callbackFn: (p, data, opts, cb) => fsCb.writeFile(p, data, opts, cb),
        args: [tempArquivoCaminho, payload, { encoding: 'utf-8', mode: 0o600 }]
    });
    await withFsFallback({
        promisifiedKey: 'rename',
        callbackKey: 'rename',
        promisifiedFn: (oldP, newP) => fs.rename(oldP, newP),
        callbackFn: (oldP, newP, cb) => fsCb.rename(oldP, newP, cb),
        args: [tempArquivoCaminho, caminho]
    });
}
export async function salvarBinarioAtomico(caminho, dados) {
    assertInsideRoot(caminho);
    const dir = path.dirname(caminho);
    await withFsFallback({
        promisifiedKey: 'mkdir',
        callbackKey: 'mkdir',
        promisifiedFn: (p, opts) => fs.mkdir(p, opts),
        callbackFn: (p, opts, cb) => fsCb.mkdir(p, opts, cb),
        args: [dir, { recursive: true, mode: 0o700 }]
    });
    const temporaryValor = path.join(dir, `.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}.bin`);
    await withFsFallback({
        promisifiedKey: 'writeFile',
        callbackKey: 'writeFile',
        promisifiedFn: (p, data, opts) => fs.writeFile(p, data, opts),
        callbackFn: (p, data, opts, cb) => fsCb.writeFile(p, data, opts, cb),
        args: [temporaryValor, dados]
    });
    await withFsFallback({
        promisifiedKey: 'rename',
        callbackKey: 'rename',
        promisifiedFn: (oldP, newP) => fs.rename(oldP, newP),
        callbackFn: (oldP, newP, cb) => fsCb.rename(oldP, newP, cb),
        args: [temporaryValor, caminho]
    });
}
export let salvarBinario = salvarBinarioAtomico;
salvarBinario = wrapVitestSpy(salvarBinarioAtomico);
async function withFsFallback(config) {
    const { promisifiedKey, callbackKey, promisifiedFn, callbackFn, args } = config;
    const promisifiedExists = safeGet(fs, promisifiedKey);
    if (typeof promisifiedExists === 'function') {
        return await promisifiedFn(...args);
    }
    const cbExists = safeGet(fsCb, callbackKey);
    if (typeof cbExists === 'function') {
        return await new Promise((resolve, reject) => {
            callbackFn(...args, (err, result) => {
                if (err)
                    reject(err);
                else
                    resolve(result);
            });
        });
    }
    if (IS_TEST)
        return undefined;
    const errKey = `fs${promisifiedKey.charAt(0).toUpperCase() + promisifiedKey.slice(1)}Indisponivel`;
    const errMsg = messages.ExcecoesMensagens[errKey] || `fs.${promisifiedKey} indisponível`;
    throw new Error(errMsg);
}
//# sourceMappingURL=persistence.js.map