"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getParameterEsiExtension = exports.parameterAddressToColor = exports.isParameterValueDisabled = exports.disabledValueParameters = exports.resolveParameterIndexAndSubindex = exports.createPdoParameterValues = exports.valueTypeToParameterTypeValueKeyMap = exports.mapValueTypeToBitSize = exports.mapParameterToPdoSubItemValue = exports.fillPdoParametersWithMappingEntries = exports.filterTxPdoParameters = exports.filterRxPdoParameters = exports.isPdoIndex = exports.isTxPdoIndex = exports.isRxPdoIndex = exports.isSmmParameterIndex = exports.mapParameterTypeValueToObject = exports.parseParameterValue = exports.getParameterValue = exports.getParameterTypeValue = exports.getParameterTypeValueDefault = exports.compareParameters = exports.intersectionParametersWithDifferentValues = exports.intersectionParameters = exports.differenceParameters = exports.parametersCompareFn = exports.isParameterId = exports.splitParameterId = exports.parameterIdRegExp = exports.makeParameterId = exports.betaParameterIds = exports.parameterIdToRadix = exports.parameterIdToPlotAxis = exports.parameterIdToPlotColor = void 0;
const types_1 = require("./types");
const lodash_1 = require("lodash");
const util_1 = require("./util");
exports.parameterIdToPlotColor = {
    '0x20F0:00': '#FFFFFF',
    '0x6041:00': '#CCCCCC',
    '0x6064:00': '#B82698',
    '0x607A:00': '#FF7F2A',
    '0x6062:00': '#FF7F2A',
    '0x60FC:00': '#FF7F2A',
    '0x606C:00': '#A2CD39',
    '0x60FF:00': '#FF5555',
    '0x6077:00': '#FFDD55',
    '0x6071:00': '#C8BEB7', // Target torque
};
exports.parameterIdToPlotAxis = {
    '0x20F0:00': 'y1',
    '0x6064:00': 'y1',
    '0x607A:00': 'y1',
    '0x6062:00': 'y1',
    '0x60FC:00': 'y1', // Position demand internal value
};
exports.parameterIdToRadix = {
    '0x1000:00': 16,
    '0x2501:00': 2,
    '0x2502:00': 2,
    '0x2503:00': 2,
    '0x2504:00': 2,
    '0x2601:00': 2,
    '0x2602:00': 2,
    '0x2603:00': 2,
    '0x2604:00': 2,
    '0x2611:01': 2,
    '0x2611:02': 2,
    '0x603F:00': 16,
    '0x6041:00': 2,
    '0x60FD:00': 2,
    '0x6502:00': 2,
    '0x6621:01': 2,
    '0x6621:02': 2,
    '0x6621:03': 2,
    '0x6621:04': 2,
};
exports.betaParameterIds = [
    '0x2003:06',
    '0x2010:04',
    '0x2010:05',
    '0x2010:13',
    '0x2010:14',
    '0x2010:15',
    '0x2010:16',
    '0x2010:17', // Torque controller: Virtual Braking Chopper notification level
];
function makeParameterId(a, b) {
    let index = 0;
    let subindex = 0;
    if (Array.isArray(a)) {
        if (a[0]) {
            index = a[0];
        }
        if (a[1]) {
            subindex = a[1];
        }
    }
    else if (typeof a === 'object') {
        if (a === null || a === void 0 ? void 0 : a.index) {
            index = a.index;
        }
        if (a === null || a === void 0 ? void 0 : a.subindex) {
            subindex = a.subindex;
        }
    }
    else {
        if (typeof a === 'number') {
            index = a;
        }
        if (typeof b === 'number') {
            subindex = b;
        }
    }
    if (index < 0) {
        throw new Error(`The provided index to makeParameterId must be greater than 0: ${index}`);
    }
    if (subindex < 0) {
        throw new Error(`The provided subindex to makeParameterId must be greater than 0: ${subindex}`);
    }
    const indexMaxLength = index > 0xffff ? 8 : 4;
    const x = index.toString(16).toUpperCase().padStart(indexMaxLength, '0');
    const y = subindex.toString(16).toUpperCase().padStart(2, '0');
    return `0x${x}:${y}`;
}
exports.makeParameterId = makeParameterId;
/**
 * Example of parameter id is "0x2110:1A" where:
 * - 0x2110 is object index in hexadecimal format
 * - 1A is subindex in hexadecimal format
 */
