import * as i0 from '@angular/core';
import { Injectable, Pipe, EventEmitter, Component, Output, Input, NgModule } from '@angular/core';
import { bufferToHexString, hexStringToBuffer } from '@iotize/common/byte-converter';
import * as i2 from '@iotize/ionic';
import { TapInfo, createCacheKey, toTapInfoKeyObject, PendingCallManager, LibError, isTapResultCodeError, LibCommonModule, CONFIGURATION_MODE_VERSION, FACTORY_RESET_MODE_VERSION } from '@iotize/ionic';
import { HostProtocol, TargetProtocol, TapResponse, SpecialFeature, TapError, TapResponseStatusError } from '@iotize/tap';
import { converters } from '@iotize/tap/service/core';
import { NfcConnectionPriority, NfcPairingMode } from '@iotize/tap/service/impl/interface';
import { WifiSSIDVisibility, WifiKeyVisibility, WifiMode } from '@iotize/tap/service/impl/wifi';
import { combineLatest, BehaviorSubject, Subject, of, defer, concat } from 'rxjs';
import { map, switchMap, distinctUntilChanged, shareReplay, tap, filter, first } from 'rxjs/operators';
import * as i5 from '@angular/common';
import * as i3 from '@ionic/angular';
import * as i1 from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ResultCode } from '@iotize/tap/client/api';
import { createDebugger } from '@iotize/common/debug';
import { deepEqual, deepCopy } from '@iotize/common/utility';
import { isCodeError } from '@iotize/common/error';
import { tapResponseStatusToString } from '@iotize/tap/client/impl';
import { DotAccessor } from '@iotize/common/data-accessor';
import { IoTizeStudioToTapConfigConverter } from '@iotize/tap/config/iotize-studio';
import { xmlToJson } from '@iotize/common/xml';
import { ADMIN_USER } from '@iotize/tap/configurator';
import { ConnectionState } from '@iotize/tap/protocol/api';

