"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePlantModelCsv = exports.convertCommutationAngleOffsetToElectricalDegrees = exports.computeGearRatio = exports.factoryResetDefaultFilesToKeep = exports.isFilenamePart = exports.emptyAppBase64 = exports.stackUnlockFilename = exports.computeZeroHomeOffsetFromPosition = exports.parseDeviceLogContent = exports.parseDeviceLog = exports.isValidDeviceRefObj = exports.isDeviceRef = exports.makeDeviceRefObj = exports.ensureDeviceRef = exports.getDeviceRefFromDeviceRefObj = exports.filenamePartRegExp = void 0;
const util_1 = require("./util");
exports.filenamePartRegExp = /^(.*?)(?:\.zip(?:\.part\d+)?)$/;
/**
 * Returns the first defined value from a {@link DeviceRefObj}.
 */
function getDeviceRefFromDeviceRefObj(deviceRefObj) {
    for (const [_, value] of Object.entries(deviceRefObj)) {
        if (value !== undefined) {
            return value;
        }
    }
    return null;
}
exports.getDeviceRefFromDeviceRefObj = getDeviceRefFromDeviceRefObj;
/**
 * Ensures that a {@link DeviceRef} is numeric if possible.
 *
 * If the value is numeric-like, it is converted to a number; otherwise the original string is returned.
 */
function ensureDeviceRef(deviceRef) {
    const n = Number(deviceRef);
    return isNaN(n) ? deviceRef : n;
}
exports.ensureDeviceRef = ensureDeviceRef;
/**
 * Converts a {@link DeviceRef} into a structured {@link DeviceRefObj}.
 *
 * - Numbers ≤ `0xffff` are treated as device positions.
 * - Larger numbers are treated as device addresses.
 * - Strings are treated as serial numbers.
 */
function makeDeviceRefObj(deviceRef) {
    deviceRef = ensureDeviceRef(deviceRef);
    if (typeof deviceRef === 'number') {
        if (deviceRef <= 0xffff) {
            return { devicePosition: deviceRef };
        }
        else {
            return { deviceAddress: deviceRef };
        }
    }
    else {
        return { deviceSerialNumber: deviceRef };
    }
}
exports.makeDeviceRefObj = makeDeviceRefObj;
/**
 * Type guard verifying that a value is a valid {@link DeviceRef}.
 */
function isDeviceRef(deviceRef) {
    return (deviceRef !== undefined &&
        deviceRef !== null &&
        (typeof deviceRef === 'string' || (typeof deviceRef === 'number' && deviceRef >= 0)));
}
exports.isDeviceRef = isDeviceRef;
/**
 * Validates the structure of a {@link DeviceRefObj}.
 */
function isValidDeviceRefObj(obj) {
    return (Number.isFinite(Number(obj.deviceAddress)) ||
        (typeof obj.devicePosition === 'number' && obj.devicePosition >= 0) ||
        typeof obj.deviceSerialNumber === 'string');
}
exports.isValidDeviceRefObj = isValidDeviceRefObj;
/**
 * Parses a single device log line.
 *
 * Expected format:
 * `HH:MM:SS.mmm LEVEL|MESSAGE`
 */
function parseDeviceLog(line) {
    const re = /^(\d{2}:\d{2}:\d{2}\.\d+)\s(.*)\|(.*)$/gm;
    const result = re.exec(line);
    if (result) {
        return {
            time: result[1].trim(),
            level: result[2].trim(),
            message: result[3].trim(),
        };
    }
    return;
}
exports.parseDeviceLog = parseDeviceLog;
/**
 * Parses multi-line log content into an array of {@link DeviceLogLine}.
 */
function parseDeviceLogContent(content) {
    return content
        .split('\n')
        .map((v) => parseDeviceLog(v))
        .filter((l) => l !== undefined);
}
exports.parseDeviceLogContent = parseDeviceLogContent;
/**
 * Computes the "zero home offset" given a home offset and the current device position.
 */
function computeZeroHomeOffsetFromPosition(homeOffset, position) {
    return homeOffset - position;
}
exports.computeZeroHomeOffsetFromPosition = computeZeroHomeOffsetFromPosition;
/**
 * Name of the FoE file used to unlock protected filesystem operations.
 *
 * Sending a FoE read command with this filename allows writing or deleting
 * protected files (i.e., files whose names start with a dot).
 */
exports.stackUnlockFilename = 'fs-stackunlock=DD1317';
/**
 * Base64-encoded empty firmware image used during Factory Reset
 * to remove the currently installed firmware.
 */
exports.emptyAppBase64 = 'UEsDBBQACAAIABBye08AAAAAAAAAAAAAAAANACAAYXBwX2VtcHR5LmJpblVUDQAHMHfeXTB33l0wd95ddXgLAAEE6AMAAAToAwAAAwBQSwcIAAAAAAIAAAAAAAAAUEsBAhQDFAAIAAgAEHJ7TwAAAAACAAAAAAAAAA0AIAAAAAAAAAAAAKSBAAAAAGFwcF9lbXB0eS5iaW5VVA0ABzB33l0wd95dMHfeXXV4CwABBOgDAAAE6AMAAFBLBQYAAAAAAQABAFsAAABdAAAAAAA=';
/**
 * Returns `true` if the filename represents a multi-part ZIP file.
 */