exports.parameterIdRegExp = /^0x([0-9a-fA-F]{4,}):([0-9a-fA-F]{2})$/;
/**
 * Split device parameter id into parts.
 *
 * @param id - parameter id like "0x2110:1A"
 * @returns tuple of index and subindex
 */
function splitParameterId(id) {
    const match = id.match(exports.parameterIdRegExp);
    if (!match) {
        throw new Error(`Parameter id "${id}" doesn't match the regular expression: ${exports.parameterIdRegExp}`);
    }
    return [
        parseInt(match[1], 16),
        parseInt(match[2], 16), // subindex
    ];
}
exports.splitParameterId = splitParameterId;
/**
 * Check if the provided string matches the parameter ID.
 */
function isParameterId(id) {
    return exports.parameterIdRegExp.test(id);
}
exports.isParameterId = isParameterId;
/**
 * Parameter comparison function.
 *
 * Typically provided to sorting functions. Compares parameters by index and subindex.
 */
function parametersCompareFn(a, b) {
    if (a.index > b.index) {
        return 1;
    }
    else if (a.index < b.index) {
        return -1;
    }
    else {
        if (a.subindex > b.subindex) {
            return 1;
        }
        else if (a.subindex < b.subindex) {
            return -1;
        }
        else {
            return 0;
        }
    }
}
exports.parametersCompareFn = parametersCompareFn;
/**
 * Creates an array of parameters not included in the other given array. Compares by index and subindex.
 */
function differenceParameters(p1, p2) {
    return (0, lodash_1.differenceWith)(p1, p2, (a, b) => a.index === b.index && a.subindex === b.subindex);
}
exports.differenceParameters = differenceParameters;
/**
 * Creates an array of unique parameters that are included in both given arrays. Compares by index and subindex.
 */
function intersectionParameters(p1, p2) {
    return (0, lodash_1.intersectionWith)(p1, p2, (a, b) => a.index === b.index && a.subindex === b.subindex);
}
exports.intersectionParameters = intersectionParameters;
/**
 * Creates an array of unique parameters that are included in both given arrays, but only if the values of parameters differ.
 */
function intersectionParametersWithDifferentValues(p1, p2, epsilon = 0) {
    return (0, lodash_1.intersectionWith)(p1, p2, (a, b) => {
        if (a.index === b.index && a.subindex === b.subindex) {
            // Use an epsilon or a tolerance to account for the limitations of floating-point representation.
            if (epsilon && typeof a.value === 'number' && typeof b.value === 'number') {
                return Math.abs(a.value - b.value) > epsilon;
            }
            else if (Array.isArray(a.value) && Array.isArray(b.value)) {
                return JSON.stringify(a.value) !== JSON.stringify(b.value);
            }
            else {
                return a.value !== b.value;
            }
        }
        return false;
    });
}
exports.intersectionParametersWithDifferentValues = intersectionParametersWithDifferentValues;
/**
 * Compare parameters.
 *
 * Compare parameters by creating the parameters comparison result, which includes the differences, intersections, and comparisons {@link ParametersComparison}.
 */