const TAP_CONFIG_PATTERN = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/;
const IPV4_PATTERN = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
const IPV4_MASK_PATTERN = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
const SSID_PATTERN = /^[^!#;+\]\/"\t][^+\]\/"\t]{0,30}[^ !#;+\]\/"\t]$|^[^ !#;+\]\/"\t]$/;
function regexValidator(pattern) {
    return (control) => {
        const value = control.value;
        return !pattern.test(value)
            ? { invalidFormat: { value: control.value } }
            : null;
    };
}
const validateTapVersion = regexValidator(TAP_CONFIG_PATTERN);
function enumFormatter(data) {
    return (value) => {
        if (Array.isArray(value)) {
            if (value.length === 0) {
                return '-';
            }
            return value.map((key) => HostProtocol[key]).join(', ');
        }
        else {
            return data[value];
        }
    };
}
function isTapConfigured(version) {
    return !(version.major === 0 && version.minor === 0 && version.patch === 0);
}
function enumToOptions(mapping) {
    const keys = Object.keys(mapping).filter((k) => typeof mapping[k] === 'number');
    // console.log('enumToOptions', result);
    return keys.map((key) => {
        return { key: mapping[key], text: key };
    });
}
function stringToEnumArray(v, mapping) {
    return v
        .split(',')
        .map((item) => item.trim())
        .map((item) => {
        if (!(item in mapping)) {
            throw new Error(`Invalid value "${item}". Must one of the following: ${Object.keys(mapping).join(', ')}`);
        }
        return mapping[item];
    });
}
var WifiTxPowerEnum;
(function (WifiTxPowerEnum) {
    WifiTxPowerEnum[WifiTxPowerEnum["default"] = 0] = "default";
    WifiTxPowerEnum[WifiTxPowerEnum["10 dBm"] = 40] = "10 dBm";
    WifiTxPowerEnum[WifiTxPowerEnum["12.5 dBm"] = 50] = "12.5 dBm";
    WifiTxPowerEnum[WifiTxPowerEnum["15 dBm"] = 60] = "15 dBm";
    WifiTxPowerEnum[WifiTxPowerEnum["17.5 dBm"] = 70] = "17.5 dBm";
    WifiTxPowerEnum[WifiTxPowerEnum["20 dBm"] = 80] = "20 dBm";
})(WifiTxPowerEnum || (WifiTxPowerEnum = {}));
const defaultInfoResolverConfig = [
    {
        key: TapInfo.serialNumber,
        title: 'Serial number',
        description: 'Unique identifier for your Tap',
        editable: false,
        getValue: (tap) => tap.service.device.getSerialNumber(),
    },
    {
        key: TapInfo.modelName,
        title: 'Model name',
        description: "Tap model's name",
        editable: false,
        getValue: (tap) => tap.service.device.getFirmwareFlavor(),
    },
    {
        key: TapInfo.tapManufacturer,
        title: 'Manufacturer',
        description: 'Name of the Tap manufacturer',
        editable: false,
        getValue: (tap) => tap.service.device.getManufacturer(),
    },
    {
        key: TapInfo.HostProtocol,
        title: 'Protocol',
        description: 'Communication protocol between your TapNLink and your application',
        getValue: (tap) => {
            return tap.service.interface.getCurrentHostProtocol();
        },
        putValue: (tap, protocol) => {
            return tap.service.interface.putCurrentHostProtocol(protocol);
        },
        input: {
            type: 'select',
            options: enumToOptions(HostProtocol),
        },
        viewFormatter: enumFormatter(HostProtocol),
    },
    {
        key: TapInfo.appName,
        title: 'Application name',
        description: 'Also used when broadcasting with BLE or Wi-Fi',
        input: {
            maxLength: 32,
        },
        getValue: (tap) => tap.service.interface.getAppName(),
        putValue: (tap, value) => {
            return tap.service.interface.putAppName(value);
        },
    },
    {
        key: TapInfo.configVersion,
        title: 'Tap configuration version',
        description: 'Tap configuration version loaded on your device',
        input: {
            pattern: TAP_CONFIG_PATTERN,
        },
        viewFormatter: (v) => {
            if (v === '0.0.0') {
                return 'FACTORY RESET';
            }
            else if (v === '255.255.65535') {
                return 'CONFIGURATION MODE';
            }
            else if (v) {
                return `v${v}`;
            }
            else {
                return 'UNKNOWN';
            }
        },
        formValidators: [validateTapVersion],
        editFormatter: {
            read: (input) => {
                return input;
            },
            write: (v) => {
                return v || '0.0.0';
            },
        },
        getValue: (tap) => tap.service.interface.getConfigVersion(),
        putValue: (tap, version) => {
            // const versionNumber = (v.major << 24) | (v.minor << 16) | v.patch;
            // console.log('As version number: ', versionNumber);
            return tap.service.interface.putConfigVersion(version);
        },
    },
    {
        title: 'Minimal transmission period',
        description: 'Used as an indication for mobile application wether or not it should send data log packets to the cloud',
        key: TapInfo.CloudUploadPeriod,
        viewFormatter: (v) => `${v} ms`,
        input: {
            type: 'number',
        },
        getValue: (tap) => tap.service.cloud.getDataLogUploadPeriod(),
        putValue: async function (tap, value) {
            return tap.service.cloud.putDataLogUploadPeriod(value);
        },
    },
    {
        title: 'MQTT broker',
        description: 'Mqtt broker used for synchronization ',
        key: TapInfo.CloudEndpoint,
        getValue: async (tap) => {
            const hostname = (await tap.service.mqtt.getBrokerHostname()).body();
            const port = (await tap.service.mqtt.getBrokerPort()).body();
            return `${hostname}:${port}`;
        },
        putValue: async (tap, value) => {
            const [hostname, port] = value.split(':');
            const setPort = await tap.service.mqtt.putBrokerPort(port || '1883');
            setPort.successful();
            return tap.service.mqtt.putBrokerHostname(hostname);
        },
    },
    {
        title: 'MQTT request topic',
        description: 'Topic used for incomming MQTT requests',
        key: TapInfo.MqttRequestTopic,
        getValue: async (tap) => {
            return (await tap.service.mqtt.getRequestTopic()).body();
        },
        putValue: async (tap, value) => {
            return await tap.service.mqtt.putRequestTopic(value);
        },
    },
    {
        title: 'Target protocol configuration',
        description: 'Raw target protocol configuration',
        key: TapInfo.TargetProtocolConfiguration,
        viewFormatter: (v) => `${bufferToHexString(v)}`,
        editFormatter: {
            read: (input) => {
                return input ? hexStringToBuffer(input) : new Uint8Array();
            },
            write: (v) => {
                return v ? bufferToHexString(v) : '';
            },
        },
        getValue: async (tap) => {
            const result = await tap.service.target.getModbusTcpConfiguration();
            result.successful();
            return result.rawBody();
        },
        putValue: async (tap, value) => {
            return await tap.lwm2m.put('/1027//21', value);
        },
    },
    {
        title: 'MQTT response topic',
        description: 'Topic used for MQTT response',
        key: TapInfo.MqttResponseTopic,
        getValue: async (tap) => {
            return (await tap.service.mqtt.getResponseTopic()).body();
        },
        putValue: async (tap, value) => {
            return await tap.service.mqtt.putResponseTopic(value);
        },
    },
    {
        title: 'Cloud key',
        description: 'Cloud key used for authentification',
        key: TapInfo.CloudKey,
        getValue: (tap) => tap.service.mqtt.getPassword(),
        putValue: async (tap, value) => {
            return tap.service.mqtt.putPassword(value);
        },
    },
    // {
    //     title: 'MQTT endpoint',
    //     description: 'MQTT endpoint used',
    //     key: TapInfo.MqttEndpoint,
    //     input: {
    //         maxLength: 128
    //     },
    //     getValue: async (tap) => {
    //         const hostname = (await tap.service.mqtt.getBrokerHostname()).body();
    //         const port = (await tap.service.mqtt.getBrokerPort()).body();
    //         return `${hostname}:${port}`;
    //     },
    //     putValue: async function (tap: Tap, value: string) {
    //         const [hostname, port] = value.split(':');
    //         await tap.service.mqtt.putBrokerPort(port);
    //         return await tap.service.mqtt.putBrokerHostname(value);
    //     }
    // },
    {
        title: 'MQTT broker hostname',
        description: 'MQTT broker hostname (for exemple user.cloud.iotize.com)',
        key: TapInfo.MqttBrokerHostname,
        input: {
            maxLength: 128,
        },
        getValue: async (tap) => tap.service.mqtt.getBrokerHostname(),
        putValue: async (tap, value) => tap.service.mqtt.putBrokerHostname(value),
    },
    {
        title: 'MQTT broker port',
        description: 'MQTT broker port (for exemple 1883 or 8883)',
        key: TapInfo.MqttBrokerPort,
        input: {
            maxLength: 128,
            type: 'number',
        },
        getValue: async (tap) => tap.service.mqtt.getBrokerPort(),
        putValue: async (tap, value) => tap.service.mqtt.putBrokerPort(value.toString()),
    },
    {
        title: 'MQTT password',
        description: 'Mqtt password',
        key: TapInfo.MqttPassword,
        input: {
            maxLength: 32,
        },
        getValue: async (tap) => {
            return (await tap.service.mqtt.getPassword()).body();
        },
        putValue: async function (tap, value) {
            return await tap.service.mqtt.putPassword(value);
        },
    },
    {
        title: 'MQTT relay net key',
        description: 'Mqtt relay net key',
        key: TapInfo.MqttRelayNetKey,
        input: {
            maxLength: 32,
        },
        getValue: async (tap) => {
            return (await tap.service.mqtt.getRelayNetKey()).body();
        },
        putValue: async function (tap, value) {
            return await tap.service.mqtt.putRelayNetKey(value);
        },
    },
    // {
    //     title: 'MQTT service name',
    //     description: 'MQTT service name',
    //     key: TapInfo.MqttServiceName,
    //     input: {
    //         maxLength: 32
    //     },
    //     getValue: async (tap) => {
    //         return (await tap.service.mqtt.getBrokerPort()).body();
    //     },
    //     putValue: async function (tap: Tap, value: string) {
    //         return await tap.service.mqtt.putBrokerPort(value);
    //     }
    // },
    {
        title: 'MQTT client id',
        description: 'Unique identifier of the client. Can be anything you want but it must be uniq. \
        Only one client with the same id can be connected at the same time',
        key: TapInfo.MqttClientId,
        input: {
            maxLength: 32,
        },
        getValue: async (tap) => {
            return (await tap.service.mqtt.getClientId()).body();
        },
        putValue: async function (tap, value) {
            return await tap.service.mqtt.putClientId(value);
        },
    },
    {
        title: 'MQTT password',
        description: 'MQTT password',
        input: {
            maxLength: 128,
        },
        key: TapInfo.MqttPassword,
        getValue: (tap) => {
            return tap.service.mqtt.getPassword();
        },
        putValue: (tap, val) => {
            return tap.service.mqtt.putPassword(val);
        },
    },
    {
        title: 'MQTT username',
        description: 'MQTT username',
        input: {
            maxLength: 32,
        },
        key: TapInfo.MqttUsername,
        getValue: (tap) => {
            return tap.service.mqtt.getUsername();
        },
        putValue: (tap, val) => {
            return tap.service.mqtt.putUsername(val);
        },
    },
    {
        title: 'NFC priority',
        description: 'Enable if NFC has priority over other protocols. \
        It means that a new NFC connection will force disconnection of other connected clients. ',
        input: {
            type: 'toggle',
            // options: enumToOptions(NfcConnectionPriority)
        },
        key: TapInfo.NFCConnectionPriority,
        getValue: async (tap) => {
            const value = (await tap.service.interface.getNfcConnectionPriority()).body();
            return value === NfcConnectionPriority.NFC_PRIORITY;
        },
        putValue: (tap, val) => {
            const mode = val
                ? NfcConnectionPriority.NFC_NON_PRIORITY
                : NfcConnectionPriority.NFC_PRIORITY;
            return tap.service.interface.putNfcConnectionPriority(mode);
        },
    },
    {
        title: 'NFC mandatory to connect to the Tap',
        description: 'Choose whether or not NFC must be used to connect to the Tap. ',
        input: {
            type: 'select',
            options: [
                {
                    key: NfcPairingMode.NO,
                    text: 'No',
                },
                {
                    key: NfcPairingMode.MANDATORY,
                    text: 'Yes',
                },
                {
                    key: NfcPairingMode.MANDATORY_FOR_LOGIN,
                    text: 'Only to login',
                },
            ],
        },
        key: TapInfo.NFCPairingMode,
        getValue: (tap) => {
            return tap.service.interface.getNfcPairingMode();
        },
        putValue: (tap, val) => {
            return tap.service.interface.putNfcPairingMode(val);
        },
    },
    {
        title: 'Packet count',
        description: 'The current number of data log packet in memory',
        key: TapInfo.DataLogPacketCount,
        getValue: (tap) => tap.service.dataLog.getPacketCount(),
    },
    {
        title: 'Maximum packet count',
        description: 'Maximum number of data log packet the Tap is able to store',
        key: TapInfo.DataLogMaxPacketCount,
        viewFormatter: (v) => {
            return v === 0 ? 'NO LIMIT' : v.toString();
        },
        input: {
            type: 'number',
        },
        getValue: (tap) => tap.service.dataLog.getMaxPacketCount(),
        // putValue: (tap: Tap, value: number) => {
        //     return tap.service.dataLog.putMaxPacketCount()
        // }
    },
    {
        title: 'Target connected',
        description: 'Wether or not Tap is connected to the target application',
        key: TapInfo.IsTargetConnected,
        // viewFormatter: (v: boolean) => v ? 'YES' : 'NO',
        getValue: (tap) => tap.service.target.isConnected(),
        putValue: (tap, enabled) => {
            if (enabled) {
                return tap.service.target.connect();
            }
            else {
                return tap.service.target.disconnect();
            }
        },
        input: {
            type: 'toggle',
        },
    },
    {
        title: 'Universal link',
        description: 'Will be used for iOS app',
        key: TapInfo.universalLink,
        icon: 'link',
        getValue: (tap) => tap.service.interface.getUniversalLink(),
        putValue: (tap, value) => {
            return tap.service.interface.putUniversalLink(value);
        },
    },
    {
        title: 'Android application record',
        description: 'For example "com.iotize.app.tapmanager"',
        key: TapInfo.androidApplicationRecord,
        // TODO replace with getAndroidApplicationId()
        getValue: async (tap) => {
            const appPath = (await tap.service.interface.getAppPath()).body();
            if (appPath.startsWith('$4/')) {
                return appPath.substring(3);
            }
            else {
                return false;
            }
        },
        putValue: async (tap, value) => {
            return tap.service.interface.putAppPath(`$4/${value}`);
        },
    },
    {
        title: 'Encryption',
        description: 'If enabled, it will use encryption for communication between the mobile device \
        (smartphone, PC, ...) and your TapNLink',
        key: TapInfo.useEncryption,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return lockOptions.scramActivated;
        },
    },
    {
        title: 'Encryption iteration number',
        description: 'Number of iteration for encryption algorithm. Higher it is, the more secure it gets',
        key: TapInfo.scramIterationNumber,
        input: {
            type: 'number',
            min: 0,
            max: 0xffff,
        },
        getValue: async (tap) => {
            return (await tap.service.scram.getHashIteration()).body();
        },
    },
    {
        title: 'Authorized hardware factory reset',
        description: 'If enabled, user can perform a hardware factory reset',
        key: TapInfo.lockFactoryReset,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return !lockOptions.disableHardwareFactoryReset;
        },
    },
    {
        title: 'SCRAM activated',
        description: 'If enabled, requests will be encrypted using SCRAM when logged in',
        key: TapInfo.InterfaceSecurityScramActivated,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return lockOptions.scramActivated;
        },
    },
    {
        title: 'Disabled hardware factory reset',
        description: 'If enabled, factory reset with hardware piin will be disabled',
        key: TapInfo.InterfaceSecurityDisableHardwareFactoryReset,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return lockOptions.disableHardwareFactoryReset;
        },
    },
    {
        title: 'LWM2M factory reset disabled',
        description: 'If enabled, factory reset with LWM2M request will be disabled',
        key: TapInfo.InterfaceSecurityDisabledResourceFactoryReset,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return lockOptions.disableResourceFactoryReset;
        },
    },
    {
        title: 'Admin login with UID disabled',
        description: 'If enabled, login as an admin with UID will be disabled',
        key: TapInfo.InterfaceSecurityDisabledLoginWithUID,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return lockOptions.disableLoginWithUID;
        },
    },
    {
        title: 'Use password hash',
        description: 'If true, user passwords will be stored as hash',
        key: TapInfo.hashPassword,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return lockOptions.hashPassword;
        },
    },
    {
        title: 'Authorize login with UID',
        description: 'If enabled, it will authorized a user to log in as an administrator using a unique identifier',
        key: TapInfo.isLoginWithUIDEnabled,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return !lockOptions.disableLoginWithUID;
        },
    },
    {
        title: 'Authorize factory reset using LWM2M',
        description: 'If enabled, it will authorized an administrator user to do a factory reset using the API',
        key: TapInfo.isLWM2MFactoryResetEnabled,
        input: {
            type: 'toggle',
        },
        getValue: async (tap) => {
            const lockOptions = (await tap.service.interface.getSecurityOptions()).body();
            return !lockOptions.disableResourceFactoryReset;
        },
    },
    {
        title: 'Security options',
        description: 'Security options',
        key: TapInfo.lockOptions,
        putValue: (tap, value) => {
            return tap.service.interface.putSecurityOptions(value);
        },
        getValue: (tap) => {
            return tap.service.interface.getSecurityOptions();
        },
    },
    {
        title: 'Application path',
        description: 'Contains the path to the application to load when connecting to this device. It may be an application id or a website URL.',
        key: TapInfo.appPath,
        putValue: (tap, value) => {
            return tap.service.interface.putAppPath(value);
        },
        getValue: (tap) => tap.service.interface.getAppPathResolved(),
    },
    {
        title: 'Username',
        description: 'Username used for login',
        key: TapInfo.ProfileName,
        getValue: (tap, groupId) => {
            return tap.service.group.getName(groupId);
        },
        putValue: (tap, value, groupId) => {
            return tap.service.group.putName(groupId, value);
        },
    },
    {
        title: 'Parent group ID',
        description: 'Identifier of group from where this user inherits his rights',
        key: TapInfo.parentGroupId,
        getValue: (tap, groupId) => {
            return tap.service.group.getAlias(groupId);
        },
        putValue: (tap, value, groupId) => {
            return tap.service.group.putAlias(groupId, value);
        },
    },
    // {
    //   title: 'Clear data log packet store',
    //   description: 'This action cannot be undone',
    //   getValue: false,
    //   key: TapInfo.DataLogClearPacketStore,
    //   actions: [
    //     {
    //       title: 'Clear',
    //       icon: 'trash',
    //       call: (tap: Tap) => {
    //         return tap.service.dataLog.flush();
    //       },
    //     },
    //   ],
    // },
    {
        title: 'Session life time',
        description: 'Duration of the session in seconds (it does not apply right away, it will only be used on following logins)',
        key: TapInfo.ProfileSessionLifeTime,
        getValue: (tap, groupId) => {
            return tap.service.group.getSessionLifetime(groupId);
        },
        putValue: (tap, value, groupId) => {
            return tap.service.group.putSessionLifetime(groupId, value);
        },
    },
    {
        title: 'Target protocol',
        description: 'Communication protocol used between the Tap and the micro controller',
        key: TapInfo.TargetProtocol,
        getValue: (tap) => {
            return tap.service.target.getProtocol();
        },
        putValue: (tap, protocol) => {
            return tap.service.target.putProtocol(protocol);
        },
        // TODO
        setValue: (tap, protocol) => {
            return tap.service.target.setProtocol(protocol);
        },
        viewFormatter: enumFormatter(TargetProtocol),
        input: {
            type: 'select',
            options: enumToOptions(TargetProtocol).filter((f) => {
                return f.key !== TargetProtocol.JTAG;
            }),
        },
    },
    {
        title: 'Available protocols',
        description: 'Communication protocols available on your device',
        key: TapInfo.availableHostProtocols,
        viewFormatter: enumFormatter(HostProtocol),
        editFormatter: {
            write: (v) => {
                // console.log('Host protocols: ', v);
                if (!v || v.length === 0) {
                    return 'NONE';
                }
                return v.map((key) => HostProtocol[key]).join(', ');
            },
            read: (v) => {
                return stringToEnumArray(v, HostProtocol);
            },
        },
        getValue: (tap) => tap.service.interface.getAvailableHostProtocols(),
    },
    {
        title: 'Authorized protocols',
        description: 'Communication protocols authorized on your device',
        key: TapInfo.authorizedHostProtocols,
        input: {
            type: 'select',
            multiple: true,
            options: [],
        },
        viewFormatter: enumFormatter(HostProtocol),
        getValue: (tap) => tap.service.interface.getAuthorizedHostProtocol(),
        putValue: (tap, protocols) => {
            return tap.service.interface.putAuthorizedHostProtocol(protocols);
        },
        init: async (_service, config, infoResolver) => {
            const hostProtocols = await infoResolver.getValue(TapInfo.availableHostProtocols);
            if (config.input !== undefined) {
                config.input.options = hostProtocols.map((key) => {
                    return { key: key, text: HostProtocol[key] };
                });
            }
        },
    },
    // {
    //   title: 'Enable/Disable data log acquisition on your device',
    //   key: TapInfo.DataLogToggleAcquisition,
    //   getValue: (tap: Tap) => {
    //     return tap.service.dataLog.isRunning();
    //   },
    //   actions: [
    //     {
    //       title: 'start',
    //       icon: 'play',
    //       color: 'primary',
    //       call: (tap: Tap) => tap.service.dataLog.run(),
    //       triggersRefresh: [TapInfo.DatalogAcquisitionRunning],
    //     },
    //     {
    //       title: 'stop',
    //       icon: 'pause',
    //       color: 'danger',
    //       call: (tap: Tap) => tap.service.dataLog.stop(),
    //       triggersRefresh: [TapInfo.DatalogAcquisitionRunning],
    //     },
    //   ],
    // },
    {
        key: TapInfo.BundleDatalogPeriod,
        title: 'Data log acquisition period',
        description: 'Define variable value acquisition period (in milliseconds)',
        input: {
            type: 'number',
        },
        getValue: function (tap, bundleId) {
            if (bundleId === undefined) {
                throw new Error('Missing parameter bundleId');
            }
            return tap.service.bundle.getDataLogPeriod(bundleId);
        },
        putValue: (tap, value, bundleId) => {
            if (bundleId === undefined) {
                throw new Error('Missing parameter bundleId');
            }
            return tap.service.bundle.putDataLogPeriod(bundleId, value);
        },
    },
    {
        key: TapInfo.configPath,
        getValue: (tap) => tap.service.interface.getConfigPath(),
        title: 'Configuration file path',
    },
    {
        key: TapInfo.tapFirmwareVersion,
        title: 'Tap firmware version',
        description: 'IoTize Tap firmware version currently loaded on the Tap',
        getValue: (tap) => tap.service.device.getFirmwareVersion(),
    },
    {
        key: TapInfo.DatalogAcquisitionRunning,
        getValue: (tap) => tap.service.dataLog.isRunning(),
        title: 'Data log acquision running',
        description: 'Is data log acquisition running',
    },
    {
        key: TapInfo.AdpStats,
        title: 'ADP info',
        getValue: (tap) => tap.service.tapnpass.getStatus(),
    },
    {
        key: TapInfo.AdpVersion,
        title: 'ADP version',
        getValue: (tap) => tap.service.tapnpass.getStatus(),
        viewFormatter: (stats) => {
            const v = stats.header.version;
            return `${v.major}.${v.minor}.${v.patch}`;
        },
        editable: false,
    },
    {
        key: TapInfo.SinglePacketInfo,
        title: 'Single packet info',
        getValue: (tap) => {
            return tap.service.singlePacket.getInfo();
        },
    },
    {
        key: TapInfo.protocolHostInactivityPeriod,
        title: 'Disconnection inactivity delay',
        description: 'Duration (in seconds) after which the Tap will disconnect the connected client if there is no communication',
        input: {
            type: 'number',
            min: 0,
            max: 255,
        },
        getValue: (tap) => {
            return tap.service.interface.getHostInactivityPeriod();
        },
        putValue: (tap, value) => {
            return tap.service.interface.putHostInactivityPeriod(value);
        },
    },
    {
        key: TapInfo.bleMacAddress,
        title: 'BLE Mac address',
        description: 'For example: "C4:A2:EF:45:23"',
        editable: false,
        getValue: (tap) => {
            return tap.service.ble.getAddress();
        },
    },
    {
        key: TapInfo.WifiHostname,
        title: 'Current Tap IP/Hostname',
        description: 'IPV4 address',
        editable: false,
        getValue: (tap) => {
            return tap.service.wifi.getHostname();
        },
        viewFormatter: (v) => {
            if (v === '0.0.0.0') {
                return 'NO IP';
            }
            return v;
        },
    },
    {
        key: TapInfo.WifiSSID,
        title: 'Wi-Fi SSID',
        description: 'Wifi SSID (Service Set IDentifier)',
        editable: false,
        input: {
            minLength: 1,
            maxLength: 32,
        },
        formValidators: [regexValidator(SSID_PATTERN)],
        getValue: (tap) => {
            return tap.service.wifi.getSSID();
        },
        putValue: (tap, value) => {
            return tap.service.wifi.putSSID(value);
        },
    },
    {
        key: TapInfo.WifiSSIDVisibility,
        title: 'SSID visibility',
        description: 'Wether or not SSID is visible',
        editable: false,
        getValue: (tap) => {
            return tap.service.wifi.getSSIDVisibility();
        },
        putValue: (tap, value) => {
            return tap.service.wifi.putSSIDVisibility(value);
        },
        input: {
            type: 'select',
            options: enumToOptions(WifiSSIDVisibility),
        },
        viewFormatter: enumFormatter(WifiSSIDVisibility),
    },
    {
        key: TapInfo.WifiKeyVisibility,
        title: 'Key visibility',
        description: 'Wether or not Wi-Fi key can be read',
        editable: false,
        getValue: (tap) => {
            return tap.service.wifi.getKeyVisibility();
        },
        putValue: (tap, value) => {
            return tap.service.wifi.putKeyVisibility(value);
        },
        input: {
            type: 'select',
            options: enumToOptions(WifiKeyVisibility),
        },
        viewFormatter: enumFormatter(WifiKeyVisibility),
    },
    {
        key: TapInfo.WifiTxPower,
        title: 'Transceive power',
        description: 'Change Wi-Fi transceive power',
        editable: false,
        getValue: (tap) => {
            return tap.service.wifi.getTxPower();
        },
        putValue: (tap, value) => {
            return tap.service.wifi.putTxPower(value);
        },
        viewFormatter: enumFormatter(WifiTxPowerEnum),
        input: {
            type: 'select',
            options: enumToOptions(WifiTxPowerEnum),
        },
    },
    {
        key: TapInfo.NetworkInfraIp,
        title: 'Wi-Fi IP',
        description: 'Use ip address 0.0.0.0 if you want to let Tap use DHCP to obtain an IP address',
        getValue: (tap) => tap.service.wifi.getIp(),
        putValue: (tap, value) => tap.service.wifi.putIp(value),
        formValidators: [regexValidator(IPV4_PATTERN)],
        viewFormatter: (value) => {
            if (value === '0.0.0.0') {
                return 'Dynamic (DHCP)';
            }
            return value;
        },
    },
    {
        key: TapInfo.NetworkIpMask,
        title: 'Wi-Fi IP mask',
        description: 'For Wi-Fi ip',
        getValue: (tap) => tap.service.wifi.getIpMask(),
        putValue: (tap, value) => tap.service.wifi.putIpMask(value),
        formValidators: [regexValidator(IPV4_MASK_PATTERN)],
        isDisplayed: ({ tapConfigItemStateService }) => {
            return combineLatest([
                tapConfigItemStateService.valueChange(TapInfo.WifiMode),
                tapConfigItemStateService.valueChange(TapInfo.NetworkInfraIp),
            ]).pipe(map(([mode, ip]) => {
                return mode === WifiMode.NETWORK && ip !== '0.0.0.0';
            }));
        },
    },
    {
        key: TapInfo.NetworkGatewayIp,
        title: 'Wi-Fi gateway IP address',
        getValue: (tap) => tap.service.wifi.getGatewayIp(),
        putValue: (tap, value) => tap.service.wifi.putGatewayIp(value),
        formValidators: [regexValidator(IPV4_PATTERN)],
        isDisplayed: ({ tapConfigItemStateService }) => {
            return combineLatest([
                tapConfigItemStateService.valueChange(TapInfo.WifiMode),
                tapConfigItemStateService.valueChange(TapInfo.NetworkInfraIp),
            ]).pipe(map(([mode, ip]) => {
                return mode === WifiMode.NETWORK && ip !== '0.0.0.0';
            }));
        },
    },
    {
        key: TapInfo.WifiMode,
        title: 'Wi-Fi mode',
        description: 'Your Tap can either connect to an existing Wi-Fi network or behave as a network',
        editable: false,
        input: {
            type: 'select',
            // options: enumToOptions(WifiMode),
            options: [
                {
                    icon: 'git-network',
                    key: WifiMode.NETWORK,
                    text: 'Network',
                },
                {
                    icon: 'people',
                    key: WifiMode.PEER_TO_PEER,
                    text: 'Access point',
                },
            ],
        },
        getValue: (tap) => {
            return tap.service.wifi.getMode();
        },
        putValue: (tap, value) => {
            return tap.service.wifi.putMode(value);
        },
    },
    {
        key: TapInfo.isHostProtocolAuthorized,
        title: 'Authorized',
        description: 'Enable to authorized the given protocol to be used to connect to this device',
        editable: false,
        input: {
            type: 'toggle',
        },
        getValue: async (tap, protocol) => {
            if (protocol === undefined) {
                throw new Error('Illegal argument error: missing protocol');
            }
            const protocols = (await tap.service.interface.getAuthorizedHostProtocol()).body();
            return protocols.find((p) => p === protocol) !== undefined;
        },
        putValue: async (tap, value, protocol) => {
            const protocols = (await tap.service.interface.getAuthorizedHostProtocol()).body();
            if (protocol === undefined) {
                throw new Error('Illegal argument error: missing protocol');
            }
            const indexOfProtocol = protocols.findIndex((p) => p === protocol);
            if (value) {
                if (indexOfProtocol === -1) {
                    protocols.push(protocol);
                }
            }
            else {
                if (indexOfProtocol >= 0) {
                    protocols.splice(indexOfProtocol, 1);
                }
            }
            return tap.service.interface.putAuthorizedHostProtocol(protocols);
        },
    },
    {
        key: TapInfo.WifiKey,
        title: 'Wi-Fi key',
        description: 'Wi-Fi key',
        editable: false,
        input: {
            type: 'password',
            minLength: 8,
            maxLength: 128,
        },
        getValue: (tap) => {
            return tap.service.wifi.getKey();
        },
        putValue: (tap, value) => {
            return tap.service.wifi.putKey(value);
        },
    },
    {
        key: TapInfo.bundleName,
        title: 'Bundle name',
        description: 'Friendly bundle name to display',
        editable: false,
        input: {
            minLength: 1,
            maxLength: 16,
        },
        getValue: (tap, bundleId) => {
            return tap.service.bundle.getName(bundleId);
        },
        putValue: (tap, value, bundleId) => {
            return tap.service.bundle.putName(bundleId, value);
        },
    },
    {
        key: TapInfo.variableMetaData,
        title: 'Variable meta name',
        description: 'Additional variable data',
        editable: false,
        input: {},
        getValue: async (tap, variableId) => {
            const metaData = (await tap.service.variable.getRawMeta(variableId)).body();
            return converters.ascii.decode(metaData);
        },
        putValue: (tap, value, bundleId) => {
            const data = converters.ascii.encode(value);
            return tap.service.variable.putRawMeta(bundleId, data);
        },
    },
    {
        key: TapInfo.profilePassword,
        title: 'User password',
        description: 'New user password',
        editable: false,
        input: {
            type: 'password',
            minLength: 1,
            maxLength: 16,
        },
        viewFormatter: () => {
            return '********';
        },
        putValue: async (tap, value, profileId) => {
            await tap.auth.changePassword(value, profileId);
            return TapResponse.SUCCESS(); // TODO fix
        },
    },
    {
        key: TapInfo.ProductKey,
        title: 'Product key',
        description: 'Product key is used to associate your device to your TapCLOUD account',
        editable: false,
        getValue: async (tap, variableId) => {
            return (await tap.service.interface.getProductKey()).body();
        },
    },
    {
        title: 'Memory space left',
        key: TapInfo.DeviceMemoryFree,
        getValue: async (tap, variableId) => {
            const value = (await tap.service.device.getMemoryFree()).body();
            return Math.floor((value / 1024) * 100) / 100 + ' KB';
        },
    },
    {
        title: 'Local time',
        description: 'Tap local time',
        key: TapInfo.TimeLocalTime,
        getValue: async (tap, variableId) => {
            const localTime = (await tap.service.time.getLocalTime()).body();
            const date = new Date(Date.UTC(localTime.year, 0, localTime.dayOfYear));
            date.setHours(localTime.hours);
            date.setMinutes(localTime.minutes);
            date.setSeconds(localTime.seconds);
            // {"seconds":54,"minutes":3,"hours":8,"dayOfMonth":8,"month":8,"year":122,"dayOfWeek":4,"dayOfYear":250,"isdst":0}
            return `${date.toUTCString()}`;
        },
    },
    {
        title: 'NTP server',
        key: TapInfo.TimeNtpServer,
        input: {
            maxLength: 255,
        },
        description: 'Tap NTP server to use',
        getValue: async (tap) => {
            return (await tap.service.time.getNtpServer()).body();
        },
        putValue: async (tap, body) => {
            return await tap.service.time.putNtpServer(body);
        },
    },
    {
        title: 'Time zone',
        description: 'Tap time zone',
        key: TapInfo.TimeTimeZone,
        getValue: async (tap) => {
            return (await tap.service.time.getTimeZone()).body();
        },
        putValue: async (tap, body) => {
            return await tap.service.time.putTimeZone(body);
        },
    },
    {
        title: 'Product code',
        description: 'Product code is used to share behaviors between mutiple Tap',
        key: TapInfo.InterfaceProductCode,
        getValue: async (tap) => {
            return (await tap.service.interface.getProductCode()).body();
        },
        putValue: async (tap, body) => {
            return await tap.service.interface.putProductCode(body);
        },
    },
    {
        title: 'Last error code',
        description: 'Device last error code',
        key: TapInfo.DeviceLastErrorCode,
        getValue: async (tap) => {
            return (await tap.service.device.getLastErrorCode()).body();
        },
    },
    {
        title: 'NFC auto log profile id',
        description: 'Profile id that will be logged in when starting an encrypted session with NFC',
        key: TapInfo.NFCAutologProfileId,
        getValue: async (tap) => {
            return (await tap.service.interface.getNfcAutologProfileId()).body();
        },
        putValue: async (tap, body) => {
            return await tap.service.interface.putNfcAutologProfileId(body);
        },
    },
    {
        key: TapInfo.InterfaceSpecialFeatureSWDDirect,
        ...createSpecialFeatureDAO(SpecialFeature.SWD_DIRECT_ACCESS, 'SWD direct'),
    },
    {
        key: TapInfo.InterfaceSpecialFeatureSerial,
        ...createSpecialFeatureDAO(SpecialFeature.SERIAL_ACCESS, 'Serial'),
    },
    {
        key: TapInfo.InterfaceSpecialFeatureModbusDirect,
        ...createSpecialFeatureDAO(SpecialFeature.MODBUS_DIRECT_ACCESS, 'ModBus direct'),
    },
];
function createSpecialFeatureDAO(feature, title) {
    return {
        title,
        description: `Bundle identifier that is authorized to acccess ${title}`,
        input: {
            type: 'number',
            min: 0,
            max: 65535,
        },
        getValue: async (tap) => {
            const result = (await tap.service.interface.getSpecialFeatureProfile(feature)).body();
            if (result === 65535 - (feature - 100)) {
                return 'UNAUTHORIZED';
            }
            else {
                return result;
            }
        },
        putValue: async (tap, body) => {
            return await tap.service.interface.putSpecialFeatureProfile(feature, body);
        },
    };
}

