<script setup>

import { ref, useTemplateRef, onMounted, computed } from 'vue';
import { Button, SingleSelect, InputGroup, FormGroup, TextInput, Checkbox } from '@cloudron/pankow';
import { isValidDomain } from '@cloudron/pankow/utils';
import { ISTATES } from '../../constants.js';
import PortBindings from '../PortBindings.vue';
import AppsModel from '../../models/AppsModel.js';
import DomainsModel from '../../models/DomainsModel.js';

const props = defineProps([ 'app' ]);

const appsModel = AppsModel.create();
const domainsModel = DomainsModel.create();

const domains = ref([]);
const busy = ref(false);
const errorMessage = ref('');
const errorObject = ref({});
const overwriteDns = ref(false);
const needsOverwriteDns = ref(false);
const domain = ref('');
const subdomain = ref('');
const secondaryDomains = ref({});
const aliases = ref([]);
const redirects =ref([]);
const tcpPorts = ref({});
const udpPorts = ref({});
const domainProvider = computed(() => {
  const tmp = domains.value.find((d) => d.domain === domain.value);
  return tmp ? tmp.provider : '';
});

function isNoopOrManual(domain) {
  const tmp = domains.value.find(d => d.domain === domain);
  return tmp ? (tmp.provider === 'noop' || tmp.provider === 'manual') : false;
}

function onAddAlias() {
  aliases.value.push({
    domain: domain.value,
    subdomain: ''
  });
}

function onRemoveAlias(index) {
  aliases.value.splice(index, 1);
}

function onAddRedirect() {
  redirects.value.push({
    domain: domain.value,
    subdomain: ''
  });
}

