<script setup>

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

import { ref, useTemplateRef, inject } from 'vue';
import { Dialog, TextInput, EmailInput, FormGroup, Checkbox, MultiSelect, SingleSelect } from '@cloudron/pankow';
import { ROLES } from '../constants.js';
import ImagePicker from '../components/ImagePicker.vue';
import DashboardModel from '../models/DashboardModel.js';
import ProfileModel from '../models/ProfileModel.js';
import UsersModel from '../models/UsersModel.js';
import GroupsModel from '../models/GroupsModel.js';

const dashboardModel = DashboardModel.create();
const profileModel = ProfileModel.create();
const usersModel = UsersModel.create();
const groupsModel = GroupsModel.create();

const emit = defineEmits([ 'success' ]);

const dialog = useTemplateRef('dialog');
const imagePicker = useTemplateRef('imagePicker');
const form = useTemplateRef('form');

const refreshProfile = inject('refreshProfile');

// also determines if new or edit mode
const user = ref(null);
const roles = ref([]);
const profile = ref({});
const busy = ref(false);
const profileLocked = ref(false);
const formError = ref({});
const displayName = ref('');
const email = ref('');
const fallbackEmail = ref('');
const avatarUrl = ref('');
const username = ref('');
const role = ref('');
const localGroupIds = ref([]);
const allGroups = ref([]);
const allLocalGroups = ref([]);
const active = ref(true);
const sendInvite = ref(false);
const isSelf = ref(false);

let avatarFile = 'src';
function onAvatarChanged(file) {
  avatarFile = file;
}

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

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

  busy.value = true;
  formError.value = {};

  const data = {
    email: email.value,
    fallbackEmail: fallbackEmail.value,
    displayName: displayName.value,
    role: role.value
  };

  let userId = user.value ? user.value.id : null;

  // can only be set not updated
  if ((!user.value || !user.value.username) && username.value) data.username = username.value;

  const isExternal = user.value && user.value.source;

  // update or add
  if (!isExternal) {
    let error, result;
    if (user.value) [error] = await usersModel.update(user.value.id, data);
    else [error, result] = await usersModel.add(data);
    if (error) {
      const message = error.body ? error.body.message : '';
      if (error.status === 409) {
        if (message.toLowerCase().indexOf('email') !== -1) {
          formError.value.email = 'Email already taken';
        } else if (message.toLowerCase().indexOf('username') !== -1 || message.toLowerCase().indexOf('mailbox') !== -1) {
          formError.value.username = 'Username already taken';
        } else {
          // should not happen!!
          console.error(message);
        }
      } else if (error.status === 400) {
        if (message.toLowerCase().indexOf('email') !== -1) {
          formError.value.email = 'Invalid Email';
          formError.value.emailAttempted = email.value;
        } else if (message.toLowerCase().indexOf('username') !== -1) {
          formError.value.username = message;
        } else {
          // should not happen!!
          console.error(error);
        }
      } else {
        console.error(error);
      }
      busy.value = false;
      return;
    }

    userId = user.value ? user.value.id : result.id;
  }

  // for some reason only user adding supports role setting directly, but not user edit, so we have to do this separately
  if (user.value && profile.value.id !== userId) {
    const [error] = await usersModel.setRole(userId, role.value);
    if (error) {
      formError.value.role = error.body ? error.body.message : 'Internal error';
      busy.value = false;
      return console.error(error);
    }
  }

  if (profile.value.id !== userId) {
    const [error] = await usersModel.setActive(userId, active.value);
    if (error) {
      formError.value.generic = error.body ? error.body.message : 'Internal error';
      busy.value = false;
      return console.error(error);
    }
  }

  const [groupError] = await usersModel.setLocalGroups(userId, localGroupIds.value);
  if (groupError) {
    formError.value.generic = groupError.body ? groupError.body.message : 'Internal error';
    busy.value = false;
    return console.error(groupError);
  }

  if (sendInvite.value) {
    const [error] = await usersModel.sendInviteEmail(userId, email.value);
    if (error) {
      formError.value.generic = error.body ? error.body.message : 'Internal error';
      busy.value = false;
      return console.error(error);
    }
  }

  let avatarError = null;
  if (avatarFile === 'fallback') { // user reset the icon
      [avatarError] = await usersModel.unsetAvatar(userId);
      if (isSelf.value) await refreshProfile();
  } else if (avatarFile !== 'src') { // user loaded custom icon
    [avatarError] = await usersModel.setAvatar(userId, avatarFile);
    if (isSelf.value) await refreshProfile();
  }
  if (avatarError) {
      formError.value.generic = avatarError.body ? avatarError.body.message : 'Internal error';
      busy.value = false;
      return console.error(avatarError);
  }

  emit('success');

  dialog.value.close();
  busy.value = false;
}

