<script setup>

import { ref, computed, useTemplateRef, onMounted } from 'vue';
import { marked } from 'marked';
import { Button, Dialog, Dropdown, FormGroup, TextInput } from 'pankow';
import { prettyDate, prettyFileSize } from 'pankow/utils';
import AccessControl from './AccessControl.vue';
import PortBindings from './PortBindings.vue';
import DomainsModel from '../models/DomainsModel.js';
import AppsModel from '../models/AppsModel.js';
import DashboardModel from '../models/DashboardModel.js';
import { PROXY_APP_ID } from '../constants.js';

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

const STEP = Object.freeze({
  DETAILS: Symbol('details'),
  INSTALL: Symbol('install'),
});

const domainsModel = DomainsModel.create(API_ORIGIN, localStorage.token);
const appsModel = AppsModel.create(API_ORIGIN, localStorage.token);
const dashboardModel = DashboardModel.create(API_ORIGIN, localStorage.token);

// reactive
const busy = ref(false);
const formError = ref({});
const app = ref({});
const manifest = ref({});
const step = ref(STEP.DETAILS);
const dialog = useTemplateRef('dialogHandle');
const locationInput = useTemplateRef('locationInput');
const description = computed(() => marked.parse(manifest.value.description || ''));
const domains = ref([]);
const formValid = computed(() => {
  if (!domain.value) return false;
  if (manifest.value.id === PROXY_APP_ID) {
    try {
      new URL(upstreamUri.value);
      // eslint-disable-next-line no-unused-vars
    } catch (e) {
      return false;
    }
  }

  return true;
});

function setStep(newStep) {
  step.value = newStep;
  if (newStep === STEP.INSTALL) setTimeout(() => locationInput.value.$el.focus(), 500);
}

// form data
const location = ref('');
// accessRestriction:
// object = SSO with user groups
// true = SSO all
// false = NOSSO
const accessRestriction = ref(true);
const domain = ref({});
const tcpPorts = ref({});
const udpPorts = ref({});
const secondaryDomains = ref({});
const upstreamUri = ref('');

async function submit() {
  const config = {
    subdomain: location.value,
    domain: domain.value.domain,
    accessRestriction: typeof accessRestriction.value === 'object' ? accessRestriction.value : null,
  };

  if (manifest.value.optionalSso) config.sso = accessRestriction.value;

  const finalPorts = {};
  for (const p in tcpPorts.value) {
    const port = tcpPorts.value[p];
    if (port.enabled) finalPorts[p] = port.value;
  }
  for (const p in udpPorts.value) {
    const port = udpPorts.value[p];
    if (port.enabled) finalPorts[p] = port.value;
  }
  config.ports = finalPorts;

  const finalSecondaryDomains = {};
  for (var p in secondaryDomains.value) {
    finalSecondaryDomains[p] = {
      subdomain: secondaryDomains.value[p].value,
      domain: secondaryDomains.value[p].domain
    };
  }
  config.secondaryDomains = finalSecondaryDomains;

  if (manifest.value.id === PROXY_APP_ID) {
    config.upstreamUri = upstreamUri.value;
  }

  busy.value = true;
  const error = await appsModel.install(manifest.value, config);
  busy.value = false;

  if (!error) {
    dialog.value.close();
    return window.location.href = '#/apps';
  }

  formError.value = {};
  if (error.status === 'Conflict' && error.message.indexOf('port') !== -1) {
    const match = error.message.match(/.*port.(.*)/);
    formError.value.port = match ? parseInt(match[1]) : null;
  } else if (error.status === 'Conflict' && error.message.indexOf('primary location') !== -1) {
    formError.value.location = true;
  } else {
    console.error('Failed to install:', error);
  }
}

function onClose() {
  setTimeout(() => step.value = STEP.DETAILS, 2000);
}

onMounted(async () => {
  domains.value = await domainsModel.list();
  const config = await dashboardModel.getConfig();

  // preselect with dashboard domain
  domain.value = domains.value.find(d => d.domain === config.adminDomain) || domains.value[0];
});

defineExpose({
  open(a) {
    step.value = STEP.DETAILS;
    app.value = a;
    manifest.value = a.manifest;

    tcpPorts.value = a.manifest.tcpPorts;
    udpPorts.value = a.manifest.udpPorts;

    secondaryDomains.value = a.manifest.httpPorts;
    for (const p in secondaryDomains.value) {
      const port = secondaryDomains.value[p];
      port.value = port.defaultValue;
      port.domain = domains.value[0].domain;
    }

    dialog.value.open();
  }
});

