"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validatePredicateOperation = exports.validateOperation = exports.validateOperations = void 0;
const validate_1 = require("@jsonjoy.com/json-pointer/lib/validate");
const hasOwnProperty_1 = require("@jsonjoy.com/util/lib/hasOwnProperty");
const validateOperations = (ops, allowMatchesOp = false) => {
    if (!Array.isArray(ops))
        throw new Error('Not a array.');
    if (!ops.length)
        throw new Error('Empty operation patch.');
    for (let i = 0; i < ops.length; i++) {
        const op = ops[i];
        try {
            (0, exports.validateOperation)(op, allowMatchesOp);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            throw new Error(`Error in operation [index = ${i}] (${message}).`);
        }
    }
};
exports.validateOperations = validateOperations;
const validateOperation = (op, allowMatchesOp) => {
    if (!op || typeof op !== 'object')
        throw new Error('OP_INVALID');
    const path = op.path;
    if (typeof path !== 'string')
        throw new Error('OP_PATH_INVALID');
    (0, validate_1.validateJsonPointer)(path);
    switch (op.op) {
        case 'add':
            validateOperationAdd(op);
            break;
        case 'remove':
            validateOperationRemove(op);
            break;
        case 'replace':
            validateOperationReplace(op);
            break;
        case 'copy':
            validateOperationCopy(op);
            break;
        case 'move':
            validateOperationMove(op);
            break;
        case 'flip':
            break;
        case 'inc':
            validateOperationInc(op);
            break;
        case 'str_ins':
            validateOperationInsertText(op);
            break;
        case 'str_del':
            validateOperationRemoveText(op);
            break;
        case 'extend':
            validateOperationExtend(op);
            break;
        case 'merge':
            validateOperationMerge(op);
            break;
        case 'split':
            validateOperationSplit(op);
            break;
        default:
            (0, exports.validatePredicateOperation)(op, allowMatchesOp);
    }
};
exports.validateOperation = validateOperation;
const validatePredicateOperation = (op, allowMatchesOp) => {
    if (!op || typeof op !== 'object')
        throw new Error('OP_INVALID');
    (0, validate_1.validateJsonPointer)(op.path);
    switch (op.op) {
        case 'test':
            validateOperationTest(op);
            break;
        case 'test_type':
            validateOperationTestType(op);
            break;
        case 'test_string':
            validateOperationTestString(op);
            break;
        case 'test_string_len':
            validateOperationTestStringLen(op);
            break;
        case 'matches':
            if (!allowMatchesOp)
                throw new Error('"matches" operation not allowed.');
            validateOperationPredicateWithValueAndCase(op);
            break;
        case 'contains':
        case 'ends':
        case 'starts':
            validateOperationPredicateWithValueAndCase(op);
            break;
        case 'in':
            if (!Array.isArray(op.value))
                throw new Error('"in" operation "value" must be an array.');
            break;
        case 'more':
        case 'less':
            if (typeof op.value !== 'number')
                throw new Error('Value must be a number.');
            break;
        case 'type':
            validateValueString(op.value);
            validateTestType(op.value);
            break;
        case 'defined':
        case 'undefined':
            break;
        case 'and':
        case 'or':
        case 'not':
            if (!Array.isArray(op.apply))
                throw new Error(`"${op.op}" predicate operators must be an array.`);
            if (!op.apply.length)
                throw new Error('Predicate list is empty.');
            for (const predicate of op.apply)
                (0, exports.validatePredicateOperation)(predicate, allowMatchesOp);
            break;
        default:
            throw new Error('OP_UNKNOWN');
    }
};
exports.validatePredicateOperation = validatePredicateOperation;
const validateOperationAdd = (op) => {
    validateValue(op.value);
};
const validateOperationRemove = (op) => {
    if ((0, hasOwnProperty_1.hasOwnProperty)(op, 'oldValue') && op.oldValue === undefined)
        throw new Error('Invalid oldValue.');
};
const validateOperationReplace = (op) => {
    if ((0, hasOwnProperty_1.hasOwnProperty)(op, 'oldValue') && op.oldValue === undefined)
        throw new Error('Invalid oldValue.');
};
const validateOperationCopy = (op) => {
    const from = op.from;
    if (typeof from !== 'string')
        throw new Error('OP_FROM_INVALID');
    (0, validate_1.validateJsonPointer)(from);
};
const validateOperationMove = (op) => {
    const from = op.from;
    if (typeof from !== 'string')
        throw new Error('OP_FROM_INVALID');
    (0, validate_1.validateJsonPointer)(from);
    const { path } = op;
    if (path.indexOf(from + '/') === 0)
        throw new Error('Cannot move into own children.');
};
const validateOperationTest = (op) => {
    validateValue(op.value);
    validateNot(op.not);
};
const validateOperationTestType = (op) => {
    if (!Array.isArray(op.type))
        throw new Error('Invalid "type" field.');
    if (op.type.length < 1)
        throw new Error('Empty type list.');
    for (const type of op.type)
        validateTestType(type);
};
const validTypes = new Set(['string', 'number', 'boolean', 'object', 'integer', 'array', 'null']);
const validateTestType = (type) => {
    if (!validTypes.has(type))
        throw new Error('Invalid type.');
};
const validateOperationTestString = (op) => {
    validateNot(op.not);
    validateNonNegativeInteger(op.pos);
    if (typeof op.str !== 'string')
        throw new Error('Value must be a string.');
};
const validateOperationTestStringLen = (op) => {
    validateNot(op.not);
    validateNonNegativeInteger(op.len);
};
const validateOperationInc = (op) => {
    if (typeof op.inc !== 'number')
        throw new Error('Invalid "inc" value.');
};
const validateOperationInsertText = (op) => {
    validateNonNegativeInteger(op.pos);
    if (typeof op.str !== 'string')
        throw new Error('Expected a string "text" field.');
};
const validateOperationRemoveText = (op) => {
    validateNonNegativeInteger(op.pos);
    if (op.str === undefined && op.len === undefined)
        throw new Error('Either "text" or "pos" need to be set.');
    if (op.str !== undefined) {
        if (typeof op.str !== 'string')
            throw new Error('Expected a string "text" field.');
    }
    else {
        validateNonNegativeInteger(op.len);
    }
};
const validateOperationExtend = (op) => {
    if (!op.props || typeof op.props !== 'object')
        throw new Error('Invalid "props" field.');
    if (op.deleteNull !== undefined)
        if (typeof op.deleteNull !== 'boolean')
            throw new Error('Expected "deleteNull" field to be boolean.');
};
const validateOperationMerge = (op) => {
    validateInteger(op.pos);
    if (op.pos < 1)
        throw new Error('Expected "pos" field to be greater than 0.');
    if (op.props)
        if (typeof op.props !== 'object')
            throw new Error('Invalid "props" field.');
};
const validateOperationSplit = (op) => {
    validateInteger(op.pos);
    if (op.props)
        if (typeof op.props !== 'object')
            throw new Error('Invalid "props" field.');
};
const validateOperationPredicateWithValueAndCase = (op) => {
    validateValueString(op.value);
    validateIgnoreCase(op.ignore_case);
};
const validateValue = (value) => {
    if (value === undefined)
        throw new Error('OP_VALUE_MISSING');
};
const validateValueString = (value) => {
    if (typeof value !== 'string')
        throw new Error('Expected "value" to be string.');
    if (value.length > 20000)
        throw new Error('Value too long.');
};
const validateIgnoreCase = (ignore) => {
    if (ignore === undefined)
        return;
    if (typeof ignore !== 'boolean')
        throw new Error('Expected "ignore_case" to be a boolean.');
};
const validateNot = (not) => {
    if (not !== undefined)
        if (typeof not !== 'boolean')
            throw new Error('Invalid "not" modifier.');
};
const validateInteger = (num) => {
    if (typeof num !== 'number' || num !== Math.round(num))
        throw new Error('Not an integer.');
};
const validateNonNegativeInteger = (num) => {
    validateInteger(num);
    if (num < 0)
        throw new Error('Number is negative.');
};
//# sourceMappingURL=validate.js.map