"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isDocker = exports.parseSecurityKeys = exports.parseJSON = exports.allSettled = exports.buffer2hex = exports.bufferFromHex = exports.isBufferAsHex = exports.verifyPsw = exports.hashPsw = exports.humanSize = exports.hasProperty = exports.removeSlash = exports.sanitizeTopic = exports.getVersion = exports.num2hex = exports.copy = exports.isValueId = exports.joinProps = exports.joinPath = exports.getPath = exports.basePath = exports.fileDate = exports.deepEqual = exports.padNumber = exports.applyMixin = exports.pkgJson = void 0;
const path_1 = __importStar(require("path"));
const crypto_1 = __importDefault(require("crypto"));
const fs_1 = require("fs");
// don't use import here, it will break the build
// eslint-disable-next-line @typescript-eslint/no-var-requires
exports.pkgJson = require('../../package.json');
let VERSION;
function applyMixin(target, mixin, includeConstructor = false) {
    // Figure out the inheritance chain of the mixin
    const inheritanceChain = [mixin];
    // eslint-disable-next-line no-constant-condition
    while (true) {
        const current = inheritanceChain[0];
        const base = Object.getPrototypeOf(current);
        if (base?.prototype) {
            inheritanceChain.unshift(base);
        }
        else {
            break;
        }
    }
    for (const ctor of inheritanceChain) {
        for (const prop of Object.getOwnPropertyNames(ctor.prototype)) {
            // Do not override the constructor
            if (includeConstructor || prop !== 'constructor') {
                Object.defineProperty(target.prototype, prop, Object.getOwnPropertyDescriptor(ctor.prototype, prop) ??
                    Object.create(null));
            }
        }
    }
}
exports.applyMixin = applyMixin;
function padNumber(num, digits) {
    return num ? num.toString().padStart(digits, '0') : 'unknown';
}
exports.padNumber = padNumber;
function deepEqual(a, b) {
    return JSON.stringify(a) === JSON.stringify(b);
}
exports.deepEqual = deepEqual;
function fileDate(date) {
    date = date || new Date();
    return date.toISOString().slice(-24).replace(/\D/g, '').slice(0, 14);
}
exports.fileDate = fileDate;
/** Where package.json is */
exports.basePath = __filename.endsWith('index.js')
    ? (0, path_1.resolve)(__dirname) // esbuild bundle
    : (0, path_1.resolve)(__dirname, '..', '..');
/**
 *  Get the base root path to application directory. When we are in a `pkg` environment
 *  the path of the snapshot is not writable
 */
function getPath(write) {
    if (write && hasProperty(process, 'pkg'))
        return process.cwd();
    else
        return exports.basePath;
}
exports.getPath = getPath;
/**
 * path.join wrapper, the first option can be a boolean and it will automatically fetch the root path
 * passing the boolean to getPath
 */
function joinPath(write, ...paths) {
    if (typeof write === 'boolean') {
        write = getPath(write);
    }
    return path_1.default.join(write, ...paths);
}
exports.joinPath = joinPath;
/**
 * Join props with a `_` and skips undefined props
 */
function joinProps(...props) {
    props = props || [];
    let ret = props[0].toString() || '';
    for (let i = 1; i < props.length; i++) {
        const p = props[i];
        if (p !== null && p !== undefined && p !== '') {
            ret += '_' + (typeof p === 'number' ? p.toString() : p);
        }
    }
    return ret;
}
exports.joinProps = joinProps;
/**
 * Checks if an object is a valueId, returns error otherwise
 */
function isValueId(v) {
    if (typeof v.commandClass !== 'number' || v.commandClass < 0) {
        return 'invalid `commandClass`';
    }
    if (v.endpoint !== undefined && v.endpoint < 0) {
        return 'invalid `endpoint`';
    }
    if (v.property === undefined ||
        (typeof v.property !== 'string' && typeof v.property !== 'number')) {
        return 'invalid `property`';
    }
    if (v.propertyKey !== undefined &&
        typeof v.propertyKey !== 'string' &&
        typeof v.propertyKey !== 'number') {
        return 'invalid `propertyKey`';
    }
    return true;
}
exports.isValueId = isValueId;
/**
 * Deep copy of an object
 */
function copy(obj) {
    return JSON.parse(JSON.stringify(obj));
}
exports.copy = copy;
/**
 * Converts a decimal to an hex number of 4 digits and `0x` as prefix
 */
function num2hex(num) {
    const hex = num >= 0 ? num.toString(16) : 'XXXX';
    return '0x' + '0'.repeat(4 - hex.length) + hex;
}
exports.num2hex = num2hex;
/**
 * Gets the actual package.json version with also the git revision number at the end of it
 */
function getVersion() {
    if (!VERSION) {
        try {
            // try to get short sha of last commit
            let rev = (0, fs_1.readFileSync)('.git/HEAD').toString().trim();
            if (rev.indexOf(':') !== -1) {
                rev = (0, fs_1.readFileSync)('.git/' + rev.substring(5))
                    .toString()
                    .trim();
            }
            VERSION = `${exports.pkgJson.version}${rev ? '.' + rev.substring(0, 7) : ''}`;
        }
        catch {
            VERSION = exports.pkgJson.version;
        }
    }
    return VERSION;
}
exports.getVersion = getVersion;
/**
 * Sanitize chars of a string to use in a topic
 *
 */
function sanitizeTopic(str, sanitizeSlash = false) {
    if (typeof str === 'number' || !str)
        return str.toString();
    if (sanitizeSlash) {
        str = removeSlash(str);
    }
    // replace spaces with '_'
    str = str.replace(/\s/g, '_');
    // remove special chars
    return str.replace(/[^A-Za-z0-9-_À-ÖØ-öø-ÿ/]/g, '');
}
exports.sanitizeTopic = sanitizeTopic;
/**
 * Removes `/` chars from strings
 */
