<script setup>

const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;

import { ref, computed, useTemplateRef, onMounted, onUnmounted } from 'vue';
import { Button, ButtonGroup, Dropdown, Icon, TableView, TextInput } from 'pankow';
import { APP_TYPES, HSTATES, ISTATES, RSTATES } from '../constants.js';
import AppsModel from '../models/AppsModel.js';
import ApplinksModel from '../models/ApplinksModel.js';
import DomainsModel from '../models/DomainsModel.js';
import ProfileModel from '../models/ProfileModel.js';
import ApplinkDialog from '../components/ApplinkDialog.vue';

const appsModel = AppsModel.create();
const domainsModel = DomainsModel.create();
const applinksModel = ApplinksModel.create();
const profileModel = ProfileModel.create();

const VIEW_TYPE = {
  LIST: 'list',
  GRID: 'grid',
};

let refreshInterval;

const ready = ref(false);
const filter = ref('');
// TODO maybe replace with app.provide and inject
const profile = ref({});
const apps = ref([]);
const viewType = ref((localStorage.appsView && (localStorage.appsView === VIEW_TYPE.GRID || localStorage.appsView === VIEW_TYPE.LIST)) ? localStorage.appsView : VIEW_TYPE.GRID);
const tagFilter = ref('');
const tagFilterOptions = ref([{
  id: '',
  name: 'All Tags',
}]);
const domainFilter = ref('');
const domainFilterOptions = ref([{
  id: '',
  domain: 'All Domains',
}]);
const stateFilter = ref('');
const stateFilterOptions = [
  { id: '', label: 'All States' },
  { id: 'running', label: 'Running' },
  { id: 'stopped', label: 'Stopped' },
  { id: 'update_available', label: 'Update Available' },
  { id: 'not_responding', label: 'Not Responding' },
];
const listColumns = {
  icon: {
    width: '32px'
  },
  label: {
    label: 'Label',
    sort: true
  },
  domain: {
    label: 'Location',
    sort: true
  },
  status: {},
  appTitle: {
    label: 'App Title',
    sort: true
  },
  sso: {
    label: 'Login',
    sort: true
  },
  actions: {}
};

const filteredApps = computed(() => {
  return apps.value.filter(a => {
    return a.fqdn.indexOf(filter.value) !== -1;
  }).filter(a => {
    if (!domainFilter.value) return true;
    return a.domain === domainFilter.value;
  }).filter(a => {
    if (!tagFilter.value) return true;
    return a.tags.indexOf(tagFilter.value) !== -1;
  }).filter(a => {
    if (!stateFilter.value) return true;

    if (stateFilter.value === 'running') return a.runState === RSTATES.RUNNING && a.health === HSTATES.HEALTHY && a.installationState === ISTATES.INSTALLED;
    if (stateFilter.value === 'stopped') return a.runState === RSTATES.STOPPED;

    // TODO implement this
    // if (stateFilter.value === 'update_available') return !!(Client.getConfig().update[a.id] && Client.getConfig().update[a.id].manifest.version && Client.getConfig().update[a.id].manifest.version !== a.manifest.version);
    if (stateFilter.value === 'update_available') return false;

    return a.runState === RSTATES.RUNNING && (a.health !== HSTATES.HEALTHY || a.installationState !== ISTATES.INSTALLED); // not responding
  });
});

const installationStateLabel = AppsModel.installationStateLabel;
const installationActive = AppsModel.installationActive;
const appProgressMessage = AppsModel.appProgressMessage;

const applinkDialog = useTemplateRef('applinkDialog');

function openAppEdit(app) {
  if (app.type === APP_TYPES.LINK) applinkDialog.value.open(app);
  else window.location.href = `#/app/${app.id}/info`;
}

function onOpenApp(app, event) {
  function stopEvent() {
    event.stopPropagation();
    event.preventDefault();
  }

  if (app.installationState !== ISTATES.INSTALLED) {
    if (app.installationState === ISTATES.ERROR && isOperator(app)) window.location.href = `#/app/${app.id}/repair`;
    return stopEvent();
  }

  // app.health can also be null to indicate insufficient data
  if (!app.health) return stopEvent();
  if (app.runState === RSTATES.STOPPED) return stopEvent();

  if (app.health === HSTATES.UNHEALTHY || app.health === HSTATES.ERROR || app.health === HSTATES.DEAD) {
    if (isOperator(app)) window.location.href = `#/app/${app.id}/repair`;
    return stopEvent();
  }

  // TODO
  // if (app.pendingPostInstallConfirmation && $scope.appPostInstallConfirm) {
  //     $scope.appPostInstallConfirm.show(app);
  //     return stopEvent();
  // }
}