const debug = createDebugger('@iotize/ionic:config');

class TapConfigItemStateService {
    constructor(tapInfoDAOService, tapInfoCacheService) {
        this.tapInfoDAOService = tapInfoDAOService;
        this.tapInfoCacheService = tapInfoCacheService;
        this._pendingChanges = new BehaviorSubject([]);
        this.loading = false;
        this.hasPendingChanges = this._pendingChanges.pipe(map((changes) => changes.length > 0));
        this._saveProgress = new Subject();
    }
    /**
     * Value change taking into acccount the edited value.
     * If no editaded value for the key, it will take the lasted fetched value from the Tap
     * @param key
     * @returns
     */
    valueChange(key) {
        return this._pendingChanges.pipe(switchMap((changes) => {
            const hashKey = createCacheKey(toTapInfoKeyObject(key));
            const editedValue = changes.find((entry) => createCacheKey(entry.key) === hashKey);
            if (editedValue !== undefined) {
                return of(editedValue.value);
            }
            else {
                return this.tapInfoCacheService.valueChange(key);
            }
        }), distinctUntilChanged(deepEqual), shareReplay(1));
    }
    getPendingChangeForKey(key) {
        return this._pendingChanges.pipe(map((changes) => {
            const hashKey = createCacheKey(toTapInfoKeyObject(key));
            return changes.find((entry) => createCacheKey(entry.key) === hashKey)
                ?.value;
        }), distinctUntilChanged(deepEqual), shareReplay(1));
    }
    get pendingChangesChange() {
        return this._pendingChanges.asObservable();
    }
    get saveProgress() {
        return this._saveProgress.asObservable();
    }
    async savePendingChanges() {
        try {
            this.loading = true;
            await this._savePendingChangesWithProgress().toPromise();
        }
        finally {
            this.loading = false;
        }
    }
    _savePendingChangesWithProgress() {
        const configItemStateChanges = this.getPendingChangesSnapshot().filter(({ key }) => {
            return key.key !== TapInfo.configVersion;
        });
        const observables = configItemStateChanges.map(({ key, value }) => {
            return defer(async () => {
                await this.tapInfoDAOService.put(key, value);
                this.tapInfoDAOService.get(key).catch((err) => { });
            });
        });
        const total = configItemStateChanges.length;
        const saveProgress = concat(...observables).pipe(map((info, index) => {
            return {
                total,
                loaded: index + 1,
                payload: info,
            };
        }), tap(this._saveProgress));
        return saveProgress;
    }
    clearPendingChanges() {
        this._pendingChanges.next([]);
    }
    getPendingChangesSnapshot() {
        return deepCopy(this._pendingChanges.value);
    }
    setPendingChange(info, newValue) {
        // Make sure we don't store any other info otherwise deepCopy may failed
        info = {
            key: info.key,
            params: info.params,
        };
        const hashKey = createCacheKey(info);
        const pendingChangesSnapshot = this._pendingChanges.value;
        const item = pendingChangesSnapshot.find((entry) => createCacheKey(entry.key) === hashKey);
        if (!item) {
            pendingChangesSnapshot.push({
                key: info,
                value: newValue,
            });
        }
        else {
            item.value = newValue;
        }
        this._pendingChanges.next(pendingChangesSnapshot);
    }
    removePendingChange(info) {
        const hashKey = createCacheKey(info);
        const pendingChangesSnapshot = this._pendingChanges.value;
        const indexOfItem = pendingChangesSnapshot.findIndex((entry) => createCacheKey(entry.key) === hashKey);
        if (indexOfItem >= 0) {
            pendingChangesSnapshot.splice(indexOfItem, 1);
            this._pendingChanges.next(pendingChangesSnapshot);
        }
    }
}
/** @nocollapse */ TapConfigItemStateService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigItemStateService, deps: [{ token: i2.TapInfoDAOService }, { token: i2.TapInfoCacheService }], target: i0.ɵɵFactoryTarget.Injectable });
/** @nocollapse */ TapConfigItemStateService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigItemStateService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigItemStateService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }], ctorParameters: function () { return [{ type: i2.TapInfoDAOService }, { type: i2.TapInfoCacheService }]; } });

