<script setup>

import { ref, computed, onMounted, useTemplateRef, inject } from 'vue';
import { Button, SingleSelect, FormGroup } from '@cloudron/pankow';
import { setLanguage } from '../i18n.js';
import { TOKEN_TYPES } from '../constants.js';
import AppPasswords from '../components/AppPasswords.vue';
import SettingsItem from '../components/SettingsItem.vue';
import EditableField from '../components/EditableField.vue';
import PrimaryEmailDialog from '../components/PrimaryEmailDialog.vue';
import FallbackEmailDialog from '../components/FallbackEmailDialog.vue';
import PasswordChangeDialog from '../components/PasswordChangeDialog.vue';
import DisableTwoFADialog from '../components/DisableTwoFADialog.vue';
import EnableTwoFADialog from '../components/EnableTwoFADialog.vue';
import Section from '../components/Section.vue';
import ApiTokens from '../components/ApiTokens.vue';
import ImagePicker from '../components/ImagePicker.vue';

import DashboardModel from '../models/DashboardModel.js';
import ProfileModel from '../models/ProfileModel.js';
import CloudronModel from '../models/CloudronModel.js';
import TokensModel from '../models/TokensModel.js';

const dashboardModel = DashboardModel.create();
const profileModel = ProfileModel.create();
const cloudronModel = CloudronModel.create();
const tokensModel = TokensModel.create();

const profile = inject('profile');
const refreshProfile = inject('refreshProfile');

const savingDisplayName = ref(false);

const config = ref({});
const primaryEmailDialog = useTemplateRef('primaryEmailDialog');
const fallbackEmailDialog = useTemplateRef('fallbackEmailDialog');
const passwordChangeDialog = useTemplateRef('passwordChangeDialog');
const disableTwoFADialog = useTemplateRef('disableTwoFADialog');

// Language selector
const languages = ref([]);
const language = ref('');
async function onSelectLanguage(lang) {
  const error = await profileModel.setLanguage(lang);
  if (error) return console.error('Failed to set language', error);

  await setLanguage(lang, true);
}

// Profile edits
async function onDisplayNameSave(newName) {
  savingDisplayName.value = true;

  const error = await profileModel.setDisplayName(newName);
  savingDisplayName.value = false;

  if (error) return console.error('Failed to set displayName', error);

  profile.value.displayName = newName;
}

function onChangeEmail(currentEmail) {
  primaryEmailDialog.value.open(currentEmail);
}

async function onChangeFallbackEmail(currentFallbackEmail) {
  fallbackEmailDialog.value.open(currentFallbackEmail);
}

async function onAvatarSubmit(file) {
  await profileModel.setAvatar(file);
  await refreshProfile();
}

async function onAvatarUnset() {
  await profileModel.unsetAvatar();
  await refreshProfile();
}

async function onPasswordChange() {
  passwordChangeDialog.value.open();
}


// Tokens
const webadminTokens = ref([]);
const cliTokens = ref([]);
const revokeTokensBusy = ref(false);

async function onRevokeAllWebAndCliTokens() {
  revokeTokensBusy.value = true;

  // filter current access token to be able to logout still
  const tokens = webadminTokens.value.concat(cliTokens.value).filter(t => t.accessToken !== localStorage.token);
  for (const token of tokens) {
    const [error] = await tokensModel.remove(token.id);
    if (error) console.error(error);
  }

  await profileModel.logout();
}

const enableTwoFADialog = useTemplateRef('enableTwoFADialog');
const has2FA = computed(() => profile.value.totpEnabled || profile.value.hasPasskey);

async function onOpenTwoFASetupDialog(method) {
  enableTwoFADialog.value.open(method);
}

async function onEnableTwoFASuccess() {
  await refreshProfile();
}

async function onTwoFADisable(method) {
  disableTwoFADialog.value.open(method);
}

async function onTwoFADisableSuccess() {
  await refreshProfile();
}

// Init
onMounted(async () => {
  let [error, result] = await dashboardModel.config();
  if (error) return console.error(error);
  config.value = result;

  await refreshProfile();

  [error, result] = await cloudronModel.languages();
  languages.value = result.map(l => {
    const displayNames = new Intl.DisplayNames(['en'], { type: 'language' });

    // hack for chinese simplified
    l = l === 'zh_Hans' ? 'zh' : l;

    return {
      id: l,
      display: displayNames.of(l) || l,
    };
  }).sort((a, b) => {
    return a.display.localeCompare(b.display);
  });

  const usedLang = window.localStorage.NG_TRANSLATE_LANG_KEY || 'en';
  language.value = languages.value.find(l => l.id === usedLang).id;

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

  // dashboard and development clientIds were issued with 7.5.0
  webadminTokens.value = result.filter(function (c) { return c.clientId === TOKEN_TYPES.ID_WEBADMIN || c.clientId === TOKEN_TYPES.ID_DEVELOPMENT || c.clientId === 'dashboard' || c.clientId === 'development'; });
  cliTokens.value = result.filter(function (c) { return c.clientId === TOKEN_TYPES.ID_CLI; });

  // check if we should show the 2fa setup
  if (config.value.mandatory2FA && !profile.value.totpEnabled && !profile.value.hasPasskey) {
    onOpenTwoFASetupDialog();
  }
});