function isOperator(app) {
  return app.accessLevel === 'operator' || app.accessLevel === 'admin';
}

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

  const applinks = await applinksModel.list();

  // amend properties to mimick full app
  for (const applink of applinks) {
    applink.type = APP_TYPES.LINK;
    applink.fqdn = applink.upstreamUri;
    applink.manifest = { addons: {}};
    applink.installationState = ISTATES.INSTALLED;
    applink.runState = RSTATES.RUNNING;
    applink.health = HSTATES.HEALTHY;
    applink.iconUrl = `/api/v1/applinks/${applink.id}/icon?access_token=${localStorage.token}&ts=${applink.ts}`;
    applink.accessLevel = profile.value.isAtLeastAdmin ? 'admin' : 'user';

    result.push(applink);
  }

  apps.value = result;

  // gets all tags used by all apps, flattens the arrays and new Set() will dedupe
  const tags = [...new Set(apps.value.map(a => a.tags).flat())].map(t => { return { id: t, name: t }; });
  tagFilterOptions.value = [{ id: '', name: 'All Tags', }].concat(tags);
}

function toggleView() {
  viewType.value = viewType.value === VIEW_TYPE.LIST ? VIEW_TYPE.GRID : VIEW_TYPE.LIST;
  localStorage.appsView = viewType.value;
}

onMounted(async () => {
  let [error, result] = await profileModel.get();
  if (error) return console.error(error);

  profile.value = result;

  await refreshApps();

  [error, result] = await domainsModel.list();
  if (error) return console.error(error);

  domainFilterOptions.value = domainFilterOptions.value.concat(result.map(d => { d.id = d.domain; return d; }));
  domainFilter.value = domainFilterOptions.value[0].id;

  stateFilter.value = stateFilterOptions[0].id;
  tagFilter.value = tagFilterOptions.value[0].id;

  ready.value = true;

  refreshInterval = setInterval(refreshApps, 5000);
});

onUnmounted(() => {
  clearInterval(refreshInterval);
});

</script>

<template>
  <div>
    <ApplinkDialog ref="applinkDialog" @success="refreshApps()"/>

    <h1 class="section-header">
      {{ $t('apps.title') }}
      <div style="display: flex; gap: 4px; flex-wrap: wrap; margin-top: 10px;">
        <TextInput v-model="filter" placeholder="Filter ..." />
        <Dropdown outline tool :options="tagFilterOptions" option-key="id" option-label="name" v-model="tagFilter"></Dropdown>
        <Dropdown outline tool :options="stateFilterOptions" option-key="id" v-model="stateFilter"></Dropdown>
        <Dropdown outline tool :options="domainFilterOptions" option-key="id" option-label="domain" v-model="domainFilter"></Dropdown>
        <Button tool @click="toggleView()" :icon="viewType === VIEW_TYPE.GRID ? 'fas fa-list' : 'fas fa-grip'"></Button>
      </div>
    </h1>

    <div v-show="ready">
      <TransitionGroup name="grid-animation" tag="div" class="grid" v-if="viewType === VIEW_TYPE.GRID">
        <a v-for="app in filteredApps" :key="app.id" class="grid-item" @click="onOpenApp(app, $event)" :href="'https://' + app.fqdn" target="_blank">
          <div class="config" v-show="isOperator(app)" @click.prevent="openAppEdit(app)"><Icon icon="fa-solid fa-cog" /></div>
          <img :src="API_ORIGIN + app.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'"/>
          <div class="grid-item-label">{{ app.label || app.subdomain || app.fqdn }}</div>
          <div class="grid-item-task-label">{{ installationStateLabel(app) }}</div>
          <div class="apps-progress" v-show="isOperator(app)">
            <div class="apps-progress-filled" :style="{ width: app.progress+'%' }"></div>
          </div>
        </a>
      </TransitionGroup>

      <div class="list" v-if="viewType === VIEW_TYPE.LIST">
        <TableView :columns="listColumns" :model="filteredApps">
          <template #icon="slotProps">
            <a :href="'https://' + slotProps.fqdn" target="_blank">
              <img class="list-icon" :src="API_ORIGIN + slotProps.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'"/>
            </a>
          </template>
          <template #label="slotProps">
            <a :href="'https://' + slotProps.fqdn" target="_blank">
              {{ slotProps.label || slotProps.subdomain || slotProps.fqdn }}
            </a>
          </template>
          <template #appTitle="slotProps">
            {{ slotProps.manifest.title }}
          </template>
          <template #domain="slotProps">
            <a :href="'https://' + slotProps.fqdn" target="_blank">
              {{ slotProps.fqdn }}
            </a>
          </template>
          <template #status="slotProps">
            <div class="list-status">
              {{ installationStateLabel(slotProps) }}
              <div class="apps-progress" v-show="isOperator(slotProps)">
                <div class="apps-progress-filled" :style="{ width: slotProps.progress+'%' }"></div>
              </div>
            </div>
          </template>
          <template #sso="slotProps">
            <div v-show="slotProps.type !== APP_TYPES.LINK">
              <Icon icon="fa-brands fa-openid" v-show="slotProps.ssoAuth && slotProps.manifest.addons.oidc" v-tooltip="$t('apps.auth.openid')" />
              <Icon icon="fas fa-user" v-show="slotProps.ssoAuth && (!slotProps.manifest.addons.oidc && !slotProps.manifest.addons.email)" v-tooltip="$t('apps.auth.sso')" />
              <Icon icon="far fa-user" v-show="!slotProps.ssoAuth && !slotProps.manifest.addons.email" v-tooltip="$t('apps.auth.nosso')" />
              <Icon icon="fas fa-envelope" v-show="slotProps.manifest.addons.email" v-tooltip="$t('apps.auth.email')" />
            </div>
          </template>
          <template #actions="slotProps">
            <div class="table-actions">
              <ButtonGroup>
                <Button small secondary tool v-if="slotProps.type !== APP_TYPES.LINK" :href="'/logs.html?appId=' + slotProps.id" target="_blank" v-tooltip="$t('app.logsActionTooltip')" icon="fas fa-align-left"></Button>
                <Button small secondary tool v-if="slotProps.type !== APP_TYPES.PROXIED && slotProps.type !== APP_TYPES.LINK" :href="'/terminal.html?id=' + slotProps.id" target="_blank" v-tooltip="$t('app.terminalActionTooltip')" icon="fa fa-terminal"></Button>
                <Button small secondary tool v-if="slotProps.manifest.addons.localstorage" :href="'/filemanager.html#/home/app/' + slotProps.id" target="_blank" v-tooltip="$t('app.filemanagerActionTooltip')" icon="fas fa-folder"></Button>
              </ButtonGroup>

              <Button small secondary tool @click="openAppEdit(slotProps)" icon="fa-solid fa-cog"></Button>
            </div>
          </template>
        </TableView>
      </div>

      <div class="empty-placeholder" v-if="apps.length === 0">
        <!-- for admins -->
        <div v-if="profile.isAtLeastAdmin">
          <h4>{{ $t('apps.noApps.title') }}</h4>
          <h5 v-html="$t('apps.noApps.description', { appStoreLink: '#/appstore' })"></h5>
        </div>

        <!-- for non-admins -->
        <div v-if="!profile.isAtLeastAdmin">
          <h4>{{ $t('apps.noAccess.title') }}</h4>
          <h5>{{ $t('apps.noAccess.description') }}</h5>
        </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: translateY(30px);
}