class TapRequestErrorToStringPipe {
    transform(value, action = 'read') {
        if (!value) {
            return undefined;
        }
        return userFriendlyError(value, action).message;
    }
}
/** @nocollapse */ TapRequestErrorToStringPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapRequestErrorToStringPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
/** @nocollapse */ TapRequestErrorToStringPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: TapRequestErrorToStringPipe, name: "tapRequestErrorToString" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapRequestErrorToStringPipe, decorators: [{
            type: Pipe,
            args: [{
                    name: 'tapRequestErrorToString',
                }]
        }] });
function userFriendlyError(error, action) {
    if (isCodeError(TapError.Code.ExecuteRequestError, error)) {
        error = error.cause || error;
    }
    if (isCodeError(TapResponseStatusError.Code.ResponseStatusError, error)) {
        const tapResponseStatusError = error;
        const statusCode = tapResponseStatusError.response.status;
        let message;
        switch (statusCode) {
            case ResultCode.UNAUTHORIZED:
                message = `You are not authorized to ${action} this value. Try to login first.`;
                break;
            case ResultCode.RESOURCE_LOCKED:
                message = `This value is ${action} protected`;
                break;
            case ResultCode.NOT_IMPLEMENTED:
                message = `This configuration is not available with your current firmware version or Tap model`;
                break;
            default:
                message = tapResponseStatusToString(statusCode);
        }
        error = new Error(message);
    }
    return error;
}