function compareParameters(p1, p2, epsilon = 0) {
    const d1 = differenceParameters(p1, p2);
    const d2 = differenceParameters(p2, p1);
    const i1 = intersectionParametersWithDifferentValues(p1, p2, epsilon);
    const i2 = intersectionParametersWithDifferentValues(p2, p1, epsilon);
    const c1 = i1.map((p, index) => (Object.assign(Object.assign({}, p), { valueOther: i2[index].value })));
    const c2 = i2.map((p, index) => (Object.assign(Object.assign({}, p), { valueOther: i1[index].value })));
    return {
        differences: [d1, d2],
        intersections: [i1, i2],
        comparisons: [c1, c2],
    };
}
exports.compareParameters = compareParameters;
function getParameterTypeValueDefault(key) {
    if (key === 'floatValue' || key === 'intValue' || key === 'uintValue') {
        return 0;
    }
    else if (key === 'rawValue') {
        return Uint8Array.from([]);
    }
    else if (key === 'stringValue') {
        return '';
    }
    else {
        return 0;
    }
}
exports.getParameterTypeValueDefault = getParameterTypeValueDefault;
function getParameterTypeValue(parameter) {
    return types_1.MotionMasterMessage.Status.DeviceParameterValues.ParameterValue.create(parameter).typeValue;
}
exports.getParameterTypeValue = getParameterTypeValue;
function getParameterValue(parameter) {
    var _a, _b;
    const key = getParameterTypeValue(parameter);
    if ('value' in parameter) {
        return (_a = parameter['value']) !== null && _a !== void 0 ? _a : (key ? getParameterTypeValueDefault(key) : 0);
    }
    if (key && key in parameter) {
        return (_b = parameter[key]) !== null && _b !== void 0 ? _b : getParameterTypeValueDefault(key);
    }
    return 0;
}
exports.getParameterValue = getParameterValue;
function parseParameterValue(type, value) {
    var _a, _b;
    if (type === 'floatValue') {
        return typeof value === 'number' ? value : parseFloat(value);
    }
    else if (type === 'intValue') {
        return typeof value === 'number' ? value : parseInt(value, 10);
    }
    else if (type === 'rawValue') {
        if (typeof value === 'number') {
            return Uint8Array.from([value]);
        }
        else {
            let bytes = [];
            if (value.startsWith('0x')) {
                bytes =
                    (_b = (_a = value
                        .slice(2)
                        .match(/.{1,2}/g)) === null || _a === void 0 ? void 0 : _a.map((h) => parseInt(h, 16))) !== null && _b !== void 0 ? _b : [];
            }
            else {
                bytes = value
                    .split(',')
                    .map((c) => c.trim())
                    .map((c) => parseInt(c, 10));
            }
            return Uint8Array.from(bytes);
        }
    }
    else if (type === 'stringValue') {
        return typeof value === 'string' ? value : value.toString();
    }
    else if (type === 'uintValue') {
        return typeof value === 'number' ? value : parseInt(value, 10);
    }
    else {
        throw new Error(`Unrecognized ParameterTypeValueKey: ${type}`);
    }
}
exports.parseParameterValue = parseParameterValue;
function mapParameterTypeValueToObject(type, value) {
    var _a, _b;
    const p = {};
    if (type === 'floatValue') {
        p['floatValue'] = typeof value === 'number' ? value : parseFloat(value);
    }
    else if (type === 'intValue') {
        p['intValue'] = typeof value === 'number' ? value : parseInt(value, 10);
    }
    else if (type === 'rawValue') {
        if (typeof value === 'number') {
            p['rawValue'] = Uint8Array.from([value]);
        }
        else {
            let bytes = [];
            if (value.startsWith('0x')) {
                bytes =
                    (_b = (_a = value
                        .slice(2)
                        .match(/.{1,2}/g)) === null || _a === void 0 ? void 0 : _a.map((h) => parseInt(h, 16))) !== null && _b !== void 0 ? _b : [];
            }
            else {
                bytes = value
                    .split(',')
                    .map((c) => c.trim())
                    .map((c) => parseInt(c, 10));
            }
            p['rawValue'] = Uint8Array.from(bytes);
        }
    }
    else if (type === 'stringValue') {
        p['stringValue'] = typeof value === 'string' ? value : value.toString();
    }
    else if (type === 'uintValue') {
        p['uintValue'] = typeof value === 'number' ? value : parseInt(value, 10);
    }
    return p;
}
exports.mapParameterTypeValueToObject = mapParameterTypeValueToObject;
/**
 * The safety object index range in the CiA 402 profile is 0x6600 - 0x67EF.
 * In the manufacturer-specific area, we mirror this range with 0x2600 - 0x27EF.
 * However, prior to SMM, custom objects were added with indexes starting at 0x27.
 * To avoid conflicts, we need to adjust the condition to include only object indexes up to 0x2700.
 */
