/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AppRequest } from './models';
import { IApp } from './interfaces';
declare global {
    interface Window {
        App: IApp | undefined;
        NwaHandler: any
        select: unknown;
    }
}

function generateDeviceId() {
    const deviceId = self.crypto.randomUUID();
    localStorage.setItem('deviceId', deviceId);
    return deviceId;
}

function hasMinVersion(version) {
    const matchApp = /(\d+)\.(\d+)\.(\d+)\+(\d+) NWA/i.exec(navigator.userAgent);
    const matchMin = /(\d+)\.(\d+)\.(\d+)/i.exec(version);

    if (!matchApp || !matchMin) {
        return false;
    }

    const [majorA, minorA, patchA] = matchApp.slice(1).map(Number);
    const [majorB, minorB, patchB] = matchMin.slice(1).map(Number);

    if (majorA !== majorB) {
        return majorA > majorB;
    }
    if (minorA !== minorB) {
        return minorA > minorB;
    }
    return patchA >= patchB;
}

function getMeta(name) {
    const meta = document.querySelector('meta[name="' + name + '"]');

    if (meta) {
        return meta.getAttribute('content');
    } else {
        return '#FFFFFFFF';
    }
}

function getInfos() {
    const nVer = navigator.appVersion;
    const nAgt = navigator.userAgent;
    let browserName = navigator.appName;
    let fullVersion = '' + parseFloat(navigator.appVersion);
    let majorVersion = parseInt(navigator.appVersion, 10);
    let nameOffset, verOffset, ix;

    // In Opera, the true version is after "Opera" or after "Version"
    if ((verOffset = nAgt.indexOf('Opera')) !== -1) {
        browserName = 'Opera';
        fullVersion = nAgt.substring(verOffset + 6);
        if ((verOffset = nAgt.indexOf('Version')) !== -1) {
            fullVersion = nAgt.substring(verOffset + 8);
        }
    }
    // In MSIE, the true version is after "MSIE" in userAgent
    else if ((verOffset = nAgt.indexOf('MSIE')) !== -1) {
        browserName = 'Microsoft Internet Explorer';
        fullVersion = nAgt.substring(verOffset + 5);
    }
    // In Chrome, the true version is after "Chrome"
    else if ((verOffset = nAgt.indexOf('Chrome')) !== -1) {
        browserName = 'Chrome';
        fullVersion = nAgt.substring(verOffset + 7);
    }
    // In Safari, the true version is after "Safari" or after "Version"
    else if ((verOffset = nAgt.indexOf('Safari')) !== -1) {
        browserName = 'Safari';
        fullVersion = nAgt.substring(verOffset + 7);
        if ((verOffset = nAgt.indexOf('Version')) !== -1) {
            fullVersion = nAgt.substring(verOffset + 8);
        }
    }
    // In Firefox, the true version is after "Firefox"
    else if ((verOffset = nAgt.indexOf('Firefox')) !== -1) {
        browserName = 'Firefox';
        fullVersion = nAgt.substring(verOffset + 8);
    }
    // In most other browsers, "name/version" is at the end of userAgent
    else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
        browserName = nAgt.substring(nameOffset, verOffset);
        fullVersion = nAgt.substring(verOffset + 1);
        if (browserName.toLowerCase() === browserName.toUpperCase()) {
            browserName = navigator.appName;
        }
    }
    // trim the fullVersion string at semicolon/space if present
    if ((ix = fullVersion.indexOf(';')) !== -1) {
        fullVersion = fullVersion.substring(0, ix);
    }
    if ((ix = fullVersion.indexOf(' ')) !== -1) {
        fullVersion = fullVersion.substring(0, ix);
    }

    majorVersion = parseInt('' + fullVersion, 10);
    if (isNaN(majorVersion)) {
        fullVersion = '' + parseFloat(navigator.appVersion);
        majorVersion = parseInt(navigator.appVersion, 10);
    }

    return {
        browserName,
        fullVersion,
        majorVersion,
    };
}

let App: any;

