/**
 * Persistent record of a registered machine.
 *
 * Lives at `<configDir>/machine.json` (mode 0600). Created the first time
 * `spectral serve` runs after login; reused across restarts so a single
 * developer machine appears as one logical agent in the backend's machine
 * list, regardless of how many times the CLI process restarts.
 *
 * Why a separate file from `config.json`?
 *  - `config.json` carries the team API key (rotated on logout). The
 *    machine identity outlives logout/login: if you `logout` then
 *    `login` with the same team, your machine should keep its `machineId`
 *    so the backend's audit log stays continuous.
 *  - The machine JWT is short-lived (decoded `exp` checked by
 *    `registration.ts`). Splitting it out keeps the auth file minimal and
 *    avoids any risk of accidentally serializing the JWT into a
 *    `spectral login` log line.
 *
 * Schema is intentionally minimal — Batch 3 will add capability flags;
 * unknown fields are preserved on round-trip via `extra` so older CLIs
 * don't strip data the backend later cares about.
 */
import { mkdirSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { getConfigDir } from "../config.js";
const KNOWN_KEYS = new Set([
    "machineId",
    "machineName",
    "machineJwt",
    "teamId",
    "ownerId",
    "visibility",
    "registeredAt",
    "hostname",
    "version",
]);
export function getMachineFile() {
    return join(getConfigDir(), "machine.json");
}
/**
 * Read the persisted machine record. Returns `null` when the file is
 * missing or malformed — callers treat both as "needs registration".
 *
 * We intentionally do NOT throw on parse errors: a corrupted machine.json
 * should self-heal on the next `spectral serve` (re-register, overwrite),
 * not block startup forever.
 */
export async function loadMachine() {
    let raw;
    try {
        raw = await readFile(getMachineFile(), "utf8");
    }
    catch {
        return null;
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch {
        return null;
    }
    if (typeof parsed.machineId !== "string" ||
        typeof parsed.machineName !== "string" ||
        typeof parsed.machineJwt !== "string" ||
        typeof parsed.registeredAt !== "number" ||
        typeof parsed.hostname !== "string" ||
        typeof parsed.version !== "string") {
        return null;
    }
    const extra = {};
    for (const [k, v] of Object.entries(parsed)) {
        if (!KNOWN_KEYS.has(k))
            extra[k] = v;
    }
    return {
        machineId: parsed.machineId,
        machineName: parsed.machineName,
        machineJwt: parsed.machineJwt,
        teamId: typeof parsed.teamId === "string" ? parsed.teamId : undefined,
        ownerId: typeof parsed.ownerId === "string" ? parsed.ownerId : undefined,
        visibility: typeof parsed.visibility === "string" ? parsed.visibility : undefined,
        registeredAt: parsed.registeredAt,
        hostname: parsed.hostname,
        version: parsed.version,
        extra: Object.keys(extra).length > 0 ? extra : undefined,
    };
}
/**
 * Write the machine record with restrictive permissions. Creates the
 * config directory if missing (matches `writeConfig` in `config.ts`).
 *
 * Atomicity: we do NOT write-then-rename. The record is small, the file
 * is single-writer (one `spectral serve` at a time per `$SPECTRAL_CONFIG_DIR`),
 * and a torn write self-heals via `loadMachine` returning null + re-register.
 */
export async function saveMachine(rec) {
    const file = getMachineFile();
    mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
    // Reassemble: known fields in stable order, then any forward-compat keys.
    const toWrite = {
        machineId: rec.machineId,
        machineName: rec.machineName,
        machineJwt: rec.machineJwt,
        registeredAt: rec.registeredAt,
        hostname: rec.hostname,
        version: rec.version,
    };
    if (rec.teamId !== undefined)
        toWrite.teamId = rec.teamId;
    if (rec.ownerId !== undefined)
        toWrite.ownerId = rec.ownerId;
    if (rec.visibility !== undefined)
        toWrite.visibility = rec.visibility;
    if (rec.extra) {
        for (const [k, v] of Object.entries(rec.extra))
            toWrite[k] = v;
    }
    await writeFile(file, JSON.stringify(toWrite, null, 2) + "\n", {
        mode: 0o600,
        encoding: "utf8",
    });
}