class TapConfigItemComponent {
    constructor(formBuilder, tapService, toastController, infoResolver, tapInfoDAOService, tapInfoCacheService, tapConfigItemState, tapInfoConfigService) {
        this.tapService = tapService;
        this.toastController = toastController;
        this.infoResolver = infoResolver;
        this.tapInfoDAOService = tapInfoDAOService;
        this.tapInfoCacheService = tapInfoCacheService;
        this.tapConfigItemState = tapConfigItemState;
        this.tapInfoConfigService = tapInfoConfigService;
        this.focusOut = new EventEmitter();
        this.onSubmit = new EventEmitter();
        this.refresh = false;
        this.editMode = false;
        this.globalSubmit = false;
        this.loading = false;
        this.placeholder = '';
        this.ressourceAvailable = true;
        this.form = formBuilder.group({
            field: [''],
        });
        this.field = this.form.get('field');
        this.pendingCallManager = PendingCallManager.create(this.tapService);
    }
    set info(configOrKey) {
        this._resetErrors();
        let config;
        if (typeof configOrKey === 'object') {
            const defaultConfig = this.tapInfoConfigService.findByKey(configOrKey);
            if (defaultConfig) {
                config = { ...defaultConfig, ...configOrKey };
            }
            else {
                config = configOrKey;
            }
        }
        else {
            config = this.tapInfoConfigService.findByKey({
                key: configOrKey,
            });
            if (!config) {
                console.warn(`Failed to find configuration with key "${configOrKey}"`);
                return;
            }
        }
        this.onConfigChange(config);
        this.updateDisplayValue();
        // this.fetchValueIfRequired();
    }
    get info() {
        return this.config;
    }
    get originalValue() {
        return this.config.resolvedValue?.value;
    }
    isEdited() {
        return this.field.value !== this.originalValue;
    }
    get config() {
        if (!this._config) {
            throw LibError.componentArgumentRequired('TapConfigItemComponent', 'info');
        }
        return this._config;
    }
    get inputOptions() {
        return this.info.input || {};
    }
    get isEditable() {
        return (((this.editable === true && this.config.putValue) ||
            this.config.editable) &&
            (!this.readError ||
                !isTapResultCodeError(this.readError, ResultCode.NOT_IMPLEMENTED)));
    }
    _resetErrors() {
        this.setReadError(undefined);
        this.writeError = undefined;
        this.ressourceAvailable = true;
    }
    get pendingEditCall() {
        return this.pendingCallManager.pendingCall;
    }
    onConfigChange(config) {
        this._valueSubscription?.unsubscribe();
        this._cancelChangeSubscription?.unsubscribe();
        this._config = config;
        this._cancelChangeSubscription = this.tapConfigItemState
            .getPendingChangeForKey(config)
            .subscribe((v) => {
            if (v === undefined) {
                this.setFieldValue(this.originalValue, {
                    emitEvent: false,
                });
            }
        });
        this.initializeConfig();
        if (config.input && config.input.type === 'password') {
            this.setDisplayValue('');
        }
        if (config.formValidators) {
            this.field.setValidators(config.formValidators);
        }
        this._valueSubscription = this.tapInfoCacheService
            .infoChange(config)
            .subscribe((newValue) => {
            config.resolvedValue = newValue;
            if (newValue.error !== undefined) {
                this.setReadError(newValue.error);
            }
            else {
                this.setReadError(undefined);
                if (newValue.value !== undefined) {
                    this.setDisplayValue(newValue.value);
                    this.setFieldValue(newValue.value);
                }
                else {
                    this.displayedValue = undefined;
                }
            }
        });
    }
    initializeConfig() {
        if (this.config.init) {
            this.config
                .init(this.tapService, this.config, this.infoResolver)
                .catch((err) => {
                this.setReadError(err);
            });
        }
    }
    setWriteError(error) {
        this.writeError = error;
    }
    setReadError(error) {
        this.readError = error;
        this.ressourceAvailable = error
            ? !isTapResultCodeError(error, ResultCode.NOT_IMPLEMENTED)
            : true;
    }
    ngOnInit() {
        this._formValueSubscription = this.field.valueChanges.subscribe((newValue) => {
            if (this.isEdited()) {
                this.tapConfigItemState.setPendingChange(this.info, newValue);
            }
            else {
                this.tapConfigItemState.removePendingChange(this.info);
            }
        });
    }
    ngOnDestroy() {
        this._valueSubscription?.unsubscribe();
        this._formValueSubscription?.unsubscribe();
        this._cancelChangeSubscription?.unsubscribe();
        this.pendingCallManager.destroy();
    }
    submit($event) {
        if (!this.globalSubmit) {
            debug('submit', $event);
            this.field.updateValueAndValidity();
            if (!this.field.valid) {
                this.writeError = new Error('Invalid value');
                return;
            }
            // this.onSubmit.emit(event);
            const value = this.getFieldValue();
            // debug(`Creating call new value:`, value, `valid: ${this.field.valid}`)
            const call = async () => {
                await this.tapInfoDAOService.put(this.info, value);
                if (this.info.getValue) {
                    this.tapInfoDAOService.get(this.info).catch((err) => { });
                }
                return value;
            };
            this.runCall(call);
        }
    }
    runCall(call) {
        this.loading = true;
        this.pendingCallManager
            .exec(call)
            .then(async (value) => {
            await this.showToast('Value saved!', 'success');
            this.config.resolvedValue = {
                loadDate: new Date(),
                value: value,
            };
            this.writeError = undefined;
            this.editMode = false;
            this.updateDisplayValue();
        })
            .catch((err) => {
            this.setWriteError(err);
            this.field.setErrors({
                write: err.message,
            });
        })
            .finally(() => {
            this.loading = false;
        });
    }
    onValueClick() {
        if (this.editable) {
            this.editMode = true;
        }
    }
    async explainPendingCall() {
        await this.showToast('Please reconnect your Tap to save pending changes');
    }
    onFocusOut() {
        this.focusOut.emit(this.field.value);
    }
    cancelEdit($event) {
        debug(this.config.key, 'cancel edit', $event);
        if (this.globalSubmit) {
            this.restaureValue();
        }
        else {
            this.writeError = undefined;
            if (this.pendingCallManager.hasPendingCall()) {
                this.pendingCallManager.cancel();
            }
            else {
                this.editMode = false;
                this.restaureValue();
            }
        }
    }
    getFieldValue() {
        let fieldValue = this.field.value;
        if (this.config.editFormatter) {
            fieldValue = this.config.editFormatter.read(fieldValue);
        }
        return fieldValue;
    }
    async showToast(msg, color) {
        const toast = await this.toastController.create({
            message: msg,
            duration: 2000,
            position: 'bottom',
            color: color,
        });
        await toast.present();
    }
    refreshValue() {
        return this._execRefreshKey();
    }
    _execRefreshKey() {
        return this.infoResolver.refreshKey(this.config);
    }
    restaureValue() {
        this.setFieldValue(this.originalValue);
        this.tapConfigItemState.removePendingChange(this.config);
    }
    setFieldValue(value, options) {
        const formattedValue = this.config.editFormatter
            ? this.config.editFormatter.write(value)
            : value;
        debug(this.config.key, 'setFieldValue with', formattedValue);
        this.field.setValue(formattedValue, options);
    }
    updateDisplayValue() {
        if (this.config.resolvedValue) {
            if (this.config.resolvedValue.error) {
                this.displayedValue = this.config.resolvedValue.error.message;
            }
            else {
                const value = this.config.resolvedValue.value;
                this.setDisplayValue(value);
                this.setFieldValue(value);
            }
        }
        else {
            debug(this.config.key, 'Value is not loaded yet', this.config.resolvedValue);
            this.displayedValue = undefined;
        }
    }
    setDisplayValue(value) {
        if (value instanceof Error) {
            return value.message;
        }
        else {
            let stringValue;
            if (this.config.viewFormatter) {
                stringValue = this.config.viewFormatter(value);
            }
            else if (typeof value === 'object') {
                stringValue = JSON.stringify(value);
            }
            else if (this.config.input?.type === 'select') {
                stringValue =
                    this.getSelectedOption(value)?.text ||
                        (value !== undefined ? value.toString() : '');
            }
            else {
                stringValue = value !== undefined ? value.toString() : '';
            }
            // debug(`${this.config.key} setDisplayValue with`, stringValue);
            this.displayedValue = stringValue;
            return stringValue;
        }
    }
    getSelectedOption(value) {
        return this.config.input?.options?.find((option) => option.key === value);
    }
    get selectedOption() {
        return this.getSelectedOption(this.config.resolvedValue?.value);
    }
    async fetchValue() {
        if (this.config.getValue) {
            this.infoResolver.refreshKey(this.config);
            const result = await this.tapInfoCacheService
                .infoChange(this.config)
                .pipe(filter((v) => !!v.loadDate), first())
                .toPromise();
        }
    }
}
/** @nocollapse */ TapConfigItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigItemComponent, deps: [{ token: i1.FormBuilder }, { token: i2.CurrentDeviceService }, { token: i3.ToastController }, { token: i2.TapInfoRequestService }, { token: i2.TapInfoDAOService }, { token: i2.TapInfoCacheService }, { token: TapConfigItemStateService }, { token: i2.TapInfoConfigService }], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ TapConfigItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: TapConfigItemComponent, selector: "tap-config-item", inputs: { editable: "editable", info: "info", refresh: "refresh", editMode: "editMode", globalSubmit: "globalSubmit" }, outputs: { focusOut: "focusOut", onSubmit: "onSubmit" }, ngImport: i0, template: "<ion-item *ngIf=\"_config\" [formGroup]=\"form\" [lines]=\"isEditable ? '' : 'none'\">\n  <ion-skeleton-text\n    animated\n    *ngIf=\"\n      displayedValue === undefined && readError === undefined;\n      else valueFetchedTemplate\n    \"\n  ></ion-skeleton-text>\n  <ng-template #valueFetchedTemplate>\n    <ng-container\n      *ngIf=\"ressourceAvailable; else ressourceNotAvailableTemplate\"\n    >\n      <ng-container *ngIf=\"!editMode; else editModeTemplate\">\n        <ion-text\n          color=\"warning\"\n          class=\"read-value-error\"\n          (click)=\"onValueClick()\"\n          [class]=\"isEditable ? 'cell' : ''\"\n          *ngIf=\"readError; else noReadErrorTemplate\"\n        >\n          <ion-icon name=\"alert-circle\"></ion-icon>\n          {{ readError | tapRequestErrorToString }}\n        </ion-text>\n        <ng-template #noReadErrorTemplate>\n          <ng-container [ngSwitch]=\"inputOptions.type\">\n            <ion-toggle\n              *ngSwitchCase=\"'toggle'\"\n              [disabled]=\"true\"\n              formControlName=\"field\"\n            ></ion-toggle>\n            <div\n              (click)=\"onValueClick()\"\n              *ngSwitchCase=\"'select'\"\n              class=\"item-value\"\n              [class]=\"isEditable ? 'cell' : ''\"\n            >\n              <ion-icon\n                *ngIf=\"selectedOption?.icon\"\n                [name]=\"selectedOption?.icon\"\n              ></ion-icon>\n              <ion-text class=\"tap-config-item-value\">\n                {{ displayedValue }}\n              </ion-text>\n            </div>\n            <ion-text\n              (click)=\"onValueClick()\"\n              class=\"item-value tap-config-item-value\"\n              *ngSwitchDefault\n              [class]=\"isEditable ? 'cell' : ''\"\n            >\n              {{ displayedValue }}\n            </ion-text>\n          </ng-container>\n        </ng-template>\n      </ng-container>\n      <ng-template #editModeTemplate>\n        <ng-container [ngSwitch]=\"inputOptions.type\">\n          <ion-toggle\n            *ngSwitchCase=\"'toggle'\"\n            [disabled]=\"loading || !!pendingEditCall\"\n            formControlName=\"field\"\n          ></ion-toggle>\n          <ion-select\n            style=\"max-width: 100% !important\"\n            *ngSwitchCase=\"'select'\"\n            formControlName=\"field\"\n            [disabled]=\"!isEditable\"\n            [multiple]=\"inputOptions.multiple\"\n          >\n            <ion-select-option\n              *ngFor=\"let option of inputOptions.options\"\n              [value]=\"option.key\"\n            >\n              <ion-icon *ngIf=\"option.icon\" [name]=\"option.icon\"></ion-icon>\n              {{ option.text }}\n            </ion-select-option>\n          </ion-select>\n          <ion-input\n            *ngSwitchDefault\n            (keydown.enter)=\"submit($event)\"\n            (keydown.escape)=\"cancelEdit($event)\"\n            [disabled]=\"!isEditable || !!pendingEditCall\"\n            [maxlength]=\"inputOptions.maxLength\"\n            [minlength]=\"inputOptions.minLength\"\n            [max]=\"inputOptions.max\"\n            [min]=\"inputOptions.min\"\n            [pattern]=\"inputOptions.pattern\"\n            [placeholder]=\"placeholder\"\n            [type]=\"inputOptions.type\"\n            appAutofocus\n            class=\"cellInput\"\n            formControlName=\"field\"\n          >\n          </ion-input>\n        </ng-container>\n      </ng-template>\n      <ion-buttons slot=\"end\">\n        <ion-button\n          (click)=\"explainPendingCall()\"\n          *ngIf=\"pendingEditCall\"\n          [disabled]=\"false\"\n          color=\"warning\"\n        >\n          <ion-icon name=\"alert-circle\"></ion-icon>\n        </ion-button>\n        <ion-button\n          (click)=\"editMode = true\"\n          *ngIf=\"!globalSubmit && !editMode && info.putValue\"\n        >\n          <ion-icon name=\"create\"></ion-icon>\n        </ion-button>\n        <ion-button\n          (click)=\"submit($event)\"\n          *ngIf=\"!globalSubmit && editMode && !pendingEditCall\"\n          [disabled]=\"loading || pendingEditCall\"\n        >\n          <ion-icon name=\"send\"></ion-icon>\n        </ion-button>\n        <ion-button\n          (click)=\"cancelEdit()\"\n          *ngIf=\"isEditable && editMode && (!globalSubmit || isEdited())\"\n          color=\"quaternary\"\n          [disabled]=\"loading\"\n        >\n          <ion-icon name=\"close\"></ion-icon>\n        </ion-button>\n        <ion-button\n          (click)=\"refreshValue()\"\n          *ngIf=\"refresh && info.getValue && !editMode\"\n          [disabled]=\"loading\"\n        >\n          <ion-icon name=\"sync\"></ion-icon>\n        </ion-button>\n      </ion-buttons>\n      <ion-spinner *ngIf=\"loading\" slot=\"end\"></ion-spinner>\n    </ng-container>\n    <ng-template #ressourceNotAvailableTemplate>\n      <ion-text color=\"warning\" class=\"read-value-error\">\n        <ion-icon name=\"alert-circle\"></ion-icon>\n        Not available with your firmware version or Tap model\n      </ion-text>\n    </ng-template>\n  </ng-template>\n</ion-item>\n<ion-item *ngIf=\"writeError\">\n  <ion-text color=\"danger\" class=\"tap-config-item-error\">\n    <ion-icon name=\"alert-circle\"></ion-icon>\n    Failed to write value: {{ writeError | tapRequestErrorToString }}\n  </ion-text>\n</ion-item>\n", styles: [".cell{border:2px solid transparent;padding:11px 0;width:100%;height:100%}.cell:hover{cursor:pointer;border:2px dashed #c8c8c8}ion-item{--padding-start: 0px}.cellInput{border:2px solid transparent;padding:11px 20px;width:100%;height:100%}ion-select{min-width:150px}.read-value-error{font-weight:700}ion-buttons{margin-inline-start:5px}.item-value,ion-select,ion-input{font-weight:700;font-weight:1.05em}\n"], dependencies: [{ kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i5.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i5.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "component", type: i3.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i3.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i3.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i3.IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "debounce", "disabled", "enterkeyhint", "inputmode", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: i3.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i3.IonSelect, selector: "ion-select", inputs: ["cancelText", "compareWith", "disabled", "interface", "interfaceOptions", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "value"] }, { kind: "component", type: i3.IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: i3.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i3.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i3.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i3.IonToggle, selector: "ion-toggle", inputs: ["checked", "color", "disabled", "enableOnOffLabels", "mode", "name", "value"] }, { kind: "directive", type: i3.BooleanValueAccessor, selector: "ion-checkbox,ion-toggle" }, { kind: "directive", type: i3.SelectValueAccessor, selector: "ion-range, ion-select, ion-radio-group, ion-segment, ion-datetime" }, { kind: "directive", type: i3.TextValueAccessor, selector: "ion-input:not([type=number]),ion-textarea,ion-searchbar" }, { kind: "directive", type: i2.AutoFocusDirective, selector: "[appAutofocus]", inputs: ["appAutoFocus"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "pipe", type: TapRequestErrorToStringPipe, name: "tapRequestErrorToString" }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigItemComponent, decorators: [{
            type: Component,
            args: [{ selector: 'tap-config-item', template: "<ion-item *ngIf=\"_config\" [formGroup]=\"form\" [lines]=\"isEditable ? '' : 'none'\">\n  <ion-skeleton-text\n    animated\n    *ngIf=\"\n      displayedValue === undefined && readError === undefined;\n      else valueFetchedTemplate\n    \"\n  ></ion-skeleton-text>\n  <ng-template #valueFetchedTemplate>\n    <ng-container\n      *ngIf=\"ressourceAvailable; else ressourceNotAvailableTemplate\"\n    >\n      <ng-container *ngIf=\"!editMode; else editModeTemplate\">\n        <ion-text\n          color=\"warning\"\n          class=\"read-value-error\"\n          (click)=\"onValueClick()\"\n          [class]=\"isEditable ? 'cell' : ''\"\n          *ngIf=\"readError; else noReadErrorTemplate\"\n        >\n          <ion-icon name=\"alert-circle\"></ion-icon>\n          {{ readError | tapRequestErrorToString }}\n        </ion-text>\n        <ng-template #noReadErrorTemplate>\n          <ng-container [ngSwitch]=\"inputOptions.type\">\n            <ion-toggle\n              *ngSwitchCase=\"'toggle'\"\n              [disabled]=\"true\"\n              formControlName=\"field\"\n            ></ion-toggle>\n            <div\n              (click)=\"onValueClick()\"\n              *ngSwitchCase=\"'select'\"\n              class=\"item-value\"\n              [class]=\"isEditable ? 'cell' : ''\"\n            >\n              <ion-icon\n                *ngIf=\"selectedOption?.icon\"\n                [name]=\"selectedOption?.icon\"\n              ></ion-icon>\n              <ion-text class=\"tap-config-item-value\">\n                {{ displayedValue }}\n              </ion-text>\n            </div>\n            <ion-text\n              (click)=\"onValueClick()\"\n              class=\"item-value tap-config-item-value\"\n              *ngSwitchDefault\n              [class]=\"isEditable ? 'cell' : ''\"\n            >\n              {{ displayedValue }}\n            </ion-text>\n          </ng-container>\n        </ng-template>\n      </ng-container>\n      <ng-template #editModeTemplate>\n        <ng-container [ngSwitch]=\"inputOptions.type\">\n          <ion-toggle\n            *ngSwitchCase=\"'toggle'\"\n            [disabled]=\"loading || !!pendingEditCall\"\n            formControlName=\"field\"\n          ></ion-toggle>\n          <ion-select\n            style=\"max-width: 100% !important\"\n            *ngSwitchCase=\"'select'\"\n            formControlName=\"field\"\n            [disabled]=\"!isEditable\"\n            [multiple]=\"inputOptions.multiple\"\n          >\n            <ion-select-option\n              *ngFor=\"let option of inputOptions.options\"\n              [value]=\"option.key\"\n            >\n              <ion-icon *ngIf=\"option.icon\" [name]=\"option.icon\"></ion-icon>\n              {{ option.text }}\n            </ion-select-option>\n          </ion-select>\n          <ion-input\n            *ngSwitchDefault\n            (keydown.enter)=\"submit($event)\"\n            (keydown.escape)=\"cancelEdit($event)\"\n            [disabled]=\"!isEditable || !!pendingEditCall\"\n            [maxlength]=\"inputOptions.maxLength\"\n            [minlength]=\"inputOptions.minLength\"\n            [max]=\"inputOptions.max\"\n            [min]=\"inputOptions.min\"\n            [pattern]=\"inputOptions.pattern\"\n            [placeholder]=\"placeholder\"\n            [type]=\"inputOptions.type\"\n            appAutofocus\n            class=\"cellInput\"\n            formControlName=\"field\"\n          >\n          </ion-input>\n        </ng-container>\n      </ng-template>\n      <ion-buttons slot=\"end\">\n        <ion-button\n          (click)=\"explainPendingCall()\"\n          *ngIf=\"pendingEditCall\"\n          [disabled]=\"false\"\n          color=\"warning\"\n        >\n          <ion-icon name=\"alert-circle\"></ion-icon>\n        </ion-button>\n        <ion-button\n          (click)=\"editMode = true\"\n          *ngIf=\"!globalSubmit && !editMode && info.putValue\"\n        >\n          <ion-icon name=\"create\"></ion-icon>\n        </ion-button>\n        <ion-button\n          (click)=\"submit($event)\"\n          *ngIf=\"!globalSubmit && editMode && !pendingEditCall\"\n          [disabled]=\"loading || pendingEditCall\"\n        >\n          <ion-icon name=\"send\"></ion-icon>\n        </ion-button>\n        <ion-button\n          (click)=\"cancelEdit()\"\n          *ngIf=\"isEditable && editMode && (!globalSubmit || isEdited())\"\n          color=\"quaternary\"\n          [disabled]=\"loading\"\n        >\n          <ion-icon name=\"close\"></ion-icon>\n        </ion-button>\n        <ion-button\n          (click)=\"refreshValue()\"\n          *ngIf=\"refresh && info.getValue && !editMode\"\n          [disabled]=\"loading\"\n        >\n          <ion-icon name=\"sync\"></ion-icon>\n        </ion-button>\n      </ion-buttons>\n      <ion-spinner *ngIf=\"loading\" slot=\"end\"></ion-spinner>\n    </ng-container>\n    <ng-template #ressourceNotAvailableTemplate>\n      <ion-text color=\"warning\" class=\"read-value-error\">\n        <ion-icon name=\"alert-circle\"></ion-icon>\n        Not available with your firmware version or Tap model\n      </ion-text>\n    </ng-template>\n  </ng-template>\n</ion-item>\n<ion-item *ngIf=\"writeError\">\n  <ion-text color=\"danger\" class=\"tap-config-item-error\">\n    <ion-icon name=\"alert-circle\"></ion-icon>\n    Failed to write value: {{ writeError | tapRequestErrorToString }}\n  </ion-text>\n</ion-item>\n", styles: [".cell{border:2px solid transparent;padding:11px 0;width:100%;height:100%}.cell:hover{cursor:pointer;border:2px dashed #c8c8c8}ion-item{--padding-start: 0px}.cellInput{border:2px solid transparent;padding:11px 20px;width:100%;height:100%}ion-select{min-width:150px}.read-value-error{font-weight:700}ion-buttons{margin-inline-start:5px}.item-value,ion-select,ion-input{font-weight:700;font-weight:1.05em}\n"] }]
        }], ctorParameters: function () { return [{ type: i1.FormBuilder }, { type: i2.CurrentDeviceService }, { type: i3.ToastController }, { type: i2.TapInfoRequestService }, { type: i2.TapInfoDAOService }, { type: i2.TapInfoCacheService }, { type: TapConfigItemStateService }, { type: i2.TapInfoConfigService }]; }, propDecorators: { focusOut: [{
                type: Output
            }], onSubmit: [{
                type: Output
            }], editable: [{
                type: Input
            }], info: [{
                type: Input
            }], refresh: [{
                type: Input
            }], editMode: [{
                type: Input
            }], globalSubmit: [{
                type: Input
            }] } });

