/**
 * Vigthoria CLI — shared local-state helpers.
 *
 * Single source for:
 *   • Offline-mode detection (VIGTHORIA_OFFLINE=1) — suppresses every
 *     background network call (update checks, gateway preflights, etc.).
 *   • Telemetry opt-out (VIGTHORIA_TELEMETRY_DISABLED=1).
 *   • Update-check cache, so `npm view` is not invoked on every CLI start.
 *   • Cross-platform safe file mode enforcement for tokens / sessions.
 *
 * No external network calls — pure local helpers.  Safe to import from
 * any command module.
 */
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
const STATE_DIR = path.join(os.homedir(), '.vigthoria');
const STATE_FILE = path.join(STATE_DIR, 'cli-state.json');
/** 24h is plenty for an update-check ping. */
export const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
/** 60s gateway-preflight cache so back-to-back CLI invocations stay fast. */
export const GATEWAY_PREFLIGHT_TTL_MS = 60 * 1000;
function readStateUnchecked() {
    try {
        if (!fs.existsSync(STATE_FILE)) {
            return {};
        }
        const text = fs.readFileSync(STATE_FILE, 'utf8');
        if (!text.trim()) {
            return {};
        }
        const parsed = JSON.parse(text);
        return parsed && typeof parsed === 'object' ? parsed : {};
    }
    catch {
        // Corrupt state file should never crash the CLI — treat as empty.
        return {};
    }
}
export function ensureStateDir() {
    try {
        if (!fs.existsSync(STATE_DIR)) {
            fs.mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
        }
        if (process.platform !== 'win32') {
            try {
                fs.chmodSync(STATE_DIR, 0o700);
            }
            catch { /* best-effort */ }
        }
    }
    catch {
        // Best-effort — caller will see write failure later if it matters.
    }
}
/** Best-effort lock-down: `0o600` on POSIX, no-op on Windows (NTFS ACL ≠ chmod). */
export function secureFileMode(filePath) {
    if (process.platform === 'win32') {
        return;
    }
    try {
        fs.chmodSync(filePath, 0o600);
    }
    catch {
        // File may not exist yet, or filesystem may not support chmod.
    }
}
/** Atomic write helper — write to temp then rename, never half-written JSON. */
export function atomicWriteJson(targetPath, value, mode = 0o600) {
    ensureStateDir();
    const parent = path.dirname(targetPath);
    try {
        if (!fs.existsSync(parent))
            fs.mkdirSync(parent, { recursive: true });
    }
    catch { /* ignore */ }
    const tmpPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
    fs.writeFileSync(tmpPath, JSON.stringify(value, null, 2), 'utf8');
    if (process.platform !== 'win32') {
        try {
            fs.chmodSync(tmpPath, mode);
        }
        catch { /* ignore */ }
    }
    fs.renameSync(tmpPath, targetPath);
}
export function loadCliState() {
    return readStateUnchecked();
}
export function saveCliState(state) {
    atomicWriteJson(STATE_FILE, state);
}
export function getCliStateFile() {
    return STATE_FILE;
}
/**
 * Offline mode — set VIGTHORIA_OFFLINE=1 to skip every non-essential
 * outbound HTTP call. Useful for restricted networks, air-gapped runs,
 * and deterministic CI.
 */
export function isOfflineMode() {
    const flag = String(process.env.VIGTHORIA_OFFLINE || '').trim().toLowerCase();
    return flag === '1' || flag === 'true' || flag === 'yes';
}
/** Telemetry opt-out (also implied when offline mode is on). */
export function isTelemetryDisabled() {
    if (isOfflineMode())
        return true;
    const flag = String(process.env.VIGTHORIA_TELEMETRY_DISABLED || '').trim().toLowerCase();
    return flag === '1' || flag === 'true' || flag === 'yes';
}
/** When true the startup update check is suppressed (env override or offline). */
export function isUpdateCheckSuppressed() {
    if (isOfflineMode())
        return true;
    const flag = String(process.env.VIGTHORIA_NO_UPDATE_CHECK || '').trim();
    return flag === '1';
}
/** Cached read for the latest known version — null when stale or absent. */
export function readCachedLatestVersion(maxAgeMs = UPDATE_CHECK_TTL_MS) {
    const state = readStateUnchecked();
    const entry = state.updateCheck;
    if (!entry || !entry.latestVersion || typeof entry.checkedAt !== 'number') {
        return null;
    }
    if (Date.now() - entry.checkedAt > maxAgeMs) {
        return null;
    }
    return entry.latestVersion;
}
export function writeCachedLatestVersion(latestVersion, notifiedFor) {
    try {
        const state = readStateUnchecked();
        state.updateCheck = {
            checkedAt: Date.now(),
            latestVersion,
            notifiedFor: notifiedFor || state.updateCheck?.notifiedFor,
        };
        saveCliState(state);
    }
    catch {
        // Caching is a nice-to-have, never block CLI on write failure.
    }
}
export function readGatewayPreflightCache(maxAgeMs = GATEWAY_PREFLIGHT_TTL_MS) {
    const state = readStateUnchecked();
    const entry = state.gatewayPreflight;
    if (!entry || typeof entry.checkedAt !== 'number' || typeof entry.valid !== 'boolean') {
        return null;
    }
    if (Date.now() - entry.checkedAt > maxAgeMs) {
        return null;
    }
    return entry.valid;
}
export function writeGatewayPreflightCache(valid) {
    try {
        const state = readStateUnchecked();
        state.gatewayPreflight = { checkedAt: Date.now(), valid };
        saveCliState(state);
    }
    catch {
        // Best-effort.
    }
}
/**
 * Validate an npm package spec used by `vigthoria update --from <spec>`.
 * Returns `true` when the spec is acceptable.  Rejects whitespace and
 * shell metacharacters so the spec can never break out of `execFile` /
 * a Windows `shell:true` fallback into command injection.
 */
const NPM_SPEC_ALLOWLIST = /^[A-Za-z0-9_./@:+\\=-]+$/;
const DISALLOWED_SHELL_CHARS = /[;&|`$<>(){}\[\]"'\s]/;
export function isSafeNpmPackageSpec(spec) {
    const candidate = String(spec || '').trim();
    if (!candidate)
        return false;
    if (candidate.length > 512)
        return false;
    if (DISALLOWED_SHELL_CHARS.test(candidate))
        return false;
    if (!NPM_SPEC_ALLOWLIST.test(candidate))
        return false;
    return true;
}
