import _ from "underscore";

import { visitInputs } from "@/components/Form/utilities";
import { isEmpty } from "@/utils/utils";

export class WorkflowRunModel {
    constructor(runData) {
        this.runData = runData;
        this.name = runData.name;
        this.workflowResourceParameters = runData.workflow_resource_parameters;
        this.hasWorkflowResourceParameters = !_.isEmpty(this.workflowResourceParameters);
        this.historyId = runData.history_id;
        this.workflowId = runData.id;

        this.hasUpgradeMessages = runData.has_upgrade_messages;
        this.hasStepVersionChanges = !_.isEmpty(runData.step_version_changes);

        this.steps = [];
        this.links = [];
        this.parms = [];
        this.wpInputs = {};
        this.parameterInputLabels = [];

        let hasOpenToolSteps = false;
        let hasReplacementParametersInToolForm = false;

        _.each(runData.steps, (step, i) => {
            const isParameterStep = step.step_type == "parameter_input";
            var title = `${parseInt(i + 1)}: ${step.step_label || step.step_name}`;
            if (step.annotation) {
                title += ` - ${step.annotation}`;
            }
            if (step.step_version) {
                title += ` (Galaxy Version ${step.step_version})`;
            }
            step = Object.assign(
                {
                    index: i,
                    fixed_title: _.escape(title),
                    expanded: i == 0 || isDataStep(step) || isParameterStep,
                    errors: step.messages,
                },
                step,
            );
            this.steps[i] = step;
            this.links[i] = [];
            this.parms[i] = {};
            if (isParameterStep && step.step_label) {
                this.parameterInputLabels.push(step.step_label);
            }
        });

        // build linear index of step input pairs
        _.each(this.steps, (step, i) => {
            visitInputs(step.inputs, (input, name) => {
                this.parms[i][name] = input;
            });
        });

        // iterate through data input modules and collect linked sub steps
        _.each(this.steps, (step, i) => {
            _.each(step.output_connections, (output_connection) => {
                _.each(this.steps, (sub_step, j) => {
                    if (sub_step.step_index === output_connection.input_step_index) {
                        this.links[i].push(sub_step);
                    }
                });
            });
        });

        // convert all connected data inputs to hidden fields with proper labels,
        // and track the linked source step
        _.each(this.steps, (step, i) => {
            _.each(this.steps, (sub_step, j) => {
                var connections_by_name = {};
                _.each(step.output_connections, (connection) => {
                    if (sub_step.step_index === connection.input_step_index) {
                        connections_by_name[connection.input_name] = connection;
                    }
                });
                _.each(this.parms[j], (input, name) => {
                    var connection = connections_by_name[name];
                    if (connection) {
                        input.connected = true;
                        input.type = "hidden";
                        input.help = input.step_linked ? `${input.help}, ` : "";
                        input.help += `Connected to '${connection.output_name}' from Step ${parseInt(i) + 1}`;
                        input.step_linked = input.step_linked || [];
                        input.step_linked.push({ index: step.index, step_type: step.step_type });
                    }
                });
            });
        });

        // identify and configure workflow parameters
        var wp_count = 0;

        const _ensureWorkflowParameter = (wp_name) => {
            return (this.wpInputs[wp_name] = this.wpInputs[wp_name] || {
                label: wp_name,
                name: wp_name,
                type: "text",
                color: `hsl( ${++wp_count * 100}, 70%, 30% )`,
                cls: "ui-input-linked",
                links: [],
                optional: true,
            });
        };

        function _handleWorkflowParameter(value, callback) {
            var re = /\$\{(.+?)\}/g;
            var match;
            while ((match = re.exec(String(value)))) {
                var wp_name = match[1];
                callback(_ensureWorkflowParameter(wp_name));
            }
        }

        _.each(this.steps, (step, i) => {
            _.each(this.parms[i], (input, name) => {
                _handleWorkflowParameter(input.value, (wp_input) => {
                    hasReplacementParametersInToolForm = true;
                    wp_input.links.push(step);
                    input.wp_linked = true;
                    input.type = "text";
                    input.cls = "ui-input-linked";
                });
            });
            _.each(step.replacement_parameters, (wp_name) => {
                if (this.parameterInputLabels.indexOf(wp_name) == -1) {
                    _ensureWorkflowParameter(wp_name);
                }
            });
        });

        // select fields are shown for dynamic fields if all putative data inputs are available,
        // or if an explicit reference is specified as data_ref and available
        _.each(this.steps, (step) => {
            if (step.step_type == "tool") {
                let dataResolved = true;
                visitInputs(step.inputs, (input, name, context) => {
                    const isRuntimeValue = input.value && input.value.__class__ == "RuntimeValue";
                    const isDataInput = ["data", "data_collection"].indexOf(input.type) != -1;
                    const dataRef = context[input.data_ref];
                    if (input.step_linked && !isDataStep(input.step_linked)) {
                        dataResolved = false;
                    }
                    if (input.options && ((input.options.length == 0 && !dataResolved) || input.wp_linked)) {
                        input.is_workflow = true;
                    }
                    if (dataRef) {
                        input.is_workflow =
                            (dataRef.step_linked && !isDataStep(dataRef.step_linked)) || input.wp_linked;
                    }
                    if ((isDataInput && !input.optional) || (!isDataInput && isRuntimeValue && !input.step_linked)) {
                        step.expanded = true;
                        hasOpenToolSteps = true;
                    }
                    if (isRuntimeValue) {
                        input.value = null;
                    }
                    input.flavor = "workflow";
                    if (!isRuntimeValue && !isDataInput && input.type !== "hidden" && !input.wp_linked) {
                        if (input.optional || (!isEmpty(input.value) && input.value !== "")) {
                            input.collapsible_value = input.value;
                            input.collapsible_preview = true;
                        }
                    }
                });
            }
        });
        this.hasOpenToolSteps = hasOpenToolSteps;
        this.hasReplacementParametersInToolForm = hasReplacementParametersInToolForm;
    }

