import { logFieldValueDiagnostic, summarizeField, summarizeFieldValue, } from "../fieldValueDiagnostics";
export function buildRemoteCaretFieldPatches({ remoteCarets, structureFields, activeInlineEdit, }) {
    const patches = [];
    const patchedElementKeys = new Set();
    for (const caret of Object.values(remoteCarets)) {
        if (typeof caret.value !== "string" || !caret.fieldId || !caret.item) {
            continue;
        }
        for (const structureField of structureFields) {
            if (!structureField.elementKey ||
                patchedElementKeys.has(structureField.elementKey) ||
                !bridgeIdsMatch(caret.fieldId, structureField.fieldId) ||
                !bridgeItemsMatch(caret.item, structureField.item) ||
                structureField.elementKey === activeInlineEdit?.elementKey ||
                activeInlineEditMatchesField(activeInlineEdit, structureField)) {
                continue;
            }
            patchedElementKeys.add(structureField.elementKey);
            patches.push({
                elementKey: structureField.elementKey,
                fieldId: structureField.fieldId,
                isRichText: structureField.isRichText,
                source: "remote-caret",
                value: caret.value,
            });
        }
    }
    return patches;
}
/**
 * True when a remote session is live-syncing this field (an active remote
 * caret carries a draft value for it). While that holds, saved-value echo
 * patches must not be applied to the canvas: during active typing the saved
 * value is always several keystrokes stale, and rolling the text back makes
 * concurrent caret measurements map offsets against the shorter text — the
 * remote caret visibly jumps backward and the text flickers.
 */
export function hasRemoteCaretDraftForField({ remoteCarets, fieldId, item, }) {
    if (!remoteCarets || !fieldId || !item)
        return false;
    return Object.values(remoteCarets).some((caret) => typeof caret.value === "string" &&
        bridgeIdsMatch(caret.fieldId ?? undefined, fieldId) &&
        bridgeItemsMatch(caret.item, item));
}
export function isBridgePatchableTextField(field, isRichText) {
    const fieldType = field.type.toLowerCase();
    return (isRichText === true ||
        fieldType === "rich text" ||
        fieldType === "single-line text" ||
        fieldType === "multi-line text" ||
        fieldType === "memo");
}
export function getBridgeFieldPatchValue(field) {
    if (field.rawValue !== undefined && field.rawValue !== null) {
        return String(field.rawValue);
    }
    if (field.value === undefined || field.value === null)
        return "";
    return typeof field.value === "string" ? field.value : String(field.value);
}
export function buildBridgeFieldPatchSignature(patch, observedTextContent) {
    return JSON.stringify({
        value: patch.value,
        isRichText: !!patch.isRichText,
        source: patch.source ?? "",
        observedTextContent: observedTextContent ?? "",
    });
}
/**
 * True when the last patch sent for a field (recorded as its dedupe signature)
 * put a suggestion preview on the canvas. Such fields need an explicit restore
 * patch when suggestion display turns off — the host markup still shows the
 * merged suggestion text until something overwrites it.
 */
