"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyOperation = applyOperation;
exports.applyPatch = applyPatch;
const clone_1 = require("@jsonjoy.com/util/lib/json-clone/clone");
const hasOwnProperty_1 = require("@jsonjoy.com/util/lib/hasOwnProperty");
const json_pointer_1 = require("@jsonjoy.com/json-pointer");
const deepEqual_1 = require("@jsonjoy.com/util/lib/json-equal/deepEqual");
const { isArray } = Array;
function applyOperation(doc, operation) {
    const path = operation.path;
    const isRoot = !path;
    if (isRoot) {
        switch (operation.op) {
            case 'add':
            case 'replace':
                doc = operation.value;
                return { doc: operation.value, old: doc };
            case 'remove':
                return { doc: null, old: doc };
            case 'move': {
                const { val } = (0, json_pointer_1.findByPointer)(operation.from, doc);
                return { doc: val, old: doc };
            }
            case 'copy': {
                const { val } = (0, json_pointer_1.findByPointer)(operation.from, doc);
                return { doc: val, old: doc };
            }
            case 'test': {
                if (!(0, deepEqual_1.deepEqual)(operation.value, doc))
                    throw new Error('TEST');
                return { doc };
            }
        }
        return { doc };
    }
    let indexOfSlash = 0;
    let indexAfterSlash = 1;
    let obj = doc;
    let key = '';
    while (indexOfSlash > -1) {
        indexOfSlash = path.indexOf('/', indexAfterSlash);
        key = indexOfSlash > -1 ? path.substring(indexAfterSlash, indexOfSlash) : path.substring(indexAfterSlash);
        indexAfterSlash = indexOfSlash + 1;
        if (isArray(obj)) {
            const length = obj.length;
            if (key === '-')
                key = length;
            else {
                const key2 = ~~key;
                if ('' + key2 !== key)
                    throw new Error('INVALID_INDEX');
                key = key2;
                if (key < 0 || key > length)
                    throw new Error('INVALID_INDEX');
            }
            if (indexOfSlash === -1) {
                switch (operation.op) {
                    case 'add': {
                        const old = obj[key];
                        if (key < obj.length)
                            obj.splice(key, 0, operation.value);
                        else
                            obj.push(operation.value);
                        return { doc, old };
                    }
                    case 'replace': {
                        const old = obj[key];
                        obj[key] = operation.value;
                        return { doc, old };
                    }
                    case 'remove': {
                        const old = obj[key];
                        obj.splice(key, 1);
                        return { doc, old };
                    }
                    case 'move': {
                        const removeResult = applyOperation(doc, { op: 'remove', path: operation.from });
                        return applyOperation(removeResult.doc, { op: 'add', path: operation.path, value: removeResult.old });
                    }
                    case 'copy': {
                        const old = obj[key];
                        const { val } = (0, json_pointer_1.findByPointer)(operation.from, doc);
                        const value = (0, clone_1.clone)(val);
                        if (key < obj.length)
                            obj.splice(key, 0, value);
                        else
                            obj.push(value);
                        return { doc, old };
                    }
                    case 'test': {
                        if (!(0, deepEqual_1.deepEqual)(operation.value, obj[key]))
                            throw new Error('TEST');
                        return { doc };
                    }
                }
                break;
            }
            obj = obj[key];
        }
        else if (typeof obj === 'object' && !!obj) {
            key = (0, json_pointer_1.unescapeComponent)(key);
            if (indexOfSlash === -1) {
                switch (operation.op) {
                    case 'add': {
                        const old = obj[key];
                        obj[key] = operation.value;
                        return { doc, old };
                    }
                    case 'replace': {
                        const old = obj[key];
                        obj[key] = operation.value;
                        return { doc, old };
                    }
                    case 'remove': {
                        const old = obj[key];
                        delete obj[key];
                        return { doc, old };
                    }
                    case 'move': {
                        const removeResult = applyOperation(doc, { op: 'remove', path: operation.from });
                        const addResult = applyOperation(doc, { op: 'add', path: operation.path, value: removeResult.old });
                        return addResult;
                    }
                    case 'copy': {
                        const { val } = (0, json_pointer_1.findByPointer)(operation.from, doc);
                        const value = (0, clone_1.clone)(val);
                        const old = obj[key];
                        obj[key] = value;
                        return { doc, old };
                    }
                    case 'test': {
                        if (!(0, deepEqual_1.deepEqual)(operation.value, obj[key]))
                            throw new Error('TEST');
                        return { doc };
                    }
                }
                break;
            }
            obj = (0, hasOwnProperty_1.hasOwnProperty)(obj, key) ? obj[key] : undefined;
        }
        else
            throw new Error('NOT_FOUND');
    }
    return { doc };
}
function applyPatch(doc, patch, options) {
    if (!options.mutate)
        doc = (0, clone_1.clone)(doc);
    const res = [];
    for (let i = 0; i < patch.length; i++) {
        const operation = patch[i];
        const opResult = applyOperation(doc, operation);
        res.push(opResult);
        doc = opResult.doc;
    }
    return { doc, res };
}
//# sourceMappingURL=v3.js.map