function isSmmParameterIndex(index) {
    return (index >= 0x2600 && index <= 0x26ff) || (index >= 0x6600 && index <= 0x67ef);
}
exports.isSmmParameterIndex = isSmmParameterIndex;
function isRxPdoIndex(index) {
    return (0, lodash_1.inRange)(index, 0x1600, 0x1800);
}
exports.isRxPdoIndex = isRxPdoIndex;
function isTxPdoIndex(index) {
    return (0, lodash_1.inRange)(index, 0x1a00, 0x1c00);
}
exports.isTxPdoIndex = isTxPdoIndex;
function isPdoIndex(index) {
    return isRxPdoIndex(index) || isTxPdoIndex(index);
}
exports.isPdoIndex = isPdoIndex;
function filterRxPdoParameters(parameters, excludeSubIndex000 = false) {
    return parameters.filter((p) => isRxPdoIndex(p.index) && (excludeSubIndex000 ? p.subindex > 0 : true)); // RxPDOs: Index area 0x1600 to 0x17FF
}
exports.filterRxPdoParameters = filterRxPdoParameters;
function filterTxPdoParameters(parameters, excludeSubIndex000 = false) {
    return parameters.filter((p) => isTxPdoIndex(p.index) && (excludeSubIndex000 ? p.subindex > 0 : true)); // TxPDOs: Index area 0x1A00 to 0x1BFF
}
exports.filterTxPdoParameters = filterTxPdoParameters;
/**
 * Fills in missing PDO mapping entries for each parameter group that starts with a subindex of 0.
 *
 * For each group of parameters with the same `index` and starting at `subindex` 0, this function:
 * 1. Adds the existing parameters to the output.
 * 2. Appends placeholder `Parameter` entries with incremented subindexes until the total count reaches `entriesPerPdo`.
 *
 * This is useful for ensuring that each PDO mapping table has a consistent number of entries,
 * even if some are unused and need to be filled with default placeholder values.
 *
 * @param parameters - The list of existing parameters to process.
 * @param entriesPerPdo - The number of total entries (including placeholders) each PDO should have. Defaults to 10.
 *
 * @returns A new array of `Parameter` objects where each PDO group is filled with entries up to `entriesPerPdo`.
 *
 * @example
 * ```ts
 * const params: Parameter[] = [
 *   { index: 0x1600, subindex: 0, ... },
 *   { index: 0x1600, subindex: 1, ... },
 * ];
 *
 * const filled = fillPdoParametersWithMappingEntries(params, 3);
 * // filled will include the two original entries plus one default placeholder with subindex 2
 * ```
 */
function fillPdoParametersWithMappingEntries(parameters, entriesPerPdo = 10) {
    const output = [];
    let i = 0;
    while (i < parameters.length) {
        if (parameters[i].subindex === 0) {
            let j = i;
            while (parameters[j] && parameters[j].index === parameters[i].index) {
                output.push(parameters[j]);
                j++;
            }
            let d = j - i;
            while (d <= entriesPerPdo) {
                output.push({
                    id: parameters[i].id,
                    index: parameters[i].index,
                    subindex: d,
                    name: parameters[i].name,
                    uintValue: 0,
                    value: 0,
                    valueType: types_1.ValueType.UNSIGNED32,
                    group: parameters[i].group,
                    typeValueKey: 'uintValue',
                    readAccess: true,
                    writeAccess: true,
                });
                d++;
            }
            i = j;
        }
        else {
            i++;
        }
    }
    return output;
}
exports.fillPdoParametersWithMappingEntries = fillPdoParametersWithMappingEntries;
function mapParameterToPdoSubItemValue(parameter) {
    const { index, subindex } = parameter;
    if (parameter.valueType && parameter.valueType < types_1.ValueType.VISIBLE_STRING) {
        const buffer = new ArrayBuffer(4);
        const view = new DataView(buffer);
        const bitSize = mapValueTypeToBitSize(parameter.valueType);
        view.setUint16(0, index);
        view.setUint8(2, subindex);
        view.setUint8(3, bitSize);
        return view.getUint32(0);
    }
    return 0;
}
exports.mapParameterToPdoSubItemValue = mapParameterToPdoSubItemValue;
function mapValueTypeToBitSize(valueType) {
    switch (valueType) {
        case types_1.ValueType.UNSPECIFIED:
            return 0;
        case types_1.ValueType.BOOLEAN:
            return 1;
        case types_1.ValueType.INTEGER8:
        case types_1.ValueType.UNSIGNED8:
            return 8;
        case types_1.ValueType.INTEGER16:
        case types_1.ValueType.UNSIGNED16:
            return 16;
        case types_1.ValueType.INTEGER32:
        case types_1.ValueType.UNSIGNED32:
        case types_1.ValueType.REAL32:
            return 32;
    }
    throw new Error(`Non-numeric value type "${valueType}" (data types with variable length) cannot be mapped to bit size. You will have to get this info from ESI.`);
}
exports.mapValueTypeToBitSize = mapValueTypeToBitSize;
exports.valueTypeToParameterTypeValueKeyMap = new Map([
    [types_1.ValueType.BOOLEAN, 'uintValue'],
    [types_1.ValueType.INTEGER8, 'intValue'],
    [types_1.ValueType.INTEGER16, 'intValue'],
    [types_1.ValueType.INTEGER32, 'intValue'],
    [types_1.ValueType.UNSIGNED8, 'uintValue'],
    [types_1.ValueType.UNSIGNED16, 'uintValue'],
    [types_1.ValueType.UNSIGNED32, 'uintValue'],
    [types_1.ValueType.REAL32, 'floatValue'],
    [types_1.ValueType.VISIBLE_STRING, 'stringValue'],
    [types_1.ValueType.OCTET_STRING, 'rawValue'],
    [types_1.ValueType.UNICODE_STRING, 'stringValue'],
    [types_1.ValueType.TIME_OF_DAY, 'uintValue'],
]);
function createPdoParameterValues(parameters, values) {
    const output = parameters.map((parameter, i, arr) => {
        const { index, subindex, typeValue } = parameter;
        let value = values[i];
        if (subindex === 0) {
            value = arr.reduce((n, p, j) => (p.index === index && p.subindex > 0 && values[j] ? n + 1 : n), 0);
        }
        return [index, subindex, value, typeValue];
    });
    for (let i = 0; i < output.length - 1; i++) {
        const [, subindex1, value1] = output[i];
        if (subindex1 > 0 && value1 === 0) {
            let j = i + 1;
            let [, subindex2, value2] = output[j];
            while (subindex2 > subindex1 && output[j]) {
                if (typeof value2 === 'number' && value2 > 0) {
                    output[i][2] = value2;
                    output[j][2] = 0;
                    break;
                }
                if (output[++j]) {
                    [, subindex2, value2] = output[j];
                }
            }
        }
    }
    return output;
}
exports.createPdoParameterValues = createPdoParameterValues;
/**
 * Resolves the index and subindex for the given parameter.
 *
 * @param parameter - The parameter to resolve, which can be a `ParameterIndexSubindex` object, a string, or a tuple of two numbers.
 * @returns A tuple containing the index and subindex.
 */
