/**
 * Maps Neo4j node labels to schema.org types and builds JSON-LD payloads
 * suitable for inline `<script type="application/ld+json">` blocks on
 * rendered pages.
 *
 * Coverage matches Task 354 spec §1 — the seven page types that the
 * platform's typed nodes cover natively. Labels outside this set fall
 * through to a generic `schema:Thing` projection; callers should treat
 * a generic result as a doc gap, not a success.
 *
 * The mapper is deliberately strict: every emitted JSON-LD object names
 * `@context` and `@type` and carries `@id` when an `elementId` is
 * available. Missing required properties for the chosen type produce a
 * `MissingPropertyError`; callers surface this rather than emitting an
 * invalid block.
 */
export class MissingPropertyError extends Error {
    schemaType;
    missing;
    constructor(schemaType, missing) {
        super(`JSON-LD ${schemaType} requires ${missing.join(", ")} but received none`);
        this.schemaType = schemaType;
        this.missing = missing;
        this.name = "MissingPropertyError";
    }
}
/**
 * Label-to-schema.org-type resolution.
 *
 * - `LocalBusiness` keeps its own schema type (it's a schema.org subtype
 *   of `Organization`, but is more specific and SEO-relevant when set).
 * - `Article` covers the platform's narrative `CreativeWork` label when
 *   it has article-shaped fields (`headline` or `title`).
 * - `FAQPage` is recognised by the `FAQPage` label OR a `Question`
 *   collection — the caller picks.
 * - `RealEstateListing` is the schema.org subtype of `Product` used by
 *   estate-agent verticals.
 */