export function isSuggestionPreviewPatchSignature(signature) {
    if (!signature)
        return false;
    try {
        const parsed = JSON.parse(signature);
        return parsed.source === "suggestion-preview";
    }
    catch {
        return false;
    }
}
export function buildBridgeFieldPatchPayload({ field, structureField, display, activeInlineEdit, }) {
    if (!structureField.elementKey) {
        logFieldValueDiagnostic("bridge-patch", "skipped patch", {
            reason: "missing element key",
            field: summarizeField(field),
            structureField: summarizeStructureField(structureField),
            displayValue: summarizeFieldValue(display.value),
            displaySource: display.source,
        });
        return undefined;
    }
    if (!isBridgePatchableTextField(field, structureField.isRichText)) {
        logFieldValueDiagnostic("bridge-patch", "skipped patch", {
            reason: "field not patchable",
            field: summarizeField(field),
            structureField: summarizeStructureField(structureField),
            displayValue: summarizeFieldValue(display.value),
            displaySource: display.source,
        });
        return undefined;
    }
    if (structureField.elementKey === activeInlineEdit?.elementKey ||
        activeInlineEditMatchesField(activeInlineEdit, structureField)) {
        logFieldValueDiagnostic("bridge-patch", "skipped patch", {
            reason: "active inline edit",
            field: summarizeField(field),
            structureField: summarizeStructureField(structureField),
            activeInlineEdit: activeInlineEdit
                ? {
                    ...activeInlineEdit,
                    value: summarizeFieldValue(activeInlineEdit.value),
                }
                : undefined,
            displayValue: summarizeFieldValue(display.value),
            displaySource: display.source,
        });
        return undefined;
    }
    const patch = {
        elementKey: structureField.elementKey,
        fieldId: structureField.fieldId,
        isRichText: structureField.isRichText || field.type.toLowerCase() === "rich text",
        source: display.source,
        value: display.value,
    };
    logFieldValueDiagnostic("bridge-patch", "prepared patch", {
        field: summarizeField(field),
        structureField: summarizeStructureField(structureField),
        patch: {
            ...patch,
            value: summarizeFieldValue(patch.value),
        },
    });
    return patch;
}
export function reconcileActiveBridgeInlineEditAfterRefresh(activeInlineEdit, refreshedFields) {
    if (!activeInlineEdit)
        return null;
    // refreshStarted has no structure snapshot. Retain the live session until
    // refreshCompleted can confirm whether its field survived the DOM update.
    if (!refreshedFields)
        return activeInlineEdit;
    const refreshedField = refreshedFields.find((field) => field.elementKey === activeInlineEdit.elementKey ||
        activeInlineEditMatchesField(activeInlineEdit, field));
    if (!refreshedField)
        return null;
    if (refreshedField.elementKey === activeInlineEdit.elementKey) {
        return activeInlineEdit;
    }
    // A host refresh can replace the field element while retaining the same
    // item/field identity. Follow its new key so subsequent bridge events still
    // belong to the existing edit session.
    return {
        ...activeInlineEdit,
        elementKey: refreshedField.elementKey,
    };
}
export function reduceActiveBridgeInlineEditForRefreshEvent(activeInlineEdit, event) {
    if (event.type === "started") {
        return reconcileActiveBridgeInlineEditAfterRefresh(activeInlineEdit, undefined);
    }
    if (event.type === "completed" && event.applied === "host-app") {
        // A host app reports request acceptance before its render lands. Its
        // refreshCompleted structure still describes the old DOM, so retain the
        // current identity until the mutation-driven structureUpdated event.
        return activeInlineEdit ?? null;
    }
    return reconcileActiveBridgeInlineEditAfterRefresh(activeInlineEdit, event.fields);
}
function summarizeStructureField(structureField) {
    return {
        elementKey: structureField.elementKey,
        fieldId: structureField.fieldId,
        isRichText: structureField.isRichText,
        item: structureField.item,
        textContent: summarizeFieldValue(structureField.textContent),
    };
}
export function activeInlineEditMatchesField(activeInlineEdit, structureField) {
    return !!(activeInlineEdit?.fieldId &&
        bridgeIdsMatch(activeInlineEdit.fieldId, structureField.fieldId) &&
        bridgeItemsMatch(activeInlineEdit.item, structureField.item));
}
function bridgeItemsMatch(left, right) {
    if (!left || !right)
        return false;
    return (bridgeIdsMatch(left.id, right.id) &&
        normalizeBridgeValue(left.language) ===
            normalizeBridgeValue(right.language) &&
        left.version === right.version);
}
function bridgeIdsMatch(left, right) {
    return normalizeBridgeId(left) === normalizeBridgeId(right);
}
function normalizeBridgeId(value) {
    return normalizeBridgeValue(value).replace(/[{}]/g, "");
}
function normalizeBridgeValue(value) {
    return (value ?? "").trim().toLowerCase();
}
//# sourceMappingURL=bridgeFieldPatch.js.map