// @crash-shield-ignore-file — config/cache reader for Planu-controlled JSON; writer is this codebase, shape guaranteed by build/seed.
// base-store.ts — Shared JSON read/write logic for all stores
import { readFile, mkdir } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { homedir } from 'node:os';
import { writeJsonSafe } from '../engine/safety/atomic-writer.js';
import { fastHashProjectPath } from '../engine/core-bridge.js';
/**
 * Runtime-safe JSON.parse with typed fallback.
 *
 * Guards against three common failure modes:
 *   1. Malformed JSON (syntax error) — returns fallback.
 *   2. JSON primitives (number, string, null) when an object was expected —
 *      returns fallback to avoid `as ConcreteType` lying to the type system.
 *   3. Missing fields on the parsed object — callers should still Partial-check
 *      before accessing specific fields, but the result is guaranteed to be
 *      an object reference (or the fallback), never null/number/string.
 *
 * Use at every JSON read boundary (fs, network, IPC) instead of
 * `JSON.parse(raw) as ConcreteType`. Addresses Crash Shield rule
 * JSON_PARSE_UNSAFE_CAST.
 */
export function safeJsonParse(raw, fallback) {
    try {
        const parsed = JSON.parse(raw);
        if (parsed === null || typeof parsed !== 'object') {
            return fallback;
        }
        return parsed;
    }
    catch {
        return fallback;
    }
}
/**
 * Hash a project path into a deterministic projectId using SHA-256.
 */
export function hashProjectPath(projectPath) {
    return fastHashProjectPath(projectPath).slice(0, 16);
}
/**
 * Ensure the parent directory for a file path exists.
 */
async function ensureDir(filePath) {
    await mkdir(dirname(filePath), { recursive: true });
}
/**
 * Read a JSON file from disk. Returns the parsed contents, or the provided
 * default value when the file does not exist.
 */
export async function readJson(filePath, defaultValue) {
    try {
        const raw = await readFile(filePath, 'utf-8');
        return safeJsonParse(raw, defaultValue);
    }
    catch (err) {
        if (isNodeError(err) && err.code === 'ENOENT') {
            return defaultValue;
        }
        throw err;
    }
}
/**
 * Write a value to a JSON file, creating parent directories as needed.
 */
export async function writeJson(filePath, data) {
    await ensureDir(filePath);
    try {
        await writeJsonSafe(filePath, data);
    }
    catch (err) {
        const displayPath = filePath.split('/').slice(-2).join('/');
        /* v8 ignore start -- filesystem error guard */
        if (isNodeError(err) && (err.code === 'ENOSPC' || err.code === 'EACCES')) {
            console.warn(`[Planu] Cannot write ${filePath}: ${err.code}`);
        }
        /* v8 ignore stop */
        throw new Error(`[Planu] Failed to write ${displayPath}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
    }
}
/**
 * Resolve the data directory for a given project.
 * Layout: ~/.planu/data/projects/{projectId}/ (absolute, independent of cwd)
 *
 * Symmetric with globalDataDir(). Override via PLANU_PROJECT_DATA_DIR
 * environment variable (useful in tests and CI).
 *
 * Validates that projectId is a safe hex hash (no path traversal).
 * projectId is always produced by hashProjectPath(), which returns a 16-char hex string.
 */
export function projectDataDir(projectId) {
    if (!projectId ||
        projectId.includes('..') ||
        projectId.includes('/') ||
        projectId.includes('\\')) {
        throw new Error(`[Planu] Invalid projectId: "${projectId}"`);
    }
    const root = process.env.PLANU_PROJECT_DATA_DIR ?? join(homedir(), '.planu', 'data');
    return `${root}/projects/${projectId}`;
}
/**
 * Resolve the global data directory.
 * Layout: ~/.planu/data/global/ (absolute, independent of cwd)
 *
 * Using an absolute path anchored to the user's home directory ensures that
 * global state (license, usage, registered projects) persists across sessions
 * regardless of which project directory the MCP server is started from.
 *
 * Override via PLANU_GLOBAL_DATA_DIR environment variable (useful in tests
 * and CI environments where HOME is not writable).
 */
export function globalDataDir() {
    return process.env.PLANU_GLOBAL_DATA_DIR ?? join(homedir(), '.planu', 'data', 'global');
}
// --- internal helpers ---
function isNodeError(err) {
    return err instanceof Error && 'code' in err;
}
//# sourceMappingURL=base-store.js.map