function resolveParameterIndexAndSubindex(parameter) {
    if (Array.isArray(parameter)) {
        return parameter;
    }
    else if (typeof parameter === 'string') {
        return splitParameterId(parameter);
    }
    else {
        const { index, subindex } = parameter;
        return [index, subindex];
    }
}
exports.resolveParameterIndexAndSubindex = resolveParameterIndexAndSubindex;
/**
 * This array contains the list of parameters that are disabled by setting their value to a specific value.
 */
exports.disabledValueParameters = [
    /**
     * Protection: Undervoltage setpoint.
     * Beside 0, negative values are also considered disabled. We could introduce a range check here.
     */
    { index: 0x2006, subindex: 1, disabledValue: 0 },
    /**
     * Protection: Overvoltage setpoint.
     */
    { index: 0x2006, subindex: 2, disabledValue: 2147483647 },
    /**
     * Protection: Overcurrent setpoint.
     */
    { index: 0x2006, subindex: 3, disabledValue: 2147483647 },
    /**
     * Torque window: Value.
     */
    { index: 0x2014, subindex: 1, disabledValue: 65535 },
    /**
     * Velocity feed forward: Gain.
     */
    { index: 0x2015, subindex: 1, disabledValue: 0 },
    /**
     * Velocity following error: Window.
     */
    { index: 0x2018, subindex: 1, disabledValue: 65535 },
    /**
     * Velocity feedback filter: Cutoff frequency.
     */
    { index: 0x2021, subindex: 2, disabledValue: 0 },
    /**
     * Position feedback filter: Cutoff frequency.
     */
    { index: 0x2022, subindex: 2, disabledValue: 0 },
    /**
     * Following error window.
     */
    { index: 0x6065, subindex: 0, disabledValue: 4294967295 },
    /**
     * Position window.
     */
    { index: 0x6067, subindex: 0, disabledValue: 4294967295 },
    /**
     * Velocity window.
     */
    { index: 0x606d, subindex: 0, disabledValue: 65535 },
    /**
     * Software position limit: Min position limit.
     */
    { index: 0x607d, subindex: 1, disabledValue: -2147483648 },
    /**
     * Software position limit: Max position limit.
     */
    { index: 0x607d, subindex: 2, disabledValue: 2147483647 },
];
/**
 * Determines if the value of the given parameter is considered disabled.
 *
 * For example, the "Velocity Following Error: Window" parameter is considered disabled when its value is 65535.
 *
 * @param parameter - The parameter to check, which can be an object with `index`, `subindex`, and `value` properties, or a tuple of three numbers.
 * @returns `true` if the parameter is disabled, `false` otherwise.
 */
