import dialogs from '$lib/dialogs';
import { startProgress } from '$lib/progress';
import { get, writable } from 'svelte/store';
import applicationInited from './inited';
import queries from './queries';
import windowTitle from './windowtitle';

import ExportDialog from '$organisms/connection/collection/dialogs/export.svelte';
import IndexDetailDialog from '$organisms/connection/collection/dialogs/indexdetail.svelte';
import QueryChooserDialog from '$organisms/connection/collection/dialogs/querychooser.svelte';
import DumpDialog from '$organisms/connection/database/dialogs/dump.svelte';
import HostDetailDialog from '$organisms/connection/host/dialogs/hostdetail.svelte';

import {
  CreateIndex,
  DropCollection,
  DropDatabase,
  DropIndex,
  GetIndexes,
  HostLogs,
  Hosts,
  OpenCollection,
  OpenConnection,
  OpenDatabase,
  PerformDump,
  PerformFindExport,
  RemoveHost,
  RenameCollection,
  TruncateCollection
} from '$wails/go/app/App';

const { set, subscribe } = writable({});
const getValue = () => get({ subscribe });
let hostTreeInited = false;

async function refresh() {
  const hosts = await Hosts();
  const hostTree = getValue();

  for (const [ hostKey, hostDetails ] of Object.entries(hosts)) {
    hostTree[hostKey] = hostTree[hostKey] || {};
    const host = hostTree[hostKey];
    host.key = hostKey;
    host.name = hostDetails.name;
    host.uri = hostDetails.uri;

    host.open = async function() {
      const { databases: dbNames, status, statusError, systemInfo, systemInfoError } = await OpenConnection(hostKey);
      host.status = status;
      host.statusError = statusError;
      host.systemInfo = systemInfo;
      host.systemInfoError = systemInfoError;
      host.databases = host.databases || {};

      if (!dbNames) {
        return;
      }

      for (const dbKey of dbNames.sort((a, b) => a.localeCompare(b))) {
        host.databases[dbKey] = host.databases[dbKey] || {};
      }

      for (const [ dbKey, database ] of Object.entries(host.databases)) {
        if (!database.new && !dbNames.includes(dbKey)) {
          delete host.databases[dbKey];
          continue;
        }

        database.key = dbKey;
        database.hostKey = hostKey;
        database.collections = database.collections || {};

        delete database.new;

        database.open = async function() {
          const { collections: collNames, stats, statsError } = await OpenDatabase(hostKey, dbKey);
          database.stats = stats;
          database.statsError = statsError;

          if (!collNames) {
            return;
          }

          for (const collKey of collNames.sort((a, b) => a.localeCompare(b))) {
            database.collections[collKey] = database.collections[collKey] || {};
          }

          for (const [ collKey, collection ] of Object.entries(database.collections)) {
            if (!collection.new && !collNames.includes(collKey)) {
              delete database.collections[collKey];
              continue;
            }

            collection.key = collKey;
            collection.dbKey = dbKey;
            collection.hostKey = hostKey;
            collection.indexes = collection.indexes || [];

            delete collection.new;

            collection.open = async function() {
              const { stats, statsError } = await OpenCollection(hostKey, dbKey, collKey);

              collection.stats = stats;
              collection.statsError = statsError;

              await refresh();
            };

            collection.rename = async function() {
              const newCollKey = await dialogs.enterText('Rename collection', `Enter a new name for collection ${collKey}.`, collKey);
              if (newCollKey && (newCollKey !== collKey)) {
                const progress = startProgress(`Renaming collection "${collKey}" to "${newCollKey}"…`);
                const ok = await RenameCollection(hostKey, dbKey, collKey, newCollKey);
                await database.open();
                progress.end();
                return ok;
              }
            };

            collection.export = function(query) {
              const dialog = dialogs.new(ExportDialog, { collection, query });

              return new Promise(resolve => {
                dialog.$on('export', async event => {
                  const success = await PerformFindExport(hostKey, dbKey, collKey, JSON.stringify(event.detail.exportInfo));
                  if (success) {
                    dialog.$close();
                    resolve();
                  }
                });
              });
            };

            collection.dump = function() {
              const dialog = dialogs.new(DumpDialog, { info: {
                hostKey,
                dbKey,
                collKeys: [ collKey ],
              } });

              return new Promise(resolve => {
                dialog.$on('dump', async event => {
                  const success = await PerformDump(JSON.stringify(event.detail.info));
                  if (success) {
                    dialog.$close();
                    resolve();
                  }
                });
              });
            };

            collection.truncate = async function() {
              await TruncateCollection(hostKey, dbKey, collKey);
              await refresh();
            };

            collection.drop = async function() {
              const success = await DropCollection(hostKey, dbKey, collKey);
              if (success) {
                await refresh();
              }
            };

            collection.getIndexes = async function() {
              collection.indexes = [];
              const { indexes, error } = await GetIndexes(hostKey, dbKey, collKey);

              if (error) {
                return error;
              }

              for (const indexDetails of indexes) {
                const index = {
                  name: indexDetails.name,
                  background: indexDetails.background || false,
                  unique: indexDetails.unique || false,
                  sparse: indexDetails.sparse || false,
                  model: indexDetails.model,
                };

                index.drop = async function() {
                  const hasBeenDropped = await DropIndex(hostKey, dbKey, collKey, index.name);
                  return hasBeenDropped;
                };

                collection.indexes.push(index);
              }
            };

            collection.getIndexByName = function(indesName) {
              return collection.indexes.find(idx => idx.name = indesName);
            };

            collection.newIndex = function() {
              const dialog = dialogs.new(IndexDetailDialog, { collection });

              return new Promise(resolve => {
                dialog.$on('create', async event => {
                  const newIndexName = await CreateIndex(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(event.detail.index));
                  if (newIndexName) {
                    dialog.$close();
                  }
                  resolve(newIndexName);
                });
              });
            };

            collection.openQueryChooser = function(queryToSave = undefined) {
              const dialog = dialogs.new(QueryChooserDialog, { collection, queryToSave });

              return new Promise(resolve => {
                dialog.$on('select', async event => {
                  dialog.$close();
                  resolve(event.detail.query);
                });

                dialog.$on('create', async event => {
                  const ok = await queries.create(event.detail.query);
                  if (ok) {
                    dialog.$close();
                    resolve(event.detail.query);
                  }
                });
              });
            };
          }

          await refresh();
          windowTitle.setSegments(dbKey, host.name, 'Rolens');
        };

        database.dump = function() {
          const dialog = dialogs.new(DumpDialog, { info: { hostKey, dbKey } });

          return new Promise(resolve => {
            dialog.$on('dump', async event => {
              const success = await PerformDump(JSON.stringify(event.detail.info));
              if (success) {
                dialog.$close();
                resolve();
              }
            });
          });
        };

        database.drop = async function() {
          const progress = startProgress(`Dropping database "${dbKey}"…`);
          const success = await DropDatabase(hostKey, dbKey);

          if (success) {
            await refresh();
          }

          progress.end();
        };

        database.newCollection = async function() {
          const name = await dialogs.enterText('Create a collection', 'Note: collections in MongoDB do not exist until they have at least one item. Your new collection will not persist on the server; fill it to have it created.', '');
          if (name) {
            database.collections[name] = { key: name, new: true };
            await database.open();
          }
        };
      }

      await refresh();
    };

    host.newDatabase = async function() {
      const name = await dialogs.enterText('Create a database', 'Enter the database name. Note: databases in MongoDB do not exist until they have a collection and an item. Your new database will not persist on the server; fill it to have it created.', '');
      if (name) {
        host.databases[name] = { key: name, new: true };
        await host.open();
      }
    };

    host.edit = async function() {
      const dialog = dialogs.new(HostDetailDialog, { hostKey });
      return new Promise(resolve => {
        dialog.$on('close', () => {
          refresh().then(resolve);
        });
      });
    };

    host.getLogs = async function(filter = 'global') {
      return await HostLogs(hostKey, filter);
    };

    host.remove = async function() {
      await RemoveHost(hostKey);
      await refresh();
    };
  }

  hostTreeInited = true;
  set(hostTree);
}

function newHost() {
  const dialog = dialogs.new(HostDetailDialog, { hostKey: '' });
  return new Promise(resolve => {
    dialog.$on('close', () => {
      refresh().then(resolve);
    });
  });
}

applicationInited.defer(refresh);

const hostTree = {
  refresh,
  subscribe,
  get: getValue,
  newHost,
  hasBeenInited: () => hostTreeInited,
};

export default hostTree;
