import { ErrorCode, ValueTag } from '@bilig/protocol';
import { exactLookupNumberKey, normalizeExactLookupNumber, sameExactLookupNumber as formulaSameExactLookupNumber } from '@bilig/formula';
export function directLookupVersionMatches(lookupSheet, lookup) {
    if ((lookupSheet?.structureVersion ?? 0) !== lookup.structureVersion) {
        return false;
    }
    const currentColumnVersion = lookupSheet?.columnVersions?.[lookup.col] ?? 0;
    return currentColumnVersion === lookup.columnVersion || currentColumnVersion === lookup.tailPatch?.columnVersion;
}
export function withOptionalLookupStringIds(request) {
    return {
        sheetName: request.sheetName,
        row: request.row,
        col: request.col,
        oldValue: request.oldValue,
        newValue: request.newValue,
        ...(request.oldStringId === undefined ? {} : { oldStringId: request.oldStringId }),
        ...(request.newStringId === undefined ? {} : { newStringId: request.newStringId }),
        ...(request.inputCellIndex === undefined ? {} : { inputCellIndex: request.inputCellIndex }),
    };
}
export function normalizeExactLookupKey(value, lookupString, stringId = 0) {
    switch (value.tag) {
        case ValueTag.Empty:
            return 'e:';
        case ValueTag.Number:
            return exactLookupNumberKey(value.value);
        case ValueTag.Boolean:
            return value.value ? 'b:1' : 'b:0';
        case ValueTag.String:
            return `s:${(stringId !== 0 ? lookupString(stringId) : value.value).toUpperCase()}`;
        case ValueTag.Error:
            return undefined;
    }
}
export function normalizeExactNumericValue(value) {
    return value.tag === ValueTag.Number ? normalizeExactLookupNumber(value.value) : undefined;
}
export function normalizeExactNumericNumber(value) {
    return normalizeExactLookupNumber(value);
}
export function sameExactNumericValue(left, right) {
    return formulaSameExactLookupNumber(left, right);
}
export function sameExactNumericValueAsNormalized(left, normalizedRight) {
    return normalizeExactLookupNumber(left) === normalizedRight;
}
export function normalizeApproximateNumericValue(value) {
    switch (value.tag) {
        case ValueTag.Empty:
            return 0;
        case ValueTag.Number:
            return Object.is(value.value, -0) ? 0 : value.value;
        case ValueTag.Boolean:
            return value.value ? 1 : 0;
        case ValueTag.String:
        case ValueTag.Error:
            return undefined;
    }
}
export function normalizeApproximateTextValue(value, lookupString, stringId = 0) {
    switch (value.tag) {
        case ValueTag.Empty:
            return '';
        case ValueTag.String:
            return (stringId !== 0 ? lookupString(stringId) : value.value).toUpperCase();
        case ValueTag.Number:
        case ValueTag.Boolean:
        case ValueTag.Error:
            return undefined;
    }
}
export function exactLookupLiteralNumericValue(value) {
    return typeof value === 'number' ? normalizeExactLookupNumber(value) : undefined;
}
export function canSkipUniformApproximateNumericTailWrite(directLookup, row, operandNumeric, oldNumeric, newNumeric) {
    if (directLookup.repeatedRunLength !== undefined) {
        return false;
    }
    if (directLookup.matchMode === 1 && directLookup.step > 0) {
        return row === directLookup.rowEnd && oldNumeric > operandNumeric && newNumeric > operandNumeric && newNumeric >= oldNumeric;
    }
    if (directLookup.matchMode === -1 && directLookup.step < 0) {
        return row === directLookup.rowEnd && oldNumeric < operandNumeric && newNumeric < operandNumeric && newNumeric <= oldNumeric;
    }
    return false;
}
export function canSkipUniformApproximateNumericTailWriteFromCurrentResult(cellStore, formulaCellIndex, directLookup, row, oldNumeric, newNumeric) {
    if (directLookup.repeatedRunLength !== undefined) {
        return false;
    }
    if (!((directLookup.matchMode === 1 && directLookup.step > 0 && row === directLookup.rowEnd && newNumeric >= oldNumeric) ||
        (directLookup.matchMode === -1 && directLookup.step < 0 && row === directLookup.rowEnd && newNumeric <= oldNumeric))) {
        return false;
    }
    return ((cellStore.tags[formulaCellIndex] ?? ValueTag.Empty) === ValueTag.Number &&
        (cellStore.numbers[formulaCellIndex] ?? 0) < directLookup.length);
}
export function canSkipUniformExactNumericTailWriteFromCurrentResult(cellStore, formulaCellIndex, directLookup, row, oldNumeric, newNumeric) {
    if (directLookup.tailPatch !== undefined || row !== directLookup.rowEnd || directLookup.length < 2) {
        return false;
    }
    const expectedOldTail = directLookup.start + directLookup.step * (directLookup.length - 1);
    if (!sameExactNumericValue(oldNumeric, expectedOldTail)) {
        return false;
    }
    if (!(directLookup.step > 0 ? newNumeric > oldNumeric : newNumeric < oldNumeric)) {
        return false;
    }
    return ((cellStore.tags[formulaCellIndex] ?? ValueTag.Empty) === ValueTag.Number &&
        (cellStore.numbers[formulaCellIndex] ?? 0) < directLookup.length);
}
export function directLookupRowBounds(directLookup) {
    switch (directLookup.kind) {
        case 'exact':
        case 'approximate':
            return {
                rowStart: directLookup.prepared.rowStart,
                rowEnd: directLookup.prepared.rowEnd,
            };
        case 'exact-uniform-numeric':
        case 'approximate-uniform-numeric':
            return {
                rowStart: directLookup.rowStart,
                rowEnd: directLookup.rowEnd,
            };
    }
}
export function exactUniformLookupCurrentResult(directLookup, lookupValue) {
    const numericResult = exactUniformLookupNumericResult(directLookup, lookupValue);
    return numericResult === undefined ? { kind: 'error', code: ErrorCode.NA } : { kind: 'number', value: numericResult };
}
export function exactUniformLookupNumericResult(directLookup, lookupValue) {
    const normalizedLookupValue = normalizeExactLookupNumber(lookupValue);
    const tailPatch = directLookup.tailPatch;
    if (tailPatch === undefined && directLookup.step === 1) {
        if (!Number.isInteger(normalizedLookupValue)) {
            return undefined;
        }
        const position = normalizedLookupValue - normalizeExactLookupNumber(directLookup.start) + 1;
        return position >= 1 && position <= directLookup.length ? position : undefined;
    }
    if (tailPatch === undefined && directLookup.step === -1) {
        if (!Number.isInteger(normalizedLookupValue)) {
            return undefined;
        }
        const position = normalizeExactLookupNumber(directLookup.start) - normalizedLookupValue + 1;
        return position >= 1 && position <= directLookup.length ? position : undefined;
    }
    if (tailPatch !== undefined) {
        if (sameExactNumericValue(normalizedLookupValue, tailPatch.newNumeric)) {
            return tailPatch.row - directLookup.rowStart + 1;
        }
        if (sameExactNumericValue(normalizedLookupValue, tailPatch.oldNumeric)) {
            return undefined;
        }
    }
    if (directLookup.step === 1) {
        if (!Number.isInteger(normalizedLookupValue)) {
            return undefined;
        }
        const position = normalizedLookupValue - normalizeExactLookupNumber(directLookup.start) + 1;
        return position >= 1 && position <= directLookup.length ? position : undefined;
    }
    if (directLookup.step === -1) {
        if (!Number.isInteger(normalizedLookupValue)) {
            return undefined;
        }
        const position = normalizeExactLookupNumber(directLookup.start) - normalizedLookupValue + 1;
        return position >= 1 && position <= directLookup.length ? position : undefined;
    }
    const relative = (normalizedLookupValue - normalizeExactLookupNumber(directLookup.start)) / normalizeExactLookupNumber(directLookup.step);
    const nearestOffset = Math.round(relative);
    if (nearestOffset < 0 || nearestOffset >= directLookup.length) {
        return undefined;
    }
    const candidate = directLookup.start + directLookup.step * nearestOffset;
    return sameExactNumericValue(candidate, normalizedLookupValue) ? nearestOffset + 1 : undefined;
}
export function approximateUniformLookupCurrentResult(directLookup, lookupValue) {
    const numericResult = approximateUniformLookupNumericResult(directLookup, lookupValue);
    if (numericResult !== undefined) {
        return { kind: 'number', value: numericResult };
    }
    if ((directLookup.matchMode === 1 && directLookup.step > 0 && lookupValue < directLookup.start) ||
        (directLookup.matchMode === -1 && directLookup.step < 0 && lookupValue > directLookup.start)) {
        return { kind: 'error', code: ErrorCode.NA };
    }
    return undefined;
}
export function approximateUniformLookupNumericResult(directLookup, lookupValue) {
    if (directLookup.repeatedRunLength !== undefined) {
        return approximateRepeatedUniformLookupNumericResult(directLookup.length, directLookup.start, directLookup.step, directLookup.repeatedRunLength, directLookup.matchMode, lookupValue);
    }
    const tailPatch = directLookup.tailPatch;
    if (tailPatch === undefined && directLookup.matchMode === 1 && directLookup.step === 1) {
        if (lookupValue < directLookup.start) {
            return undefined;
        }
        const position = Math.floor(lookupValue - directLookup.start) + 1;
        return position >= directLookup.length ? directLookup.length : position;
    }
    if (tailPatch === undefined && directLookup.matchMode === -1 && directLookup.step === -1) {
        if (lookupValue > directLookup.start) {
            return undefined;
        }
        const position = Math.floor(directLookup.start - lookupValue) + 1;
        return position >= directLookup.length ? directLookup.length : position;
    }
    const lastValue = directLookup.start + directLookup.step * (directLookup.length - 1);
    if (directLookup.matchMode === 1 && directLookup.step > 0) {
        if (lookupValue < directLookup.start) {
            return undefined;
        }
        if (tailPatch !== undefined && tailPatch.row === directLookup.rowEnd && tailPatch.newNumeric > tailPatch.oldNumeric) {
            if (lookupValue >= tailPatch.newNumeric) {
                return directLookup.length;
            }
            if (lookupValue >= tailPatch.oldNumeric) {
                return directLookup.length - 1;
            }
        }
        if (lookupValue >= lastValue) {
            return directLookup.length;
        }
        const position = directLookup.step === 1
            ? Math.floor(lookupValue - directLookup.start) + 1
            : Math.floor((lookupValue - directLookup.start) / directLookup.step) + 1;
        return Math.min(directLookup.length, Math.max(1, position));
    }
    if (directLookup.matchMode === -1 && directLookup.step < 0) {
        if (lookupValue > directLookup.start) {
            return undefined;
        }
        if (tailPatch !== undefined && tailPatch.row === directLookup.rowEnd && tailPatch.newNumeric < tailPatch.oldNumeric) {
            if (lookupValue <= tailPatch.newNumeric) {
                return directLookup.length;
            }
            if (lookupValue <= tailPatch.oldNumeric) {
                return directLookup.length - 1;
            }
        }
        if (lookupValue <= lastValue) {
            return directLookup.length;
        }
        const position = directLookup.step === -1
            ? Math.floor(directLookup.start - lookupValue) + 1
            : Math.floor((directLookup.start - lookupValue) / -directLookup.step) + 1;
        return Math.min(directLookup.length, Math.max(1, position));
    }
    return undefined;
}
export function approximateRepeatedUniformLookupNumericResult(length, repeatedUniformStart, repeatedUniformStep, repeatedUniformRunLength, matchMode, lookupValue) {
    if (repeatedUniformStart === undefined || repeatedUniformStep === undefined || repeatedUniformRunLength === undefined || length <= 0) {
        return undefined;
    }
    const groupCount = Math.ceil(length / repeatedUniformRunLength);
    const lastValue = repeatedUniformStart + repeatedUniformStep * (groupCount - 1);
    if (matchMode === 1 && repeatedUniformStep > 0) {
        if (lookupValue < repeatedUniformStart) {
            return undefined;
        }
        if (lookupValue >= lastValue) {
            return length;
        }
        const group = Math.floor((lookupValue - repeatedUniformStart) / repeatedUniformStep);
        const position = (group + 1) * repeatedUniformRunLength;
        return position >= length ? length : position;
    }
    if (matchMode === -1 && repeatedUniformStep < 0) {
        if (lookupValue > repeatedUniformStart) {
            return undefined;
        }
        if (lookupValue <= lastValue) {
            return length;
        }
        const group = Math.floor((repeatedUniformStart - lookupValue) / -repeatedUniformStep);
        const position = (group + 1) * repeatedUniformRunLength;
        return position >= length ? length : position;
    }
    return undefined;
}
export function approximateRepeatedUniformLookupCurrentResult(prepared, matchMode, lookupValue) {
    const { repeatedUniformStart, repeatedUniformStep, repeatedUniformRunLength, length } = prepared;
    const numericResult = approximateRepeatedUniformLookupNumericResult(length, repeatedUniformStart, repeatedUniformStep, repeatedUniformRunLength, matchMode, lookupValue);
    if (numericResult !== undefined) {
        return { kind: 'number', value: numericResult };
    }
    if (repeatedUniformStart === undefined || repeatedUniformStep === undefined || repeatedUniformRunLength === undefined || length <= 0) {
        return undefined;
    }
    const groupCount = Math.ceil(length / repeatedUniformRunLength);
    const lastValue = repeatedUniformStart + repeatedUniformStep * (groupCount - 1);
    if (matchMode === 1 && repeatedUniformStep > 0) {
        if (lookupValue < repeatedUniformStart) {
            return { kind: 'error', code: ErrorCode.NA };
        }
        return lookupValue >= lastValue ? { kind: 'number', value: length } : undefined;
    }
    if (matchMode === -1 && repeatedUniformStep < 0) {
        if (lookupValue > repeatedUniformStart) {
            return { kind: 'error', code: ErrorCode.NA };
        }
        return lookupValue <= lastValue ? { kind: 'number', value: length } : undefined;
    }
    return undefined;
}
//# sourceMappingURL=direct-lookup-helpers.js.map