import { buildNextVerification, buildVerificationMetaEntries, generateMetaTags, generateNextMetadata, renderMetaTagsHtml, renderVerificationMetaHtml, } from './meta-tags.js';
import { resolveRobotsDirectives, resolveSeoEnvironment } from './robots.js';
import { applySiteTitleTemplate } from './title-templates.js';
/**
 * Maps a collection slug to the most appropriate Schema.org type when the
 * config doesn't specify one. Conservative fallback to WebPage.
 *
 * Mirrors `@actuate-media/plugin-seo` `detectSchemaType()` but is duplicated
 * here so cms-core has no runtime dependency on the SEO plugin.
 */
const SLUG_TO_SCHEMA = {
    posts: 'BlogPosting',
    post: 'BlogPosting',
    blog: 'BlogPosting',
    articles: 'Article',
    article: 'Article',
    news: 'NewsArticle',
    pages: 'WebPage',
    page: 'WebPage',
    team: 'Person',
    staff: 'Person',
    author: 'Person',
    authors: 'Person',
    events: 'Event',
    event: 'Event',
    faq: 'FAQPage',
    faqs: 'FAQPage',
    services: 'Service',
    service: 'Service',
    jobs: 'JobPosting',
    careers: 'JobPosting',
    locations: 'LocalBusiness',
    location: 'LocalBusiness',
    testimonials: 'Review',
    testimonial: 'Review',
    products: 'Product',
    product: 'Product',
    'case-studies': 'Article',
    'case-study': 'Article',
    portfolios: 'CreativeWork',
    portfolio: 'CreativeWork',
};
function detectSchemaType(collectionSlug) {
    const normalized = collectionSlug.toLowerCase();
    if (SLUG_TO_SCHEMA[normalized])
        return SLUG_TO_SCHEMA[normalized];
    // Soft match — `blog-posts` → BlogPosting, etc.
    for (const [key, schemaType] of Object.entries(SLUG_TO_SCHEMA)) {
        if (normalized.includes(key))
            return schemaType;
    }
    return 'WebPage';
}
function trimTrailingSlash(s) {
    return s.replace(/\/+$/, '');
}
function buildDocUrl(baseUrl, urlPrefix, slug, isHome) {
    const base = trimTrailingSlash(baseUrl);
    if (isHome)
        return base || '/';
    const prefix = (urlPrefix ?? '').replace(/^\/|\/$/g, '');
    if (prefix)
        return `${base}/${prefix}/${slug}`;
    return `${base}/${slug}`;
}
function pickString(...candidates) {
    for (const c of candidates) {
        if (typeof c === 'string' && c.trim().length > 0)
            return c;
    }
    return undefined;
}
function asIsoDate(v) {
    if (!v)
        return undefined;
    if (v instanceof Date)
        return v.toISOString();
    if (typeof v === 'string') {
        const d = new Date(v);
        return isNaN(d.getTime()) ? undefined : d.toISOString();
    }
    return undefined;
}
/**
 * Compose Schema.org / JSON-LD for a document. Heuristic mapping from common
 * field names → schema fields, so default site content (title, description,
 * author, etc.) "just works" without per-collection wiring.
 */
function composeJsonLd(type, doc, meta, site) {
    const data = (doc.data ?? {});
    const page = (doc.pageSettings ?? {});
    // Doc-level override: if structuredData is already populated, use it verbatim
    // (the editor knows best). We still add @context if it's missing.
    if (doc.structuredData &&
        typeof doc.structuredData === 'object' &&
        Object.keys(doc.structuredData).length > 0) {
        const sd = doc.structuredData;
        return {
            '@context': sd['@context'] ?? 'https://schema.org',
            ...sd,
        };
    }
    const schema = {
        '@context': 'https://schema.org',
        '@type': type,
        name: meta.title,
        headline: meta.title,
        description: meta.description,
        url: meta.url,
    };
    if (meta.image)
        schema.image = meta.image;
    // Article-family types.
    if (type === 'Article' || type === 'BlogPosting' || type === 'NewsArticle') {
        const datePublished = asIsoDate(doc.publishedAt ?? data.publishedAt ?? page.publishedAt);
        const dateModified = asIsoDate(doc.updatedAt ?? data.updatedAt ?? page.updatedAt) ?? datePublished;
        if (datePublished)
            schema.datePublished = datePublished;
        if (dateModified)
            schema.dateModified = dateModified;
        const authorName = pickString(data.authorName, data.author, data.author?.name);
        if (authorName) {
            schema.author = { '@type': 'Person', name: authorName };
        }
        const orgName = site?.organization?.name ?? site?.siteName;
        if (orgName) {
            schema.publisher = {
                '@type': 'Organization',
                name: orgName,
                ...(site?.organization?.logo
                    ? { logo: { '@type': 'ImageObject', url: site.organization.logo } }
                    : {}),
            };
        }
    }
    // LocalBusiness / Service.
    if (type === 'LocalBusiness' || type === 'Service') {
        const address = data.address ?? page.address;
        if (address && typeof address === 'object') {
            schema.address = { '@type': 'PostalAddress', ...address };
        }
        const phone = pickString(data.phone, data.telephone, page.phone);
        if (phone)
            schema.telephone = phone;
        const priceRange = pickString(data.priceRange, page.priceRange);
        if (priceRange)
            schema.priceRange = priceRange;
    }
    // Strip undefined to keep JSON clean.
    for (const k of Object.keys(schema)) {
        if (schema[k] === undefined)
            delete schema[k];
    }
    return schema;
}
/**
 * Compose page meta + JSON-LD from a CMS document + collection config.
 * Pure function — safe to call in any runtime (Edge, Node, browser).
 */
