"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DiffOperation = void 0;
exports.lineModeDiff = lineModeDiff;
exports.convertFlowToStepList = convertFlowToStepList;
exports.convertStepListToFlow = convertStepListToFlow;
exports.flattenTestFlowsIntoSteps = flattenTestFlowsIntoSteps;
exports.constructTestFlowsFromSteps = constructTestFlowsFromSteps;
exports.flattenTestFlowsIntoLineDiffingFormat = flattenTestFlowsIntoLineDiffingFormat;
exports.convertDiffingFormatIntoDiffingSteps = convertDiffingFormatIntoDiffingSteps;
exports.diffTests = diffTests;
exports.diffFlows = diffFlows;
const typescript_api_client_1 = require("@mablhq-internal/typescript-api-client");
const importer_1 = require("../importer");
const EvaluateFlowStep_1 = require("../steps/EvaluateFlowStep");
const diffPatchMatch_1 = __importDefault(require("./diffPatchMatch"));
const fast_json_stable_stringify_1 = __importDefault(require("fast-json-stable-stringify"));
var DiffOperation;
(function (DiffOperation) {
    DiffOperation["DELETE"] = "DELETE";
    DiffOperation["EQUAL"] = "EQUAL";
    DiffOperation["INSERT"] = "INSERT";
})(DiffOperation || (exports.DiffOperation = DiffOperation = {}));
function isLegacyFlow(flow) {
    return !!flow.script && !flow.json_steps?.steps;
}
function hasAnyLegacyFlows(flows) {
    return flows.some(isLegacyFlow);
}
function lineModeDiff(text1, text2) {
    const dmp = new diffPatchMatch_1.default();
    const lineToCharDiff = dmp.diff_linesToChars(text1, text2);
    const lineText1 = lineToCharDiff.chars1;
    const lineText2 = lineToCharDiff.chars2;
    const lineArray = lineToCharDiff.lineArray;
    const diffs = dmp.diff_main(lineText1, lineText2, false);
    dmp.diff_charsToLines(diffs, lineArray);
    const typedDiff = diffs.map((diff) => ({
        operation: convertOperationValue(diff[0]),
        text: diff[1],
    }));
    return typedDiff;
}
function convertOperationValue(op) {
    switch (op) {
        case -1:
            return DiffOperation.DELETE;
        case 0:
            return DiffOperation.EQUAL;
        case 1:
            return DiffOperation.INSERT;
        default:
            return DiffOperation.EQUAL;
    }
}
function parseFlowIntoStepList(flow) {
    if (flow.flow_type === typescript_api_client_1.FlowTypeEnum.Mobile) {
        return (0, importer_1.interpretStepsFromObjectsInFlow)(flow, true);
    }
    return (0, importer_1.parseMablScriptIntoSteps)(flow);
}
function convertFlowToStepList(flow, flowConfig) {
    if (!flow.reusable &&
        (flow.flow_type === typescript_api_client_1.FlowTypeEnum.Mablscript ||
            flow.flow_type === typescript_api_client_1.FlowTypeEnum.Mobile)) {
        return parseFlowIntoStepList(flow);
    }
    const evaluateFlowStep = new EvaluateFlowStep_1.EvaluateFlowStep({
        flow: { invariant_id: flow?.invariant_id ?? '' },
        config: flowConfig,
    });
    if (flow.description) {
        evaluateFlowStep.setDescription(flow.description);
    }
    return [evaluateFlowStep];
}
function convertStepListToFlow(steps, flowType = typescript_api_client_1.FlowTypeEnum.Mablscript, useMablscriptV2Format = false) {
    if (steps.length > 1 && steps.some((step) => (0, EvaluateFlowStep_1.isEvaluateFlowStep)(step))) {
        throw new Error('EvaluateFlow step(s) cannot be combined with other steps in a flow');
    }
    if ((0, EvaluateFlowStep_1.isEvaluateFlowStep)(steps[0])) {
        const evaluateFlowStep = steps[0];
        const { flowDescriptor, config: flowConfig } = evaluateFlowStep;
        const description = evaluateFlowStep.getDescription();
        const flow = { ...flowDescriptor };
        if (description) {
            flow.description = description;
        }
        return { flow, flowConfig };
    }
    const stepAnnotations = steps.reduce((annotations, step, index) => {
        const annotation = step.getAnnotation();
        if (annotation) {
            const key = Number(index).toString();
            annotations[key] = annotation;
        }
        return annotations;
    }, {});
    const scriptDescription = steps.map(getStepDescription).join('\n');
    const useJsonSteps = flowType === typescript_api_client_1.FlowTypeEnum.Mobile ||
        (flowType === typescript_api_client_1.FlowTypeEnum.Mablscript && useMablscriptV2Format);
    const flow = {
        script_description: scriptDescription,
        step_annotations: stepAnnotations,
    };
    if (useJsonSteps) {
        flow.json_steps = {
            steps: steps.map((step) => step.getFormattedStep()),
        };
    }
    else {
        flow.script = (0, importer_1.mablscriptTheLoadedSteps)(steps);
    }
    return { flow };
}
function getStepDescription(step) {
    if (step.getStepVersion() === 2) {
        return step.stepDescription();
    }
    return step.getDescription() ?? '';
}
function flattenTestFlowsIntoSteps(flows, flowConfigs) {
    return flows.flatMap((flow, index) => convertFlowToStepList(flow, flowConfigs?.[index]));
}
function constructTestFlowsFromSteps(steps, flowType = typescript_api_client_1.FlowTypeEnum.Mablscript, useMablscriptV2Format = false) {
    const flowSteps = [];
    let chunk = [];
    for (const step of steps) {
        if ((0, EvaluateFlowStep_1.isEvaluateFlowStep)(step)) {
            if (chunk.length > 0) {
                flowSteps.push(chunk);
                flowSteps.push([step]);
                chunk = [];
            }
            else {
                flowSteps.push([step]);
            }
        }
        else {
            chunk.push(step);
        }
    }
    if (chunk.length > 0) {
        flowSteps.push(chunk);
    }
    const output = {
        flows: [],
        flowConfigs: {},
    };
    flowSteps.reduce((output, chunk, index) => {
        const { flow, flowConfig } = convertStepListToFlow(chunk, flowType, useMablscriptV2Format);
        output.flows.push(flow);
        if (flowConfig) {
            const key = Number(index).toString();
            output.flowConfigs[key] = flowConfig;
        }
        return output;
    }, output);
    return output;
}
function flattenTestFlowsIntoLineDiffingFormat(flows, flowConfig, omitStepId = false) {
    const flattenedSteps = flattenTestFlowsIntoSteps(flows, flowConfig);
    const diffingFormat = addTrailingNewline(flattenedSteps.map((step) => step.toLineDiffFormat(omitStepId)).join('\n'));
    return { flattenedSteps, diffingFormat };
}
function convertDiffingFormatIntoDiffingSteps(diffs, sourceSteps, targetSteps) {
    const leftSteps = [...sourceSteps];
    const rightSteps = [...targetSteps];
    leftSteps.forEach((step) => {
        step.setStepSourceIndexInFlow(0);
    });
    rightSteps.forEach((step) => {
        step.setStepSourceIndexInFlow(0);
    });
    const stepDiffs = diffs.map((diff) => {
        const lineDiffs = diff.text.trim().split('\n');
        const stepDiffCount = lineDiffs.length;
        let steps = [];
        switch (diff.operation) {
            case DiffOperation.EQUAL:
                steps = leftSteps.splice(0, stepDiffCount);
                rightSteps.splice(0, stepDiffCount);
                break;
            case DiffOperation.INSERT:
                steps = rightSteps.splice(0, stepDiffCount);
                break;
            case DiffOperation.DELETE:
                steps = leftSteps.splice(0, stepDiffCount);
                break;
        }
        return {
            operation: diff.operation,
            steps,
        };
    });
    return stepDiffs;
}
function diffTests(test1Flows, test1FlowConfig, test2Flows, test2FlowConfig) {
    const omitStepId = hasAnyLegacyFlows(test1Flows) || hasAnyLegacyFlows(test2Flows);
    const { flattenedSteps: flattenedTest1Steps, diffingFormat: test1DiffingFormat, } = flattenTestFlowsIntoLineDiffingFormat(test1Flows, test1FlowConfig, omitStepId);
    const { flattenedSteps: flattenedTest2Steps, diffingFormat: test2DiffingFormat, } = flattenTestFlowsIntoLineDiffingFormat(test2Flows, test2FlowConfig, omitStepId);
    const diffs = lineModeDiff(test1DiffingFormat, test2DiffingFormat);
    return convertDiffingFormatIntoDiffingSteps(diffs, flattenedTest1Steps, flattenedTest2Steps);
}
function diffFlows(flow1, flow2) {
    const omitStepId = isLegacyFlow(flow1) || isLegacyFlow(flow2);
    const flow1Steps = parseFlowIntoStepList(flow1);
    const formattedFlow1Steps = addTrailingNewline(flow1Steps.map((step) => step.toLineDiffFormat(omitStepId)).join('\n'));
    const flow2Steps = parseFlowIntoStepList(flow2);
    const formattedFlow2Steps = addTrailingNewline(flow2Steps.map((step) => step.toLineDiffFormat(omitStepId)).join('\n'));
    const flattenedDiffs = lineModeDiff(formattedFlow1Steps, formattedFlow2Steps);
    const stepDiffs = convertDiffingFormatIntoDiffingSteps(flattenedDiffs, flow1Steps, flow2Steps);
    const parametersDiff = diffFlowVariables(flow1.parameters, flow2.parameters);
    const inputVariablesDiff = diffFlowVariables(flow1.variables?.inputs, flow2.variables?.inputs);
    return { stepDiff: stepDiffs, parametersDiff, inputVariablesDiff };
}
function diffFlowVariables(flow1Variables, flow2Variables) {
    const flow1VariablesFlattened = flattenFlowVariables(flow1Variables);
    const flow2VariablesFlattened = flattenFlowVariables(flow2Variables);
    const diffs = lineModeDiff(flow1VariablesFlattened, flow2VariablesFlattened);
    const flowVariablesDiff = diffs.map((diff) => {
        const variablesStringified = diff.text.trim().split('\n');
        const reassembledVariables = variablesStringified.map((variablesText) => JSON.parse(variablesText));
        return {
            operation: diff.operation,
            variables: reassembledVariables,
        };
    });
    return flowVariablesDiff;
}
function flattenFlowVariables(flowVariables) {
    return (flowVariables
        ?.sort((a, b) => a.name.localeCompare(b.name))
        .map((parameter) => (0, fast_json_stable_stringify_1.default)(parameter))
        .join('\n') ?? '');
}
function addTrailingNewline(text) {
    if (!text) {
        return text;
    }
    return text + '\n';
}
