/**
 * Schema validator — validates memory-write inputs against the LoadedSchema
 * produced by schema-loader.ts. Pure function, no I/O, no side effects.
 *
 * Checks run in order; first failure throws with a descriptive message.
 * Every error names the specific violation and the source file that defined
 * the rule so the agent can self-correct on retry.
 *
 *   1. accountId: non-empty trimmed string
 *   2. label: at least one input label is recognised in the loaded schema
 *   3. synonyms: no property key appears in the wrongToCorrect map — this
 *      runs BEFORE required-props so that a `firstName` typo is reported as
 *      "rename firstName → givenName" (actionable) instead of
 *      "missing required property givenName" (leaves the agent to figure
 *      out why their write was incomplete).
 *   4. required properties: for the primary (first recognised) label, every
 *      required group is satisfied — simple groups by presence, any-of
 *      groups by at least one disjunct present
 *
 * "Present" means the properties object has the key and the value is not
 * null, not undefined, and not an empty string after trimming. `0`, `false`,
 * and empty arrays are considered present — those are legitimate values the
 * agent may want to persist.
 */
/**
 * Validate a write against the loaded schema. Throws on the first violation.
 * The caller is responsible for logging the error before re-throwing — this
 * function deliberately does not write to stderr so it stays a pure unit.
 */
export function validateWrite(input, schema) {
    const { labels, properties, accountId } = input;
    // ---------------------------------------------------------------------
    // 1. accountId non-empty
    // ---------------------------------------------------------------------
    if (typeof accountId !== "string" || accountId.trim().length === 0) {
        throw new Error("memory-write: accountId is required and must be a non-empty string");
    }
    // ---------------------------------------------------------------------
    // 2. At least one label is recognised
    // ---------------------------------------------------------------------
    if (!Array.isArray(labels) || labels.length === 0) {
        throw new Error("memory-write: labels is required and must contain at least one Neo4j label");
    }
    const recognisedLabels = labels.filter((l) => schema.labels.has(l));
    if (recognisedLabels.length === 0) {
        const known = Array.from(schema.labels.keys()).sort().join(", ");
        throw new Error(`memory-write: unknown Neo4j label(s) [${labels.join(", ")}]. Recognised labels loaded from schema references: ${known}`);
    }
    // The "primary" label is the first recognised one. If an input has
    // multiple recognised labels (e.g. ["Person", "Applicant"] where both are
    // defined) we take the first — the agent's stated intent comes first.
    const primaryLabelName = recognisedLabels[0];
    const primary = schema.labels.get(primaryLabelName);
    // ---------------------------------------------------------------------
    // 3. No property key is a known Schema.org synonym (wrong name)
    //
    // Runs before the required-properties check so that a `firstName` typo
    // is reported as "rename firstName → givenName" (actionable) instead of
    // "missing required property givenName" (leaves the agent guessing why
    // the write was incomplete).
    //
    // Only the agent-supplied keys are checked — `accountId` is platform-
    // injected and never a synonym candidate.
    // ---------------------------------------------------------------------
    for (const key of Object.keys(properties)) {
        const hit = schema.wrongToCorrect.get(key);
        if (hit) {
            throw new Error(`memory-write: property name '${key}' is not Schema.org — use '${hit.correct}' instead (${hit.sourceFile} property naming rules)`);
        }
    }
    // ---------------------------------------------------------------------
    // 4. Required properties for the primary label
    //
    // The evaluation uses the EFFECTIVE node properties — what memory-write
    // will actually persist to Neo4j. That's the agent-supplied `properties`
    // merged with the top-level `accountId` parameter, which memoryWrite()
    // always spreads into the node at write time. Without this merge, the
    // schema's "accountId is required" rule would reject every write because
    // `accountId` arrives as a separate top-level param, not inside
    // `properties`.
    // ---------------------------------------------------------------------
    const effectiveProperties = {
        ...properties,
        accountId,
    };
    for (const group of primary.required) {
        if (!groupIsSatisfied(group, effectiveProperties)) {
            throw new Error(`memory-write: missing required property ${formatGroup(group)} for label ${primaryLabelName} (${primary.sourceFile})`);
        }
    }
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * A value is "present" if it is defined, not null, and not an empty string
 * after trimming. Numeric zero, boolean false, and empty arrays count as
 * present — the agent may legitimately want to persist those.
 */
function isPresent(value) {
    if (value === undefined || value === null)
        return false;
    if (typeof value === "string")
        return value.trim().length > 0;
    return true;
}
function groupIsSatisfied(group, properties) {
    if (group.kind === "simple") {
        return isPresent(properties[group.name]);
    }
    // any-of
    return group.names.some((name) => isPresent(properties[name]));
}
function formatGroup(group) {
    if (group.kind === "simple")
        return `'${group.name}'`;
    return group.names.map((n) => `'${n}'`).join(" or ");
}
//# sourceMappingURL=schema-validator.js.map