/**
 * Memory schema helpers — database open/directory helpers and repo/project ID
 * resolution. Used by MemoryEngine at construction time.
 */
import Database from 'better-sqlite3';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';
import { createHash } from 'crypto';
import { execAsync } from '../../utils/exec-async.js';
import { resolveNexusStateDir } from '../runtime-registry.js';
// ─── Repo / project ID ───────────────────────────────────────────────────────
let _cachedRepoId;
let _cachedProjectId;
/** Derive a stable repoId from workspace path (sync). Git-remote-based ID warmed async via warmRepoId(). */
export function resolveCurrentRepoId() {
    if (_cachedRepoId)
        return _cachedRepoId;
    const workspaceRoot = process.env.NEXUS_WORKSPACE_ROOT?.trim();
    const cwd = workspaceRoot ? path.resolve(workspaceRoot) : process.cwd();
    _cachedRepoId = createHash('sha256').update(cwd).digest('hex').slice(0, 12);
    return _cachedRepoId;
}
/** Async warm: attempt to upgrade cwd-based repoId to git-remote hash if available. No-op if ID already stabilised. */
export async function warmRepoId() {
    const workspaceRoot = process.env.NEXUS_WORKSPACE_ROOT?.trim();
    const cwd = workspaceRoot ? path.resolve(workspaceRoot) : process.cwd();
    try {
        const { stdout } = await execAsync('git config --get remote.origin.url', { cwd });
        const remote = stdout.trim();
        if (remote) {
            _cachedRepoId = createHash('sha256').update(remote).digest('hex').slice(0, 12);
        }
    }
    catch { /* not a git repo or no remote — cwd-based fallback stands */ }
}
export function resolveCurrentProjectId() {
    if (_cachedProjectId)
        return _cachedProjectId;
    const workspaceRoot = process.env.NEXUS_WORKSPACE_ROOT?.trim();
    _cachedProjectId = createHash('sha256').update(path.resolve(workspaceRoot || process.cwd())).digest('hex').slice(0, 12);
    return _cachedProjectId;
}
// ─── DB directory / path helpers ─────────────────────────────────────────────
export function resolvePreferredStateRoot() {
    const explicit = process.env.NEXUS_STATE_DIR?.trim();
    if (explicit) {
        return explicit;
    }
    const homeStateRoot = path.join(os.homedir(), '.nexus-prime');
    try {
        ensureWritableDirectory(homeStateRoot);
        return homeStateRoot;
    }
    catch {
        return resolveNexusStateDir();
    }
}
export function isWritableStorageError(error) {
    const message = error instanceof Error ? error.message : String(error || '');
    return ['readonly', 'EACCES', 'EROFS', 'ENOENT', 'permission denied', 'SQLITE_CANTOPEN']
        .some((fragment) => message.includes(fragment));
}
export function ensureWritableDirectory(target) {
    fs.mkdirSync(target, { recursive: true });
    fs.accessSync(target, fs.constants.W_OK);
}
export function resolveFallbackStateRoot(preferredStateRoot) {
    const candidates = [
        preferredStateRoot,
        process.env.NEXUS_STATE_DIR?.trim(),
        path.join(os.tmpdir(), 'nexus-prime-state'),
        path.join(process.cwd(), '.nexus-prime-state'),
    ].filter((entry) => Boolean(entry));
    const seen = new Set();
    for (const candidate of candidates) {
        const normalized = path.resolve(candidate);
        if (seen.has(normalized)) {
            continue;
        }
        seen.add(normalized);
        try {
            ensureWritableDirectory(candidate);
            return candidate;
        }
        catch {
            // Try the next candidate.
        }
    }
    throw new Error(`No writable Nexus state root available outside ${preferredStateRoot}`);
}
export function openMemoryDatabase(requestedDbPath) {
    const preferredStateRoot = resolvePreferredStateRoot();
    const defaultDbPath = path.join(preferredStateRoot, 'memory.db');
    const requestedDbRoot = path.dirname(requestedDbPath);
    const requestedAbsolute = path.resolve(requestedDbPath);
    const defaultAbsolute = path.resolve(defaultDbPath);
    const usesCustomDbPath = requestedAbsolute !== defaultAbsolute;
    const desiredStateRoot = usesCustomDbPath ? requestedDbRoot : preferredStateRoot;
    try {
        ensureWritableDirectory(desiredStateRoot);
        ensureWritableDirectory(requestedDbRoot);
        return {
            db: new Database(requestedDbPath),
            dbPath: requestedDbPath,
            stateRoot: desiredStateRoot,
            fallbackApplied: false,
        };
    }
    catch (error) {
        if (!isWritableStorageError(error)) {
            throw error;
        }
        const fallbackReason = error instanceof Error ? error.message : String(error || 'unwritable database path');
        const fallbackStateRoot = resolveFallbackStateRoot(desiredStateRoot);
        const fallbackDbPath = path.join(fallbackStateRoot, path.basename(requestedDbPath) || 'memory.db');
        ensureWritableDirectory(fallbackStateRoot);
        console.error(`[MemoryEngine] Falling back from ${requestedDbPath} to ${fallbackDbPath}: ${fallbackReason}`);
        return {
            db: new Database(fallbackDbPath),
            dbPath: fallbackDbPath,
            stateRoot: fallbackStateRoot,
            fallbackApplied: true,
            fallbackReason,
        };
    }
}