</script>

<template>
  <Dialog ref="dialogHandle" @close="onClose()">
    <div class="content" :class="{ 'step-detail': step === STEP.DETAILS, 'step-install': step === STEP.INSTALL }">
      <div class="header">
        <div class="summary">
          <div class="title">{{ manifest.title }}</div>
          <div class="lastUpdated">{{ $t('appstore.installDialog.lastUpdated', { date: prettyDate(app.creationDate) }) }}</div>
          <div class="memoryRequirement">{{ $t('appstore.installDialog.memoryRequirement', { size: prettyFileSize(manifest.memoryLimit) }) }}</div>
          <div class="author"><a :href="manifest.website" target="_blank">Website</a></div>
        </div>
        <img class="icon" :src="app.iconUrl" />
      </div>
      <Transition name="slide-left" mode="out-in">
        <div v-if="step === STEP.DETAILS">
          <Button @click="setStep(STEP.INSTALL)" icon="fa-solid fa-circle-down">Install {{ manifest.title }}</Button>
          <div class="screenshots">
            <img class="screenshot" v-for="image in manifest.mediaLinks" :key="image" :src="image"/>
          </div>
          <div class="description" v-html="description"></div>
        </div>
        <div v-else-if="step === STEP.INSTALL">
          <form @submit.prevent="submit()" autocomplete="off">
            <fieldset :disabled="busy">
              <input style="display: none;" type="submit" :disabled="!formValid" />

              <FormGroup :class="{ 'has-error': formError.location }">
                <label for="location">{{ $t('appstore.installDialog.location') }}</label>
                <div>
                  <TextInput id="location" ref="locationInput" v-model="location" />
                  <Dropdown v-model="domain" :options="domains" option-label="domain" />
                </div>
              </FormGroup>

              <p class="text-small text-warning" v-show="domain.provider === 'noop' || domain.provider === 'manual'" v-html="$t('appstore.installDialog.manualWarning', { location: ((location ? location + '.' : '') + domain.domain) })"></p>

              <FormGroup v-for="(port, key) in secondaryDomains" :key="key">
                <label :for="'secondaryDomainInput' + key">{{ port.title }}</label>
                <small>{{ port.description }}</small>
                <div>
                  <TextInput :id="'secondaryDomainInput' + key" v-model="port.value" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
                  <Dropdown v-model="port.domain" :options="domains" option-label="domain" option-key="domain" />
                </div>
              </FormGroup>

              <FormGroup :class="{ 'has-error': formError.upstreamUri }" v-show="manifest.id === PROXY_APP_ID">
                <label for="upstreamUri">Upstream URI</label>
                <TextInput id="upstreamUri" v-model="upstreamUri" />
              </FormGroup>

              <PortBindings v-model:tcp-ports="tcpPorts" v-model:udp-ports="udpPorts" :error="formError"/>
              <AccessControl v-model="accessRestriction" :manifest="manifest"/>

              <Button style="margin-top: 15px" @click="submit" icon="fa-solid fa-circle-down" :disabled="!formValid" :loading="busy">Install {{ manifest.title }}</Button>
            </fieldset>
          </form>
        </div>
      </Transition>
    </div>
  </Dialog>
</template>

<style scoped>

.content {
  max-width: 100%;
  max-height: 100%;
  transition: all 0.25s;
}

.step-detail {
  width: 1024px;
}

.step-install {
  width: 480px;
}

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 4px;
}

.summary {
  display: flex;
  flex-direction: column;
  font-size: 14px;
}

.title {
  font-size: 32px;
  margin-bottom: 10px;
}

.icon {
  width: 128px;
  height: 128px;
  object-fit: contain;
  margin-right: 20px;
}

.description {
  margin: 0 4px;
}

.screenshots {
  margin: 10px 4px;
  display: flex;
  gap: 20px;
  width: 100%;
  padding: 10px 0;

  scroll-snap-type: x mandatory;
  overflow: auto;
}

.screenshot {
  display: inline-block;
  border-radius: var(--pankow-border-radius);
  height: 300px;
  object-size: contain;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}

</style>
