import { enqueueSuggestedEditFieldTask } from "../client/suggestedEditFieldQueue";
import { createOrUpdateSuggestedEdit } from "../services/suggestedEditsService";
import { getPendingFieldSuggestions, getSuggestedEditItemLanguage, getSuggestedEditItemVersion, getSuggestionFieldIdentityKey, getSuggestionSelectionKey, } from "./suggestionDisplayValue";
import { isSuggestedEditRemoved, upsertSuggestedEdit, } from "./suggestedEditState";
import { applySuggestionPatchToValue } from "./suggestionPatch";
function getActionKey(suggestion) {
    return getSuggestionFieldIdentityKey({
        itemId: suggestion.itemId,
        language: getSuggestedEditItemLanguage(suggestion),
        version: getSuggestedEditItemVersion(suggestion),
        fieldId: suggestion.fieldId,
    });
}
function findFreshSuggestion(editContext, id) {
    return editContext.getCurrentSuggestedEdits().find((edit) => edit.id === id);
}
function upsertFromServer(editContext, suggestion, expectedNewValue) {
    editContext.setSuggestedEdits((current) => upsertSuggestedEdit(current, suggestion, {
        source: "save-ack",
        expectedNewValue,
    }));
}
export async function sha256SuggestedEditFieldValue(value) {
    const bytes = new TextEncoder().encode(value);
    const hash = await crypto.subtle.digest("SHA-256", bytes);
    return Array.from(new Uint8Array(hash))
        .map((byte) => byte.toString(16).padStart(2, "0"))
        .join("");
}
async function persistRebasedSibling({ sibling, baseValue, editContext, }) {
    let current = sibling;
    let currentBase = baseValue;
    for (let attempt = 0; attempt < 2; attempt += 1) {
        const rebased = currentBase === current.newValue
            ? currentBase
            : applySuggestionPatchToValue(currentBase, current.oldValue, current.newValue);
        if (rebased === false || typeof rebased !== "string")
            return false;
        const subsumed = rebased === currentBase;
        const candidate = subsumed
            ? { ...current, status: "applied" }
            : {
                ...current,
                oldValue: currentBase,
                newValue: rebased,
                updated: new Date().toISOString(),
                updatedBy: editContext.user?.name || "unknown",
            };
        const result = await createOrUpdateSuggestedEdit(candidate, {
            expectedUpdated: current.updated,
            expectedFieldHash: await sha256SuggestedEditFieldValue(currentBase),
        });
        if (result.type === "success" && result.data) {
            upsertFromServer(editContext, result.data, subsumed ? current.newValue : rebased);
            return true;
        }
        if (result.type !== "conflict")
            return false;
        if (result.suggestion)
            upsertFromServer(editContext, result.suggestion);
        if (result.suggestion?.status !== "pending" ||
            typeof result.fieldValue !== "string") {
            return true;
        }
        current = result.suggestion;
        currentBase = result.fieldValue;
    }
    return false;
}
async function rebaseSiblingSuggestions({ appliedEdit, newFieldValue, fieldItem, editContext, }) {
    const itemId = fieldItem?.id ?? appliedEdit.itemId;
    const itemLanguage = fieldItem?.language ?? getSuggestedEditItemLanguage(appliedEdit);
    const itemVersion = fieldItem?.version ?? getSuggestedEditItemVersion(appliedEdit);
    const siblings = getPendingFieldSuggestions(editContext.getCurrentSuggestedEdits(), {
        fieldId: appliedEdit.fieldId,
        itemId,
        language: itemLanguage,
        version: itemVersion,
        pageItemId: appliedEdit.mainItemId,
        pageItemLanguage: appliedEdit.mainItemLanguage,
        pageItemVersion: appliedEdit.mainItemVersion > 0
            ? appliedEdit.mainItemVersion
            : undefined,
    }).filter((candidate) => candidate.id !== appliedEdit.id && !isSuggestedEditRemoved(candidate.id));
    let failures = 0;
    for (const sibling of siblings) {
        const fresh = findFreshSuggestion(editContext, sibling.id);
        if (!fresh ||
            fresh.status !== "pending" ||
            isSuggestedEditRemoved(fresh.id)) {
            continue;
        }
        try {
            if (!(await persistRebasedSibling({
                sibling: fresh,
                baseValue: newFieldValue,
                editContext,
            }))) {
                failures += 1;
            }
        }
        catch (error) {
            failures += 1;
            console.warn("Failed to rebase sibling suggestion", error);
        }
    }
    const selectionKey = getSuggestionSelectionKey({
        itemId,
        language: itemLanguage,
        fieldId: appliedEdit.fieldId,
    });
    editContext.setSuggestionVariantSelection((current) => {
        if (!(selectionKey in current))
            return current;
        const next = { ...current };
        delete next[selectionKey];
        return next;
    });
    if (failures > 0) {
        editContext.showErrorToast({
            summary: "Some suggestions could not be rebased",
            details: "They remain available as conflict variants.",
        });
    }
}
async function applyOrReplaceSuggestedEdit({ suggestion, editContext, replace, allowRejected, }) {
    return enqueueSuggestedEditFieldTask(getActionKey(suggestion), async () => {
        const fresh = findFreshSuggestion(editContext, suggestion.id);
        const canApplyFreshSuggestion = fresh?.status === "pending" ||
            (allowRejected && fresh?.status === "rejected");
        if (!fresh ||
            !canApplyFreshSuggestion ||
            isSuggestedEditRemoved(fresh.id)) {
            editContext.showErrorToast({
                summary: "Suggestion is no longer pending",
            });
            return { type: "missing" };
        }
        const itemDescriptor = {
            id: fresh.itemId,
            language: getSuggestedEditItemLanguage(fresh),
            version: getSuggestedEditItemVersion(fresh),
        };
        const field = await editContext.itemsRepository.getField({
            item: itemDescriptor,
            fieldId: fresh.fieldId,
        });
        if (!field)
            return { type: "missing" };
        const resolvedItemDescriptor = field.descriptor?.item ?? itemDescriptor;
        const currentValue = field.rawValue || "";
        const patchedCandidate = replace
            ? fresh.newValue
            : applySuggestionPatchToValue(currentValue, fresh.oldValue, fresh.newValue);
        if (patchedCandidate === false || typeof patchedCandidate !== "string") {
            return { type: "patch-conflict" };
        }
        await editContext.operations.editField({
            field: { fieldId: fresh.fieldId, item: resolvedItemDescriptor },
            value: patchedCandidate,
            rawValue: patchedCandidate,
            forceMode: "edit",
            refresh: "immediate",
        });
        const result = await createOrUpdateSuggestedEdit({ ...fresh, status: "applied" }, { expectedUpdated: fresh.updated });
        let actionResult;
        if (result.type === "success" && result.data) {
            upsertFromServer(editContext, result.data, fresh.newValue);
            actionResult = { type: "success", suggestion: result.data };
        }
        else if (result.type === "conflict") {
            if (result.suggestion)
                upsertFromServer(editContext, result.suggestion);
            actionResult = { type: "conflict", suggestion: result.suggestion };
        }
        else {
            editContext.showErrorToast({
                summary: result.summary ||
                    "Suggestion was applied but its status could not be saved",
                details: result.details,
            });
            actionResult = { type: "error" };
        }
        await rebaseSiblingSuggestions({
            appliedEdit: fresh,
            newFieldValue: patchedCandidate,
            fieldItem: resolvedItemDescriptor,
            editContext,
        });
        return actionResult;
    });
}
export function applySuggestedEditPatch(suggestion, editContext) {
    return applyOrReplaceSuggestedEdit({
        suggestion,
        editContext,
        replace: false,
        allowRejected: false,
    });
}
export function applyRejectedSuggestedEditPatch(suggestion, editContext) {
    return applyOrReplaceSuggestedEdit({
        suggestion,
        editContext,
        replace: false,
        allowRejected: true,
    });
}
export function replaceSuggestedEditCompletely(suggestion, editContext) {
    return applyOrReplaceSuggestedEdit({
        suggestion,
        editContext,
        replace: true,
        allowRejected: false,
    });
}
export function rejectSuggestedEditAction(suggestion, editContext) {
    return enqueueSuggestedEditFieldTask(getActionKey(suggestion), async () => {
        const fresh = findFreshSuggestion(editContext, suggestion.id);
        if (!fresh ||
            fresh.status !== "pending" ||
            isSuggestedEditRemoved(fresh.id)) {
            return { type: "missing" };
        }
        const result = await createOrUpdateSuggestedEdit({ ...fresh, status: "rejected" }, { expectedUpdated: fresh.updated });
        if (result.type === "success" && result.data) {
            upsertFromServer(editContext, result.data, fresh.newValue);
            return { type: "success", suggestion: result.data };
        }
        if (result.type === "conflict") {
            if (result.suggestion)
                upsertFromServer(editContext, result.suggestion);
            return { type: "conflict", suggestion: result.suggestion };
        }
        editContext.showErrorToast({
            summary: result.summary || "Failed to reject suggestion",
            details: result.details,
        });
        return { type: "error" };
    });
}
//# sourceMappingURL=applySuggestedEditActions.js.map