<script setup>

import { useI18n } from 'vue-i18n';
const i18n = useI18n();
const t = i18n.t;

import moment from 'moment';
import { ref, computed, useTemplateRef, onActivated, onMounted, onUnmounted, inject, watch, nextTick } from 'vue';
import { TextInput, ProgressBar, InputDialog, SingleSelect } from '@cloudron/pankow';
import AppsModel from '../models/AppsModel.js';
import AppstoreModel from '../models/AppstoreModel.js';
import AppInstallDialog from '../components/AppInstallDialog.vue';
import AppStoreItem from '../components/AppStoreItem.vue';

const appsModel = AppsModel.create();
const appstoreModel = AppstoreModel.create();

const ready = ref(false);
const apps = ref([]);
const search = ref('');

// clear category on search
watch(search, (newValue) => {
  if (newValue) category.value ='';
});

function filterForNewApps(apps) {
    var minApps = apps.length < 12 ? apps.length : 12; // prevent endless loop
    var tmp = [];
    var i = 0;

    do {
        var offset = moment().subtract(i++, 'days');
        tmp = apps.filter(function (app) { return moment(app.publishedAt).isAfter(offset); });
    } while(tmp.length < minApps);

    return tmp;
}

const filteredApps = computed(() => {
  if (category.value) {
    if (category.value === 'new') {
      return filterForNewApps(apps.value);
    } else {
      return apps.value.filter(a => {
        if (a.manifest.tags.join().toLowerCase().indexOf(category.value) !== -1) return true;
        return false;
      });
    }
  }

  if (!search.value) return apps.value;

  const s = search.value.toLowerCase();

  return apps.value.filter(a => {
    if (a.manifest.title.toLowerCase().indexOf(s) !== -1) return true;
    if (a.manifest.tagline.toLowerCase().indexOf(s) !== -1) return true;
    if (a.manifest.tags.join().toLowerCase().indexOf(s) !== -1) return true;
    return false;
  });
});
const filteredAllApps = computed(() => {
  return filteredApps.value.filter(a => !a.featured).sort((a, b) => { return a.manifest.title.localeCompare(b.manifest.title); });
});
const filteredPopularApps = computed(() => {
  return filteredApps.value.filter(a => a.featured);
});
const appInstallDialog = useTemplateRef('appInstallDialog');
const searchInput = useTemplateRef('searchInput');
const inputDialog = useTemplateRef('inputDialog');
const features = inject('features');
const installedApps = ref([]);
const appstoreTokenError = ref(false);

const category = ref('');
const categories = [
  { id: '', label: t('appstore.category.all') },
  { id: 'new', label: t('appstore.category.newApps') },
  { id: 'analytics', label: 'Analytics'},
  { id: 'automation', label: 'Automation'},
  { id: 'blog', label: 'Blog'},
  { id: 'chat', label: 'Chat'},
  { id: 'crm', label: 'CRM'},
  { id: 'document', label: 'Documents'},
  { id: 'email', label: 'Email'},
  { id: 'federated', label: 'Federated'},
  { id: 'finance', label: 'Finance'},
  { id: 'forum', label: 'Forum'},
  { id: 'fun', label: 'Fun'},
  { id: 'gallery', label: 'Gallery'},
  { id: 'game', label: 'Games'},
  { id: 'git', label: 'Code Hosting'},
  { id: 'hosting', label: 'Web Hosting'},
  { id: 'learning', label: 'Learning'},
  { id: 'media', label: 'Media'},
  { id: 'no-code', label: 'No-code'},
  { id: 'notes', label: 'Notes'},
  { id: 'project', label: 'Project Management'},
  { id: 'sync', label: 'File Sync'},
  { id: 'voip', label: 'VoIP'},
  { id: 'vpn', label: 'VPN'},
  { id: 'wiki', label: 'Wiki'},
];

function onAppInstallDialogClose() {
  window.location.href = '#/appstore';
}

function onInstall(app) {
  window.location.href = `#/appstore/${app.manifest.id}?version=${app.manifest.version}`;
}

async function getAppList() {
  const [error, result] = await appstoreModel.list();
  if (error) {
    if (error.status === 402) return appstoreTokenError.value = true;
    return console.error(error);
  }

  apps.value = result;
}

async function getApp(id, version = '') {
  const [error, result] = await appstoreModel.get(id, version);
  if (error) {
    console.error(error);
    return null;
  }

  return result;
}