.grid-animation-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}

.list-icon {
  width: 32px;
  height: 32px;
}

.list-status {
  position: relative;
  height: 100%;
  display: flex;
  align-items: center;
}

.grid-item-task-label {
  opacity: 0.7;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
  color: var(--pankow-text-color);
}

.apps-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 5px;
  text-align: center;
  border-radius: 10px;
  color: var(--pankow-text-color);
}

.apps-progress-filled {
  background-color: var(--pankow-color-primary);
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  border-radius: 10px;
  z-index: 1;
  background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
  background-size: 40px 40px;
  animation: apps-progress-bar-stripes 1s linear infinite;
  transition: width 300ms;
}

@keyframes apps-progress-bar-stripes {
  from {
    background-position: 40px 0;
  }
  to {
    background-position: 0 0;
  }
}

.grid {
  display: flex;
  height: 100%;
  width: 100%;
  transition: 300ms;
  flex-wrap: wrap;
  justify-content: start;
  align-content: start;
}

.grid-item {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 190px;
  height: 180px;
  margin: 10px;
  overflow: hidden;
  border-radius: 10px;
  background-color: var(--card-background);
}

.grid-item:focus,
.grid-item:hover {
  box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
  background-color: var(--pankow-color-background-hover);
  text-decoration: none;
}

.grid-item img {
  width: 80px;
  height: 80px;
  object-fit: cover;
}

.grid-item-label {
  font-size: 18px;
  font-weight: 100;
  margin: 5px 0 5px 0;
  color: var(--pankow-text-color);
}

.grid-item:focus .grid-item-label,
.grid-item:hover .grid-item-label {
  text-decoration: none;
  color: var(--accent-color);;
}

.config {
  position: absolute;
  color: var(--pankow-text-color);
  font-size: 18px;
  cursor: pointer;
  width: 50px;
  height: 50px;
  border-top-right-radius: 10px;
  right: 0;
  top: 0;
  opacity: 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.config:focus,
.config:hover {
  text-decoration: none;
  color: var(--accent-color);;
  opacity: 1;
}

.grid-item:focus .config,
.grid-item:hover .config {
  opacity: 1;
}

.empty-placeholder {
  margin-top: 20px;
}

</style>