const form = useTemplateRef('form');
const isFormValid = ref(false);
function checkValidity() {
  isFormValid.value = form.value ? form.value.checkValidity() : false;

  if (isFormValid.value) {
    const checkForDomains = [{ domain: domain.value, subdomain: subdomain.value }];
    for (const d in secondaryDomains.value) checkForDomains.push({ domain: secondaryDomains.value[d].domain, subdomain: secondaryDomains.value[d].subdomain });
    for (const d of redirects.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
    for (const d of aliases.value) {
      let subdomain = d.subdomain;
      // see apps.js:validateLocations()
      if (d.subdomain.startsWith('*')) {
        if (subdomain === '*') continue;
        subdomain = subdomain.replace(/^\*\./, ''); // remove *.
      }
      checkForDomains.push({ domain: d.domain, subdomain: subdomain });
    }

    if (checkForDomains.find(d => !isValidDomain((d.subdomain ? (d.subdomain + '.') : '') + d.domain))) isFormValid.value = false;
  }
}

function onRemoveRedirect(index) {
  redirects.value.splice(index, 1);
}

async function onSubmit() {
  if (!form.value.reportValidity()) return;

  busy.value = true;
  errorMessage.value = '';
  errorObject.value = {};
  needsOverwriteDns.value = false;

  const checkForDomains = [{
    domain: domain.value,
    subdomain: subdomain.value,
  }];

  for (const d in secondaryDomains.value) checkForDomains.push({ domain: secondaryDomains.value[d].domain, subdomain: secondaryDomains.value[d].subdomain });
  for (const d of aliases.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
  for (const d of redirects.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });

  for (const d of checkForDomains) {
    const [error, result] = await domainsModel.checkRecords(d.domain, d.subdomain);
    if (error) {
      errorMessage.value = error.body ? error.body.message : 'Internal error';
      busy.value = false;
      return console.error(error);
    }

    if (result.needsOverwrite && !overwriteDns.value) {
      busy.value = false;
      needsOverwriteDns.value = true;
      return;
    }
  }

  // only use enabled ports
  const ports = {};
  const portsCombined = Object.assign(tcpPorts.value || {}, udpPorts.value || {});
  for (const env in portsCombined) {
    if (portsCombined[env].enabled) {
      ports[env] = portsCombined[env].value;
    }
  }

  const data = {
    overwriteDns: overwriteDns.value,
    subdomain: subdomain.value,
    domain: domain.value,
    ports: ports,
    secondaryDomains: secondaryDomains.value,
    redirectDomains: redirects.value,
    aliasDomains: aliases.value,
  };

  const [error] = await appsModel.configure(props.app.id, 'location', data);
  if (error) {
    errorMessage.value = error.body ? error.body.message : 'Internal error';
    busy.value = false;
    return console.error(error);
  }

  busy.value = false;
}

onMounted(async () => {
  const [error, result] = await domainsModel.list();
  if (error) return console.error(error);

  result.forEach(d => {
    d.label = '.' + d.domain;
  });

  domains.value = result;

  subdomain.value = props.app.subdomain;
  domain.value = props.app.domain;

  // map manifest httpPorts into existing secondary domains from app
  secondaryDomains.value = props.app.manifest.httpPorts || {};
  for (const d of props.app.secondaryDomains) {
    if (!secondaryDomains.value[d.environmentVariable]) continue;
    secondaryDomains.value[d.environmentVariable].domain = d.domain;
    secondaryDomains.value[d.environmentVariable].subdomain = d.subdomain;
  }

  aliases.value = props.app.aliasDomains;
  redirects.value = props.app.redirectDomains;

  tcpPorts.value = props.app.manifest.tcpPorts;
  udpPorts.value = props.app.manifest.udpPorts;

  // ensure we have value property
  for (const p in tcpPorts.value) {
    tcpPorts.value[p].value = tcpPorts.value[p].value || tcpPorts.value[p].defaultValue;
    tcpPorts.value[p].enabled = false;
  }
  for (const p in udpPorts.value) {
    udpPorts.value[p].value = udpPorts.value[p].value || udpPorts.value[p].defaultValue;
    udpPorts.value[p].enabled = false;
  }

  for (const p in props.app.portBindings) {
    if (tcpPorts.value[p]) {
      tcpPorts.value[p].value = props.app.portBindings[p].hostPort;
      tcpPorts.value[p].enabled = true;
    } else if (udpPorts.value[p]) {
      udpPorts.value[p].value = props.app.portBindings[p].hostPort;
      udpPorts.value[p].enabled = true;
    }
    else console.error(`Portbinding ${p} not known in manifest!`);
  }

  setTimeout(checkValidity, 100); // update state of the confirm button
});

</script>

<template>
  <div>
    <form ref="form" @submit.prevent="onSubmit()" autocomplete="off" @input="checkValidity()">
      <fieldset :disabled="busy">
        <input type="submit" style="display: none;" :disabled="(app.error && app.error.installationState !== ISTATES.PENDING_LOCATION_CHANGE) || app.taskId"/>

        <FormGroup>
          <label>{{ $t('app.location.location') }}</label>

          <div style="display: flex; gap: 10px;">
            <InputGroup style="flex-grow: 1">
              <TextInput style="flex-grow: 1" v-model="subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
              <SingleSelect :disabled="busy" :options="domains" v-model="domain" option-key="domain" option-label="label" :search-threshold="10" required/>
            </InputGroup>
            <div class="warning-label" v-if="isNoopOrManual(domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((subdomain ? subdomain + '.' : '') + domain) })"></div>
            <!-- Button just to offset the same margin on the right to align location input when alias or redirects are visible -->
            <Button danger tool icon="fa-solid fa-trash" style="visibility: hidden"/>
          </div>
        </FormGroup>

        <FormGroup v-for="item in secondaryDomains" :key="item.containerPort">
          <label :for="'secondaryDomainInput' + item.containerPort">{{ item.title }}</label>
          <small>{{ item.description }}</small>
          <InputGroup style="flex-grow: 1">
            <TextInput style="flex-grow: 1" :id="'secondaryDomainInput' + item.containerPort" v-model="item.subdomain" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
            <SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" :search-threshold="10" required/>
          </InputGroup>
          <div class="warning-label" v-if="isNoopOrManual(item.domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((item.subdomain ? item.subdomain + '.' : '') + item.domain) })"></div>
        </FormGroup>

        <PortBindings v-model:tcp="tcpPorts" v-model:udp="udpPorts" :error="errorObject" :domain-provider="domainProvider"/>

        <FormGroup v-if="app.manifest.multiDomain">
          <label>{{ $t('app.location.aliases') }} <sup><a href="https://docs.cloudron.io/apps/#aliases" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>

          <div v-for="(item, index) in aliases" :key="item" style="margin-bottom: 10px">
            <div style="display: flex; gap: 10px;">
              <InputGroup style="flex-grow: 1">
                <TextInput style="flex-grow: 1" v-model="item.subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
                <SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" :search-threshold="10"/>
              </InputGroup>
              <Button danger tool :disabled="busy" icon="fa-solid fa-trash" @click="onRemoveAlias(index)"/>
            </div>
            <div class="warning-label" v-if="isNoopOrManual(item.domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((item.subdomain ? item.subdomain + '.' : '') + item.domain) })"></div>
          </div>

          <div>
            <span v-if="aliases.length === 0">{{ $t('app.location.noAliases') }}.&nbsp;</span>
            <span class="actionable" v-if="!busy" @click="onAddAlias()">{{ $t('app.location.addAliasAction') }}</span>
          </div>
        </FormGroup>

        <FormGroup>
          <label>{{ $t('app.location.redirections') }} <sup><a href="https://docs.cloudron.io/apps/#redirections" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
          <div v-for="(item, index) in redirects" :key="item" style="margin-bottom: 10px;">
            <div style="display: flex; gap: 10px;">
              <InputGroup style="flex-grow: 1">
                <TextInput style="flex-grow: 1" v-model="item.subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
                <SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" :search-threshold="10"/>
              </InputGroup>
              <Button danger tool :disabled="busy" icon="fa-solid fa-trash" @click="onRemoveRedirect(index)"/>
            </div>
            <div class="warning-label" v-if="isNoopOrManual(item.domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((item.subdomain ? item.subdomain + '.' : '') + item.domain) })"></div>
          </div>

          <div>
            <span v-if="redirects.length === 0">{{ $t('app.location.noRedirections') }}.&nbsp;</span>
            <span class="actionable" v-if="!busy" @click="onAddRedirect()">{{ $t('app.location.addRedirectionAction') }}</span>
          </div>
        </FormGroup>

      </fieldset>
    </form>

    <br/>

    <div class="error-label" v-if="errorMessage">{{ errorMessage }}</div>
    <br v-if="errorMessage"/>

    <Checkbox v-if="needsOverwriteDns" v-model="overwriteDns" :label="$t('app.location.dnsoverwrite')"/>
    <br v-if="needsOverwriteDns"/>

    <Button @click="onSubmit()" :loading="busy" :disabled="busy || (app.error && app.error.installationState !== ISTATES.PENDING_LOCATION_CHANGE) || app.taskId || !isFormValid">{{ $t('app.location.saveAction') }}</Button>
  </div>
</template>

<style scoped>
.pankow-form-group small {
  display: block;
  margin-bottom: 0.5rem;
}
</style>
