/* eslint-disable @typescript-eslint/no-require-imports */
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/
'use strict';
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.load = load;
exports.readDefaultsFile = readDefaultsFile;
exports.sendBaseDeltas = sendBaseDeltas;
exports.writeDefaultsFile = writeDefaultsFile;
exports.writeBaseDeltasFileSync = writeBaseDeltasFileSync;
exports.writeBaseDeltasFile = writeBaseDeltasFile;
exports.writeSettingsFile = writeSettingsFile;
const fs_1 = __importDefault(require("fs"));
const node_os_1 = __importDefault(require("node:os"));
const lodash_1 = __importDefault(require("lodash"));
const path_1 = __importDefault(require("path"));
const semver_1 = __importDefault(require("semver"));
const uuid_1 = require("uuid");
const debug_1 = require("../debug");
const deltaeditor_1 = __importDefault(require("../deltaeditor"));
const ports_1 = require("../ports");
const atomicWrite_1 = require("../atomicWrite");
const unitpreferences_1 = require("../unitpreferences");
const debug = (0, debug_1.createDebug)('signalk-server:config');
let disableWriteSettings = false;
// use dynamic path so that ts compiler does not detect this
// json file, as ts compile needs to copy all (other) used
// json files under /dist
// tslint:disable-next-line
const packageJson = require('../../' + 'package.json');
function load(app) {
    app.argv = require('minimist')(process.argv.slice(2));
    const config = (app.config = app.config || {});
    const env = (app.env = process.env);
    config.getExternalHostname = getExternalHostname.bind(config, config);
    config.getExternalPort = ports_1.getExternalPort.bind(config, app);
    config.isExternalSsl = isExternalSsl.bind(null, config);
    config.appPath = config.appPath || path_1.default.normalize(__dirname + '/../../');
    debug('appPath:' + config.appPath);
    try {
        config.name = packageJson.name;
        config.author = packageJson.author;
        config.contributors = packageJson.contributors;
        config.version = packageJson.version;
        config.description = packageJson.description;
        //if dependencies are installed from tarballs like in
        //master docker build the version will be like
        //file:signalk-server-admin-ui-1.44.1.tgz
        if (!process.env.SKIP_ADMINUI_VERSION_CHECK) {
            checkPackageVersion('@signalk/server-admin-ui', packageJson, app.config.appPath);
        }
    }
    catch (err) {
        console.error('error parsing package.json', err);
        process.exit(1);
    }
    setConfigDirectory(app);
    app.config.baseDeltaEditor = new deltaeditor_1.default();
    if (lodash_1.default.isObject(app.config.settings)) {
        debug('Using settings from constructor call, not reading defaults');
        disableWriteSettings = true;
        if (config.defaults) {
            convertOldDefaultsToDeltas(app.config.baseDeltaEditor, config.defaults);
        }
    }
    else {
        readSettingsFile(app);
        if (!setBaseDeltas(app)) {
            const defaults = getFullDefaults(app);
            if (defaults) {
                convertOldDefaultsToDeltas(app.config.baseDeltaEditor, defaults);
                if (typeof app.config.settings.useBaseDeltas === 'undefined' ||
                    app.config.settings.useBaseDeltas) {
                    writeBaseDeltasFileSync(app);
                }
                else {
                    app.config.hasOldDefaults = true;
                }
            }
        }
    }
    setSelfSettings(app);
    // TRUST_PROXY env var overrides settings file — useful for container deployments
    if (process.env.TRUST_PROXY !== undefined) {
        const envVal = process.env.TRUST_PROXY;
        app.config.settings.trustProxy =
            envVal === 'true'
                ? true
                : envVal === 'false'
                    ? false
                    : isNaN(Number(envVal))
                        ? envVal
                        : Number(envVal);
    }
    // Load unit preferences
    try {
        (0, unitpreferences_1.setApplicationDataPath)(app.config.configPath);
        (0, unitpreferences_1.loadAll)();
        debug('Unit preferences loaded');
    }
    catch (err) {
        console.error('Failed to load unit preferences:', err);
        // Non-fatal - server can run without unit preferences
    }
    if (app.argv['sample-nmea0183-data']) {
        const sample = path_1.default.join(app.config.appPath, 'samples/plaka.log');
        console.log(`Using sample data from ${sample}`);
        app.config.settings.pipedProviders.push({
            id: 'nmea0183-sample-data',
            pipeElements: [
                {
                    type: 'providers/simple',
                    options: {
                        logging: false,
                        type: 'FileStream',
                        subOptions: {
                            dataType: 'NMEA0183',
                            filename: sample
                        }
                    }
                }
            ],
            enabled: true
        });
    }
    if (app.argv['sample-n2k-data']) {
        const sample = path_1.default.join(app.config.appPath, 'samples/aava-n2k.data');
        console.log(`Using sample data from ${sample}`);
        app.config.settings.pipedProviders.push({
            id: 'n2k-sample-data',
            pipeElements: [
                {
                    type: 'providers/simple',
                    options: {
                        logging: false,
                        type: 'FileStream',
                        subOptions: {
                            dataType: 'NMEA2000JS',
                            filename: sample
                        }
                    }
                }
            ],
            enabled: true
        });
    }
    if (app.argv['override-timestamps']) {
        app.config.overrideTimestampWithNow = true;
    }
    if (app.argv.securityenabled && !app.config.security) {
        app.config.settings.security = { strategy: './tokensecurity' };
    }
    if (env.SSLPORT) {
        config.settings.ssl = true;
    }
    if (!lodash_1.default.isUndefined(app.env.WSCOMPRESSION)) {
        config.settings.wsCompression =
            app.env.WSCOMPRESSION.toLowerCase() === 'true';
    }
    if (config.settings.landingPage &&
        config.settings.landingPage.charAt(0) !== '/') {
        console.error(`invalid rootUri: ${config.settings.landingPage}`);
        process.exit(1);
    }
    require('./development')(app);
    require('./production')(app);
}
function checkPackageVersion(name, pkg, appPath) {
    const expected = pkg.dependencies[name];
    let modulePackageJsonPath = path_1.default.join(appPath, 'node_modules', name, 'package.json');
    if (!fs_1.default.existsSync(modulePackageJsonPath)) {
        modulePackageJsonPath = path_1.default.join(appPath, '..', name, 'package.json');
    }
    const installed = require(modulePackageJsonPath);
    if (!semver_1.default.satisfies(installed.version, expected)) {
        console.error(`invalid version of the ${name} package is installed ${installed.version} !== ${expected}`);
        process.exit(1);
    }
}
// Establish what the config directory path is.
function getConfigDirectory({ argv, config, env }) {
    // Possible paths in order of priority.
    const configPaths = [
        env.SIGNALK_NODE_CONDFIG_DIR,
        env.SIGNALK_NODE_CONFIG_DIR,
        config.configPath,
        argv.c,
        argv.s && config.appPath,
        env.HOME && path_1.default.join(env.HOME, '.signalk'),
        config.appPath
    ];
    // Find first config directory path that has a truthy value.
    const configPath = path_1.default.resolve(lodash_1.default.find(configPaths));
    debug('configDirPath: ' + configPath);
    return configPath;
}
// Create directories and set app.config.configPath.
function setConfigDirectory(app) {
    app.config.configPath = getConfigDirectory(app);
    if (app.config.configPath !== app.config.appPath) {
        if (!fs_1.default.existsSync(app.config.configPath)) {
            fs_1.default.mkdirSync(app.config.configPath);
            debug(`configDir Created: ${app.config.configPath}`);
        }
        const configPackage = path_1.default.join(app.config.configPath, 'package.json');
        if (!fs_1.default.existsSync(configPackage)) {
            fs_1.default.writeFileSync(configPackage, JSON.stringify(pluginsPackageJsonTemplate, null, 2));
        }
        const npmrcPath = path_1.default.join(app.config.configPath, '.npmrc');
        if (!fs_1.default.existsSync(npmrcPath)) {
            fs_1.default.writeFileSync(npmrcPath, 'package-lock=false\n');
        }
        else {
            const contents = fs_1.default.readFileSync(npmrcPath);
            if (contents.indexOf('package-lock=') === -1) {
                fs_1.default.appendFileSync(npmrcPath, '\npackage-lock=false\n');
            }
        }
    }
}
function getDefaultsPath(app) {
    const defaultsFile = app.config.configPath !== app.config.appPath
        ? 'defaults.json'
        : 'settings/defaults.json';
    return path_1.default.join(app.config.configPath, defaultsFile);
}
function getBaseDeltasPath(app) {
    const defaultsFile = app.config.configPath !== app.config.appPath
        ? 'baseDeltas.json'
        : 'settings/baseDeltas.json';
    return path_1.default.join(app.config.configPath, defaultsFile);
}
function readDefaultsFile(app) {
    const defaultsPath = getDefaultsPath(app);
    const data = fs_1.default.readFileSync(defaultsPath);
    return JSON.parse(data.toString());
}
function getFullDefaults(app) {
    const defaultsPath = getDefaultsPath(app);
    try {
        const defaults = readDefaultsFile(app);
        debug(`Found defaults at ${defaultsPath.toString()}`);
        return defaults;
    }
    catch (e) {
        if (e?.code === 'ENOENT') {
            return undefined;
        }
        else {
            console.error(`unable to parse ${defaultsPath.toString()}`);
            console.error(e);
            process.exit(1);
        }
    }
    return undefined;
}
function setBaseDeltas(app) {
    const defaultsPath = getBaseDeltasPath(app);
    try {
        app.config.baseDeltaEditor.load(defaultsPath);
        debug(`Found default deltas at ${defaultsPath.toString()}`);
    }
    catch (e) {
        if (e?.code === 'ENOENT') {
            debug(`No default deltas found at ${defaultsPath.toString()}`);
            return;
        }
        else {
            console.log(e);
        }
    }
    return true;
}
function sendBaseDeltas(app) {
    const copy = JSON.parse(JSON.stringify(app.config.baseDeltaEditor.deltas));
    copy.forEach((delta) => {
        app.handleMessage('defaults', delta);
    });
}
function writeDefaultsFile(app, defaults, cb) {
    (0, atomicWrite_1.atomicWriteFile)(getDefaultsPath(app), JSON.stringify(defaults, null, 2))
        .then(() => cb())
        .catch(cb);
}
function writeBaseDeltasFileSync(app) {
    app.config.baseDeltaEditor.saveSync(getBaseDeltasPath(app));
}
function writeBaseDeltasFile(app) {
    return app.config.baseDeltaEditor.save(getBaseDeltasPath(app));
}
function setSelfSettings(app) {
    const name = app.config.baseDeltaEditor.getSelfValue('name');
    const mmsi = app.config.baseDeltaEditor.getSelfValue('mmsi');
    let uuid = app.config.baseDeltaEditor.getSelfValue('uuid');
    if (mmsi && !lodash_1.default.isString(mmsi)) {
        throw new Error(`invalid mmsi: ${mmsi}`);
    }
    if (uuid && !lodash_1.default.isString(uuid)) {
        throw new Error(`invalid uuid: ${uuid}`);
    }
    if (mmsi === null && uuid === null) {
        uuid = 'urn:mrn:signalk:uuid:' + (0, uuid_1.v4)();
        app.config.baseDeltaEditor.setSelfValue('uuid', uuid);
    }
    app.config.vesselName = name;
    if (mmsi) {
        app.selfType = 'mmsi';
        app.selfId = 'urn:mrn:imo:mmsi:' + mmsi;
        app.config.vesselMMSI = mmsi;
    }
    else if (uuid) {
        app.selfType = 'uuid';
        app.selfId = uuid;
        app.config.vesselUUID = uuid;
    }
    if (app.selfType) {
        debug(app.selfType.toUpperCase() + ': ' + app.selfId);
    }
    app.selfContext = 'vessels.' + app.selfId;
}
function readSettingsFile(app) {
    const settings = getSettingsFilename(app);
    if (!app.argv.s && !fs_1.default.existsSync(settings)) {
        console.log('Settings file does not exist, using empty settings');
        app.config.settings = {
            pipedProviders: []
        };
    }
    else {
        debug('Using settings file: ' + settings);
        try {
            app.config.settings = require(settings);
        }
        catch (_e) {
            console.error(`Error reading settings file ${settings}, using empty settings`);
            app.config.settings = {
                pipedProviders: []
            };
        }
    }
    if (lodash_1.default.isUndefined(app.config.settings.pipedProviders)) {
        app.config.settings.pipedProviders = [];
    }
    if (lodash_1.default.isUndefined(app.config.settings.interfaces)) {
        app.config.settings.interfaces = {};
    }
}
function writeSettingsFile(app, settings, cb) {
    if (!disableWriteSettings) {
        (0, atomicWrite_1.atomicWriteFile)(getSettingsFilename(app), JSON.stringify(settings, null, 2))
            .then(() => cb())
            .catch(cb);
    }
    else {
        cb();
    }
}
function getSettingsFilename(app) {
    if (process.env.SIGNALK_NODE_SETTINGS) {
        debug('Settings filename was set in environment SIGNALK_NODE_SETTINGS, overriding all other options');
        return path_1.default.resolve(process.env.SIGNALK_NODE_SETTINGS);
    }
    const settingsFile = app.argv.s || 'settings.json';
    return path_1.default.join(app.config.configPath, settingsFile);
}
function isExternalSsl(config) {
    if (process.env.EXTERNALSSL) {
        return (process.env.EXTERNALSSL === '1' ||
            process.env.EXTERNALSSL.toLowerCase() === 'true');
    }
    return !!config.settings.proxy_ssl;
}
function getExternalHostname(config) {
    if (process.env.EXTERNALHOST) {
        return process.env.EXTERNALHOST;
    }
    if (config.settings.proxy_host) {
        return config.settings.proxy_host;
    }
    else if (config.settings.hostname) {
        return config.settings.hostname;
    }
    try {
        return node_os_1.default.hostname();
    }
    catch (_ex) {
        return 'hostname_not_available';
    }
}
function scanDefaults(deltaEditor, vpath, item) {
    lodash_1.default.keys(item).forEach((key) => {
        const value = item[key];
        if (key === 'meta') {
            deltaEditor.setMeta('vessels.self', vpath, value);
        }
        else if (key === 'value') {
            deltaEditor.setSelfValue(vpath, value);
        }
        else if (lodash_1.default.isObject(value)) {
            const childPath = vpath.length > 0 ? `${vpath}.${key}` : key;
            scanDefaults(deltaEditor, childPath, value);
        }
    });
}
function convertOldDefaultsToDeltas(deltaEditor, defaults) {
    const deltas = [];
    const self = lodash_1.default.get(defaults, 'vessels.self');
    if (self) {
        lodash_1.default.keys(self).forEach((key) => {
            const value = self[key];
            if (!lodash_1.default.isString(value)) {
                scanDefaults(deltaEditor, key, value);
            }
            else {
                deltaEditor.setSelfValue(key, value);
            }
        });
        if (self.communication) {
            deltaEditor.setSelfValue('communication', self.communication);
        }
    }
    return deltas;
}
const pluginsPackageJsonTemplate = {
    name: 'signalk-server-config',
    version: '0.0.1',
    description: 'This file is here to track your plugin and webapp installs.',
    repository: {},
    license: 'Apache-2.0'
};
module.exports = {
    load,
    getConfigDirectory,
    writeSettingsFile,
    writeDefaultsFile,
    readDefaultsFile,
    sendBaseDeltas,
    writeBaseDeltasFile,
    package: packageJson
};
//# sourceMappingURL=config.js.map