const isNativeWebApp = /NWA/i.test(navigator.userAgent);
const isUpToDate = hasMinVersion('2.6.0');
if (isNativeWebApp && isUpToDate && window.NwaHandler) {
    const isAndroid = /android/i.test(navigator.userAgent);
    let requestId = 0;
    const handlers: ((...args) => boolean | void)[][] = [];
    const requests: AppRequest[] = [];
    App = new Proxy(window.NwaHandler, {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        get(target: any, propKey: string) {
            if (propKey === 'isWeb') {
                return false;
            }
            if (propKey === 'mobile' || propKey === 'nwa') {
                return true;
            }
            return (...args) => {
                if (propKey === 'notify' || propKey === 'emit') {
                    const channel = args.shift();
                    if (channel in handlers) {
                        try {
                            let success = true;
                            for (const handler of handlers[channel]) {
                                success = success && handler(...args) !== false;
                            }
                            return success;
                        } catch (e) {
                            return false;
                        }
                    } else {
                        return false;
                    }
                } else if (propKey === 'resolve' || propKey === 'reject' || propKey === 'onSuccess' || propKey === 'onError') {
                    const request = requests[args[0]];
                    if (request) {
                        if (args.length > 1) {
                            return request[propKey](args[1]);
                        } else {
                            return request[propKey]();
                        }
                    } else {
                        // eslint-disable-next-line max-len, no-console
                        console.error('Unable to resolve request. (id=' + args[0] + ', now=' + Date.now() + ', data=' + JSON.stringify(args[1]) + ')');
                    }
                } else if (target[propKey] === undefined) {
                    if (propKey === 'on') {
                        if (!handlers[args[0]]) {
                            handlers[args[0]] = [];
                        }
                        handlers[args[0]].push(args[1]);
                    }
                    const id = 'req-' + (requestId++) + '-' + Date.now();
                    if (isAndroid) {
                        if (target.request) {
                            return new Promise((resolve, reject) => {
                                requests[id] = { resolve, reject };
                                target.request(propKey, id, JSON.stringify(args));
                            });
                        } else {
                            return new Promise((resolve, reject) => {
                                requests[id] = { resolve, reject };
                                target.postMessage(propKey, id, JSON.stringify(args));
                            });
                        }
                    } else {
                        return target.postMessage(JSON.stringify({ method: propKey, params: args })).then((result) => {
                            if (result && result.compatError) {
                                throw result.compatError;
                            } else {
                                return result;
                            }
                        });
                    }
                }
            };
        },
    });
    // Register callbacks
    window.NwaHandler.resolve = App.resolve;
    window.NwaHandler.reject = App.reject;
    window.NwaHandler.notify = App.notify;
    window.NwaHandler.emit = App.notify; // Compat, to remove
    window.NwaHandler.onNotification = App.notify; // Compat, to remove

    // Bypass native browser functionalities
    if (isAndroid) {
        window.print = () => App.print();
        navigator.share = (data) => App.share(data);
        navigator.clipboard.writeText = (data) => App.copy(data);
        navigator.clipboard.readText = () => App.paste();
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        navigator.setAppBadge = (data) => App.setAppBadge(data);
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        navigator.clearAppBadge = () => App.clearAppBadge();
        navigator.vibrate = (data) => App.vibrate(data);
        window.open = (url, name) => App.openUrl(url, name);

        if (!('permissions' in navigator)) {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            navigator.permissions = {};
        }
        navigator.permissions.query = App.hasPermission;
    } else {
        const positionCallbacks: any[] = [];
        window.print = () => App.print();
        window.open = (url, name) => App.openUrl(url, name);
        navigator.share = (data) => App.share(data);
        navigator.clipboard.writeText = (data) => App.copy(data);
        navigator.clipboard.readText = () => App.paste();
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        navigator.setAppBadge = (data) => App.setAppBadge(data);
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        navigator.clearAppBadge = () => App.clearAppBadge();
        navigator.vibrate = (data) => App.vibrate(data);

        App.on('position', location => {
            if (positionCallbacks.length) {
                positionCallbacks.forEach(obj => obj.callback(location));
            } else {
                App.watchPosition(false);
            }
        });
        navigator.geolocation.watchPosition = (callback, errorCallback, options) => {
            const watchId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
            if (!positionCallbacks.length) {
                App.watchPosition(true, options).then(callback).catch(errorCallback);
            }
            positionCallbacks.push({
                id: watchId,
                callback,
            });
            return watchId;
        };
        navigator.geolocation.clearWatch = (watchId) => {
            positionCallbacks.filter((cb, i, arr) => {
                if (cb.id === watchId) {
                    arr.splice(i, 1);
                    return true;
                } else {
                    return false;
                }
            });
            if (!positionCallbacks.length) {
                App.watchPosition(false);
            }
        };
        navigator.geolocation.getCurrentPosition = (callback, errorCallback, settings) => {
            return App.getCurrentPosition(settings).then(callback).catch(errorCallback);
        };
    }

    window.select = function (params: any) {
        return App.select(params).then((files: any) => {
            const dataTransfer = new DataTransfer();
            for (const file of files) {
                const bytes = atob(file.data);
                let length = bytes.length;
                const out = new Uint8Array(length);

                // Loop and convert.
                while (length--) {
                    out[length] = bytes.charCodeAt(length);
                }
                dataTransfer.items.add(new File([out], file.name, { type: file.type }));
            }
            return dataTransfer;
        });
    };

    document.addEventListener('DOMContentLoaded', () => {
        try {
            const viewport = getMeta('viewport');
            const keyboardResize = !!viewport && viewport.includes('interactive-widget=resizes-content');
            const appBarOffset = !!viewport && !viewport.includes('viewport-fit=cover');
            const navBarOffset = !!viewport && !viewport.includes('viewport-fit=cover');
            const backgroundColor = parseInt(getMeta('theme-color')!.substring(1), 16);
            const appBarColor = parseInt(getMeta('app-bar-color')!.substring(1), 16);
            const navBarColor = parseInt(getMeta('nav-bar-color')!.substring(1), 16);

            App.updateAppStyle({
                backgroundColor,
                appBarColor,
                navBarColor,
                appBarOffset,
                navBarOffset,
                keyboardResize,
            });
        } catch (e) {
            console.error('Unable to update app style', e);
        }
    }, false);
    App.getDeviceId().then((deviceId) => localStorage.setItem('deviceId', deviceId));
} else {
    if (isNativeWebApp) {
        if (!isUpToDate) {
            console.error('Deprecated NWA version');
        }
        if (!window.NwaHandler) {
            console.error('NwaHandler not registered');
        }
    }
    const deviceId = localStorage.getItem('deviceId') ?? generateDeviceId();
    App = {
        isWeb: true,
        mobile: false,
        nwa: false,
        getDeviceInfos: async () => {
            const infos = getInfos();
            return {
                id: deviceId,
                os: 'browser',
                version: infos.fullVersion,
                model: infos.browserName,
                name: infos.browserName,
                appVersion: null,
                registrationToken: null,
            };
        },
        getDeviceId: async () => deviceId,
        on: async (event, handler) => {
            //
        },
    };
}
export default App;
