"use client";
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
import { useEditContext } from "../client/editContext";
import { usePageViewContext, } from "../page-viewer/pageViewContext";
const EditorSlotReactContext = React.createContext(undefined);
export function useSlotContext() {
    return useContext(EditorSlotReactContext);
}
function descriptorKey(descriptor, refreshToken) {
    if (!descriptor)
        return null;
    // Include refreshToken in key to force reload when it changes
    const tokenSuffix = refreshToken ? `-${refreshToken}` : "";
    return `${descriptor.id}-${descriptor.language}-${descriptor.version}${tokenSuffix}`;
}
export function useEditorSlotContext({ slotId, itemDescriptor, refreshToken, }) {
    const editContext = useEditContext();
    if (!editContext)
        return undefined;
    const fieldModificationStore = editContext.getFieldModificationStoreForSlot(slotId);
    // Use getActiveSlotId() to get the synchronous ref value, avoiding race conditions
    // where React state (activeSlotId) hasn't updated yet but the ref has.
    const isActive = editContext.getActiveSlotId() === slotId;
    // Compare mode is per-slot, not tied to active slot.
    const [compareMode, setCompareMode] = useState(false);
    const [compareTo, setCompareTo] = useState(undefined);
    const itemsRepository = editContext.itemsRepository;
    const itemsRepositoryRef = useRef(itemsRepository);
    const configuration = editContext.configuration;
    const primaryPageViewContext = usePageViewContext({
        pageItemDescriptor: itemDescriptor,
        itemsRepository,
        configuration,
    });
    // Always create the compare context hook so hooks remain unconditional.
    // (If compareTo is undefined, the hook will resolve to an "empty" page view.)
    const comparePageViewContext = usePageViewContext({
        pageItemDescriptor: compareTo,
        itemsRepository,
        configuration,
    });
    // Notify EditorShell when the slot's page model finishes building.
    // The slot context uses a stable ref identity (see below), so EditorShell
    // does not re-render when the slot's page changes. This notification
    // triggers re-evaluation of the isRefreshing state.
    const pageReadyKey = primaryPageViewContext.page
        ? [
            primaryPageViewContext.page.item?.id ?? "",
            primaryPageViewContext.page.item?.version ?? "",
            primaryPageViewContext.page.editRevision ?? "",
        ].join("|")
        : null;
    useEffect(() => {
        if (pageReadyKey) {
            editContext.notifyPageModelReady?.();
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [pageReadyKey]);
    // Keep the full viewport state in sync between the primary and compare contexts
    // so compare mode switches both panes reliably.
    useEffect(() => {
        if (!compareMode)
            return;
        if (!compareTo)
            return;
        if (comparePageViewContext.device !== primaryPageViewContext.device) {
            comparePageViewContext.setDevice(primaryPageViewContext.device);
        }
        if (comparePageViewContext.deviceWidth !== primaryPageViewContext.deviceWidth) {
            comparePageViewContext.setDeviceWidth(primaryPageViewContext.deviceWidth);
        }
        if (comparePageViewContext.deviceHeight !==
            primaryPageViewContext.deviceHeight) {
            comparePageViewContext.setDeviceHeight(primaryPageViewContext.deviceHeight);
        }
        if (comparePageViewContext.lockHeight !== primaryPageViewContext.lockHeight) {
            comparePageViewContext.setLockHeight(primaryPageViewContext.lockHeight);
        }
        if (comparePageViewContext.rotate !== primaryPageViewContext.rotate) {
            comparePageViewContext.setRotate(primaryPageViewContext.rotate);
        }
        if (comparePageViewContext.zoom !== primaryPageViewContext.zoom) {
            comparePageViewContext.setZoom(primaryPageViewContext.zoom);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [
        compareMode,
        compareTo,
        primaryPageViewContext.device,
        primaryPageViewContext.deviceWidth,
        primaryPageViewContext.deviceHeight,
        primaryPageViewContext.lockHeight,
        primaryPageViewContext.rotate,
        primaryPageViewContext.zoom,
        comparePageViewContext.zoom,
    ]);
    // Load the primary item once per descriptor.
    const [item, setItem] = useState(undefined);
    const [isLoading, setIsLoading] = useState(false);
    const [itemMissing, setItemMissing] = useState(false);
    const loadedKeyRef = useRef(null);
    const itemKey = useMemo(() => descriptorKey(itemDescriptor, refreshToken), [itemDescriptor, refreshToken]);
    // Keep a ref of the current item key for async safety.
    const itemKeyRef = useRef(itemKey);
    useEffect(() => {
        itemKeyRef.current = itemKey;
    }, [itemKey]);
    useEffect(() => {
        itemsRepositoryRef.current = itemsRepository;
    }, [itemsRepository]);
    useEffect(() => {
        const key = itemKey;
        if (!key) {
            setItem(undefined);
            loadedKeyRef.current = null;
            setItemMissing(false);
            setIsLoading(false);
            return;
        }
        if (loadedKeyRef.current === key)
            return;
        const repo = itemsRepositoryRef.current;
        if (!repo)
            return;
        let cancelled = false;
        setIsLoading(true);
        setItem(undefined);
        setItemMissing(false);
        (async () => {
            try {
                const loaded = await repo.getItem(itemDescriptor);
                if (cancelled)
                    return;
                if (loaded) {
                    setItem(loaded);
                    loadedKeyRef.current = key;
                    setItemMissing(false);
                }
                else {
                    setItem(undefined);
                    loadedKeyRef.current = null;
                    setItemMissing(true);
                }
            }
            catch (e) {
                // eslint-disable-next-line no-console
                console.error("Error loading slot item:", e);
                if (cancelled)
                    return;
                setItem(undefined);
                loadedKeyRef.current = null;
                setItemMissing(false);
            }
            finally {
                if (!cancelled)
                    setIsLoading(false);
            }
        })();
        return () => {
            cancelled = true;
        };
        // Don't re-run on itemsRepository identity changes (it can tick during typing).
    }, [
        itemKey,
        itemDescriptor.id,
        itemDescriptor.language,
        itemDescriptor.version,
        !!itemsRepository,
    ]);
    // Keep the slot-level `item` in sync when item *properties* change in the repository.
    // We intentionally ignore plain field updates here to avoid rerendering the whole slot on every keystroke.
    useEffect(() => {
        if (!itemsRepository)
            return;
        let cancelled = false;
        const unsubscribe = itemsRepository.subscribeItemsChanged((changes) => {
            const affectsThisItem = changes.some((change) => {
                const sameId = change.item?.id === itemDescriptor.id;
                const sameLanguage = change.item?.language === itemDescriptor.language;
                const versionMatches = itemDescriptor.version === 0 ||
                    change.item?.version === 0 ||
                    change.item?.version === itemDescriptor.version;
                if (!sameId || !sameLanguage || !versionMatches)
                    return false;
                const c = change.changes || {};
                return !!(c.properties || c.name || c.templateId || c.parentId);
            });
            if (!affectsThisItem)
                return;
            (async () => {
                const keyAtStart = itemKeyRef.current;
                const repo = itemsRepositoryRef.current;
                if (!repo)
                    return;
                try {
                    const loaded = await repo.getItem(itemDescriptor);
                    if (cancelled)
                        return;
                    if (itemKeyRef.current !== keyAtStart)
                        return;
                    setItem(loaded);
                }
                catch {
                    // Ignore; the descriptor-driven load effect handles retries/errors.
                }
            })();
        });
        return () => {
            cancelled = true;
            unsubscribe();
        };
    }, [
        itemsRepository,
        itemDescriptor.id,
        itemDescriptor.language,
        itemDescriptor.version,
    ]);
    // Load compare-to item when set.
    const [compareItem, setCompareItem] = useState(undefined);
    const [compareItemMissing, setCompareItemMissing] = useState(false);
    const compareLoadedKeyRef = useRef(null);
    const compareKey = useMemo(() => descriptorKey(compareTo), [compareTo]);
    useEffect(() => {
        const key = compareKey;
        if (!key) {
            setCompareItem(undefined);
            compareLoadedKeyRef.current = null;
            setCompareItemMissing(false);
            return;
        }
        if (compareLoadedKeyRef.current === key)
            return;
        const repo = itemsRepositoryRef.current;
        if (!repo)
            return;
        let cancelled = false;
        setCompareItem(undefined);
        setCompareItemMissing(false);
        (async () => {
            try {
                const loaded = await repo.getItem(compareTo);
                if (cancelled)
                    return;
                if (loaded) {
                    setCompareItem(loaded);
                    compareLoadedKeyRef.current = key;
                    setCompareItemMissing(false);
                }
                else {
                    setCompareItem(undefined);
                    compareLoadedKeyRef.current = null;
                    setCompareItemMissing(true);
                }
            }
            catch (e) {
                // eslint-disable-next-line no-console
                console.error("Error loading compare-to item:", e);
                if (cancelled)
                    return;
                setCompareItem(undefined);
                compareLoadedKeyRef.current = null;
                setCompareItemMissing(false);
            }
        })();
        return () => {
            cancelled = true;
        };
        // Don't re-run on itemsRepository identity changes (it can tick during typing).
    }, [
        compareKey,
        compareTo?.id,
        compareTo?.language,
        compareTo?.version,
        !!itemsRepository,
    ]);
    const pageViewContexts = useMemo(() => {
        return compareMode
            ? [primaryPageViewContext, comparePageViewContext]
            : [primaryPageViewContext];
    }, [compareMode, primaryPageViewContext, comparePageViewContext]);
    // Return a stable object identity so consumers can safely store/register it.
    const slotContextRef = useRef(null);
    if (!slotContextRef.current) {
        slotContextRef.current = {
            slotId,
            itemDescriptor,
            item,
            isLoading,
            itemMissing,
            pageViewContexts,
            primaryPageViewContext,
            compareMode,
            setCompareMode,
            compareTo,
            setCompareTo,
            compareItem,
            compareItemMissing,
            isActive,
            fieldModificationStore,
        };
    }
    else {
        slotContextRef.current.slotId = slotId;
        slotContextRef.current.itemDescriptor = itemDescriptor;
        slotContextRef.current.item = item;
        slotContextRef.current.isLoading = isLoading;
        slotContextRef.current.itemMissing = itemMissing;
        slotContextRef.current.pageViewContexts = pageViewContexts;
        slotContextRef.current.primaryPageViewContext = primaryPageViewContext;
        slotContextRef.current.compareMode = compareMode;
        slotContextRef.current.compareTo = compareTo;
        slotContextRef.current.compareItem = compareItem;
        slotContextRef.current.compareItemMissing = compareItemMissing;
        slotContextRef.current.isActive = isActive;
        slotContextRef.current.fieldModificationStore = fieldModificationStore;
        // setters are stable; keep initial references
    }
    return slotContextRef.current;
}
export function EditorSlotContextProvider({ value, children, }) {
    return React.createElement(EditorSlotReactContext.Provider, { value }, children);
}
//# sourceMappingURL=editorSlotContext.js.map