const LABEL_TO_SCHEMA_TYPE = {
    Organization: "Organization",
    LocalBusiness: "LocalBusiness",
    Person: "Person",
    Service: "Service",
    Product: "Product",
    CreativeWork: "Article",
    Article: "Article",
    FAQPage: "FAQPage",
    RealEstateListing: "RealEstateListing",
    Listing: "RealEstateListing",
    Event: "Event",
};
export function resolveSchemaType(label) {
    return LABEL_TO_SCHEMA_TYPE[label] ?? "Thing";
}
function readStr(v) {
    if (typeof v !== "string")
        return undefined;
    const trimmed = v.trim();
    return trimmed.length > 0 ? trimmed : undefined;
}
function readNum(v) {
    if (typeof v === "number" && Number.isFinite(v))
        return v;
    if (typeof v === "string" && v.trim().length > 0) {
        const n = Number(v);
        return Number.isFinite(n) ? n : undefined;
    }
    return undefined;
}
const TYPE_SPECS = {
    Organization: {
        required: ["name"],
        rename: { website: "url", phone: "telephone" },
    },
    LocalBusiness: {
        required: ["name"],
        rename: { website: "url", phone: "telephone" },
        enrich: (props, out) => {
            const street = readStr(props.streetAddress);
            const locality = readStr(props.addressLocality);
            const postalCode = readStr(props.postalCode);
            if (street || locality || postalCode) {
                out.address = {
                    "@type": "PostalAddress",
                    ...(street ? { streetAddress: street } : {}),
                    ...(locality ? { addressLocality: locality } : {}),
                    ...(postalCode ? { postalCode } : {}),
                };
            }
        },
    },
    Person: {
        required: ["givenName", "familyName"],
        rename: { phone: "telephone" },
        enrich: (props, out) => {
            const given = readStr(props.givenName);
            const family = readStr(props.familyName);
            if (given && family)
                out.name = `${given} ${family}`;
        },
    },
    Service: {
        required: ["name"],
        enrich: (props, out) => {
            const price = readNum(props.price);
            const currency = readStr(props.priceCurrency);
            if (price !== undefined) {
                out.offers = {
                    "@type": "Offer",
                    price,
                    ...(currency ? { priceCurrency: currency } : { priceCurrency: "GBP" }),
                };
            }
        },
    },
    Product: {
        required: ["name"],
        enrich: (props, out) => {
            const price = readNum(props.price);
            const currency = readStr(props.priceCurrency);
            if (price !== undefined) {
                out.offers = {
                    "@type": "Offer",
                    price,
                    ...(currency ? { priceCurrency: currency } : { priceCurrency: "GBP" }),
                };
            }
        },
    },
    Article: {
        required: ["title"],
        rename: { title: "headline", abstract: "description", body: "articleBody" },
    },
    FAQPage: {
        required: ["name"],
        enrich: (props, out) => {
            const questions = Array.isArray(props.questions)
                ? props.questions
                : [];
            const mainEntity = [];
            for (const q of questions) {
                const name = readStr(q.name);
                const answer = readStr(q.acceptedAnswer);
                if (!name || !answer)
                    continue;
                mainEntity.push({
                    "@type": "Question",
                    name,
                    acceptedAnswer: { "@type": "Answer", text: answer },
                });
            }
            if (mainEntity.length > 0)
                out.mainEntity = mainEntity;
        },
    },
    RealEstateListing: {
        required: ["name"],
        rename: { title: "name", abstract: "description" },
        enrich: (props, out) => {
            const price = readNum(props.price);
            const currency = readStr(props.priceCurrency);
            if (price !== undefined) {
                out.offers = {
                    "@type": "Offer",
                    price,
                    ...(currency ? { priceCurrency: currency } : { priceCurrency: "GBP" }),
                };
            }
            const street = readStr(props.streetAddress);
            const locality = readStr(props.addressLocality);
            const postalCode = readStr(props.postalCode);
            if (street || locality || postalCode) {
                out.address = {
                    "@type": "PostalAddress",
                    ...(street ? { streetAddress: street } : {}),
                    ...(locality ? { addressLocality: locality } : {}),
                    ...(postalCode ? { postalCode } : {}),
                };
            }
        },
    },
    Event: {
        required: ["name", "startDate"],
        rename: {},
    },
};
/** Plain properties always copied verbatim when present. */
const COMMON_KEYS = [
    "name",
    "description",
    "url",
    "image",
    "email",
    "telephone",
    "givenName",
    "familyName",
    "jobTitle",
    "headline",
    "articleBody",
    "datePublished",
    "dateModified",
    "startDate",
    "endDate",
    "location",
];
function applyCommon(props, out, rename = {}) {
    const renameInverse = {};
    for (const [from, to] of Object.entries(rename))
        renameInverse[from] = to;
    for (const key of COMMON_KEYS) {
        const v = props[key];
        if (typeof v === "string" || typeof v === "number") {
            out[key] = v;
        }
    }
    for (const [from, to] of Object.entries(renameInverse)) {
        const v = props[from];
        if (typeof v === "string" || typeof v === "number") {
            out[to] = v;
        }
    }
}
/**
 * Build a JSON-LD object from a typed entity. Throws
 * `MissingPropertyError` when the resolved schema.org type's required
 * properties are not satisfied.
 */
export function buildJsonLd(input) {
    const schemaType = resolveSchemaType(input.label);
    const out = {
        "@context": "https://schema.org",
        "@type": schemaType,
    };
    if (input.elementId)
        out["@id"] = input.elementId;
    if (schemaType === "Thing") {
        applyCommon(input.properties, out);
        return out;
    }
    const spec = TYPE_SPECS[schemaType];
    applyCommon(input.properties, out, spec.rename);
    spec.enrich?.(input.properties, out);
    const renamedRequired = spec.required.map((k) => spec.rename?.[k] ?? k);
    const missing = renamedRequired.filter((k) => out[k] === undefined);
    if (missing.length > 0) {
        throw new MissingPropertyError(schemaType, missing);
    }
    return out;
}
/** Render a JSON-LD object as a `<script>` block string. */
export function renderJsonLdScript(obj) {
    return `<script type="application/ld+json">\n${JSON.stringify(obj, null, 2)}\n</script>`;
}
//# sourceMappingURL=schema-mapping.js.map