"use strict";
import { Agent, WebSocketChannel } from '@wavemaker/wavepulse-agent/index';
import { EVENTS, CALLS } from './constants';
import axios from 'axios';
import RN2CSS from './rn2css.json';
import { Platform } from 'react-native';

const MAX_WAITING_TIME_FOR_CONNECTION = 30 * 1000;
let wavePulseAgent = new (class WavePulseAgent extends Agent {
    onConnection;
    onConnectionFailed;
    constructor() {
        super(...arguments);
        this.isConnected = false;
        this.closed = false;
    }
    isFlushAllowed() {
        return this.isConnected;
    }
    connect(serverUrl, channelId) {
        const pathIndex = serverUrl.indexOf('/', serverUrl.indexOf('//') + 2);
        const serverOrigin = pathIndex > 0 ? serverUrl.substring(0, pathIndex) : serverUrl;
        const contextPath = pathIndex > 0  ? serverUrl.substring(pathIndex) : undefined;
        this.setChannel(WebSocketChannel.connect({
            'url': serverOrigin || '',
            'path': contextPath + '/socket.io',
            'channelId': channelId
        }));
        this.onConnectionFailed && this.onConnectionFailed();
        setTimeout(() => {
            this.invoke(CALLS.HANDSHAKE.RELOAD);
        }, 1000);
        setTimeout(() => {
            if (!this.isConnected) {
                this.closed = true;
                this.buffer = [];
                this.onConnectionFailed && this.onConnectionFailed();
            }
        }, MAX_WAITING_TIME_FOR_CONNECTION);
        return new Promise((resolve, reject) => {
            this.onConnection = () => {
                this.onConnection = undefined;
                resolve();
            };
            this.onConnectionFailed = () => {
                this.onConnectionFailed = undefined;
                reject();
            };
        });
    }
    notify(event, type, args, buffer) {
        if (!this.closed) {
            super.notify(event, type, args, buffer);
        }
    }
})();
const _console = console;
let tryWith = (fn) => {
    return function() {
        try {
            return fn && fn.apply(undefined, arguments);
        } catch(e) {
            _console.defaultError(e);
        }
    }
};
let currentPage = null;
let currentWidgets = {};
let handler = null;
function wrapConsole() {
    const prepareLogFn = (logType, original) => {
        return tryWith(function () {
            let args = [...arguments];
            if (logType === 'error' && arguments[0].stack) {
                args.splice(0, 1, arguments[0].stack.toString());
            }
            wavePulseAgent && wavePulseAgent.notify(EVENTS.CONSOLE.LOG, 'event', [{
                    type: logType,
                    date: Date.now(),
                    message: Array.from(args)
                }]);
            original.call(_console, ...arguments);
        });
    };
    _console.defaultLog = console.log;
    _console.log = prepareLogFn('log', _console.defaultLog);
    _console.defaultError = console.error;
    _console.error = prepareLogFn('error', _console.defaultError);
    _console.defaultWarn = console.warn;
    _console.warn = prepareLogFn('warn', _console.defaultWarn);
    _console.defaultDebug = console.debug;
    _console.debug = prepareLogFn('debug', _console.defaultDebug);
    _console.defaultInfo = console.info;
    _console.info = prepareLogFn('info', _console.defaultInfo);
}
function listenPageEvents() {
    handler.subscribe('pageReady', tryWith((p) => {
        currentPage = p;
        wavePulseAgent && wavePulseAgent.notify(EVENTS.TIMELINE.EVENT, 'event', [{
                name: 'PAGE_READY',
                data: {
                    name: currentPage.pageName
                },
                startTime: handler.appConfig.diagnostics.pageStartTime,
                endTime: handler.appConfig.diagnostics.pageReadyTime
            }]);
    }));
    handler.subscribe('pageAttached', tryWith((p) => {
        currentPage = p;
        wavePulseAgent && wavePulseAgent.notify(EVENTS.TIMELINE.EVENT, 'event', [{
                name: 'PAGE_READY',
                data: {
                    name: currentPage.pageName
                },
                startTime: handler.appConfig.diagnostics.pageStartTime,
                endTime: handler.appConfig.diagnostics.pageReadyTime
            }]);
    }));
    handler.subscribe('pageDestroyed', tryWith((p) => {
        currentPage = null;
    }));
}
function listenVariableEvents() {
    handler.subscribe('beforeInvoke', tryWith((v) => {
        wavePulseAgent && wavePulseAgent.notify(EVENTS.VARIABLE.BEFORE_INVOKE, 'event', []);
        v.__diagnostics = v.__diagnostics || {};
        v.__diagnostics.invocationStartTime = Date.now();
        v.__diagnostics.processed = false;
    }));
    handler.subscribe('afterInvoke', tryWith((v) => {
        wavePulseAgent && wavePulseAgent.notify(EVENTS.VARIABLE.AFTER_INVOKE, 'event', []);
        if (v.__diagnostics.processed) {
            return;
        }
        v.__diagnostics.invocationEndTime = Date.now();
        v.__diagnostics.processed = true;
        wavePulseAgent && wavePulseAgent.notify(EVENTS.TIMELINE.EVENT, 'event', [{
                name: 'VARIABLE_INVOKE',
                data: {
                    name: v.name,
                    context: v.config._context === handler ? 'App' : v.config._context.pageName
                },
                startTime: v.__diagnostics.invocationStartTime,
                endTime: v.__diagnostics.invocationEndTime
            }]);
    }));
}
function listenServiceCalls() {
    const _axios = global['axios'] || axios;
    _axios.interceptors.request.use(tryWith(function (config) {
        try {
            config.__startTime = Date.now();
            // Do something before request is sent
            wavePulseAgent && wavePulseAgent.notify(EVENTS.SERVICE.BEFORE_CALL, 'event', [config]);
        } finally {
            return config;
        }
    }), tryWith(function (error) {
        // Do something with request error
        return Promise.reject(error);
    }));
    // Add a response interceptor
    _axios.interceptors.response.use(tryWith(function (response) {
        try {
            // Any status code that lie within the range of 2xx cause this function to trigger
            // Do something with response data
            response.config.__endTime = Date.now();
            wavePulseAgent && wavePulseAgent.notify(EVENTS.SERVICE.AFTER_CALL, 'event', [response.config, response]);
            if (!response.config.__wmVariable) {
                const config = response.config;
                wavePulseAgent && wavePulseAgent.notify(EVENTS.TIMELINE.EVENT, 'event', [{
                        name: 'NETWORK_REQUEST',
                        data: {
                            url: config.url,
                            method: config.method
                        },
                        startTime: config.__startTime,
                        endTime: config.__endTime
                    }]);
            }
        } finally {
            return response;
        }
    }), tryWith(function (error) {
        try {
            // Any status codes that falls outside the range of 2xx cause this function to trigger
            // Do something with response error
            error.config.__endTime = Date.now();
            wavePulseAgent && wavePulseAgent.notify(EVENTS.SERVICE.AFTER_CALL, 'event', [error.config, error]);
            if (!error.config.__wmVariable) {
                const config = error.config;
                wavePulseAgent && wavePulseAgent.notify(EVENTS.TIMELINE.EVENT, 'event', [{
                        name: 'NETWORK_REQUEST',
                        data: {
                            url: config.url,
                            method: config.method
                        },
                        startTime: config.__startTime,
                        endTime: config.__endTime
                    }]);
            }
        } finally{
            return Promise.reject(error);
        }
    }));
}
function buildComponentTree(componentNode) {
    if (!componentNode.instance.props.cname) {
        return;
    }
    currentWidgets[componentNode.id + ''] = componentNode.instance;
    return {
        tagName: componentNode.instance.props.cname || componentNode.instance.constructor.name,
        name: componentNode.instance.props.name,
        id: componentNode.id + '',
        children: componentNode.children.map((c) => buildComponentTree(c)).filter(c => !!c)
    };
}
function getComponentTree() {
    currentPage = handler.activePage;
    const pageIns = currentPage && Object.values(currentPage.Widgets)
        .map((w) => w.componentNode)
        .find((w) => { var _a, _b; return ((_b = (_a = w === null || w === void 0 ? void 0 : w.instance) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.cname) === 'WmPage'; });
    currentWidgets = {};
    return pageIns && buildComponentTree(pageIns);
}
function getComponentById(id) {
    return currentWidgets[id];
}
function bindCalls() {
    let lastHighLightedWidgetId = '';
    wavePulseAgent.onInvoke(CALLS.WIDGET.GET, tryWith((args) => {
        const name = args[0].name;
        return Promise.resolve(currentPage === null || currentPage === void 0 ? void 0 : currentPage.Widgets[name]);
    }));
    wavePulseAgent.onInvoke(CALLS.WIDGET.HIGHLIGHT, tryWith((args) => {
        const lw = getComponentById(lastHighLightedWidgetId);
        lw === null || lw === void 0 ? void 0 : lw.setState({
            highlight: false
        });
        const widgetIdToHighlight = args[0];
        const w = getComponentById(widgetIdToHighlight);
        w === null || w === void 0 ? void 0 : w.setState({
            highlight: true
        });
        lastHighLightedWidgetId = widgetIdToHighlight;
        return Promise.resolve('');
    }));
    wavePulseAgent.onInvoke(CALLS.WIDGET.GET_PROPERTIES_N_STYLES, tryWith((args) => {
        const widgetId = args[0];
        const w = getComponentById(widgetId);
        const ins = w.componentNode.instance;
        const styles = JSON.parse(JSON.stringify(ins.styles));
        const inlineStyles = JSON.parse(JSON.stringify(ins.proxy.styles));
        inlineStyles && Object.keys(inlineStyles).forEach(k => {
            styles[k] = styles[k] || {};
            styles[k].__trace = styles[k].__trace || [];
            styles[k].__trace.unshift({name: '@inline:' + k, value: inlineStyles[k]});
        });
        const pp = ins.propertyProvider;
        const properties = Object.keys(pp.oldProps)
            .map(k => [k, pp.overriddenProps[k] || pp.oldProps[k]])
            .filter(([k, v]) => v !== undefined && v !== null && (typeof v !== 'object' || typeof v === 'string'))
            .reduce((r, c) => {
            r[c[0]] = c[1];
            return r;
        }, {});
        Object.keys(styles).forEach(k => {
            const rnStyle = (properties.cname || '')
                .replace( /([a-z])([A-Z])/g, '$1-$2' )
                .toLowerCase().replace('wm-', 'app-')
                + '.' + k;
            if (!rnStyle || !RN2CSS[rnStyle]) {
                delete styles[k];
                return;
            }
            styles[k].__label = RN2CSS[rnStyle];
            (styles[k].__trace)?.forEach(s => {
                s['source'] = s.name.split(':')[0].substring(1);
                if (s['source'] === 'inline') {
                    return;
                }
                let cssClassname = s.name.split(':')[1]
                    .replace(new RegExp(`\.?${rnStyle}$`), '')
                    .replace(new RegExp(`.${k}$`), '');
                cssClassname = cssClassname && ('.' + cssClassname);
                s['cssSelector'] = `${cssClassname}${RN2CSS[rnStyle]}`;
            })
        });
        return Promise.resolve({ styles, properties });
    }));
    wavePulseAgent.onInvoke(CALLS.HANDSHAKE.WISH, tryWith((args) => {
        setTimeout(() => {
            wavePulseAgent.isConnected = true;
            wavePulseAgent.isClosed = false;
            wavePulseAgent.onConnection && wavePulseAgent.onConnection();
            wavePulseAgent.processBuffer();
        }, 2000);
        return Promise.resolve('Hello');
    }));
    wavePulseAgent.onInvoke(CALLS.EXPRESSION.EVAL, tryWith((args) => {
        const expr = args[0].expr;
        if (expr) {
            return new Promise((resolve, reject) => {
                try {
                    const App = handler;
                    const Page = currentPage;
                    resolve('Expression evaluation is disabled.');
                }
                catch (e) {
                    reject(JSON.stringify(e));
                }
            });
        }
        return Promise.resolve();
    }));
    wavePulseAgent.onInvoke(CALLS.UI.REFRESH, tryWith(() => {
        return Promise.resolve(currentPage === null || currentPage === void 0 ? void 0 : currentPage.refresh());
    }));
    wavePulseAgent.onInvoke(CALLS.APP.INFO, tryWith(() => {
        const appConfig = handler.appConfig;
        const App = handler;
        return Promise.resolve({
            name: appConfig.appProperties.displayName,
            applicationId: appConfig.appId,
            version: appConfig.appProperties.version,
            description: '',
            icon: '',
            defaultLocale: appConfig.appProperties.defaultLanguage,
            selectedLocale: App.getSelectedLocale(),
            defaultTheme: appConfig.appProperties.activeTheme,
            activeTheme: App.activeTheme,
            activeLandingPage: appConfig.landingPage,
            defaultLandingPage: appConfig.appProperties.homePage,
            securityEnabled: appConfig.appProperties.securityEnabled,
            serverPath: appConfig.url
        });
    }));
    wavePulseAgent.onInvoke(CALLS.STORAGE.GET_ALL, tryWith(() => {
        const App = handler;
        return App.getDependency('StorageService').getAll();
    }));
    wavePulseAgent.onInvoke(CALLS.PLATFORM.INFO, tryWith(() => {
        return Promise.resolve({
            os: Platform.OS,
            version: (Platform.Version || '') + '',
            device: ''
        });
    }));
    wavePulseAgent.onInvoke(CALLS.WIDGET.TREE, tryWith((args) => {
        let result = null;
        try {
            result = getComponentTree();
        }
        catch (e) {
            console.error(e.stack.toString());
        }
        return Promise.resolve(result);
    }));
}
wrapConsole();
listenServiceCalls();
export const createWavePulseAgent = (args) => {
    handler = args.handler;
    if (args.agent) {
        wavePulseAgent = args.agent;
    }
    else {
        handler.subscribe('appReady', tryWith((p) => {
            wavePulseAgent && wavePulseAgent.notify(EVENTS.TIMELINE.EVENT, 'event', [{
                    name: 'APP_STARTUP',
                    startTime: handler.appConfig.diagnostics.appStartTime,
                    endTime: handler.appConfig.diagnostics.appReadyTime
                }]);
        }));
    }
    listenPageEvents();
    listenVariableEvents();
    bindCalls();
    return wavePulseAgent;
};