function removeSlash(str) {
    return typeof str === 'number' ? str.toString() : str.replace(/\//g, '-');
}
exports.removeSlash = removeSlash;
/**
 * Check if an object has a property
 */
// eslint-disable-next-line @typescript-eslint/ban-types
function hasProperty(obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
}
exports.hasProperty = hasProperty;
/**
 * Gets the size in a human readable form starting from bytes
 */
function humanSize(bytes) {
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
    if (bytes === 0) {
        return 'n/a';
    }
    const i = Math.floor(Math.log(bytes) / Math.log(1024));
    if (i === 0) {
        return bytes + ' ' + sizes[i];
    }
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
}
exports.humanSize = humanSize;
async function hashPsw(password) {
    return new Promise((resolve, reject) => {
        const salt = crypto_1.default.randomBytes(8).toString('hex');
        crypto_1.default.scrypt(password, salt, 64, (err, derivedKey) => {
            if (err)
                reject(err);
            resolve(salt + ':' + derivedKey.toString('hex'));
        });
    });
}
exports.hashPsw = hashPsw;
async function verifyPsw(password, hash) {
    return new Promise((resolve, reject) => {
        const [salt, key] = hash.split(':');
        crypto_1.default.scrypt(password, salt, 64, (err, derivedKey) => {
            if (err)
                reject(err);
            resolve(key === derivedKey.toString('hex'));
        });
    });
}
exports.verifyPsw = verifyPsw;
/**
 * Checks if a string is a hex buffer
 */
function isBufferAsHex(str) {
    return /^0x([a-fA-F0-9]{2})+$/.test(str);
}
exports.isBufferAsHex = isBufferAsHex;
/**
 * Parses a buffer from a string has the form 0x[a-f0-9]+
 */
function bufferFromHex(hex) {
    return Buffer.from(hex.substr(2), 'hex');
}
exports.bufferFromHex = bufferFromHex;
/**
 * Converts a buffer to an hex string
 */
function buffer2hex(buffer) {
    if (buffer.length === 0)
        return '';
    return `0x${Buffer.from(buffer.buffer).toString('hex')}`;
}
exports.buffer2hex = buffer2hex;
function allSettled(promises) {
    const wrappedPromises = promises.map((p) => Promise.resolve(p).then((val) => ({ status: 'fulfilled', value: val }), (err) => ({ status: 'rejected', reason: err })));
    return Promise.all(wrappedPromises);
}
exports.allSettled = allSettled;
/** Parses a string to json with buffer decode support */
function parseJSON(str) {
    return JSON.parse(str, (k, v) => {
        if (v !== null &&
            typeof v === 'object' &&
            'type' in v &&
            v.type === 'Buffer' &&
            'data' in v &&
            Array.isArray(v.data)) {
            return Buffer.from(v.data);
        }
        return v;
    });
}
exports.parseJSON = parseJSON;
function parseSecurityKeys(config, options) {
    config.securityKeys = config.securityKeys || {};
    if (process.env.NETWORK_KEY) {
        config.securityKeys.S0_Legacy = process.env.NETWORK_KEY;
    }
    const availableKeys = [
        'S2_Unauthenticated',
        'S2_Authenticated',
        'S2_AccessControl',
        'S0_Legacy',
    ];
    const availableLongRangeKeys = ['S2_Authenticated', 'S2_AccessControl'];
    const envKeys = Object.keys(process.env)
        .filter((k) => k?.startsWith('KEY_'))
        .map((k) => k.substring(4));
    const longRangeEnvKeys = Object.keys(process.env)
        .filter((k) => k?.startsWith('KEY_LR_'))
        .map((k) => k.substring(7));
    // load security keys from env
    for (const k of envKeys) {
        if (availableKeys.includes(k)) {
            config.securityKeys[k] = process.env[`KEY_${k}`];
        }
    }
    // load long range security keys from env
    for (const k of longRangeEnvKeys) {
        if (availableLongRangeKeys.includes(k)) {
            config.securityKeysLongRange[k] = process.env[`KEY_LR_${k}`];
        }
    }
    options.securityKeys = {};
    options.securityKeysLongRange = {};
    // convert security keys to buffer
    for (const key in config.securityKeys) {
        if (availableKeys.includes(key) &&
            config.securityKeys[key].length === 32) {
            options.securityKeys[key] = Buffer.from(config.securityKeys[key], 'hex');
        }
    }
    config.securityKeysLongRange = config.securityKeysLongRange || {};
    // convert long range security keys to buffer
    for (const key in config.securityKeysLongRange) {
        if (availableLongRangeKeys.includes(key) &&
            config.securityKeysLongRange[key].length === 32) {
            options.securityKeysLongRange[key] = Buffer.from(config.securityKeysLongRange[key], 'hex');
        }
    }
}
exports.parseSecurityKeys = parseSecurityKeys;
function hasDockerEnv() {
    try {
        (0, fs_1.statSync)('/.dockerenv');
        return true;
    }
    catch {
        return false;
    }
}
function hasDockerCGroup() {
    try {
        return (0, fs_1.readFileSync)('/proc/self/cgroup', 'utf8').includes('docker');
    }
    catch {
        return false;
    }
}
let isDockerCached;
function isDocker() {
    // TODO: Use `??=` when targeting Node.js 16.
    isDockerCached ??= hasDockerEnv() || hasDockerCGroup();
    return isDockerCached;
}
exports.isDocker = isDocker;
