import neo4j from "neo4j-driver";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
let driver = null;
function readPassword() {
    if (process.env.NEO4J_PASSWORD)
        return process.env.NEO4J_PASSWORD;
    const passwordFile = resolve(process.env.PLATFORM_ROOT ?? resolve(import.meta.dirname, "../../../.."), "config/.neo4j-password");
    try {
        return readFileSync(passwordFile, "utf-8").trim();
    }
    catch {
        throw new Error(`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`);
    }
}
export function getDriver() {
    if (!driver) {
        // NEO4J_URI must be explicit. Silent default to:7687 was the
        // vector for the orphan data in Maxy's DB from a realagent install.
        const uri = process.env.NEO4J_URI;
        if (!uri) {
            throw new Error("[scheduling] NEO4J_URI unset — refusing to default to bolt://localhost:7687");
        }
        const user = process.env.NEO4J_USER ?? "neo4j";
        const password = readPassword();
        console.error(`[scheduling] resolved neo4j_uri=${uri}`);
        driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
    }
    return driver;
}
export function getSession() {
    return getDriver().session();
}
export async function closeDriver() {
    if (driver) {
        await driver.close();
        driver = null;
    }
}
/**
 * Thrown by getUserTimezone when called without a userId. Caller-bug class —
 * surfaces a missing wire-up rather than letting the read silently widen to
 * an account-wide query that would target a non-deterministic UserProfile row.
 */
export class MissingUserIdError extends Error {
    constructor(message) {
        super(message);
        this.name = "MissingUserIdError";
    }
}
/**
 * Fetches the user's IANA timezone from their UserProfile node.
 *
 * UserProfile read doctrine: single-row consumers key reads on
 * (accountId, userId). AccountId-only reads are forbidden here because
 * enforces (accountId, userId) uniqueness — a bare-accountId read
 * would target a non-deterministic row and (in multi-row state) point the
 * caller at a remediation that writes to a different row than the read.
 *
 * Throws MissingUserIdError when userId is empty — caller forgot to thread
 * the live userId from the MCP session.
 * Throws Error when no UserProfile exists for the (accountId, userId) pair.
 * Throws Error when the row exists but timezone is unset — message names
 * the exact UserProfile so the operator can target the same row with
 * profile-update.
 */
export async function getUserTimezone(accountId, userId) {
    if (!userId || userId.trim().length === 0) {
        console.error(`[scheduling] getUserTimezone-missing-userid caller=getUserTimezone accountId=${accountId.slice(0, 8)}`);
        throw new MissingUserIdError(`getUserTimezone called without a userId — UserProfile reads must be keyed by (accountId, userId) doctrine`);
    }
    const session = getSession();
    try {
        const result = await session.run(`MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
       RETURN up.timezone AS timezone`, { accountId, userId });
        if (result.records.length === 0) {
            throw new Error(`No UserProfile for userId ${userId.slice(0, 8)}… (account ${accountId.slice(0, 8)}…) — cannot format locale times`);
        }
        const timezone = result.records[0].get("timezone");
        if (!timezone || timezone.trim().length === 0) {
            throw new Error(`No timezone on UserProfile for userId ${userId.slice(0, 8)}… (account ${accountId.slice(0, 8)}…) — set via profile-update with profileFields: { timezone: 'IANA_TZ' }`);
        }
        return timezone;
    }
    finally {
        await session.close();
    }
}
//# sourceMappingURL=neo4j.js.map