/**
 * Built-in Meta handle for managing document metadata across route segments.
 *
 * Provides automatic deduplication: later routes override earlier ones
 * for the same meta key (title, name, property, etc.)
 *
 * @example
 * ```tsx
 * // In route handler
 * route("product/:id", (ctx) => {
 *   const meta = ctx.use(Meta);
 *   meta({ title: "Product Details" });
 *   meta({ name: "description", content: "..." });
 *   meta({ property: "og:title", content: "..." });
 * });
 *
 * // In layout (renders the collected meta tags)
 * function RootLayout() {
 *   return (
 *     <html>
 *       <head>
 *         <MetaTags />
 *       </head>
 *       ...
 *     </html>
 *   );
 * }
 * ```
 */
import { createHandle } from "../handle.js";
/**
 * Type guard for unset descriptor
 */
function isUnsetDescriptor(descriptor) {
    return (typeof descriptor === "object" &&
        descriptor !== null &&
        "unset" in descriptor &&
        typeof descriptor.unset === "string");
}
/**
 * Type guard for title descriptor (any form)
 */
function isTitleDescriptor(descriptor) {
    return (typeof descriptor === "object" &&
        descriptor !== null &&
        "title" in descriptor);
}
/**
 * Type guard for title template descriptor
 */
function isTitleTemplate(title) {
    return (typeof title === "object" &&
        title !== null &&
        "template" in title &&
        "default" in title);
}
/**
 * Type guard for absolute title descriptor
 */
function isAbsoluteTitle(title) {
    return typeof title === "object" && title !== null && "absolute" in title;
}
/**
 * Get a unique key for a meta descriptor for deduplication.
 * Returns undefined for descriptors that shouldn't be deduplicated.
 */
function getMetaKey(descriptor) {
    // Skip unset descriptors - they are processed separately
    if (isUnsetDescriptor(descriptor)) {
        return undefined;
    }
    if ("charSet" in descriptor) {
        return "charSet";
    }
    if ("title" in descriptor) {
        return "title";
    }
    if ("name" in descriptor && "content" in descriptor) {
        return `name:${descriptor.name}`;
    }
    if ("property" in descriptor && "content" in descriptor) {
        return `property:${descriptor.property}`;
    }
    if ("httpEquiv" in descriptor && "content" in descriptor) {
        return `httpEquiv:${descriptor.httpEquiv}`;
    }
    if ("script:ld+json" in descriptor) {
        // JSON-LD scripts can have multiple, don't dedupe by default
        return undefined;
    }
    if ("tagName" in descriptor) {
        // For link tags, dedupe by rel if present
        if (descriptor.tagName === "link" && "rel" in descriptor) {
            // Some link rels should be unique (canonical), others not (stylesheet)
            const uniqueRels = ["canonical", "icon", "apple-touch-icon"];
            if (uniqueRels.includes(descriptor.rel)) {
                return `link:${descriptor.rel}`;
            }
        }
        return undefined;
    }
    return undefined;
}
/**
 * Default meta descriptors included automatically.
 * These can be overridden by route handlers.
 */
const defaultMetaDescriptors = [
    { charSet: "utf-8" },
    { name: "viewport", content: "width=device-width, initial-scale=1" },
];
/**
 * Helper to add or replace a descriptor in the result array
 */
function addOrReplace(result, keyToIndex, descriptor, key) {
    if (key !== undefined && keyToIndex.has(key)) {
        result[keyToIndex.get(key)] = descriptor;
    }
    else {
        if (key !== undefined) {
            keyToIndex.set(key, result.length);
        }
        result.push(descriptor);
    }
}
/**
 * Helper to update indices after removing an element
 */
function updateIndicesAfterRemoval(keyToIndex, removedIndex) {
    for (const [key, index] of keyToIndex) {
        if (index > removedIndex) {
            keyToIndex.set(key, index - 1);
        }
    }
}
/**
 * Collect function for Meta handle.
 * Includes default meta descriptors, then deduplicates by key with later routes overriding earlier ones.
 * Supports title templates, absolute titles, and unset descriptors.
 */
function collectMeta(segments) {
    const result = [];
    const keyToIndex = new Map();
    let titleTemplate;
    // Add defaults first so they can be overridden
    for (const descriptor of defaultMetaDescriptors) {
        const key = getMetaKey(descriptor);
        if (key !== undefined) {
            keyToIndex.set(key, result.length);
        }
        result.push(descriptor);
    }
    for (const descriptors of segments) {
        for (const descriptor of descriptors) {
            // Handle unset descriptors
            if (isUnsetDescriptor(descriptor)) {
                const keyToRemove = descriptor.unset;
                if (keyToIndex.has(keyToRemove)) {
                    const idx = keyToIndex.get(keyToRemove);
                    result.splice(idx, 1);
                    keyToIndex.delete(keyToRemove);
                    updateIndicesAfterRemoval(keyToIndex, idx);
                }
                continue;
            }
            // Handle title descriptors with template/absolute support
            if (isTitleDescriptor(descriptor)) {
                const titleValue = descriptor.title;
                if (isTitleTemplate(titleValue)) {
                    // Store template for subsequent title descriptors in child segments
                    titleTemplate = titleValue.template;
                    // Set the default title
                    addOrReplace(result, keyToIndex, { title: titleValue.default }, "title");
                    continue;
                }
                if (isAbsoluteTitle(titleValue)) {
                    // Absolute title bypasses any template
                    addOrReplace(result, keyToIndex, { title: titleValue.absolute }, "title");
                    continue;
                }
                // String title - apply template if one exists
                const finalTitle = titleTemplate
                    ? titleTemplate.replace("%s", titleValue)
                    : titleValue;
                addOrReplace(result, keyToIndex, { title: finalTitle }, "title");
                continue;
            }
            // Handle all other descriptors
            const key = getMetaKey(descriptor);
            addOrReplace(result, keyToIndex, descriptor, key);
        }
    }
    return result;
}
/**
 * Built-in handle for managing document metadata.
 *
 * Use `ctx.use(Meta)` in route handlers to push meta descriptors.
 * Use `<MetaTags />` component to render them in the document head.
 */
export const Meta = createHandle(collectMeta, "__rsc_router_meta__");
//# sourceMappingURL=meta.js.map