class TapConfigItemDisplayedPipe {
    constructor(tapInfoCacheService, tapConfigItemStateService) {
        this.tapInfoCacheService = tapInfoCacheService;
        this.tapConfigItemStateService = tapConfigItemStateService;
    }
    transform(config) {
        if (!config || !config.isDisplayed) {
            return of(true);
        }
        return config.isDisplayed(this);
    }
}
/** @nocollapse */ TapConfigItemDisplayedPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigItemDisplayedPipe, deps: [{ token: i2.TapInfoCacheService }, { token: TapConfigItemStateService }], target: i0.ɵɵFactoryTarget.Pipe });
/** @nocollapse */ TapConfigItemDisplayedPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: TapConfigItemDisplayedPipe, name: "tapConfigItemDisplayed" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigItemDisplayedPipe, decorators: [{
            type: Pipe,
            args: [{
                    name: 'tapConfigItemDisplayed',
                }]
        }], ctorParameters: function () { return [{ type: i2.TapInfoCacheService }, { type: TapConfigItemStateService }]; } });

class TapConfigListComponent {
    constructor() {
        this.itemRefresh = false;
        this.editMode = false;
        this.globalSubmit = false;
        this.lines = 'full';
        this._items = [];
    }
    set items(items) {
        if (!items) {
            items = [];
        }
        this._items = items.reduce((prev, item) => {
            if (typeof item === 'string') {
                const existingItem = defaultInfoResolverConfig.find((d) => item === d.key);
                if (!existingItem) {
                    console.warn('invalid item key', item);
                }
                else {
                    prev.push(existingItem);
                }
                return prev;
            }
            const defaultOption = defaultInfoResolverConfig.find((d) => item.key === d.key);
            if (defaultOption) {
                item = { ...defaultOption, ...item };
            }
            prev.push(item);
            return prev;
        }, []);
    }
}
/** @nocollapse */ TapConfigListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ TapConfigListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: TapConfigListComponent, selector: "tap-config-list", inputs: { itemRefresh: "itemRefresh", editMode: "editMode", globalSubmit: "globalSubmit", lines: "lines", items: "items" }, ngImport: i0, template: "<ion-grid *ngIf=\"_items && _items.length > 0\">\n  <ion-row *ngFor=\"let item of _items\" class=\"tap-config-row-{{ item.key }}\">\n    <ng-container *ngIf=\"item | tapConfigItemDisplayed | async\">\n      <ion-col size-xs=\"12\" size-sm=\"6\">\n        <ion-label>\n          <span class=\"item-title\"\n            ><ion-icon\n              slot=\"start\"\n              *ngIf=\"item.icon\"\n              [name]=\"item.icon\"\n            ></ion-icon>\n            {{ item.title }}</span\n          >\n          <br *ngIf=\"item.description\" />\n          <span class=\"item-description\">{{ item.description }}</span>\n        </ion-label>\n      </ion-col>\n      <ion-col size-xs=\"12\" size-sm=\"6\">\n        <tap-config-item\n          [info]=\"item\"\n          [refresh]=\"itemRefresh\"\n          [editable]=\"editMode\"\n          [editMode]=\"editMode\"\n          [globalSubmit]=\"globalSubmit\"\n        ></tap-config-item>\n      </ion-col>\n    </ng-container>\n  </ion-row>\n</ion-grid>\n", styles: [".item-title{display:inline-block;vertical-align:middle}.item-description{font-size:.8em;color:#777}@media (prefers-color-scheme: dark){:root .item-title{color:#eee}}@media (max-width: 575px){ion-row{border-bottom:1px solid var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, .13))))}ion-grid{padding:0}}ion-col{padding-top:0;padding-bottom:0}\n"], dependencies: [{ kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i3.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i3.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i3.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i3.IonRow, selector: "ion-row" }, { kind: "component", type: TapConfigItemComponent, selector: "tap-config-item", inputs: ["editable", "info", "refresh", "editMode", "globalSubmit"], outputs: ["focusOut", "onSubmit"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: TapConfigItemDisplayedPipe, name: "tapConfigItemDisplayed" }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigListComponent, decorators: [{
            type: Component,
            args: [{ selector: 'tap-config-list', template: "<ion-grid *ngIf=\"_items && _items.length > 0\">\n  <ion-row *ngFor=\"let item of _items\" class=\"tap-config-row-{{ item.key }}\">\n    <ng-container *ngIf=\"item | tapConfigItemDisplayed | async\">\n      <ion-col size-xs=\"12\" size-sm=\"6\">\n        <ion-label>\n          <span class=\"item-title\"\n            ><ion-icon\n              slot=\"start\"\n              *ngIf=\"item.icon\"\n              [name]=\"item.icon\"\n            ></ion-icon>\n            {{ item.title }}</span\n          >\n          <br *ngIf=\"item.description\" />\n          <span class=\"item-description\">{{ item.description }}</span>\n        </ion-label>\n      </ion-col>\n      <ion-col size-xs=\"12\" size-sm=\"6\">\n        <tap-config-item\n          [info]=\"item\"\n          [refresh]=\"itemRefresh\"\n          [editable]=\"editMode\"\n          [editMode]=\"editMode\"\n          [globalSubmit]=\"globalSubmit\"\n        ></tap-config-item>\n      </ion-col>\n    </ng-container>\n  </ion-row>\n</ion-grid>\n", styles: [".item-title{display:inline-block;vertical-align:middle}.item-description{font-size:.8em;color:#777}@media (prefers-color-scheme: dark){:root .item-title{color:#eee}}@media (max-width: 575px){ion-row{border-bottom:1px solid var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, .13))))}ion-grid{padding:0}}ion-col{padding-top:0;padding-bottom:0}\n"] }]
        }], propDecorators: { itemRefresh: [{
                type: Input
            }], editMode: [{
                type: Input
            }], globalSubmit: [{
                type: Input
            }], lines: [{
                type: Input
            }], items: [{
                type: Input
            }] } });