</script>

<template>
  <div class="content" style="max-width: 700px;">
    <PrimaryEmailDialog ref="primaryEmailDialog" @success="refreshProfile"/>
    <FallbackEmailDialog ref="fallbackEmailDialog" @success="refreshProfile"/>
    <PasswordChangeDialog ref="passwordChangeDialog" @success="refreshProfile"/>
    <DisableTwoFADialog ref="disableTwoFADialog" @success="onTwoFADisableSuccess"/>
    <EnableTwoFADialog ref="enableTwoFADialog" :mandatory2FA="config.mandatory2FA" :has2FA="has2FA" @success="onEnableTwoFASuccess"/>

    <Section :title="$t('profile.title')">
      <div style="display: flex; flex-wrap: wrap; gap: 20px">
        <div style="width: 128px;">
          <ImagePicker mode="editable" :src="profile.avatarUrl" fallback-src="/img/avatar-default-symbolic.svg" :disabled="config.profileLocked" :size="512" :unset-handler="onAvatarUnset" :save-handler="onAvatarSubmit" display-width="128px"/>
        </div>
        <div style="flex-grow: 1;">
          <SettingsItem>
            <FormGroup>
              <label>{{ $t('main.username') }}</label>
              <div>{{ profile.username }}</div>
            </FormGroup>
            <div style="display: flex; align-items: center">
              <Button plain v-if="!profile.source" @click="onPasswordChange()">{{ $t('profile.changePasswordAction') }}</Button>
            </div>
          </SettingsItem>

          <SettingsItem>
            <EditableField :label="$t('main.displayName')" :saving="savingDisplayName" :value="profile.displayName" :disabled="config.profileLocked || profile.source || !profile.username" @save="onDisplayNameSave" :required="true"/>
          </SettingsItem>

          <SettingsItem>
            <FormGroup>
              <label>{{ $t('profile.primaryEmail') }}</label>
              <div>{{ profile.email }}</div>
            </FormGroup>
            <div style="display: flex; align-items: center">
              <Button tool plain @click="onChangeEmail(profile.email)" v-show="profile.username && !profile.source && !config.profileLocked">{{ $t('main.dialog.edit') }}</Button>
            </div>
          </SettingsItem>

          <SettingsItem>
            <FormGroup>
              <label>{{ $t('profile.passwordRecoveryEmail') }}</label>
              <div>{{ profile.fallbackEmail || 'No email address set' }}</div>
            </FormGroup>
            <div style="display: flex; align-items: center">
              <Button tool plain @click="onChangeFallbackEmail(profile.fallbackEmail)" v-show="profile.username && !profile.source && !config.profileLocked">{{ $t('main.dialog.edit') }}</Button>
            </div>
          </SettingsItem>

          <SettingsItem>
            <div style="display: flex; align-items: center">
              <label>{{ $t('profile.language') }}</label>
            </div>
            <div style="display: flex; align-items: center">
              <SingleSelect v-model="language" :options="languages" option-label="display" option-key="id" @select="onSelectLanguage"/>
            </div>
          </SettingsItem>

          <SettingsItem v-if="!profile.source || !config.external2FA">
            <FormGroup>
              <label>{{ $t('profile.twoFactorAuth.totpTitle') }}</label>
              <div v-if="profile.totpEnabled">{{ $t('profile.twoFactorAuth.totpEnabled') }}</div>
              <div v-else-if="profile.id">{{ $t('profile.notSet') }}</div>
            </FormGroup>
            <div v-if="profile.id" style="display: flex; align-items: center">
              <Button tool plain v-if="profile.totpEnabled" @click="onTwoFADisable('totp')">{{ $t('main.action.disable') }}</Button>
              <Button tool plain v-else @click="onOpenTwoFASetupDialog('totp')">{{ $t('main.action.setup') }}</Button>
            </div>
          </SettingsItem>

          <SettingsItem v-if="!profile.source || !config.external2FA">
            <FormGroup>
              <label>{{ $t('profile.twoFactorAuth.passkeyTitle') }}</label>
              <div v-if="profile.hasPasskey">{{ $t('profile.twoFactorAuth.passkeyEnabled') }}</div>
              <div v-else-if="profile.id">{{ $t('profile.notSet') }}</div>
            </FormGroup>
            <div v-if="profile.id" style="display: flex; align-items: center">
              <Button tool plain v-if="profile.hasPasskey" @click="onTwoFADisable('passkey')">{{ $t('main.action.disable') }}</Button>
              <Button tool plain v-else @click="onOpenTwoFASetupDialog('passkey')">{{ $t('main.action.setup') }}</Button>
            </div>
          </SettingsItem>
        </div>
      </div>
    </Section>

    <AppPasswords/>
    <ApiTokens v-if="profile.isAtLeastAdmin"/>

    <Section :title="$t('profile.loginTokens.title')">
      <div>{{ $t('profile.loginTokens.description', { webadminTokenCount: webadminTokens.length, cliTokenCount: cliTokens.length }) }}</div>
      <br/>
      <Button danger :loading="revokeTokensBusy" :disabled="revokeTokensBusy" @click="onRevokeAllWebAndCliTokens()">{{ $t('profile.loginTokens.logoutAll') }}</Button>
    </Section>
  </div>
</template>
