import { reactive, ref, watch } from 'vue';
import { defineStore } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';

import Noty from 'noty';

import {
    clearPiniaActionTrail,
    getPiniaActionTrail
} from '../plugin/piniaActionTrail';
import { debounce, parseLocation } from '../shared/utils';
import { AppDebug } from '../service/appConfig';
import { database } from '../service/database';
import { failedGetRequests } from '../service/request';
import { refreshCustomScript } from '../shared/utils/base/ui';
import { useAdvancedSettingsStore } from './settings/advanced';
import { useAvatarProviderStore } from './avatarProvider';
import { useAvatarStore } from './avatar';
import { useFavoriteStore } from './favorite';
import { useFriendStore } from './friend';
import { useGalleryStore } from './gallery';
import { useGameLogStore } from './gameLog';
import { useGameStore } from './game';
import { useGroupStore } from './group';
import { useInstanceStore } from './instance';
import { useLocationStore } from './location';
import { useModalStore } from './modal';
import { useNotificationStore } from './notification';
import { usePhotonStore } from './photon';
import { useSearchStore } from './search';
import { useUpdateLoopStore } from './updateLoop';
import { useUserStore } from './user';
import { useVrcStatusStore } from './vrcStatus';
import { useWorldStore } from './world';
import { watchState } from '../service/watchState';
import { worldRequest } from '../api';

import configRepository from '../service/config';