class TapConfigModule {
}
/** @nocollapse */ TapConfigModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
/** @nocollapse */ TapConfigModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: TapConfigModule, declarations: [TapConfigItemComponent,
        TapConfigListComponent,
        TapRequestErrorToStringPipe,
        TapConfigItemDisplayedPipe], imports: [LibCommonModule, FormsModule, ReactiveFormsModule], exports: [TapConfigItemComponent, TapConfigListComponent] });
/** @nocollapse */ TapConfigModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigModule, imports: [LibCommonModule, FormsModule, ReactiveFormsModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigModule, decorators: [{
            type: NgModule,
            args: [{
                    declarations: [
                        TapConfigItemComponent,
                        TapConfigListComponent,
                        TapRequestErrorToStringPipe,
                        TapConfigItemDisplayedPipe,
                    ],
                    exports: [TapConfigItemComponent, TapConfigListComponent],
                    imports: [LibCommonModule, FormsModule, ReactiveFormsModule],
                }]
        }] });

function parseTapConfiguratorConfig(fileContent) {
    if (isJsonString(fileContent)) {
        const config = JSON.parse(fileContent);
        // this.tapConfigPickerservice.onConfigSelected(config);
        return config;
    }
    else {
        // if (IotizeConfigFactory.fromXML(fileContent)) {
        return fromXml(fileContent);
    }
}
function isJsonString(str) {
    try {
        const json = JSON.parse(str);
        return typeof json === 'object';
    }
    catch (e) {
        return false;
    }
}
function fromXml(fileContent) {
    // console.log('File content', fileContent);
    // const parser = new DOMParser();
    // const xmlConfig = parser.parseFromString(fileContent, 'application/xml');
    // let xmlConfig = parse(fileContent);
    const xmlConfig = iotizeStudioConfigToJson(fileContent);
    const config = new IoTizeStudioToTapConfigConverter().convert(new DotAccessor(xmlConfig));
    return { config };
}
function iotizeStudioConfigToJson(fileContent) {
    return xmlToJson(fileContent, {
        attributeNamePrefix: '',
        ignoreAttributes: false,
    });
}

