import { diffWords } from "diff";
import { applySuggestionPatchToValue } from "./suggestionPatch";
export function getSuggestedEditItemLanguage(suggestion) {
    return suggestion.itemLanguage ?? suggestion.mainItemLanguage;
}
export function getSuggestedEditItemVersion(suggestion) {
    return suggestion.itemVersion ?? suggestion.mainItemVersion;
}
function normalizeIdentityPart(value) {
    return (value ?? "").trim().toLowerCase().replace(/[{}]/g, "");
}
export function getSuggestionFieldIdentityKey({ itemId, language, version, fieldId, }) {
    return [
        normalizeIdentityPart(itemId),
        normalizeIdentityPart(language),
        version,
        normalizeIdentityPart(fieldId),
    ].join(":");
}
export function getSuggestionSelectionKey({ itemId, language, fieldId, }) {
    return [
        normalizeIdentityPart(itemId),
        normalizeIdentityPart(language),
        normalizeIdentityPart(fieldId),
    ].join(":");
}
function toTimestamp(value) {
    if (!value)
        return 0;
    const timestamp = new Date(value).getTime();
    return Number.isFinite(timestamp) ? timestamp : 0;
}
function matchesFieldSuggestion(suggestion, field) {
    const pageVersionMatches = field.pageItemVersion == null ||
        suggestion.mainItemVersion === 0 ||
        suggestion.mainItemVersion === field.pageItemVersion;
    const itemVersion = getSuggestedEditItemVersion(suggestion);
    const fieldVersionMatches = itemVersion === 0 || itemVersion === field.version;
    return (suggestion.status === "pending" &&
        suggestion.fieldId === field.fieldId &&
        suggestion.itemId === field.itemId &&
        getSuggestedEditItemLanguage(suggestion) === field.language &&
        (!field.pageItemId || suggestion.mainItemId === field.pageItemId) &&
        (!field.pageItemLanguage ||
            suggestion.mainItemLanguage === field.pageItemLanguage) &&
        pageVersionMatches &&
        fieldVersionMatches);
}
export function getPendingFieldSuggestions(suggestedEdits, field) {
    return suggestedEdits
        .filter((suggestion) => matchesFieldSuggestion(suggestion, field))
        .sort((left, right) => {
        const timeDelta = toTimestamp(left.created) - toTimestamp(right.created);
        return timeDelta || left.id.localeCompare(right.id);
    });
}
export function getBaselineFieldValue({ repositoryValue, modifiedFields, field, }) {
    const modifiedField = modifiedFields?.find((candidate) => candidate.fieldId === field.fieldId &&
        candidate.item.id === field.itemId &&
        candidate.item.language === field.language &&
        candidate.item.version === field.version);
    return modifiedField ? (modifiedField.value ?? "") : (repositoryValue ?? "");
}
export function mergePendingSuggestionValues(baselineValue, suggestions) {
    return partitionPendingSuggestionValues(baselineValue, suggestions)
        .mergedValue;
}
export function partitionPendingSuggestionValues(baselineValue, suggestions) {
    let mergedValue = baselineValue;
    const cleanSuggestions = [];
    const conflicts = [];
    for (const suggestion of suggestions) {
        const patchedCandidate = applySuggestionPatchToValue(mergedValue, suggestion.oldValue, suggestion.newValue);
        if (patchedCandidate === false || typeof patchedCandidate !== "string") {
            conflicts.push(suggestion);
            continue;
        }
        mergedValue = patchedCandidate;
        cleanSuggestions.push(suggestion);
    }
    return { mergedValue, cleanSuggestions, conflicts };
}
/**
 * Returns the pending suggestions that cannot coexist with the requested
 * suggestion. Conflict membership is deliberately pairwise and symmetric:
 * the suggestion chosen as the fold baseline must not appear conflict-free
 * merely because it sorted first.
 */
export function getConflictingSuggestionPeers(suggestions, suggestionId) {
    const suggestion = suggestions.find((candidate) => candidate.id === suggestionId);
    if (!suggestion)
        return [];
    return suggestions.filter((candidate) => {
        if (candidate.id === suggestion.id)
            return false;
        if (candidate.newValue === suggestion.newValue)
            return false;
        const candidateAfterSuggestion = applySuggestionPatchToValue(suggestion.newValue, candidate.oldValue, candidate.newValue);
        const suggestionAfterCandidate = applySuggestionPatchToValue(candidate.newValue, suggestion.oldValue, suggestion.newValue);
        return (candidateAfterSuggestion === false && suggestionAfterCandidate === false);
    });
}
function escapeHtml(value) {
    return value
        .replaceAll("&", "&amp;")
        .replaceAll("<", "&lt;")
        .replaceAll(">", "&gt;")
        .replaceAll('"', "&quot;")
        .replaceAll("'", "&#39;");
}
export function getSuggestionDiffHtml(baselineValue, mergedValue) {
    return diffWords(baselineValue, mergedValue)
        .map((part) => {
        let style = "color: gray;";
        if (part.added) {
            style = "color: green;";
        }
        else if (part.removed) {
            style = "color: red; text-decoration: line-through;";
        }
        return `<span style="${style}">${escapeHtml(part.value)}</span>`;
    })
        .join("");
}
export function getSuggestionDisplayValue({ repositoryValue, modifiedFields, suggestedEdits, field, mode, format = "plain", selectedVariantId: requestedVariantId, }) {
    const baselineValue = getBaselineFieldValue({
        repositoryValue,
        modifiedFields,
        field,
    });
    const suggestions = mode === "suggestions"
        ? getPendingFieldSuggestions(suggestedEdits, field)
        : [];
    const partition = suggestions.length
        ? partitionPendingSuggestionValues(baselineValue, suggestions)
        : { mergedValue: baselineValue, cleanSuggestions: [], conflicts: [] };
    const mergedValue = partition.mergedValue;
    const variants = partition.conflicts.length
        ? [
            { id: "merged", value: mergedValue },
            ...partition.conflicts.map((suggestion) => ({
                id: suggestion.id,
                value: suggestion.newValue,
                suggestion,
            })),
        ]
        : [];
    const fallbackVariantId = partition.cleanSuggestions.length
        ? "merged"
        : partition.conflicts[0]?.id;
    const selectedVariantId = variants.some((variant) => variant.id === requestedVariantId)
        ? requestedVariantId
        : fallbackVariantId;
    const selectedValue = variants.find((variant) => variant.id === selectedVariantId)?.value ??
        mergedValue;
    const displayValue = mode === "suggestions" && suggestions.length && format === "diff"
        ? getSuggestionDiffHtml(baselineValue, selectedValue)
        : selectedValue;
    return {
        baselineValue,
        mergedValue,
        selectedValue,
        displayValue,
        suggestions,
        hasSuggestions: suggestions.length > 0,
        variants,
        conflicts: partition.conflicts,
        selectedVariantId,
    };
}
//# sourceMappingURL=suggestionDisplayValue.js.map