import { diffChars } from "diff";
import { getLanguagesAndVersions, getPageComponents, } from "../services/contentService";
export async function resolveVersionDiffTargets({ bridgeStructure, pageItem, getItem, baseline, previousVersionCache, }) {
    const renderedFields = bridgeStructure
        ? collectRenderedFieldsFromBridge(bridgeStructure, pageItem)
        : [];
    const renderedComponents = bridgeStructure
        ? collectRenderedComponentsFromBridge(bridgeStructure)
        : [];
    const itemsToResolve = getUniqueItems([
        pageItem,
        ...renderedFields.map((field) => field.descriptor.item),
    ]);
    if (renderedFields.length === 0 && renderedComponents.length === 0) {
        return { fieldTargets: [], componentTargets: [] };
    }
    const baselineEntries = baseline.type === "compare"
        ? resolveCompareBaselineEntries(itemsToResolve, baseline.compareTo)
        : await resolvePreviousVersionBaselineEntries(itemsToResolve, getItem, previousVersionCache);
    const baselinePageEntry = baselineEntries.find((entry) => entry.currentItemKey === itemKey(pageItem));
    if (baselineEntries.length === 0) {
        return { fieldTargets: [], componentTargets: [] };
    }
    const currentItemsByCurrentItemKey = new Map();
    const baselineItemsByCurrentItemKey = new Map();
    await Promise.all(baselineEntries.map(async (entry) => {
        const [currentItem, baselineItem] = await Promise.all([
            getItem(entry.currentDescriptor),
            getItem(entry.baselineDescriptor),
        ]);
        if (currentItem) {
            currentItemsByCurrentItemKey.set(entry.currentItemKey, currentItem);
        }
        if (baselineItem) {
            baselineItemsByCurrentItemKey.set(entry.currentItemKey, baselineItem);
        }
    }));
    const fieldTargets = renderedFields
        .map((renderedField) => {
        const currentItemKey = itemKey(renderedField.descriptor.item);
        const currentItem = currentItemsByCurrentItemKey.get(currentItemKey);
        const baselineItem = baselineItemsByCurrentItemKey.get(currentItemKey);
        const currentField = currentItem?.fields.find((field) => normalizeId(field.id) ===
            normalizeId(renderedField.descriptor.fieldId));
        const baselineField = baselineItem?.fields.find((field) => normalizeId(field.id) ===
            normalizeId(renderedField.descriptor.fieldId));
        if (!baselineField)
            return undefined;
        const baselineText = getFieldText(baselineField);
        const currentText = currentField
            ? getFieldText(currentField)
            : renderedField.currentText;
        if (baselineText === currentText)
            return undefined;
        const ranges = getChangedCurrentRanges(baselineText, renderedField.currentText);
        return {
            ...renderedField,
            baselineText,
            ranges,
            hasDeletionOnlyChange: ranges.length === 0,
        };
    })
        .filter((target) => !!target);
    const [currentProviderComponents, baselineProviderComponents] = await Promise.all([
        getPageComponents(pageItem),
        baselinePageEntry
            ? getPageComponents(baselinePageEntry.baselineDescriptor)
            : Promise.resolve([]),
    ]);
    const baselineProviderIds = new Set(baselineProviderComponents.map((component) => normalizeId(component.id)));
    const componentTargets = currentProviderComponents
        .filter((component) => !baselineProviderIds.has(normalizeId(component.id)))
        .map(componentToDiffTarget);
    return { fieldTargets, componentTargets };
}
function collectRenderedFieldsFromBridge(structure, fallbackItem) {
    const renderedFieldsByKey = new Map();
    structure.fields.forEach((field) => {
        if (!field.fieldId)
            return;
        const item = field.item
            ? {
                ...field.item,
                language: field.item.language || fallbackItem.language,
                version: field.item.version !== undefined
                    ? field.item.version
                    : fallbackItem.version,
                database: field.item.database ?? fallbackItem.database,
            }
            : fallbackItem;
        if (!item.id)
            return;
        const descriptor = {
            item,
            fieldId: field.fieldId,
        };
        const key = fieldKey(descriptor);
        if (!renderedFieldsByKey.has(key)) {
            renderedFieldsByKey.set(key, {
                descriptor,
                currentText: getBridgeFieldTextContent(field),
            });
        }
    });
    return Array.from(renderedFieldsByKey.values());
}
function getBridgeFieldTextContent(field) {
    const value = field.textContent;
    return typeof value === "string" ? value : "";
}
function resolveCompareBaselineEntries(currentItems, compareTo) {
    if (!compareTo?.language)
        return [];
    return currentItems
        .map((item) => {
        const baselineDescriptor = {
            ...item,
            language: compareTo.language,
            version: compareTo.version,
            database: item.database ?? compareTo.database,
        };
        return {
            currentItemKey: itemKey(item),
            currentDescriptor: item,
            baselineDescriptor,
        };
    })
        .filter((entry) => !!entry);
}
async function resolvePreviousVersionBaselineEntries(currentItems, getItem, previousVersionCache) {
    const entries = await Promise.all(currentItems.map(async (item) => {
        const currentItem = await getItem(item);
        const currentVersion = currentItem?.version || item.version;
        if (!currentVersion || currentVersion <= 1)
            return undefined;
        const cacheKey = `${normalizeId(item.id)}|${item.language}|${currentVersion}`;
        let previousVersion = previousVersionCache.get(cacheKey);
        if (previousVersion === undefined) {
            const result = await getLanguagesAndVersions({
                ...item,
                version: currentVersion,
            });
            const versions = result?.data?.versions?.map((x) => x.version) || [];
            previousVersion =
                versions
                    .filter((version) => version < currentVersion)
                    .sort((a, b) => b - a)[0] ?? null;
            previousVersionCache.set(cacheKey, previousVersion);
        }
        if (!previousVersion)
            return undefined;
        return {
            currentItemKey: itemKey(item),
            currentDescriptor: item,
            baselineDescriptor: {
                ...item,
                version: previousVersion,
            },
        };
    }));
    return entries.filter((entry) => !!entry);
}
function getUniqueItems(itemDescriptors) {
    const items = new Map();
    itemDescriptors.forEach((item) => {
        const key = itemKey(item);
        if (!items.has(key))
            items.set(key, item);
    });
    return Array.from(items.values());
}
function collectRenderedComponentsFromBridge(structure) {
    return structure.components.map((component) => ({
        id: component.id,
        visualId: component.datasourceItem?.id || component.id,
        dataItemId: component.datasourceItem?.id,
        dataTypeId: component.typeId,
        dataPlaceholderKey: component.placeholderKey,
    }));
}
function componentToDiffTarget(component) {
    const visualId = component.dataSourceItem?.id || component.id;
    return {
        id: component.id,
        visualId,
        dataItemId: component.dataSourceItem?.id,
        dataTypeId: component.renderingItemId,
        providerPlaceholder: component.placeholder,
        providerRenderingItemId: component.renderingItemId,
    };
}
function getChangedCurrentRanges(baselineText, currentText) {
    const ranges = [];
    let currentPosition = 0;
    diffChars(baselineText, currentText).forEach((part) => {
        if (part.added) {
            ranges.push([currentPosition, currentPosition + part.value.length]);
            currentPosition += part.value.length;
            return;
        }
        if (!part.removed) {
            currentPosition += part.value.length;
        }
    });
    return mergeRanges(ranges);
}
function mergeRanges(ranges) {
    if (ranges.length <= 1)
        return ranges;
    const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
    const merged = [];
    sorted.forEach(([start, end]) => {
        const previous = merged[merged.length - 1];
        if (previous && start <= previous[1] + 1) {
            previous[1] = Math.max(previous[1], end);
            return;
        }
        merged.push([start, end]);
    });
    return merged;
}
function getFieldText(field) {
    const value = typeof field.rawValue === "string"
        ? field.rawValue
        : typeof field.value === "string"
            ? field.value
            : field.value == null
                ? ""
                : String(field.value);
    return stripHtml(value);
}
function stripHtml(value) {
    if (!value.includes("<"))
        return value;
    if (typeof DOMParser === "undefined")
        return value.replace(/<[^>]*>/g, "");
    const parsed = new DOMParser().parseFromString(value, "text/html");
    return parsed.body.textContent || "";
}
export function fieldKey(descriptor) {
    return `${itemKey(descriptor.item)}|${normalizeId(descriptor.fieldId)}`;
}
function itemKey(item) {
    return `${normalizeId(item.id)}|${item.language}|${item.version}`;
}
function normalizeId(id) {
    return (id || "").replace(/[{}-]/g, "").toLowerCase();
}
//# sourceMappingURL=versionDiffTargets.js.map