export function composePageMeta(input) {
    const { doc, collection, config } = input;
    const site = config?.seo;
    const collectionSeo = collection?.seo;
    const data = (doc.data ?? {});
    const page = (doc.pageSettings ?? {});
    const siteUrl = input.siteUrl?.replace(/\/+$/, '') ?? site?.siteUrl?.replace(/\/+$/, '') ?? '';
    const siteName = site?.siteName ?? 'Site';
    const slug = (doc.slug ?? data.slug ?? '') || '';
    const isHome = slug === 'home' || slug === 'index' || slug === '';
    const url = buildDocUrl(siteUrl, collection?.urlPrefix, slug, isHome);
    const schemaType = pickString(page.schemaType, data.schemaType, collectionSeo?.defaultSchemaType) ??
        input.defaultSchemaType ??
        (collection?.slug ? detectSchemaType(collection.slug) : 'WebPage');
    // Title: an explicit per-document SEO title always wins. Otherwise apply the
    // site-wide title template (Settings → SEO "Meta Defaults"), falling back to
    // the raw document title when no template is configured.
    const explicitTitle = pickString(page.metaTitle, data.metaTitle);
    const rawTitle = pickString(data.title, page.title) ?? 'Untitled';
    // Apply the site-wide template only when one is configured, so sites that
    // never set a template keep their existing bare-title behaviour.
    const hasTitleTemplate = typeof site?.titleTemplate === 'string' && site.titleTemplate.trim() !== '';
    const title = explicitTitle
        ? explicitTitle
        : hasTitleTemplate
            ? applySiteTitleTemplate(site.titleTemplate, {
                page: rawTitle,
                site: siteName,
                type: pickString(collection?.labels?.singular, schemaType),
                author: pickString(data.authorName, data.author),
                date: asIsoDate(doc.publishedAt ?? data.publishedAt)?.slice(0, 10),
                category: pickString(data.category, data.categories?.[0]),
                tag: pickString(data.tags?.[0], data.tag),
            })
            : rawTitle;
    // Description: per-document value wins, then the site-wide default.
    const description = pickString(page.metaDescription, data.metaDescription, data.excerpt, data.description, site?.defaultMetaDescription);
    const canonical = pickString(page.canonical, data.canonical) ?? url;
    // Single source of truth for robots resolution (shared with the /seo/meta
    // render path). Honors the `robotsPolicy` select field, explicit
    // noindex/nofollow booleans, per-collection + site defaults, and the
    // non-production indexing guard so staging deploys aren't indexed.
    const robots = resolveRobotsDirectives({
        pageData: {
            robotsPolicy: page.robotsPolicy ?? data.robotsPolicy,
            noIndex: page.noIndex ?? data.noIndex ?? collectionSeo?.defaultRobots?.noIndex,
            noFollow: page.noFollow ?? data.noFollow ?? collectionSeo?.defaultRobots?.noFollow,
        },
        settings: {
            defaultNoIndex: site?.robots?.defaultNoIndex,
            defaultNoFollow: site?.robots?.defaultNoFollow,
            noIndexNonProduction: site?.robots?.noIndexNonProduction,
        },
        environment: resolveSeoEnvironment(input.environment),
    });
    const ogImageUrl = pickString(page.ogImage, data.ogImage, site?.defaultOgImage) ??
        // The dynamic OG endpoint is served by the CMS API router, which is mounted
        // under /api/cms — not the site root — so the default must include it or it
        // 404s for social crawlers.
        (siteUrl ? `${siteUrl}/api/cms/og.png?title=${encodeURIComponent(title)}` : undefined);
    const metaInput = {
        title,
        description,
        url,
        siteName,
        canonical,
        noIndex: robots.noIndex,
        noFollow: robots.noFollow,
        type: schemaType === 'Article' || schemaType === 'BlogPosting' || schemaType === 'NewsArticle'
            ? 'article'
            : 'website',
        ogImage: ogImageUrl ? { url: ogImageUrl } : undefined,
        twitterSite: site?.twitterHandle,
        publishedTime: asIsoDate(doc.publishedAt),
        modifiedTime: asIsoDate(doc.updatedAt),
        author: pickString(data.authorName, data.author),
        locale: config?.i18n?.defaultLocale,
    };
    const meta = generateMetaTags(metaInput);
    let metaHtml = renderMetaTagsHtml(metaInput);
    let metadata = generateNextMetadata(metaInput);
    // Site-wide search-engine verification meta tags (Settings → SEO). These are
    // injected into every public page's head; values are escaped and emitted only
    // as safe `<meta>` tags.
    const verificationEntries = buildVerificationMetaEntries(site?.verification);
    if (verificationEntries.length > 0) {
        for (const e of verificationEntries) {
            const key = e.name ?? e.property;
            if (key)
                meta[key] = e.content;
        }
        const verificationHtml = renderVerificationMetaHtml(site?.verification);
        if (verificationHtml)
            metaHtml = `${metaHtml}\n${verificationHtml}`;
        const nextVerification = buildNextVerification(site?.verification);
        if (nextVerification)
            metadata = { ...metadata, verification: nextVerification };
    }
    const jsonLd = composeJsonLd(schemaType, doc, { title, description, url, image: ogImageUrl }, site);
    const jsonLdHtml = jsonLd
        ? `<script type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, '\\u003c')}</script>`
        : '';
    return { url, title, description, canonical, jsonLd, jsonLdHtml, meta, metaHtml, metadata };
}
export { detectSchemaType };
//# sourceMappingURL=page-meta.js.map