function isFilenamePart(filename) {
    return exports.filenamePartRegExp.test(filename);
}
exports.isFilenamePart = isFilenamePart;
/**
 * Default set of files preserved during Factory Reset.
 */
exports.factoryResetDefaultFilesToKeep = [
    '.assembly_config',
    '.factory_config',
    '.hardware_description',
    '.variant',
    'stack_info.json',
];
/**
 * Computes the effective mechanical gear ratio between motor revolutions and
 * shaft revolutions. The returned value represents:
 *
 *    gearRatio = motorRevolutions / shaftRevolutions
 *
 * Special handling is applied when both the torque controller and the position
 * controller use the same encoder source. In that case, there is no physical
 * external gearing, meaning both controllers observe identical positions, so
 * the gear ratio is considered to be 1.
 *
 * If shaft revolutions equals 0 (invalid or unavailable data), a default gear
 * ratio of 1 is returned as a safe fallback.
 *
 * @param props Object containing:
 * - `motorRevolutions` Number of motor-side revolutions defined in device parameters.
 * - `shaftRevolutions` Number of shaft-side revolutions defined in device parameters.
 * - `torqueControllerEncoderSource` Encoder source used by the torque controller. A value of 0 means no assigned encoder.
 * - `positionControllerEncoderSource` Encoder source used by the position controller. A value of 0 means no assigned encoder.
 *
 * @returns The computed gear ratio. Produces:
 * - `1` if both controllers use the same encoder source, or if shaftRevolutions is `0`.
 * - `(motorRevolutions / shaftRevolutions)` otherwise.
 */
function computeGearRatio(props) {
    const { motorRevolutions, shaftRevolutions, torqueControllerEncoderSource, positionControllerEncoderSource } = props;
    // Encoder source is considered assigned when value is non-zero.
    // If both controllers reference the same encoder configuration,
    // there is effectively no external gearing observed.
    const bothControllersUseSameEncoderConfiguration = torqueControllerEncoderSource !== 0 && torqueControllerEncoderSource === positionControllerEncoderSource;
    if (bothControllersUseSameEncoderConfiguration) {
        // Both controllers read the same encoder signals, therefore no mechanical ratio exists.
        return 1;
    }
    // If shaftRevolutions is valid, compute actual ratio.
    if (shaftRevolutions !== 0) {
        return motorRevolutions / shaftRevolutions;
    }
    // Fallback: Cannot compute ratio due to invalid denominator.
    // Safe assumption is to return unity.
    return 1;
}
exports.computeGearRatio = computeGearRatio;
/**
 * Converts a commutation angle offset from encoder counts
 * to electrical degrees.
 *
 * The input value is assumed to be in the range `[0, 4095]`,
 * representing a full electrical revolution. The result is
 * rounded to the nearest integer degree.
 *
 * @param value - Commutation angle offset in encoder counts
 * @returns Commutation angle in electrical degrees (0–360)
 */
function convertCommutationAngleOffsetToElectricalDegrees(value) {
    return Math.round((value / 4095) * 360);
}
exports.convertCommutationAngleOffsetToElectricalDegrees = convertCommutationAngleOffsetToElectricalDegrees;
/**
 * Parses a CSV representation of a plant model into a `PlantModel` object.
 *
 * The CSV is expected to have up to three lines:
 * 1. Numerator coefficients (comma-separated)
 * 2. Denominator coefficients (comma-separated), with the last two values representing friction and inertia
 * 3. Optional chirp signal parameters: startFrequency, endFrequency, torqueAmplitude, duration, signalType
 *
 * If `content` is null, undefined, or empty, a `PlantModel` with default values is returned.
 *
 * @param content - The CSV content as a string or Uint8Array. Can be null or undefined.
 * @returns A `PlantModel` object with the parsed numerator, denominator, friction, inertia,
 *          and optional chirp signal parameters (`startFrequency`, `endFrequency`,
 *          `torqueAmplitude`, `durationSeconds`, `signalType`).
 */
function parsePlantModelCsv(content) {
    if (!content) {
        return {
            denominator: [],
            friction: 0,
            inertia: 0,
            numerator: [],
            signalType: 0,
            startFrequency: 0,
            durationSeconds: 0,
            endFrequency: 0,
            torqueAmplitude: 0,
        };
    }
    const contentStr = typeof content === 'string' ? content : (0, util_1.decodeTextContent)(content);
    const [numerator, denominator, chirpSignal] = contentStr.split('\n');
    const [friction, inertia] = denominator.split(',').reverse();
    const plantModel = {
        numerator: [...numerator.split(',').map((v) => parseFloat(v))],
        denominator: [...denominator.split(',').map((v) => parseFloat(v))],
        friction: parseFloat(friction),
        inertia: parseFloat(inertia),
    };
    if (chirpSignal != undefined && chirpSignal.trim() !== '') {
        const [startFrequency, endFrequency, torqueAmplitude, duration, signalType] = chirpSignal.split(',');
        plantModel.startFrequency = parseFloat(startFrequency);
        plantModel.endFrequency = parseFloat(endFrequency);
        plantModel.torqueAmplitude = parseFloat(torqueAmplitude);
        plantModel.durationSeconds = parseFloat(duration);
        plantModel.signalType = parseInt(signalType, 10);
    }
    return plantModel;
}
exports.parsePlantModelCsv = parsePlantModelCsv;
//# sourceMappingURL=device.js.map