defineExpose({
  async open(u = null) {
    u = u ? JSON.parse(JSON.stringify(u)) : null; // make a copy
    busy.value = false;
    formError.value = {};
    user.value = u;
    displayName.value = u ? u.displayName : '';
    email.value = u ? u.email : '';
    fallbackEmail.value = u ? u.fallbackEmail : '';
    username.value = u ? u.username : '';
    role.value = u ? u.role : ROLES.USER;
    sendInvite.value = false;
    active.value = u ? u.active : true;
    avatarUrl.value = u ? `${u.avatarUrl}&ts=${Date.now()}` : ''; // there is already access token in query param
    avatarFile = u?.avatarUrl ? 'src' : 'fallback';

    let [error, result] = await groupsModel.list();
    if (error) return console.error(error);
    result.forEach(g => g.label = g.name);
    allGroups.value = result;
    allLocalGroups.value = result.filter(g => !g.source);
    localGroupIds.value = u ? u.groupIds.filter(gid => allLocalGroups.value.find(g => g.id === gid)) : [];

    [error, result] = await profileModel.get();
    if (error) return console.error(error);
    profile.value = result;
    isSelf.value = u && u.id === profile.value.id;

    roles.value = [
      { id: ROLES.USER, name: t('users.role.user'), disabled: false },
      { id: ROLES.USER_MANAGER, name: t('users.role.usermanager'), disabled: false },
      { id: ROLES.MAIL_MANAGER, name: t('users.role.mailmanager'), disabled: false },
      { id: ROLES.ADMIN, name: t('users.role.admin'), disabled: !profile.value.isAtLeastAdmin },
      { id: ROLES.OWNER, name: t('users.role.owner'), disabled: !profile.value.isAtLeastOwner },
    ];

    [error, result] = await dashboardModel.config();
    if (error) return console.error(error);
    profileLocked.value = result.profileLocked;

    imagePicker.value.reset();
    dialog.value.open();

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

</script>

<template>
  <Dialog ref="dialog"
    :title="user ? $t('users.editUserDialog.title') : $t('users.addUserDialog.title')"
    :confirm-label="user ? $t('main.dialog.save') : $t('users.addUserDialog.addUserAction')"
    :confirm-busy="busy"
    :confirm-active="!busy && isFormValid"
     reject-style="secondary"
    :reject-label="$t('main.dialog.cancel')"
    :reject-active="!busy"
    @confirm="onSubmit()"
  >
    <form @submit.prevent="onSubmit()" autocomplete="off" ref="form" @input="validateForm()">
      <fieldset :disabled="busy">
        <input type="submit" style="display: none;" />

        <div style="display: flex; justify-content: center;">
          <div style="width: 80px;">
            <ImagePicker ref="imagePicker" mode="simple" :src="avatarUrl" fallback-src="/img/avatar-default-symbolic.svg" :size="512" @changed="onAvatarChanged" display-width="80px"/>
          </div>
        </div>

        <div class="text-warning" v-if="user && user.source">{{ $t('users.editUserDialog.externalLdapWarning') }}</div>
        <div class="error-label" v-if="formError.generic">{{ formError.generic }}</div>

        <!-- if profile edit is locked a username has to be set here . username is editable if none is set -->
        <FormGroup :has-error="!!formError.username">
          <label for="usernameInput">{{ $t('users.user.username') }}</label>
          <TextInput id="usernameInput" v-model="username" :required="!user?.username && profileLocked" :readonly="user?.username ? true : false" />
          <small v-if="!user?.username && !profileLocked" class="helper-text">{{ $t('users.user.usernamePlaceholder') }}</small>
          <div class="error-label" v-if="formError.username">{{ formError.username }}</div>
        </FormGroup>

        <FormGroup :has-error="!!formError.email">
          <label for="emailInput">{{ $t('users.user.primaryEmail') }} <sup><a href="https://docs.cloudron.io/profile/#primary-email" class="help" target="_blank" tabindex="-1"><i class="fa fa-question-circle"></i></a></sup></label>
          <EmailInput id="emailInput" v-model="email" :readonly="user?.source ? true : false" :required="user?.source ? false : true" />
          <div class="error-label" v-if="formError.email">{{ formError.email }}</div>
        </FormGroup>

        <FormGroup style="flex-grow: 1">
          <label for="displayNameInput">{{ $t('users.user.fullName') }}</label>
          <TextInput id="displayNameInput" v-model="displayName" :readonly="user?.source ? true : false"/>
          <small v-if="!user || !user.username" class="helper-text">{{ $t('users.user.displayNamePlaceholder') }}</small> <!-- don't show if user has already signed up -->
        </FormGroup>

        <FormGroup>
          <label for="fallbackEmailInput">{{ $t('users.user.recoveryEmail') }} <sup><a href="https://docs.cloudron.io/profile/#password-recovery-email" class="help" target="_blank" tabindex="-1"><i class="fa fa-question-circle"></i></a></sup></label>
          <EmailInput id="fallbackEmailInput" v-model="fallbackEmail" />
          <small class="helper-text">{{ $t('users.user.fallbackEmailPlaceholder') }}</small>
        </FormGroup>

        <FormGroup v-if="profile.isAtLeastAdmin" :has-error="formError.role">
          <label for="roleInput">{{ $t('users.user.role') }} <sup><a href="https://docs.cloudron.io/user-management/#roles" class="help" target="_blank" tabindex="-1"><i class="fa fa-question-circle"></i></a></sup></label>
          <SingleSelect id="roleInput" v-model="role" :options="roles" option-key="id" option-label="name" :disabled="isSelf"/>
          <div class="error-label" v-if="formError.role">{{ formError.role }}</div>
        </FormGroup>

        <!-- local groups. they can have local and external users -->
        <FormGroup>
          <label for="groupsInput">{{ $t('users.user.groups') }}</label>
          <div v-if="allGroups.length === 0">{{ $t('users.user.noGroups') }}</div>
          <MultiSelect v-if="allLocalGroups.length" v-model="localGroupIds" option-key="id" :options="allLocalGroups" :search-threshold="20" />
        </FormGroup>

        <!-- on add, this is hidden for now, until we figure why one would want to add an inactive user -->
        <Checkbox v-if="user" v-model="active" :disabled="isSelf" :label="$t('users.user.activeCheckbox')" help-url="https://docs.cloudron.io/user-management/#disable-user"/>
        <Checkbox v-else v-model="sendInvite" :label="$t('users.addUserDialog.sendInviteCheckbox')" />
      </fieldset>
    </form>
  </Dialog>
</template>
