import * as i0 from '@angular/core';
import { EventEmitter, Component, Output, Input, NgModule } from '@angular/core';
import * as i1 from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import * as i3 from '@ionic/angular';
import { isCodeError } from '@iotize/common/error';
import * as i2 from '@iotize/ionic';
import { TapInfo, PendingCallManager, getFormFieldOrError, LibError, LibCommonModule } from '@iotize/ionic';
import { HostProtocol, TargetProtocol, TapResponse, TapError, TapResponseStatusError } from '@iotize/tap';
import { ResultCode } from '@iotize/tap/client/api';
import { StringConverter, tapResponseStatusToString } from '@iotize/tap/client/impl';
import { NfcConnectionPriority, NfcPairingMode } from '@iotize/tap/service/impl/interface';
import { WifiMode } from '@iotize/tap/service/impl/wifi';
import { createDebugger } from '@iotize/common/debug';
import * as i4 from '@angular/common';
import { ConnectionState } from '@iotize/tap/protocol/api';
import { DomElementAccessor } from '@iotize/common/data-accessor';
import { IoTizeStudioToTapConfigConverter } from '@iotize/tap/config/iotize-studio';

const TAP_CONFIG_PATTERN = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/;
const IPV4_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];
    });
}
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) {
                return `v${v}`;
            }
            else {
                return 'Not configured';
            }
        },
        formValidators: [validateTapVersion],
        editFormatter: {
            read: (input) => {
                const parts = input.split('.');
                return {
                    major: parseInt(parts[0], 10),
                    minor: parseInt(parts[1], 10),
                    patch: parseInt(parts[2], 10),
                };
            },
            write: (v) => {
                return v ? `${v.major}.${v.minor}.${v.patch}` : '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: '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: '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) => {
                    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),
        // (v: HostProtocol[]) => {
        //     if (!v || v.length == 0) {
        //         return 'NONE'
        //     }
        //     return v.map(key => HostProtocol[key]).join(', ');
        // },
        // editFormatter: {
        //     write: (v: HostProtocol[]): string => {
        //         if (!v || v.length == 0) {
        //             return 'NONE'
        //         }
        //         return v.map(key => HostProtocol[key]).join(', ');
        //     },
        //     read: (v: string): HostProtocol[] => {
        //         let protocols: HostProtocol[] = stringToEnumArray<HostProtocol>(v, HostProtocol);
        //         return protocols;
        //     }
        // },
        getValue: (tap) => tap.service.interface.getAuthorizedHostProtocol(),
        putValue: (tap, protocols) => {
            return tap.service.interface.putAuthorizedHostProtocol(protocols);
        },
        init: async (service, config) => {
            const hostProtocols = await service.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) => {
            return tap.service.dataLog.isRunning();
        },
        actions: [
            {
                title: 'start',
                icon: 'play',
                color: 'primary',
                call: (tap) => tap.service.dataLog.run(),
                triggersRefresh: [TapInfo.DatalogAcquisitionRunning],
            },
            {
                title: 'stop',
                icon: 'pause',
                color: 'danger',
                call: (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.isTapConfigured,
    //     title: 'Tap is configured',
    //     input: {
    //         type: 'toggle'
    //     },
    //     getValue: async function (tap: Tap) {
    //         let version = (await tap.service.interface.getConfigVersion()).body();
    //         return !(version.major == 0 && version.minor == 0 && version.patch == 0);
    //     },
    //     editable: false
    // },
    {
        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.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.NetworkGatewayIp,
        title: 'Wi-Fi gateway IP address',
        getValue: (tap) => tap.service.wifi.getGatewayIp(),
        formValidators: [regexValidator(IPV4_PATTERN)],
    },
    {
        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 StringConverter.ascii().decode(metaData);
        },
        putValue: (tap, value, bundleId) => {
            const data = StringConverter.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
        },
    },
];

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

class TapConfigItemComponent {
    constructor(formBuilder, tapService, toastController) {
        this.tapService = tapService;
        this.toastController = toastController;
        this.focusOut = new EventEmitter();
        this.onSubmit = new EventEmitter();
        this.refresh = false;
        this.loading = false;
        this.placeholder = '';
        this.editMode = false;
        this.displayedValue = '';
        this.form = formBuilder.group({
            field: [''],
        });
        this.pendingCallManager = PendingCallManager.create(this.tapService);
    }
    set info(config) {
        const defaultConfig = defaultInfoResolverConfig.find((v) => config.key === v.key);
        if (defaultConfig) {
            config = { ...defaultConfig, ...config };
        }
        this.onConfigChange(config);
        this.updateDisplayValue();
        // this.fetchValueIfRequired();
    }
    get info() {
        return this.config;
    }
    get hasButtons() {
        return this.refresh || this.editable || this.pendingEditCall;
    }
    get originalValue() {
        return this.config.resolvedValue?.value;
    }
    get field() {
        return getFormFieldOrError(this.form, 'field');
    }
    get config() {
        if (!this._config) {
            throw LibError.componentArgumentRequired('TapConfigItemComponent', 'info');
        }
        return this._config;
    }
    get inputOptions() {
        return this.info.input || {};
    }
    get editable() {
        return this.config.editable;
    }
    get pendingEditCall() {
        return this.pendingCallManager.pendingCall;
    }
    onConfigChange(config) {
        this.valueSubscription?.unsubscribe();
        this._config = config;
        this.initializeConfig();
        const params = this.params || config.params;
        if (config.input && config.input.type === 'password') {
            this.setDisplayValue('');
        }
        if (config.params && !this.params) {
            this.params = config.params;
        }
        if (config.formValidators) {
            this.field.setValidators(config.formValidators);
        }
        this.valueSubscription = this.tapService
            .getKeyInfo$(config.key, params)
            .subscribe((newValue) => {
            config.resolvedValue = newValue;
            if (newValue.error) {
                this.setReadError(newValue.error);
            }
            else {
                this.readError = undefined;
                this.setDisplayValue(newValue.value);
                this.setFieldValue(newValue.value);
            }
        });
    }
    initializeConfig() {
        if (this.config.init) {
            this.config.init(this.tapService, this.config).catch((err) => {
                this.setReadError(err);
            });
        }
    }
    setWriteError(error) {
        this.writeError = this.userFriendlyError(error, 'write');
    }
    setReadError(error) {
        this.readError = this.userFriendlyError(error, 'read');
    }
    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;
                default:
                    message = tapResponseStatusToString(statusCode);
            }
            error = new Error(message);
        }
        return error;
    }
    ngOnDestroy() {
        // this.tapService.unregisterEventListener(this);
        this.valueSubscription?.unsubscribe();
        this.pendingCallManager.destroy();
        // this.pendingTapCall.unregister(this);
    }
    close() { }
    submit($event) {
        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 params = this.params || this.config.params;
        const call = async () => {
            await this.tapService.putInfo(this.info.key, value, params);
            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);
    }
    isEdited() {
        return this.field.value !== this.originalValue;
    }
    cancelEdit($event) {
        debug(this.config.key, 'cancel edit', $event);
        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;
    }
    // setValue(value: any) {
    //   debug(`Set value`, value);
    //   this.displayedValue = this.resolveValueForView();
    //   if (this._info.editFormatter) {
    //     value = this._info.editFormatter.write(value);
    //   }
    //   // let formValue = this.field.value;
    //   this.field.setValue(
    //     value
    //   );
    // }
    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.tapService.infoResolver.refreshKey(this.config.key, this.params || this.config.params);
    }
    restaureValue() {
        this.setFieldValue(this.originalValue);
    }
    setFieldValue(value) {
        const formattedValue = this.config.editFormatter
            ? this.config.editFormatter.write(value)
            : value;
        debug(this.config.key, 'setFieldValue with', formattedValue);
        this.field.setValue(formattedValue);
    }
    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);
        }
        // TODO loading
    }
    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 fetchValueIfRequired() {
    //   console.log('fetchValueIfRequired');
    //   if (!this.config.resolvedValue) {
    //     await this.fetchValue();
    //   }
    // }
    async fetchValue() {
        if (this.config.getValue) {
            const refreshedValue = await this.tapService.infoResolver.refreshKey(this.config.key, this.params);
        }
    }
}
/** @nocollapse */ /** @nocollapse */ TapConfigItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.2", ngImport: i0, type: TapConfigItemComponent, deps: [{ token: i1.FormBuilder }, { token: i2.CurrentDeviceService }, { token: i3.ToastController }], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ /** @nocollapse */ TapConfigItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.2", type: TapConfigItemComponent, selector: "tap-config-item", inputs: { params: "params", info: "info", refresh: "refresh", editMode: "editMode" }, outputs: { focusOut: "focusOut", onSubmit: "onSubmit" }, ngImport: i0, template: "<ion-item *ngIf=\"_config\" [formGroup]=\"form\" [lines]=\"editable ? '' : 'none'\">\n  <ion-toggle\n    *ngIf=\"inputOptions.type === 'toggle'; else viewEditSeparated\"\n    [disabled]=\"!editMode || loading || !!pendingEditCall\"\n    formControlName=\"field\"\n  ></ion-toggle>\n  <ng-template #viewEditSeparated>\n    <ng-container *ngIf=\"!editMode; else editModeTemplate\">\n      <ion-text\n        color=\"warning\"\n        class=\"read-value-error\"\n        (click)=\"onValueClick()\"\n        [class]=\"editable ? 'cell' : ''\"\n        *ngIf=\"readError; else noReadErrorTemplate\"\n      >\n        <ion-icon name=\"alert-circle\"></ion-icon> {{ readError.message }}\n      </ion-text>\n      <ng-template #noReadErrorTemplate>\n        <ng-container [ngSwitch]=\"inputOptions.type\">\n          <div\n            (click)=\"onValueClick()\"\n            *ngSwitchCase=\"'select'\"\n            [class]=\"editable ? '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=\"tap-config-item-value\"\n            *ngSwitchDefault\n            [class]=\"editable ? '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-select\n          *ngSwitchCase=\"'select'\"\n          formControlName=\"field\"\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]=\"!!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  </ng-template>\n  <ion-buttons *ngIf=\"hasButtons\" 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    <ng-container\n      *ngIf=\"editable; then EditableButtons; else NotEditableButtons\"\n    ></ng-container>\n    <ng-template #EditableButtons>\n      <ion-button (click)=\"editMode = true\" *ngIf=\"!editMode && info.putValue\">\n        <ion-icon name=\"create\"></ion-icon>\n      </ion-button>\n      <ion-button\n        (click)=\"submit($event)\"\n        *ngIf=\"editMode && !pendingEditCall\"\n        [disabled]=\"loading || pendingEditCall\"\n      >\n        <ion-icon name=\"send\"></ion-icon>\n      </ion-button>\n      <ion-button (click)=\"cancelEdit()\" *ngIf=\"editMode\" [disabled]=\"loading\">\n        <ion-icon name=\"close\"></ion-icon>\n      </ion-button>\n    </ng-template>\n    <ng-template #NotEditableButtons> </ng-template>\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</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.message }}\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}\n"], components: [{ type: i3.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { type: i3.IonToggle, selector: "ion-toggle", inputs: ["checked", "color", "disabled", "mode", "name", "value"] }, { type: i3.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { type: i3.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { type: i3.IonSelect, selector: "ion-select", inputs: ["cancelText", "compareWith", "disabled", "interface", "interfaceOptions", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "value"] }, { type: i3.IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { 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"] }, { type: i3.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { type: i3.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { type: i3.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { type: i3.BooleanValueAccessor, selector: "ion-checkbox,ion-toggle" }, { type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { type: i4.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { type: i4.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { type: i4.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { type: i3.SelectValueAccessor, selector: "ion-range, ion-select, ion-radio-group, ion-segment, ion-datetime" }, { type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3.TextValueAccessor, selector: "ion-input:not([type=number]),ion-textarea,ion-searchbar" }, { type: i2.AutoFocusDirective, selector: "[appAutofocus]", inputs: ["appAutoFocus"] }, { type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { type: i1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { type: i1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.2", ngImport: i0, type: TapConfigItemComponent, decorators: [{
            type: Component,
            args: [{ selector: 'tap-config-item', template: "<ion-item *ngIf=\"_config\" [formGroup]=\"form\" [lines]=\"editable ? '' : 'none'\">\n  <ion-toggle\n    *ngIf=\"inputOptions.type === 'toggle'; else viewEditSeparated\"\n    [disabled]=\"!editMode || loading || !!pendingEditCall\"\n    formControlName=\"field\"\n  ></ion-toggle>\n  <ng-template #viewEditSeparated>\n    <ng-container *ngIf=\"!editMode; else editModeTemplate\">\n      <ion-text\n        color=\"warning\"\n        class=\"read-value-error\"\n        (click)=\"onValueClick()\"\n        [class]=\"editable ? 'cell' : ''\"\n        *ngIf=\"readError; else noReadErrorTemplate\"\n      >\n        <ion-icon name=\"alert-circle\"></ion-icon> {{ readError.message }}\n      </ion-text>\n      <ng-template #noReadErrorTemplate>\n        <ng-container [ngSwitch]=\"inputOptions.type\">\n          <div\n            (click)=\"onValueClick()\"\n            *ngSwitchCase=\"'select'\"\n            [class]=\"editable ? '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=\"tap-config-item-value\"\n            *ngSwitchDefault\n            [class]=\"editable ? '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-select\n          *ngSwitchCase=\"'select'\"\n          formControlName=\"field\"\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]=\"!!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  </ng-template>\n  <ion-buttons *ngIf=\"hasButtons\" 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    <ng-container\n      *ngIf=\"editable; then EditableButtons; else NotEditableButtons\"\n    ></ng-container>\n    <ng-template #EditableButtons>\n      <ion-button (click)=\"editMode = true\" *ngIf=\"!editMode && info.putValue\">\n        <ion-icon name=\"create\"></ion-icon>\n      </ion-button>\n      <ion-button\n        (click)=\"submit($event)\"\n        *ngIf=\"editMode && !pendingEditCall\"\n        [disabled]=\"loading || pendingEditCall\"\n      >\n        <ion-icon name=\"send\"></ion-icon>\n      </ion-button>\n      <ion-button (click)=\"cancelEdit()\" *ngIf=\"editMode\" [disabled]=\"loading\">\n        <ion-icon name=\"close\"></ion-icon>\n      </ion-button>\n    </ng-template>\n    <ng-template #NotEditableButtons> </ng-template>\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</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.message }}\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}\n"] }]
        }], ctorParameters: function () { return [{ type: i1.FormBuilder }, { type: i2.CurrentDeviceService }, { type: i3.ToastController }]; }, propDecorators: { focusOut: [{
                type: Output
            }], onSubmit: [{
                type: Output
            }], params: [{
                type: Input
            }], info: [{
                type: Input
            }], refresh: [{
                type: Input
            }], editMode: [{
                type: Input
            }] } });

class TapConfigListComponent {
    constructor(tapService, toastController) {
        this.tapService = tapService;
        this.toastController = toastController;
        // dataSource: DeviceInfoDataSource;
        this.header = undefined;
        this.globalRefresh = true;
        this.itemRefresh = false;
        this.lines = 'full';
        this.columns = [
            'key',
            'value',
            // , 'action'
        ];
        this.loading = false;
        this._items = [];
    }
    set items(items) {
        if (!items) {
            items = [];
        }
        items = items.map((item) => {
            const defaultOption = defaultInfoResolverConfig.find((d) => item.key === d.key);
            if (defaultOption) {
                item = { ...defaultOption, ...item };
            }
            return item;
        });
        this._items = items;
    }
    ngOnInit() {
        this.sub = this.tapService.connectionState.subscribe((event) => {
            switch (event.newState) {
                case ConnectionState.CONNECTED:
                    for (const item of this._items) {
                        if (item.resolvedValue &&
                            item.resolvedValue.error &&
                            item.resolvedValue.error) {
                            // console.log(`Item ${item.key} has an error:`, item.resolvedValue.error);
                        }
                    }
                    //
                    break;
            }
        });
    }
    ngOnDestroy() {
        this._items = [];
        if (this.sub) {
            this.sub.unsubscribe();
        }
    }
    refresh() {
        this.loading = true;
        this.tapService.infoResolver
            .refreshKeys(this._items.map((value) => value?.key))
            .finally(() => (this.loading = false));
    }
    onSubmitValue() { }
    clickAction(item, action) {
        this.loading = true;
        action
            .call(this.tapService.tap)
            .then((response) => {
            response.successful();
            if (action.triggersRefresh) {
                return this.tapService.infoResolver.refreshKeys(action.triggersRefresh);
            }
        })
            .catch(async (err) => {
            await this.onError(err);
        })
            .then(() => {
            this.loading = false;
        });
    }
    async onError(err) {
        console.warn(err);
        const toast = await this.toastController.create({
            message: err.message || 'Unknown error',
            duration: 3000,
            position: 'bottom',
        });
        await toast.present();
    }
    async loadIfNeeded() {
        console.log('TapConfigListComponent', 'load');
        await this.tapService.infoResolver.loadKeysIfNotInCache(this._items.map((value) => value?.key));
    }
    isLoaded(item) {
        return this.tapService.infoResolver.isLoaded(item.key, this.params);
    }
}
/** @nocollapse */ /** @nocollapse */ TapConfigListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.2", ngImport: i0, type: TapConfigListComponent, deps: [{ token: i2.CurrentDeviceService }, { token: i3.ToastController }], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ /** @nocollapse */ TapConfigListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.2", type: TapConfigListComponent, selector: "tap-config-list", inputs: { header: "header", globalRefresh: "globalRefresh", itemRefresh: "itemRefresh", lines: "lines", columns: "columns", params: "params", 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    <ion-col size-xs=\"12\" size-sm=\"6\">\n      <ion-label>\n        <span class=\"item-title\">{{ item.title }}</span>\n        <ion-icon *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\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        *ngIf=\"!item.actions\"\n        [info]=\"item\"\n        [refresh]=\"itemRefresh\"\n        [params]=\"params\"\n      ></tap-config-item>\n\n      <ion-buttons *ngIf=\"item.actions\">\n        <ion-button\n          *ngFor=\"let action of item.actions\"\n          [color]=\"action.color\"\n          (click)=\"clickAction(item, action)\"\n          [disabled]=\"loading\"\n        >\n          <ion-icon *ngIf=\"action.icon\" [name]=\"action.icon\"></ion-icon>\n          {{ action.title }}\n        </ion-button>\n      </ion-buttons>\n    </ion-col>\n    <!-- <ion-spinner *ngIf=\"!isLoaded(item)\" slot=\"end\"></ion-spinner> -->\n  </ion-row>\n  <ion-toolbar *ngIf=\"globalRefresh\">\n    <ion-button\n      [disabled]=\"loading\"\n      float-right\n      (click)=\"refresh()\"\n      size=\"small\"\n    >\n      <ion-icon name=\"refresh\"></ion-icon>\n    </ion-button>\n  </ion-toolbar>\n</ion-grid>\n", styles: ["table{width:100%}.item-title{font-weight:700;font-size:1.05em}@media (prefers-color-scheme: dark){:root .item-title{color:#eee}}\n"], components: [{ type: i3.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { type: i3.IonRow, selector: "ion-row" }, { 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"] }, { type: i3.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { type: i3.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { type: TapConfigItemComponent, selector: "tap-config-item", inputs: ["params", "info", "refresh", "editMode"], outputs: ["focusOut", "onSubmit"] }, { type: i3.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { type: i3.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { type: i3.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.2", 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    <ion-col size-xs=\"12\" size-sm=\"6\">\n      <ion-label>\n        <span class=\"item-title\">{{ item.title }}</span>\n        <ion-icon *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\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        *ngIf=\"!item.actions\"\n        [info]=\"item\"\n        [refresh]=\"itemRefresh\"\n        [params]=\"params\"\n      ></tap-config-item>\n\n      <ion-buttons *ngIf=\"item.actions\">\n        <ion-button\n          *ngFor=\"let action of item.actions\"\n          [color]=\"action.color\"\n          (click)=\"clickAction(item, action)\"\n          [disabled]=\"loading\"\n        >\n          <ion-icon *ngIf=\"action.icon\" [name]=\"action.icon\"></ion-icon>\n          {{ action.title }}\n        </ion-button>\n      </ion-buttons>\n    </ion-col>\n    <!-- <ion-spinner *ngIf=\"!isLoaded(item)\" slot=\"end\"></ion-spinner> -->\n  </ion-row>\n  <ion-toolbar *ngIf=\"globalRefresh\">\n    <ion-button\n      [disabled]=\"loading\"\n      float-right\n      (click)=\"refresh()\"\n      size=\"small\"\n    >\n      <ion-icon name=\"refresh\"></ion-icon>\n    </ion-button>\n  </ion-toolbar>\n</ion-grid>\n", styles: ["table{width:100%}.item-title{font-weight:700;font-size:1.05em}@media (prefers-color-scheme: dark){:root .item-title{color:#eee}}\n"] }]
        }], ctorParameters: function () { return [{ type: i2.CurrentDeviceService }, { type: i3.ToastController }]; }, propDecorators: { header: [{
                type: Input
            }], globalRefresh: [{
                type: Input
            }], itemRefresh: [{
                type: Input
            }], lines: [{
                type: Input
            }], columns: [{
                type: Input
            }], params: [{
                type: Input
            }], items: [{
                type: Input
            }] } });

class TapConfigModule {
}
/** @nocollapse */ /** @nocollapse */ TapConfigModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.2", ngImport: i0, type: TapConfigModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
/** @nocollapse */ /** @nocollapse */ TapConfigModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.2.2", ngImport: i0, type: TapConfigModule, declarations: [TapConfigItemComponent, TapConfigListComponent], imports: [LibCommonModule, FormsModule, ReactiveFormsModule], exports: [TapConfigItemComponent, TapConfigListComponent] });
/** @nocollapse */ /** @nocollapse */ TapConfigModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.2.2", ngImport: i0, type: TapConfigModule, imports: [[LibCommonModule, FormsModule, ReactiveFormsModule]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.2", ngImport: i0, type: TapConfigModule, decorators: [{
            type: NgModule,
            args: [{
                    declarations: [TapConfigItemComponent, TapConfigListComponent],
                    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 config = new IoTizeStudioToTapConfigConverter().convert(new DomElementAccessor(xmlConfig));
    return { config };
}

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

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