function isParameterValueDisabled(parameter) {
    const [index, subindex, value] = Array.isArray(parameter)
        ? parameter
        : [parameter.index, parameter.subindex, parameter.value];
    return exports.disabledValueParameters.some((p) => p.index === index && p.subindex === subindex && p.disabledValue === value);
}
exports.isParameterValueDisabled = isParameterValueDisabled;
exports.parameterAddressToColor = {
    '0x20F0:00': '#FFFFFF',
    '0x6041:00': '#CCCCCC',
    '0x6064:00': '#B82698',
    '0x607A:00': '#FF7F2A',
    '0x6062:00': '#FF7F2A',
    '0x60FC:00': '#FF7F2A',
    '0x606C:00': '#A2CD39',
    '0x60FF:00': '#FF5555',
    '0x6077:00': '#FFDD55',
    '0x6071:00': '#C8BEB7',
    '0x60F40000:01': '#DC5361',
    '0x60F40000:02': '#D19147',
    '0x60F40000:03': '#E4CE86', // Torque following error
};
/**
 * Retrieves extended EtherCAT Slave Information (ESI) metadata for a given device parameter.
 *
 * This function extracts and constructs additional descriptive and validation
 * information for a device parameter based on the ESI file data, including
 * constraints, default values, units, descriptions, mapping capabilities,
 * and input type hints.
 *
 * The returned object extends the base device parameter with ESI-specific
 * metadata that can be used for validation, UI generation, and device
 * communication mapping.
 *
 * @param parameter - Partial device parameter containing `index`, `subindex`, `typeValueKey`,
 *                    and optionally `typeValue`. Identifies the parameter to extend.
 * @param info - The EtherCAT master or module info object containing
 *               descriptions and device dictionaries as parsed from the ESI file.
 *               (Type from `ecatmod.EtherCATInfo` expected but typed as `any` here.)
 * @param productCode - The EtherCAT device product code used to locate the matching device
 *                      description within the ESI data.
 *                      This product code can typically be read from the device's object dictionary
 *                      at index `0x1018` (Identity Object), subindex `2`.
 *
 * @returns A {@link ParameterEsiExtension} object containing extended parameter metadata,
 *          such as mandatory flags, min/max constraints, default values,
 *          units, bit sizes, descriptions, options, PDO mapping flags, SMM module flag,
 *          group information, type keys, and UI input hints.
 *
 * @remarks
 * - Supports parameter metadata extraction from both the main device profile dictionary
 *   and sub-modules (e.g., for modular devices like SMM LW2).
 * - For STRING types, the maximum length is adjusted to accommodate firmware limitations.
 * - Descriptions and options are parsed and cleaned from JSON-like strings.
 * - Handles both variable data type objects and complex objects with sub-items.
 * - Logs a warning if no matching device is found for the given product code.
 * - Preserves original unit and options for reference or restoration.
 */
