"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonPatchDiff = void 0;
const tslib_1 = require("tslib");
const deepEqual_1 = require("@jsonjoy.com/util/lib/json-equal/deepEqual");
const str = tslib_1.__importStar(require("../util/diff/str"));
const line = tslib_1.__importStar(require("../util/diff/line"));
const json_hash_1 = require("../json-hash");
const util_1 = require("@jsonjoy.com/json-pointer/lib/util");
class JsonPatchDiff {
    constructor() {
        this.patch = [];
    }
    diffVal(path, src, dst) {
        if ((0, deepEqual_1.deepEqual)(src, dst))
            return;
        this.patch.push({ op: 'replace', path, value: dst });
    }
    diffStr(path, src, dst) {
        if (src === dst)
            return;
        const patch = this.patch;
        str.apply(str.diff(src, dst), src.length, (pos, str) => patch.push({ op: 'str_ins', path, pos, str }), (pos, len, str) => patch.push({ op: 'str_del', path, pos, len, str }));
    }
    diffBin(path, src, dst) {
        throw new Error('Not implemented');
    }
    diffObj(path, src, dst) {
        const patch = this.patch;
        for (const key in src) {
            if (key in dst) {
                const val1 = src[key];
                const val2 = dst[key];
                if (val1 === val2)
                    continue;
                this.diffAny(path + '/' + (0, util_1.escapeComponent)(key), val1, val2);
            }
            else {
                patch.push({ op: 'remove', path: path + '/' + (0, util_1.escapeComponent)(key) });
            }
        }
        for (const key in dst) {
            if (key in src)
                continue;
            patch.push({ op: 'add', path: path + '/' + (0, util_1.escapeComponent)(key), value: dst[key] });
        }
    }
    diffArr(path, src, dst) {
        const srcLines = [];
        const dstLines = [];
        const srcLen = src.length;
        const dstLen = dst.length;
        for (let i = 0; i < srcLen; i++)
            srcLines.push((0, json_hash_1.structHash)(src[i]));
        for (let i = 0; i < dstLen; i++)
            dstLines.push((0, json_hash_1.structHash)(dst[i]));
        const pfx = path + '/';
        const patch = this.patch;
        const linePatch = line.diff(srcLines, dstLines);
        const length = linePatch.length;
        for (let i = length - 1; i >= 0; i--) {
            const [type, srcIdx, dstIdx] = linePatch[i];
            switch (type) {
                case 0 /* line.LINE_PATCH_OP_TYPE.EQL */:
                    break;
                case 2 /* line.LINE_PATCH_OP_TYPE.MIX */: {
                    const srcValue = src[srcIdx];
                    const dstValue = dst[dstIdx];
                    this.diff(pfx + srcIdx, srcValue, dstValue);
                    break;
                }
                case 1 /* line.LINE_PATCH_OP_TYPE.INS */:
                    patch.push({ op: 'add', path: pfx + (srcIdx + 1), value: dst[dstIdx] });
                    break;
                case -1 /* line.LINE_PATCH_OP_TYPE.DEL */:
                    patch.push({ op: 'remove', path: pfx + srcIdx });
                    break;
            }
        }
    }
    diffAny(path, src, dst) {
        // Check for type changes first
        const srcType = Array.isArray(src) ? 'array' : typeof src;
        const dstType = Array.isArray(dst) ? 'array' : typeof dst;
        if (srcType !== dstType) {
            // Different types, replace entirely
            this.diffVal(path, src, dst);
            return;
        }
        switch (typeof src) {
            case 'string': {
                if (typeof dst === 'string')
                    this.diffStr(path, src, dst);
                else
                    this.diffVal(path, src, dst);
                break;
            }
            case 'number':
            case 'boolean':
            case 'bigint': {
                this.diffVal(path, src, dst);
                break;
            }
            case 'object': {
                if (!src || !dst || typeof dst !== 'object') {
                    this.diffVal(path, src, dst);
                    return;
                }
                if (Array.isArray(src)) {
                    if (Array.isArray(dst))
                        this.diffArr(path, src, dst);
                    else
                        this.diffVal(path, src, dst);
                    return;
                }
                this.diffObj(path, src, dst);
                break;
            }
            default:
                this.diffVal(path, src, dst);
                break;
        }
    }
    diff(path, src, dst) {
        this.diffAny(path, src, dst);
        return this.patch;
    }
}
exports.JsonPatchDiff = JsonPatchDiff;
//# sourceMappingURL=JsonPatchDiff.js.map