class TapConfigurationModeService {
    constructor(tapService, tapInfoCacheService, tapConfigItemStateService) {
        this.tapService = tapService;
        this.tapInfoCacheService = tapInfoCacheService;
        this.tapConfigItemStateService = tapConfigItemStateService;
        this._loadingCounter = 0;
        this.configurationModeChange = this.tapInfoCacheService
            .valueChange(TapInfo.configVersion)
            .pipe(map((v) => {
            return v === CONFIGURATION_MODE_VERSION;
        }), tap((v) => {
            this.editMode = v;
        }), shareReplay(1));
        this.editMode = false;
        this.isConfigurationModeAvailable = this.tapService.connectionStateReplay.pipe(switchMap((event) => {
            if (event.newState === ConnectionState.CONNECTED) {
                return this.tapService.sessionState.pipe(map((sessionState) => {
                    return (sessionState.name === ADMIN_USER.username ||
                        sessionState.groupId === ADMIN_USER.id);
                }));
            }
            else {
                return of(false);
            }
        }));
    }
    get loading() {
        return this._loadingCounter > 0;
    }
    _setConfigurationVersion(v) {
        this.tapInfoCacheService.setKeyValue({ key: TapInfo.configVersion }, v);
    }
    async enableConfigurationMode() {
        try {
            this._loadingCounter++;
            const tap = this.tapService.tap;
            const [getVersionCall, putVersionCall] = await tap.service.interface.executeMultipleCalls([
                tap.service.interface.getConfigVersionCall(),
                tap.service.interface.putConfigVersionCall(CONFIGURATION_MODE_VERSION),
            ]);
            this._version = await getVersionCall.body();
            putVersionCall.successful();
            this._setConfigurationVersion(CONFIGURATION_MODE_VERSION);
        }
        finally {
            this._loadingCounter--;
        }
    }
    async applyConfigurationMode() {
        try {
            this._loadingCounter++;
            const tap = this.tapService.tap;
            await this.tapConfigItemStateService.savePendingChanges();
            const newVersionChange = this.tapConfigItemStateService
                .getPendingChangesSnapshot()
                .find(({ key }) => {
                return key.key === TapInfo.configVersion;
            });
            let newVersion;
            if (newVersionChange) {
                newVersion = newVersionChange.value;
            }
            else {
                newVersion = this._version || '1.0.0';
            }
            if (newVersion === CONFIGURATION_MODE_VERSION ||
                newVersion === FACTORY_RESET_MODE_VERSION) {
                newVersion = '1.0.0';
            }
            const [applyConfig, reloadConfig] = await tap.service.interface.executeMultipleCalls([
                tap.service.interface.putConfigVersionCall(newVersion),
                tap.service.interface.reloadConfigCall(),
            ]);
            applyConfig.successful();
            reloadConfig.successful();
            this.tapConfigItemStateService.clearPendingChanges();
            this._setConfigurationVersion(newVersion);
        }
        finally {
            this._loadingCounter--;
        }
    }
    async cancelConfigurationMode() {
        try {
            this._loadingCounter++;
            const tap = this.tapService.tap;
            const [reloadConfig, getConfigVersion] = await tap.service.interface.executeMultipleCalls([
                tap.service.interface.reloadConfigCall(),
                tap.service.interface.getConfigVersionCall(),
            ]);
            reloadConfig.successful();
            this._setConfigurationVersion(getConfigVersion.body());
            this.tapConfigItemStateService.clearPendingChanges();
        }
        finally {
            this._loadingCounter--;
        }
    }
}
/** @nocollapse */ TapConfigurationModeService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigurationModeService, deps: [{ token: i2.CurrentDeviceService }, { token: i2.TapInfoCacheService }, { token: TapConfigItemStateService }], target: i0.ɵɵFactoryTarget.Injectable });
/** @nocollapse */ TapConfigurationModeService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigurationModeService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TapConfigurationModeService, decorators: [{
            type: Injectable
        }], ctorParameters: function () { return [{ type: i2.CurrentDeviceService }, { type: i2.TapInfoCacheService }, { type: TapConfigItemStateService }]; } });

/**
 * Generated bundle index. Do not edit.
 */

export { TapConfigItemComponent, TapConfigItemStateService, TapConfigListComponent, TapConfigModule, TapConfigurationModeService, defaultInfoResolverConfig, fromXml, isJsonString, isTapConfigured, parseTapConfiguratorConfig };
//# sourceMappingURL=iotize-ionic-config.mjs.map