function getParameterEsiExtension(parameter, info, productCode) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
    const out = {};
    // Find the EtherCAT device matching the given product code.
    const ecatDevice = (_b = (_a = info.descriptions) === null || _a === void 0 ? void 0 : _a.devices) === null || _b === void 0 ? void 0 : _b.device.find((d) => {
        var _a;
        return (0, util_1.hexDecValueToNumber)((_a = d.type) === null || _a === void 0 ? void 0 : _a.productCode) === productCode;
    });
    // Assumes the device has only one profile.
    // If the device has multiple profiles, this logic may need to be extended.
    let dictionary = (_c = ecatDevice === null || ecatDevice === void 0 ? void 0 : ecatDevice.profile[0]) === null || _c === void 0 ? void 0 : _c.dictionary;
    // If no dictionary is found in the device:
    // - It may be located in a sub-module instead.
    // - The device entry may not exist at all for this product code.
    // - The same product code could potentially be shared among multiple devices.
    if (!dictionary) {
        console.warn('No matching device in ESI found by product code!', productCode);
        return out;
    }
    // Attempt to find the object by index in the primary device dictionary.
    let object = dictionary.objects.findByIndex(parameter.index);
    // If not found, look into device modules (e.g., for modular devices like SMM LW2, shared object dictionary).
    if (!object) {
        const deviceModules = (_d = info.descriptions) === null || _d === void 0 ? void 0 : _d.getDeviceModules(ecatDevice);
        if (deviceModules) {
            // Iterate modules in reverse to prioritize the most recently added ones,
            // since newer modules (like SMM LW2) tend to contain more complete definitions.
            for (let i = deviceModules.length - 1; i >= 0; i--) {
                const module = deviceModules[i];
                // Try to find the object in the module's dictionary.
                object = (_g = (_f = (_e = module.profile) === null || _e === void 0 ? void 0 : _e.dictionary) === null || _f === void 0 ? void 0 : _f.objects) === null || _g === void 0 ? void 0 : _g.findByIndex(parameter.index);
                if (object) {
                    // If found, update the active dictionary reference to the module's.
                    dictionary = module.profile.dictionary;
                    // Identify if the object belongs to a known SMM module based on moduleIdent.
                    const moduleIdent = (_j = (_h = module.type) === null || _h === void 0 ? void 0 : _h.moduleIdent) === null || _j === void 0 ? void 0 : _j.toLowerCase();
                    if (['#x22d20001', '#x22d20002', '#x22d20003', '#x22d20004'].includes(moduleIdent)) {
                        out.isSmm = true;
                    }
                    break; // Exit early once the object is found.
                }
            }
        }
    }
    // If no object was found in either the device or its modules dictionaries,
    // return the current out (likely empty) as there is nothing to extend.
    if (!object) {
        return out;
    }
    // Check if the object is of a variable data type
    if (dictionary.isObjectOfDataTypeVariable(object)) {
        // Determine if the parameter is mandatory by checking the 'category' flag ('m' = mandatory)
        out.mandatory = dictionary.adoptedObjectFlag(object, 'category') === 'm';
        // For non-floating-point types, retrieve min and max allowed values for validation
        if (!dictionary.isObjectTypeFloating(object)) {
            // Skip min/max validation for REAL (floating) types due to complexity or unsupported constraints
            out.min = dictionary.minAllowedValueForObject(object);
            out.max = dictionary.maxAllowedValueForObject(object);
        }
        // Get the default value of the object, converted to a number if possible
        out.defaultData = dictionary.defaultDataToNumberForObject(object);
        // Extract unit symbol if available; fallback to empty string if none provided
        out.unit = object.info.unit ? object.info.unitSymbol : '';
        // Store the bit size of the object (parameter size in bits)
        out.bitSize = object.bitSize;
        // Handle STRING type parameters by extracting the defined max length for validation
        const strTypeRegex = /STRING\((\d+)\)/;
        const strTypeRegexMatch = object.type.match(strTypeRegex);
        if (strTypeRegexMatch) {
            // The firmware encounters issues when STRING(50) is used, and a value of 48 or more characters is assigned.
            // Firmware will fail to load the value, resulting in an empty string. With 47 characters it works.
            const strlen = parseInt(strTypeRegexMatch[1], 10) - 3;
            out.strlen = strlen < 1 ? 1 : strlen;
        }
        // Store the exact ESI data type string (e.g., "UINT16", "STRING(50)", etc.)
        out.esiType = object.type;
        // Fetch a human-readable description for the parameter
        out.description = dictionary.getDescriptionForObject(object);
        // Attempt to retrieve options for the parameter, if any, and parse them from JSON-like string
        const options = dictionary.getOptionsForObject(object);
        if (options) {
            // Clean up line breaks before parsing to avoid JSON parse errors
            out.options = JSON.parse(options.replace(/\r?\n|\r/g, ''));
        }
        // Determine if parameter can be mapped as a RxPDO based on the 'pdoMapping' flag
        out.canBeMappedAsRxPdo = ['r', 'tr'].includes(dictionary.adoptedObjectFlag(object, 'pdoMapping'));
        // Determine if parameter can be mapped as a TxPDO based on the 'pdoMapping' flag
        out.canBeMappedAsTxPdo = ['t', 'tr'].includes(dictionary.adoptedObjectFlag(object, 'pdoMapping'));
    }
    else {
        // Assign the group name from the object's nameValue property
        out.group = object.nameValue;
        // Find the specific sub-item of the object matching the device parameter's subindex
        const subItem = dictionary.findObjectSubItemBySubIndex(object, parameter.subindex);
        if (!subItem && isPdoIndex(parameter.index)) {
            console.warn(`No subitem found for object ${parameter.index}:${parameter.subindex} ${object.nameValue}.`);
        }
        else {
            // Get the data type information for this sub-item
            const subItemType = dictionary.subItemTypeForObjectSubItem(object, subItem);
            // Determine if this sub-item is mandatory by checking its 'category' flag ('m' means mandatory)
            out.mandatory = dictionary.adoptedObjectSubItemFlag(object, subItem, 'category') === 'm';
            // For non-floating sub-item types, retrieve the minimum and maximum allowed values for validation
            // Floating-point types (REAL) skip min/max validation due to complexity or lack of constraints
            if (!dictionary.isObjectSubItemTypeFloating(object, subItem)) {
                out.min = dictionary.minAllowedValueForObjectSubItem(object, subItem);
                out.max = dictionary.maxAllowedValueForObjectSubItem(object, subItem);
            }
            // Extract the default value for the sub-item, converted to a number if possible
            out.defaultData = dictionary.defaultDataToNumberForObjectSubItem(object, subItem);
            // Use unit symbol from the sub-item's info if available; fallback to empty string otherwise
            out.unit = subItem.info.unit ? subItem.info.unitSymbol : '';
            // Store the bit size and ESI data type string of the sub-item for further use
            out.bitSize = subItemType.bitSize;
            out.esiType = dictionary.baseTypeForObjectSubItem(object, subItem);
            // Retrieve the general description for the parent object
            const objectDescription = dictionary.getDescriptionForObject(object);
            // Handle description and options differently depending on subindex and data type
            if (parameter.subindex === 0) {
                // For subindex 0, use the object description directly
                out.description = objectDescription;
            }
            else {
                if (dictionary.isObjectOfDataTypeArray(object)) {
                    // For array data types, use the object description and attempt to parse options
                    out.description = objectDescription;
                    const options = dictionary.getOptionsForObject(object);
                    if (options) {
                        // Clean up newlines before parsing JSON options
                        out.options = JSON.parse(options.replace(/\r?\n|\r/g, ''));
                    }
                }
                else {
                    // For record data types, store the object description as recordDescription
                    if (dictionary.isObjectOfDataTypeRecord(object)) {
                        out.recordDescription = objectDescription;
                    }
                    // For sub-items, get a more specific description and options
                    out.description = dictionary.getDescriptionForObjectSubItem(object, subItem);
                    const options = dictionary.getOptionsForObjectSubItem(object, subItem);
                    if (options) {
                        out.options = JSON.parse(options.replace(/\r?\n|\r/g, ''));
                    }
                }
            }
            // Determine if the sub-item can be mapped as a RxPDO based on its 'pdoMapping' flag
            out.canBeMappedAsRxPdo = ['r', 'tr'].includes(dictionary.adoptedObjectSubItemFlag(object, subItem, 'pdoMapping'));
            // Determine if the sub-item can be mapped as a TxPDO based on its 'pdoMapping' flag
            out.canBeMappedAsTxPdo = ['t', 'tr'].includes(dictionary.adoptedObjectSubItemFlag(object, subItem, 'pdoMapping'));
        }
    }
    // Correct the case where the dictionary returns the string 'undefined'
    // instead of an actual undefined value for the description.
    if (out.description === 'undefined') {
        out.description = undefined;
    }
    // Assign typeValueKey and typeValue from parameter if typeValueKey is present.
    // This prioritizes typeValueKey over typeValue for consistency.
    if (parameter.typeValueKey) {
        out.typeValueKey = parameter.typeValueKey;
        out.typeValue = parameter.typeValueKey;
    }
    else if (parameter.typeValue) {
        // Otherwise, fallback to using typeValue if available.
        out.typeValue = parameter.typeValue;
    }
    if (((_k = out.esiType) === null || _k === void 0 ? void 0 : _k.startsWith('STRING')) || ((_l = out.esiType) === null || _l === void 0 ? void 0 : _l.startsWith('ARRAY'))) {
        out.inputType = 'text';
    }
    else {
        out.inputType = 'number';
    }
    // If the parameter has predefined options, override input type to a select dropdown.
    if (out.options) {
        out.inputType = 'select';
    }
    // Support IPv4 input type for ethernet fieldbus:
    // 1. IP address
    // 2. Network mask
    // 3. Gateway
    // 4. Name server
    // 5. Name server 2
    if (parameter.index === 0x2e00 && [1, 2, 3, 4, 5].includes(parameter.subindex)) {
        out.inputType = 'ipv4';
    }
    // Preserve the original unit and options for later reference or restoration.
    out.originalUnit = out.unit;
    out.originalOptions = out.options;
    return out;
}
exports.getParameterEsiExtension = getParameterEsiExtension;
//# sourceMappingURL=parameter.js.map