import { useEffect, useRef, useState } from "react";
import { loadPlaceholderInsertOptions, } from "../services/editService";
import { getPageComponents, } from "../services/contentService";
export function normalizeComponentId(id) {
    return (id || "").replace(/[{}-]/g, "").toLowerCase();
}
export function resolveRenderedPageItemDescriptor(skeletonItem, pageItemDescriptor) {
    return {
        ...pageItemDescriptor,
        ...skeletonItem,
        database: skeletonItem.database || pageItemDescriptor?.database,
    };
}
export function usePageModel(itemsRepository, pageItemDescriptor, parheliaSettings, layoutMode = "final") {
    const [page, setPage] = useState();
    const [pageSkeleton, setPageSkeleton] = useState();
    const [insertOptions, setInsertOptions] = useState([]);
    // Cache insert options by page + layout mode + placeholder. The same placeholder
    // key can resolve differently for different pages/layout layers.
    const insertOptionsCache = useRef({});
    // 2) whenever the actual target page or layout mode changes, reset skeleton, page and clear cache
    useEffect(() => {
        setPage(undefined);
        setPageSkeleton(undefined);
        insertOptionsCache.current = {};
        setInsertOptions([]);
    }, [pageItemDescriptor, layoutMode]);
    // 3) collect all placeholder-keys in a skeleton
    const collectPlaceholderKeys = (skeleton) => {
        const keys = [];
        const collect = (c) => {
            c.placeholders.forEach((ph) => {
                keys.push(ph.key);
                ph.components.forEach(collect);
            });
        };
        collect(skeleton.rootComponent);
        return keys;
    };
    const getInsertOptionsCacheKey = (item, placeholderKey) => [
        item.database || "master",
        item.id,
        item.language,
        item.version,
        layoutMode,
        placeholderKey,
    ].join(":");
    // 4) load only missing insertOptions
    useEffect(() => {
        let cancelled = false;
        const loadInsertOptions = async () => {
            if (!pageSkeleton)
                return;
            const skeleton = pageSkeleton;
            const allKeys = collectPlaceholderKeys(skeleton);
            // find which keys we haven’t cached yet
            const missingKeys = allKeys.filter((k) => !(getInsertOptionsCacheKey(skeleton.item, k) in
                insertOptionsCache.current));
            if (missingKeys.length) {
                const newlyFetched = await loadPlaceholderInsertOptions(skeleton.item, missingKeys, layoutMode);
                if (cancelled) {
                    return;
                }
                // stash them in our cache
                newlyFetched.forEach((opt) => {
                    insertOptionsCache.current[getInsertOptionsCacheKey(skeleton.item, opt.key)] = opt;
                });
            }
            // now build the full list in the original placeholder order
            const optionsInOrder = allKeys
                .map((k) => insertOptionsCache.current[getInsertOptionsCacheKey(skeleton.item, k)])
                .filter((opt) => !!opt);
            setInsertOptions(optionsInOrder);
        };
        loadInsertOptions();
        return () => {
            cancelled = true;
        };
    }, [pageSkeleton, layoutMode]);
    const updatePageModel = async () => {
        if (!itemsRepository) {
            return;
        }
        //const start = performance.now();
        const itemsToFetch = [];
        if (!pageSkeleton) {
            setPage(undefined);
            return;
        }
        // Prevent building page model if skeleton is from a different page than the current pageItemDescriptor.
        // This avoids showing incorrect "Shared" badges when the skeleton hasn't been updated yet for the current page.
        if (pageItemDescriptor && pageSkeleton.item.id !== pageItemDescriptor.id) {
            return;
        }
        const effectivePageItemDescriptor = resolveRenderedPageItemDescriptor(pageSkeleton.item, pageItemDescriptor);
        let pageComponentsById = new Map();
        let pageComponentsByRenderingItemId = new Map();
        try {
            const pageComponents = await getPageComponents(effectivePageItemDescriptor);
            pageComponentsById = new Map(pageComponents
                .filter((component) => !!component.id)
                .map((component) => [
                normalizeComponentId(component.id),
                component,
            ]));
            pageComponentsByRenderingItemId = new Map(pageComponents
                .filter((component) => !!component.renderingItemId)
                .map((component) => [
                normalizeComponentId(component.renderingItemId),
                component,
            ]));
        }
        catch {
            // Missing layout metadata only degrades labels; the rendered markers still
            // contain enough structure to build the editable page model.
        }
        itemsToFetch.push(pageSkeleton.item);
        const collectItems = (component) => {
            if (component.datasourceItem)
                itemsToFetch.push({
                    id: component.datasourceItem.id,
                    language: component.datasourceItem.language,
                    version: component.datasourceItem.version,
                });
            component.placeholders
                .flatMap((x) => x.components)
                .forEach((c) => collectItems(c));
            component.items.forEach((i) => itemsToFetch.push(i));
        };
        collectItems(pageSkeleton.rootComponent);
        for (const component of pageComponentsById.values()) {
            if (component.dataSourceItem) {
                itemsToFetch.push(component.dataSourceItem);
            }
        }
        // Pass pageItem to getItemsStubs so it can be included in the request for authorization
        // This is critical for limited preview users - datasources need pageItem context to be authorized
        let itemStubs;
        try {
            itemStubs = await itemsRepository.getItemsStubs(itemsToFetch, effectivePageItemDescriptor);
        }
        catch (e) {
            return;
        }
        const getItem = (descriptor) => itemStubs.find((x) => x.id === descriptor.id &&
            x.language === descriptor.language &&
            x.version === descriptor.version);
        const pageItem = await itemsRepository.getItem(pageSkeleton.item);
        if (!pageItem) {
            setPage(undefined);
            return;
        }
        const buildModel = (skeleton, parentPlaceholder) => {
            const datasourceItemTemp = skeleton.datasourceItem
                ? getItem(skeleton.datasourceItem)
                : undefined;
            const datasourceItem = skeleton.datasourceItem && datasourceItemTemp
                ? { ...skeleton.datasourceItem, ...datasourceItemTemp }
                : undefined;
            const pageComponent = pageComponentsById.get(normalizeComponentId(skeleton.id));
            const renderingTypeComponent = pageComponent ||
                pageComponentsByRenderingItemId.get(normalizeComponentId(skeleton.typeId));
            const renderingName = renderingTypeComponent?.renderingItemName ||
                renderingTypeComponent?.name ||
                renderingTypeComponent?.type;
            const mapItem = (x) => {
                const item = getItem(x);
                if (!item)
                    return null;
                return { ...x, ...item };
            };
            const items = skeleton.items.map(mapItem).filter((x) => x !== null);
            const primaryRenderedItem = items[0];
            const name = skeleton.name ||
                renderingName ||
                skeleton.type ||
                datasourceItem?.displayName ||
                datasourceItem?.name ||
                primaryRenderedItem?.displayName ||
                primaryRenderedItem?.name ||
                "unknown";
            const placeholders = [];
            const component = {
                ...skeleton,
                name,
                icon: datasourceItem?.icon,
                datasourceItem,
                items,
                placeholders,
                // Prefer server-provided shared state to avoid client-side path
                // heuristics misclassifying page-local datasources as shared.
                isShared: skeleton.isShared ?? false,
                hasPersonalization: skeleton.hasPersonalization ?? pageComponent?.hasPersonalization,
                parentPlaceholder,
                editable: skeleton.editable ?? true,
                type: skeleton.type ||
                    renderingTypeComponent?.type ||
                    renderingTypeComponent?.renderingItemName ||
                    datasourceItem?.templateName ||
                    name,
                typeId: skeleton.typeId ||
                    pageComponent?.renderingItemId ||
                    datasourceItem?.templateId ||
                    "",
                canBeMoved: !datasourceItem ||
                    datasourceItem?.id !== parentPlaceholder?.parentComponent?.id,
            };
            component.placeholders = skeleton.placeholders.map((x) => {
                const options = insertOptions.find((y) => y.key === x.key);
                const editable = x.editable ?? options?.editable ?? false;
                const insOpts = options?.insertOptions ?? [];
                const placeholder = {
                    ...x,
                    components: [],
                    canSynchronize: false,
                    parentComponent: component,
                    insertOptions: insOpts,
                    editable,
                    placeholderSettingsItem: options?.placeholderSettingsItem,
                };
                placeholder.components = x.components
                    .map((y) => buildModel(y, placeholder))
                    .filter((x) => x !== undefined);
                return placeholder;
            });
            return component;
        };
        const rootComponent = buildModel(pageSkeleton.rootComponent);
        if (!rootComponent) {
            setPage(undefined);
            return;
        }
        const page = {
            item: pageItem,
            rootComponent,
            //    revision: itemsRepository.revision,
            editRevision: pageSkeleton.editRevision,
        };
        setPage(page);
    };
    useEffect(() => {
        updatePageModel();
    }, [pageSkeleton, insertOptions]);
    return {
        setPageSkeleton,
        page,
    };
}
//# sourceMappingURL=pageModelBuilder.js.map