    isConnected(stepId, inputName) {
        return this.parms[stepId][inputName].connected;
    }
}

/** Is data input module/step */
export function isDataStep(steps) {
    var lst = Array.isArray(steps) ? steps : [steps];
    for (var i = 0; i < lst.length; i++) {
        var step = lst[i];
        if (!step || !step.step_type || !step.step_type.startsWith("data")) {
            return false;
        }
    }
    return true;
}

/** Produces a dictionary of parameter replacements to be consumed by the form components */
export function getReplacements(inputs, stepData, wpData) {
    const params = {};
    visitInputs(inputs, (input, name) => {
        params[name] = input;
    });
    const replaceParams = {};
    _.each(params, (input, name) => {
        if (input.wp_linked || input.step_linked) {
            let newValue = null;
            if (input.step_linked) {
                _.each(input.step_linked, (sourceStep) => {
                    if (isDataStep(sourceStep)) {
                        const sourceData = stepData[sourceStep.index];
                        const value = sourceData && sourceData.input;
                        if (value) {
                            newValue = { values: [] };
                            _.each(value.values, (v) => {
                                newValue.values.push(v);
                            });
                        }
                    }
                });
                if (!input.multiple && newValue && newValue.values.length > 0) {
                    newValue = {
                        values: [newValue.values[0]],
                    };
                }
            }
            if (input.wp_linked) {
                newValue = input.value;
                const re = /\$\{(.+?)\}/g;
                let match;
                while ((match = re.exec(input.value))) {
                    const wpValue = wpData[match[1]];
                    if (wpValue) {
                        newValue = newValue.split(match[0]).join(wpValue);
                    }
                }
            }
            if (newValue !== undefined) {
                replaceParams[name] = newValue;
            }
        }
    });
    return replaceParams;
}