export const useVrcxStore = defineStore('Vrcx', () => {
    const gameStore = useGameStore();
    const locationStore = useLocationStore();
    const notificationStore = useNotificationStore();
    const avatarStore = useAvatarStore();
    const worldStore = useWorldStore();
    const instanceStore = useInstanceStore();
    const friendStore = useFriendStore();
    const favoriteStore = useFavoriteStore();
    const groupStore = useGroupStore();
    const userStore = useUserStore();
    const photonStore = usePhotonStore();
    const advancedSettingsStore = useAdvancedSettingsStore();
    const searchStore = useSearchStore();
    const avatarProviderStore = useAvatarProviderStore();
    const gameLogStore = useGameLogStore();
    const updateLoopStore = useUpdateLoopStore();
    const vrcStatusStore = useVrcStatusStore();
    const galleryStore = useGalleryStore();
    const { t } = useI18n();
    const modalStore = useModalStore();

    const state = reactive({
        databaseVersion: 0,
        locationX: 0,
        locationY: 0,
        sizeWidth: 800,
        sizeHeight: 600,
        windowState: '',
        externalNotifierVersion: 0
    });

    const currentlyDroppingFile = ref(null);
    const isRegistryBackupDialogVisible = ref(false);
    const ipcEnabled = ref(false);
    const clearVRCXCacheFrequency = ref(172800);
    const maxTableSize = ref(1000);
    const proxyServer = ref('');

    async function init() {
        if (LINUX) {
            window.electron.ipcRenderer.on('launch-command', (command) => {
                if (command) {
                    eventLaunchCommand(command);
                }
            });

            window.electron.onWindowPositionChanged((event, position) => {
                state.locationX = position.x;
                state.locationY = position.y;
                debounce(saveVRCXWindowOption, 300)();
            });

            window.electron.onWindowSizeChanged((event, size) => {
                state.sizeWidth = size.width;
                state.sizeHeight = size.height;
                debounce(saveVRCXWindowOption, 300)();
            });

            window.electron.onWindowStateChange((event, newState) => {
                state.windowState = newState.toString();
                debounce(saveVRCXWindowOption, 300)();
            });

            window.electron.onBrowserFocus(() => {
                vrcStatusStore.onBrowserFocus();
            });
        }

        state.databaseVersion = await configRepository.getInt(
            'VRCX_databaseVersion',
            0
        );
        updateDatabaseVersion();

        clearVRCXCacheFrequency.value = await configRepository.getInt(
            'VRCX_clearVRCXCacheFrequency',
            172800
        );

        if (!(await VRCXStorage.Get('VRCX_DatabaseLocation'))) {
            await VRCXStorage.Set('VRCX_DatabaseLocation', '');
        }
        if (!(await VRCXStorage.Get('VRCX_ProxyServer'))) {
            await VRCXStorage.Set('VRCX_ProxyServer', '');
        }
        if ((await VRCXStorage.Get('VRCX_DisableGpuAcceleration')) === '') {
            await VRCXStorage.Set('VRCX_DisableGpuAcceleration', 'false');
        }
        if (
            (await VRCXStorage.Get('VRCX_DisableVrOverlayGpuAcceleration')) ===
            ''
        ) {
            await VRCXStorage.Set(
                'VRCX_DisableVrOverlayGpuAcceleration',
                'false'
            );
        }
        proxyServer.value = await VRCXStorage.Get('VRCX_ProxyServer');
        state.locationX = parseInt(await VRCXStorage.Get('VRCX_LocationX'), 10);
        state.locationY = parseInt(await VRCXStorage.Get('VRCX_LocationY'), 10);
        state.sizeWidth = parseInt(await VRCXStorage.Get('VRCX_SizeWidth'), 10);
        state.sizeHeight = parseInt(
            await VRCXStorage.Get('VRCX_SizeHeight'),
            10
        );
        state.windowState = await VRCXStorage.Get('VRCX_WindowState');

        maxTableSize.value = await configRepository.getInt(
            'VRCX_maxTableSize',
            1000
        );
        if (maxTableSize.value > 10000) {
            maxTableSize.value = 1000;
        }
        database.setMaxTableSize(maxTableSize.value);

        refreshCustomScript();
    }

    init();

    async function updateDatabaseVersion() {
        // requires dbVars.userPrefix to be already set
        const databaseVersion = 13;
        let msgBox;
        if (state.databaseVersion < databaseVersion) {
            if (state.databaseVersion) {
                msgBox = toast.warning(
                    'DO NOT CLOSE VRCX, database upgrade in progress...',
                    { duration: Infinity, position: 'bottom-right' }
                );
            }
            console.log(
                `Updating database from ${state.databaseVersion} to ${databaseVersion}...`
            );
            try {
                await database.cleanLegendFromFriendLog(); // fix friendLog spammed with crap
                await database.fixGameLogTraveling(); // fix bug with gameLog location being set as traveling
                await database.fixNegativeGPS(); // fix GPS being a negative value due to VRCX bug with traveling
                await database.fixBrokenLeaveEntries(); // fix user instance timer being higher than current user location timer
                await database.fixBrokenGroupInvites(); // fix notification v2 in wrong table
                await database.fixBrokenNotifications(); // fix notifications being null
                await database.fixBrokenGroupChange(); // fix spam group left & name change
                await database.fixCancelFriendRequestTypo(); // fix CancelFriendRequst typo
                await database.fixBrokenGameLogDisplayNames(); // fix gameLog display names "DisplayName (userId)"
                await database.upgradeDatabaseVersion(); // update database version
                await database.vacuum(); // succ
                await database.optimize();
                await configRepository.setInt(
                    'VRCX_databaseVersion',
                    databaseVersion
                );
                console.log('Database update complete.');
                toast.dismiss(msgBox);
                if (state.databaseVersion) {
                    // only display when database exists
                    toast.success(t('message.database.upgrade_complete'));
                }
                state.databaseVersion = databaseVersion;
            } catch (err) {
                console.error(err);
                toast.dismiss(msgBox);
                toast.error(
                    'Database upgrade failed, check console for details',
                    { duration: 120000 }
                );
                AppApi.ShowDevTools();
            }
        }
    }

    function clearVRCXCache() {
        console.log('Clearing VRCX cache...');
        failedGetRequests.clear();
        userStore.cachedUsers.forEach((ref, id) => {
            if (
                !friendStore.friends.has(id) &&
                !locationStore.lastLocation.playerList.has(ref.id) &&
                id !== userStore.currentUser.id
            ) {
                userStore.cachedUsers.delete(id);
            }
        });
        worldStore.cachedWorlds.forEach((ref, id) => {
            if (
                !favoriteStore.getCachedFavoritesByObjectId(id) &&
                ref.authorId !== userStore.currentUser.id &&
                !favoriteStore.localWorldFavoritesList.includes(id)
            ) {
                worldStore.cachedWorlds.delete(id);
            }
        });
        avatarStore.cachedAvatars.forEach((ref, id) => {
            if (
                !favoriteStore.getCachedFavoritesByObjectId(id) &&
                ref.authorId !== userStore.currentUser.id &&
                !favoriteStore.localAvatarFavoritesList.includes(id) &&
                !avatarStore.avatarHistory.includes(id)
            ) {
                avatarStore.cachedAvatars.delete(id);
            }
        });
        groupStore.cachedGroups.forEach((ref, id) => {
            if (!groupStore.currentUserGroups.has(id)) {
                groupStore.cachedGroups.delete(id);
            }
        });
        instanceStore.cachedInstances.forEach((ref, id) => {
            if (
                [...friendStore.friends.values()].some(
                    (f) => f.$location?.tag === id
                )
            ) {
                return;
            }
            // delete instances over an hour old
            if (Date.parse(ref.$fetchedAt) < Date.now() - 3600000) {
                instanceStore.cachedInstances.delete(id);
            }
        });
        avatarStore.cachedAvatarNames.clear();
        userStore.customUserTags.clear();
        galleryStore.cachedEmoji.clear();
    }

    function eventVrcxMessage(data) {
        let entry;
        switch (data.MsgType) {
            case 'CustomTag':
                userStore.addCustomTag(data);
                break;
            case 'ClearCustomTags':
                userStore.customUserTags.forEach((value, key) => {
                    userStore.customUserTags.delete(key);
                    const ref = userStore.cachedUsers.get(key);
                    if (typeof ref !== 'undefined') {
                        ref.$customTag = '';
                        ref.$customTagColour = '';
                    }
                });
                break;
            case 'Noty':
                if (
                    photonStore.photonLoggingEnabled ||
                    (state.externalNotifierVersion &&
                        state.externalNotifierVersion > 21)
                ) {
                    return;
                }
                entry = {
                    created_at: new Date().toJSON(),
                    type: 'Event',
                    data: data.Data
                };
                database.addGamelogEventToDatabase(entry);
                notificationStore.queueGameLogNoty(entry);
                gameLogStore.addGameLog(entry);
                break;
            case 'External': {
                const displayName = data.DisplayName ?? '';
                const notify = data.notify ?? true;
                entry = {
                    created_at: new Date().toJSON(),
                    type: 'External',
                    message: data.Data,
                    displayName,
                    userId: data.UserId,
                    location: locationStore.lastLocation.location
                };
                database.addGamelogExternalToDatabase(entry);
                if (notify) {
                    notificationStore.queueGameLogNoty(entry);
                }
                gameLogStore.addGameLog(entry);
                break;
            }
            default:
                console.log('VRCXMessage:', data);
                break;
        }
    }

    async function saveVRCXWindowOption() {
        if (LINUX) {
            VRCXStorage.Set('VRCX_LocationX', state.locationX.toString());
            VRCXStorage.Set('VRCX_LocationY', state.locationY.toString());
            VRCXStorage.Set('VRCX_SizeWidth', state.sizeWidth.toString());
            VRCXStorage.Set('VRCX_SizeHeight', state.sizeHeight.toString());
            VRCXStorage.Set('VRCX_WindowState', state.windowState);
        }
    }

    async function processScreenshot(path) {
        let newPath = path;
        if (advancedSettingsStore.screenshotHelper) {
            const location = parseLocation(locationStore.lastLocation.location);
            const metadata = {
                application: 'VRCX',
                version: 1,
                author: {
                    id: userStore.currentUser.id,
                    displayName: userStore.currentUser.displayName
                },
                world: {
                    name: locationStore.lastLocation.name,
                    id: location.worldId,
                    instanceId: locationStore.lastLocation.location
                },
                players: []
            };
            for (const user of locationStore.lastLocation.playerList.values()) {
                metadata.players.push({
                    id: user.userId,
                    displayName: user.displayName
                });
            }
            try {
                newPath = await AppApi.AddScreenshotMetadata(
                    path,
                    JSON.stringify(metadata),
                    location.worldId,
                    advancedSettingsStore.screenshotHelperModifyFilename
                );
            } catch (e) {
                console.error('Failed to add screenshot metadata', e);
                if (e.message?.includes('UnauthorizedAccessException')) {
                    toast.error(
                        'Failed to add screenshot metadata, access denied. Make sure VRCX has permission to access the screenshot folder.',
                        { duration: 10000 }
                    );
                }
                return;
            }
            if (!newPath) {
                console.error('Failed to add screenshot metadata', path);
                return;
            }
            console.log('Screenshot metadata added', newPath);
        }
        if (advancedSettingsStore.screenshotHelperCopyToClipboard) {
            await AppApi.CopyImageToClipboard(newPath);
            console.log('Screenshot copied to clipboard', newPath);
        }
    }

    // use in C# side
    function ipcEvent(json) {
        if (!watchState.isLoggedIn) {
            return;
        }
        let data;
        try {
            data = JSON.parse(json);
        } catch {
            console.log(`IPC invalid JSON, ${json}`);
            return;
        }

        switch (data.type) {
            case 'OnEvent':
                if (!gameStore.isGameRunning) {
                    console.log('Game closed, skipped event', data);
                    return;
                }
                if (AppDebug.debugPhotonLogging || AppDebug.debugIPC) {
                    console.log(
                        'OnEvent',
                        data.OnEventData.Code,
                        'Param[254]:',
                        data.OnEventData.Parameters?.[254],
                        data.OnEventData
                    );
                }
                photonStore.parsePhotonEvent(data.OnEventData, data.dt);
                photonStore.photonEventPulse();
                break;
            case 'OnOperationResponse':
                if (!gameStore.isGameRunning) {
                    console.log('Game closed, skipped event', data);
                    return;
                }
                if (AppDebug.debugPhotonLogging || AppDebug.debugIPC) {
                    console.log(
                        'OnOperationResponse',
                        data.OnOperationResponseData.OperationCode,
                        'Param[254]:',
                        data.OnOperationResponseData.Parameters?.[254],
                        data.OnOperationResponseData
                    );
                }
                photonStore.parseOperationResponse(
                    data.OnOperationResponseData,
                    data.dt
                );
                photonStore.photonEventPulse();
                break;
            case 'OnOperationRequest':
                if (!gameStore.isGameRunning) {
                    console.log('Game closed, skipped event', data);
                    return;
                }
                if (AppDebug.debugPhotonLogging || AppDebug.debugIPC) {
                    console.log(
                        'OnOperationRequest',
                        data.OnOperationRequestData.OperationCode,
                        data.OnOperationRequestData
                    );
                }
                break;
            case 'VRCEvent':
                if (!gameStore.isGameRunning) {
                    console.log('Game closed, skipped event', data);
                    return;
                }
                if (AppDebug.debugIPC) {
                    console.log('VRCEvent:', data);
                }
                photonStore.parseVRCEvent(data);
                photonStore.photonEventPulse();
                break;
            case 'Event7List':
                if (AppDebug.debugIPC) {
                    console.log('Event7List:', data);
                }
                photonStore.photonEvent7List.clear();
                for (const [id, dt] of Object.entries(data.Event7List)) {
                    photonStore.photonEvent7List.set(parseInt(id, 10), dt);
                }
                photonStore.photonLastEvent7List = Date.parse(data.dt);
                break;
            case 'VrcxMessage':
                if (AppDebug.debugPhotonLogging || AppDebug.debugIPC) {
                    console.log('VrcxMessage:', data);
                }
                eventVrcxMessage(data);
                break;
            case 'Ping':
                if (AppDebug.debugIPC) {
                    console.log('IPC Ping');
                }
                if (!photonStore.photonLoggingEnabled) {
                    photonStore.setPhotonLoggingEnabled();
                }
                ipcEnabled.value = true;
                updateLoopStore.ipcTimeout = 60; // 30 seconds
                break;
            case 'MsgPing':
                if (AppDebug.debugIPC) {
                    console.log('MsgPing:', data);
                }
                state.externalNotifierVersion = data.version;
                break;
            case 'LaunchCommand':
                eventLaunchCommand(data.command);
                break;
            case 'VRCXLaunch':
                console.log('VRCXLaunch:', data);
                break;
            default:
                console.log('IPC:', data);
        }
    }

    /**
     * This function is called by .NET(CefCustomDragHandler#CefCustomDragHandler) when a file is dragged over a drop zone in the app window.
     * @param {string} filePath - The full path to the file being dragged into the window
     */
    function dragEnterCef(filePath) {
        currentlyDroppingFile.value = filePath;
    }

    watch(
        () => watchState.isLoggedIn,
        (isLoggedIn) => {
            isRegistryBackupDialogVisible.value = false;
            if (isLoggedIn) {
                startupLaunchCommand();
            }
        },
        { flush: 'sync' }
    );

    async function startupLaunchCommand() {
        const command = await AppApi.GetLaunchCommand();
        if (!command) {
            return;
        }
        if (command.startsWith('crash/')) {
            const crashMessage = command.replace('crash/', '');
            console.error('VRCX recovered from crash:', crashMessage);

            if (advancedSettingsStore.sentryErrorReporting) {
                try {
                    import('@sentry/vue').then((Sentry) => {
                        const trail = getPiniaActionTrail()
                            .filter((entry) => {
                                if (!entry) return false;
                                return (
                                    typeof entry.t === 'string' &&
                                    typeof entry.a === 'string'
                                );
                            })
                            .reverse();
                        const trailText = JSON.stringify(trail);
                        Sentry.withScope((scope) => {
                            scope.setLevel('fatal');
                            scope.setTag('reason', 'crash-recovery');
                            scope.setContext('pinia_actions', {
                                trailText,
                                sessionTime: performance.now() / 1000 / 60
                            });
                            Sentry.captureMessage(
                                `crash message: ${crashMessage}`
                            );
                        });

                        clearPiniaActionTrail();
                    });
                } catch (error) {
                    console.error('Error setting up Sentry feedback:', error);
                }
            }

            toast.success(t('message.crash.vrcx_reload'));
            return;
        }
        eventLaunchCommand(command);
    }

    // called from C#
    function eventLaunchCommand(input) {
        if (!watchState.isLoggedIn) {
            return;
        }
        console.log('LaunchCommand:', input);
        const args = input.split('/');
        const command = args[0];
        const commandArg = args[1]?.trim();
        let shouldFocusWindow = true;
        switch (command) {
            case 'world':
                if (
                    !searchStore.directAccessWorld(input.replace('world/', ''))
                ) {
                    // fallback for mangled world ids
                    worldStore.showWorldDialog(commandArg);
                }
                break;
            case 'avatar':
                avatarStore.showAvatarDialog(commandArg);
                break;
            case 'user':
                userStore.showUserDialog(commandArg);
                break;
            case 'group':
                groupStore.showGroupDialog(commandArg);
                break;
            case 'local-favorite-world':
                console.log('local-favorite-world', commandArg);
                const [id, group] = commandArg.split(':');
                worldRequest.getCachedWorld({ worldId: id }).then((args1) => {
                    searchStore.directAccessWorld(id);
                    favoriteStore.addLocalWorldFavorite(id, group);
                    return args1;
                });
                break;
            case 'addavatardb':
                avatarProviderStore.addAvatarProvider(
                    input.replace('addavatardb/', '')
                );
                break;
            case 'switchavatar':
                const avatarId = commandArg;
                const regexAvatarId =
                    /avtr_[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}/g;
                if (!avatarId.match(regexAvatarId) || avatarId.length !== 41) {
                    toast.error('Invalid Avatar ID');
                    break;
                }
                if (advancedSettingsStore.showConfirmationOnSwitchAvatar) {
                    avatarStore.selectAvatarWithConfirmation(avatarId);
                    // Makes sure the window is focused
                    shouldFocusWindow = true;
                } else {
                    avatarStore
                        .selectAvatarWithoutConfirmation(avatarId)
                        .then(() => {
                            new Noty({
                                type: 'success',
                                text: 'Avatar changed via launch command'
                            }).show();
                        });
                    shouldFocusWindow = false;
                }
                break;
            case 'import':
                const type = args[1];
                if (!type) break;
                const data = input.replace(`import/${type}/`, '');
                if (type === 'avatar') {
                    favoriteStore.avatarImportDialogInput = data;
                    favoriteStore.showAvatarImportDialog();
                } else if (type === 'world') {
                    favoriteStore.worldImportDialogInput = data;
                    favoriteStore.showWorldImportDialog();
                } else if (type === 'friend') {
                    favoriteStore.friendImportDialogInput = data;
                    favoriteStore.showFriendImportDialog();
                }
                break;
        }
        if (shouldFocusWindow) {
            AppApi.FocusWindow();
        }
    }

    async function backupVrcRegistry(name) {
        let regJson;
        try {
            if (WINDOWS) {
                regJson = await AppApi.GetVRChatRegistry();
            } else {
                regJson = await AppApi.GetVRChatRegistryJson();
                regJson = JSON.parse(regJson);
            }
        } catch (e) {
            console.error('Failed to get VRChat registry for backup:', e);
            return;
        }
        const newBackup = {
            name,
            date: new Date().toJSON(),
            data: regJson
        };
        let backupsJson = await configRepository.getString(
            'VRCX_VRChatRegistryBackups'
        );
        if (!backupsJson) {
            backupsJson = JSON.stringify([]);
        }
        const backups = JSON.parse(backupsJson);
        backups.push(newBackup);
        await configRepository.setString(
            'VRCX_VRChatRegistryBackups',
            JSON.stringify(backups)
        );
        // await this.updateRegistryBackupDialog();
    }

    async function checkAutoBackupRestoreVrcRegistry() {
        if (
            !advancedSettingsStore.vrcRegistryAutoBackup ||
            !advancedSettingsStore.vrcRegistryAskRestore
        ) {
            return;
        }

        // check for auto restore
        const hasVRChatRegistryFolder = await AppApi.HasVRChatRegistryFolder();
        if (!hasVRChatRegistryFolder) {
            const lastBackupDate = await configRepository.getString(
                'VRCX_VRChatRegistryLastBackupDate'
            );
            const lastRestoreCheck = await configRepository.getString(
                'VRCX_VRChatRegistryLastRestoreCheck'
            );
            if (
                !lastBackupDate ||
                (lastRestoreCheck &&
                    lastBackupDate &&
                    lastRestoreCheck === lastBackupDate)
            ) {
                // only ask to restore once and when backup is present
                return;
            }
            // popup message about auto restore
            modalStore.alert({
                description: t('dialog.registry_backup.restore_prompt'),
                title: t('dialog.registry_backup.header')
            });
            showRegistryBackupDialog();
            await AppApi.FocusWindow();
            await configRepository.setString(
                'VRCX_VRChatRegistryLastRestoreCheck',
                lastBackupDate
            );
        } else {
            await tryAutoBackupVrcRegistry();
        }
    }

    function showRegistryBackupDialog() {
        isRegistryBackupDialogVisible.value = true;
    }

    async function tryAutoBackupVrcRegistry() {
        if (!advancedSettingsStore.vrcRegistryAutoBackup) {
            return;
        }
        const date = new Date();
        const lastBackupDate = await configRepository.getString(
            'VRCX_VRChatRegistryLastBackupDate'
        );
        if (lastBackupDate) {
            const lastBackup = new Date(lastBackupDate);
            const diff = date.getTime() - lastBackup.getTime();
            const diffDays = Math.floor(diff / (1000 * 60 * 60 * 24));
            if (diffDays < 3) {
                return;
            }
        }
        let backupsJson = await configRepository.getString(
            'VRCX_VRChatRegistryBackups'
        );
        if (!backupsJson) {
            backupsJson = JSON.stringify([]);
        }
        const backups = JSON.parse(backupsJson);
        for (let i = backups.length - 1; i >= 0; i--) {
            const backupDate = new Date(backups[i].date);
            // remove backups older than 2 weeks
            if (
                backups[i].name === 'Auto Backup' &&
                backupDate.getTime() < date.getTime() - 1209600000 // 2 weeks in milliseconds
            ) {
                backups.splice(i, 1);
            }
        }
        await configRepository.setString(
            'VRCX_VRChatRegistryBackups',
            JSON.stringify(backups)
        );
        backupVrcRegistry('Auto Backup');
        await configRepository.setString(
            'VRCX_VRChatRegistryLastBackupDate',
            date.toJSON()
        );
    }

    return {
        state,

        proxyServer,
        currentlyDroppingFile,
        isRegistryBackupDialogVisible,
        ipcEnabled,
        clearVRCXCacheFrequency,
        maxTableSize,
        clearVRCXCache,
        eventVrcxMessage,
        eventLaunchCommand,
        showRegistryBackupDialog,
        checkAutoBackupRestoreVrcRegistry,
        tryAutoBackupVrcRegistry,
        processScreenshot,
        ipcEvent,
        dragEnterCef,
        backupVrcRegistry,
        updateDatabaseVersion
    };
});