async function getInstalledApps() {
  const [error, result] = await appsModel.list();
  if (error) return console.error(error);

  installedApps.value = result;
}

async function onHashChange() {
  if (window.location.hash.indexOf('#/appstore') !== 0) return;

  if (appInstallDialog.value) appInstallDialog.value.close();

  const query = window.location.hash.slice('#/appstore/'.length);
  if (query) {
    const appId = query.split('?')[0];
    const params = new URLSearchParams(window.location.hash.slice(window.location.hash.indexOf('?')));
    const version = params.get('version') || 'latest';

    const app = await getApp(appId, version);
    if (app) {
      appInstallDialog.value.open(app, installedApps.value.length >= features.value.appMaxCount);
    } else {
      inputDialog.value.info({
        title: t('appstore.appNotFoundDialog.title'),
        message: t('appstore.appNotFoundDialog.description', { appId, version }),
        confirmLabel: t('main.dialog.close'),
      });
    }
  } else {
    await nextTick();
    if (searchInput.value) searchInput.value.$el.focus();
  }
}

const view = useTemplateRef('view');
const itemWidth = ref('unset');

function setItemWidth() {
  const width = view.value.offsetWidth;
  const gap = 20; // flexbox gap value

  if (width <= 575) itemWidth.value = '100%';
  else if (width <= 800) itemWidth.value = Number((width-gap*2)/2).toFixed() + 'px';
  else if (width <= 1024) itemWidth.value = Number((width-gap*3)/3).toFixed() + 'px';
  else itemWidth.value = Number((width-gap*4)/4).toFixed() + 'px';
}

async function onActivation() {
  search.value = '';
  category.value = '';

  await getInstalledApps();
  await getAppList();

  // only deals with #/appstore/ hashes
  window.addEventListener('hashchange', onHashChange);
  onHashChange();

  window.addEventListener('resize', setItemWidth);

  setItemWidth();
}

onActivated(onActivation);

onMounted(async () => {
  await onActivation();
  ready.value = true;
});

onUnmounted(() => {
  window.removeEventListener('hashchange', onHashChange);
  window.removeEventListener('resize', setItemWidth);
});

</script>

<template>
  <div ref="view" class="content-large" style="width: 100%; height: 100%;">
    <InputDialog ref="inputDialog"/>
    <AppInstallDialog ref="appInstallDialog" @close="onAppInstallDialogClose"/>

    <div class="filter-bar">
      <SingleSelect v-model="category" :options="categories" option-key="id" option-label="label" :disabled="!ready"/>
      <TextInput ref="searchInput" @keydown.esc="search = ''" v-model="search" :disabled="!ready" :placeholder="$t('appstore.searchPlaceholder')" style="flex-grow: 1;"/>
    </div>

    <div v-if="!ready" style="margin-top: 15px">
      <ProgressBar mode="indeterminate" :show-label="false" :slim="true"/>
    </div>
    <div v-else-if="appstoreTokenError">
      Cloudron not registered. Reset registration <a href="#/cloudron-account">here</a>.
    </div>
    <div v-else>
      <div v-if="!search">
        <h4 v-show="filteredPopularApps.length">{{ $t('appstore.category.popular') }}</h4>
        <div class="grid">
          <AppStoreItem :style="{ width: itemWidth }" v-for="app in filteredPopularApps" :app="app" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)"/>
        </div>

        <h4 v-show="filteredAllApps.length">{{ $t('appstore.category.all') }}</h4>
        <div class="grid">
          <AppStoreItem :style="{ width: itemWidth }" v-for="app in filteredAllApps" :app="app" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)"/>
        </div>
      </div>
      <div v-else style="margin-top: 20px">
        <div class="grid">
          <AppStoreItem :style="{ width: itemWidth }" v-for="app in filteredApps" :app="app" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)"/>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>

.grid-animation-move,
.grid-animation-enter-active,
.grid-animation-leave-active {
  transition: all 0.2s ease;
}

.grid-animation-enter-from {
  opacity: 0;
  transform: scale(0);
}

.grid-animation-leave-to {
  opacity: 0;
  transform: scale(0);
}

.filter-bar {
  width: 100%;
  display: flex;
  gap: 6px;
  justify-content: space-between;
}

.grid {
  position: relative;
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}

</style>
