<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { email, helpers, numeric, required } from '@vuelidate/validators'
import useVuelidate from '@vuelidate/core'
import type { MailConfig, MailDriver } from '@/scripts/types/mail-config'

interface SelectOption<TValue extends string = string> {
  label: string
  value: TValue
}

const props = withDefaults(
  defineProps<{
    configData?: Partial<MailConfig>
    isSaving?: boolean
    isFetchingInitialData?: boolean
    mailDrivers?: MailDriver[]
    translationScope?: string
    submitLabel?: string
    submitIcon?: string
  }>(),
  {
    configData: () => ({}),
    isSaving: false,
    isFetchingInitialData: false,
    mailDrivers: () => [],
    translationScope: 'settings.mail',
    submitLabel: 'general.save',
    submitIcon: 'ArrowDownOnSquareIcon',
  }
)

const emit = defineEmits<{
  'submit-data': [config: MailConfig]
  'on-change-driver': [driver: MailDriver]
}>()

const { t } = useI18n()

const visibleSecrets = reactive<Record<string, boolean>>({})
const showAdvancedFields = ref(false)

const mailConfig = reactive<MailConfig>(createDefaultMailConfig())

const fallbackDrivers: MailDriver[] = ['smtp', 'mail', 'sendmail']
const encryptionOptions: SelectOption[] = [
  { label: 'None', value: 'none' },
  { label: 'TLS', value: 'tls' },
  { label: 'SSL', value: 'ssl' },
]
const smtpSchemeOptions: SelectOption[] = [
  { label: 'SMTP', value: 'smtp' },
  { label: 'SMTPS', value: 'smtps' },
]
const mailgunSchemeOptions: SelectOption[] = [
  { label: 'HTTPS', value: 'https' },
  { label: 'API', value: 'api' },
]

const availableDrivers = computed<MailDriver[]>(() => {
  return props.mailDrivers.length ? props.mailDrivers : fallbackDrivers
})

const driverOptions = computed<SelectOption<MailDriver>[]>(() => {
  return availableDrivers.value.map((driver) => ({
    label: t(`${props.translationScope}.drivers.${driver}`),
    value: driver,
  }))
})

const currentDriver = computed<MailDriver>({
  get: () => normalizeDriver(mailConfig.mail_driver, availableDrivers.value),
  set: (driver) => {
    mailConfig.mail_driver = driver
  },
})

const hasAdvancedFields = computed<boolean>(() => {
  return getAdvancedFields(currentDriver.value).length > 0
})

const rules = computed(() => {
  const commonRules = {
    mail_driver: {
      required: helpers.withMessage(t('validation.required'), required),
    },
    from_mail: {
      required: helpers.withMessage(t('validation.required'), required),
      email: helpers.withMessage(t('validation.email_incorrect'), email),
    },
    from_name: {
      required: helpers.withMessage(t('validation.required'), required),
    },
  }

  switch (currentDriver.value) {
    case 'smtp':
      return {
        ...commonRules,
        mail_host: {
          required: helpers.withMessage(t('validation.required'), required),
        },
        mail_port: {
          required: helpers.withMessage(t('validation.required'), required),
          numeric: helpers.withMessage(t('validation.numbers_only'), numeric),
        },
        mail_timeout: {
          numeric: helpers.withMessage(t('validation.numbers_only'), (value: string) => {
            if (!value) {
              return true
            }

            return /^\d+$/.test(value)
          }),
        },
      }
    case 'ses':
      return {
        ...commonRules,
        mail_ses_key: {
          required: helpers.withMessage(t('validation.required'), required),
        },
        mail_ses_secret: {
          required: helpers.withMessage(t('validation.required'), required),
        },
        mail_ses_region: {
          required: helpers.withMessage(t('validation.required'), required),
        },
      }
    case 'mailgun':
      return {
        ...commonRules,
        mail_mailgun_domain: {
          required: helpers.withMessage(t('validation.required'), required),
        },
        mail_mailgun_secret: {
          required: helpers.withMessage(t('validation.required'), required),
        },
        mail_mailgun_endpoint: {
          required: helpers.withMessage(t('validation.required'), required),
        },
      }
    case 'postmark':
      return {
        ...commonRules,
        mail_postmark_token: {
          required: helpers.withMessage(t('validation.required'), required),
        },
      }
    default:
      return commonRules
  }
})

const v$ = useVuelidate(rules, mailConfig)

watch(
  () => [props.configData, props.mailDrivers] as const,
  () => {
    syncMailConfig()
  },
  { immediate: true, deep: true }
)

function createDefaultMailConfig(): MailConfig {
  return {
    mail_driver: 'smtp',
    from_mail: '',
    from_name: '',
    mail_host: '',
    mail_port: '587',
    mail_username: '',
    mail_password: '',
    mail_encryption: 'none',
    mail_scheme: '',
    mail_url: '',
    mail_timeout: '',
    mail_local_domain: '',
    mail_sendmail_path: '/usr/sbin/sendmail -bs -i',
    mail_ses_key: '',
    mail_ses_secret: '',
    mail_ses_region: 'us-east-1',
    mail_mailgun_domain: '',
    mail_mailgun_secret: '',
    mail_mailgun_endpoint: 'api.mailgun.net',
    mail_mailgun_scheme: 'https',
    mail_postmark_token: '',
    mail_postmark_message_stream_id: '',
  }
}

function normalizeDriver(driver: MailConfig['mail_driver'], drivers: MailDriver[]): MailDriver {
  if (driver && drivers.includes(driver as MailDriver)) {
    return driver as MailDriver
  }

  return drivers[0] ?? 'smtp'
}

function syncMailConfig(): void {
  Object.assign(mailConfig, createDefaultMailConfig(), props.configData)
  mailConfig.mail_driver = normalizeDriver(mailConfig.mail_driver, availableDrivers.value)
  showAdvancedFields.value = hasAdvancedValues(currentDriver.value)
  v$.value.$reset()
}

function hasAdvancedValues(driver: MailDriver): boolean {
  const defaultMailConfig = createDefaultMailConfig()

  return getAdvancedFields(driver).some((field) => {
    const value = mailConfig[field]
    const defaultValue = defaultMailConfig[field]

    return value !== '' && value !== null && value !== undefined && value !== defaultValue
  })
}

function getAdvancedFields(driver: MailDriver): Array<keyof MailConfig> {
  switch (driver) {
    case 'smtp':
      return ['mail_scheme', 'mail_url', 'mail_timeout', 'mail_local_domain']
    case 'sendmail':
      return ['mail_sendmail_path']
    case 'mailgun':
      return ['mail_mailgun_scheme']
    case 'postmark':
      return ['mail_postmark_message_stream_id']
    default:
      return []
  }
}

function getFieldError(field: string): string | undefined {
  const validationField = v$.value[field as keyof typeof v$.value]

  if (!validationField || !('$error' in validationField) || !validationField.$error) {
    return undefined
  }

  return validationField.$errors[0]?.$message as string | undefined
}

function toggleSecret(field: string): void {
  visibleSecrets[field] = !visibleSecrets[field]
}

function getSecretInputType(field: string): string {
  return visibleSecrets[field] ? 'text' : 'password'
}

function translationKey(key: string): string {
  return `${props.translationScope}.${key}`
}

function changeDriver(value: MailDriver): void {
  currentDriver.value = value
  showAdvancedFields.value = false
  v$.value.$reset()
  emit('on-change-driver', value)
}

async function saveEmailConfig(): Promise<void> {
  v$.value.$touch()

  if (v$.value.$invalid) {
    return
  }

  emit('submit-data', { ...mailConfig, mail_driver: currentDriver.value })
}
</script>

<template>
  <form @submit.prevent="saveEmailConfig">
    <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
      <BaseInputGroup
        :label="$t(translationKey('driver'))"
        :content-loading="isFetchingInitialData"
        :error="getFieldError('mail_driver')"
        required
      >
        <BaseMultiselect
          v-model="currentDriver"
          :content-loading="isFetchingInitialData"
          :options="driverOptions"
          label="label"
          value-prop="value"
          :can-deselect="false"
          :can-clear="false"
          :invalid="v$.mail_driver.$error"
          @update:model-value="changeDriver"
        />
      </BaseInputGroup>
    </div>

    <div class="mt-8">
      <h3 class="text-sm font-semibold text-heading">
        {{ $t(translationKey('basic_settings')) }}
      </h3>
      <p
        v-if="currentDriver === 'mail' || currentDriver === 'sendmail'"
        class="mt-2 text-sm text-muted"
      >
        {{
          currentDriver === 'mail'
            ? $t(translationKey('native_mail_desc'))
            : $t(translationKey('sendmail_desc'))
        }}
      </p>

      <div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
        <template v-if="currentDriver === 'smtp'">
          <BaseInputGroup
            :label="$t(translationKey('host'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_host')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_host"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_host?.$error"
              type="text"
              @input="v$.mail_host?.$touch()"
            />
          </BaseInputGroup>

          <BaseInputGroup
            :label="$t(translationKey('port'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_port')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_port"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_port?.$error"
              type="text"
              @input="v$.mail_port?.$touch()"
            />
          </BaseInputGroup>

          <BaseInputGroup
            :label="$t(translationKey('username'))"
            :content-loading="isFetchingInitialData"
          >
            <BaseInput
              v-model.trim="mailConfig.mail_username"
              :content-loading="isFetchingInitialData"
              type="text"
            />
          </BaseInputGroup>

          <BaseInputGroup
            :label="$t(translationKey('password'))"
            :content-loading="isFetchingInitialData"
          >
            <BaseInput
              v-model.trim="mailConfig.mail_password"
              :content-loading="isFetchingInitialData"
              :type="getSecretInputType('mail_password')"
              autocomplete="off"
            >
              <template #right>
                <BaseIcon
                  :name="visibleSecrets.mail_password ? 'EyeIcon' : 'EyeSlashIcon'"
                  class="mr-1 text-muted cursor-pointer"
                  @click="toggleSecret('mail_password')"
                />
              </template>
            </BaseInput>
          </BaseInputGroup>

          <BaseInputGroup
            :label="$t(translationKey('encryption'))"
            :content-loading="isFetchingInitialData"
          >
            <BaseMultiselect
              v-model="mailConfig.mail_encryption"
              :content-loading="isFetchingInitialData"
              :options="encryptionOptions"
              label="label"
              value-prop="value"
              :can-clear="false"
              :can-deselect="false"
            />
          </BaseInputGroup>
        </template>

        <template v-if="currentDriver === 'ses'">
          <BaseInputGroup
            :label="$t(translationKey('ses_key'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_ses_key')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_ses_key"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_ses_key?.$error"
              type="text"
              @input="v$.mail_ses_key?.$touch()"
            />
          </BaseInputGroup>

          <BaseInputGroup
            :label="$t(translationKey('ses_secret'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_ses_secret')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_ses_secret"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_ses_secret?.$error"
              :type="getSecretInputType('mail_ses_secret')"
              autocomplete="off"
              @input="v$.mail_ses_secret?.$touch()"
            >
              <template #right>
                <BaseIcon
                  :name="visibleSecrets.mail_ses_secret ? 'EyeIcon' : 'EyeSlashIcon'"
                  class="mr-1 text-muted cursor-pointer"
                  @click="toggleSecret('mail_ses_secret')"
                />
              </template>
            </BaseInput>
          </BaseInputGroup>

          <BaseInputGroup
            :label="$t(translationKey('ses_region'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_ses_region')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_ses_region"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_ses_region?.$error"
              type="text"
              @input="v$.mail_ses_region?.$touch()"
            />
          </BaseInputGroup>
        </template>

        <template v-if="currentDriver === 'mailgun'">
          <BaseInputGroup
            :label="$t(translationKey('mailgun_domain'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_mailgun_domain')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_mailgun_domain"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_mailgun_domain?.$error"
              type="text"
              @input="v$.mail_mailgun_domain?.$touch()"
            />
          </BaseInputGroup>

          <BaseInputGroup
            :label="$t(translationKey('mailgun_secret'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_mailgun_secret')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_mailgun_secret"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_mailgun_secret?.$error"
              :type="getSecretInputType('mail_mailgun_secret')"
              autocomplete="off"
              @input="v$.mail_mailgun_secret?.$touch()"
            >
              <template #right>
                <BaseIcon
                  :name="visibleSecrets.mail_mailgun_secret ? 'EyeIcon' : 'EyeSlashIcon'"
                  class="mr-1 text-muted cursor-pointer"
                  @click="toggleSecret('mail_mailgun_secret')"
                />
              </template>
            </BaseInput>
          </BaseInputGroup>

          <BaseInputGroup
            :label="$t(translationKey('mailgun_endpoint'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_mailgun_endpoint')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_mailgun_endpoint"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_mailgun_endpoint?.$error"
              type="text"
              @input="v$.mail_mailgun_endpoint?.$touch()"
            />
          </BaseInputGroup>
        </template>

        <template v-if="currentDriver === 'postmark'">
          <BaseInputGroup
            :label="$t(translationKey('postmark_token'))"
            :content-loading="isFetchingInitialData"
            :error="getFieldError('mail_postmark_token')"
            required
          >
            <BaseInput
              v-model.trim="mailConfig.mail_postmark_token"
              :content-loading="isFetchingInitialData"
              :invalid="v$.mail_postmark_token?.$error"
              :type="getSecretInputType('mail_postmark_token')"
              autocomplete="off"
              @input="v$.mail_postmark_token?.$touch()"
            >
              <template #right>
                <BaseIcon
                  :name="visibleSecrets.mail_postmark_token ? 'EyeIcon' : 'EyeSlashIcon'"
                  class="mr-1 text-muted cursor-pointer"
                  @click="toggleSecret('mail_postmark_token')"
                />
              </template>
            </BaseInput>
          </BaseInputGroup>
        </template>

        <BaseInputGroup
          :label="$t(translationKey('from_mail'))"
          :content-loading="isFetchingInitialData"
          :error="getFieldError('from_mail')"
          required
        >
          <BaseInput
            v-model.trim="mailConfig.from_mail"
            :content-loading="isFetchingInitialData"
            :invalid="v$.from_mail.$error"
            type="text"
            @input="v$.from_mail.$touch()"
          />
        </BaseInputGroup>

        <BaseInputGroup
          :label="$t(translationKey('from_name'))"
          :content-loading="isFetchingInitialData"
          :error="getFieldError('from_name')"
          required
        >
          <BaseInput
            v-model.trim="mailConfig.from_name"
            :content-loading="isFetchingInitialData"
            :invalid="v$.from_name.$error"
            type="text"
            @input="v$.from_name.$touch()"
          />
        </BaseInputGroup>
      </div>
    </div>

    <div v-if="hasAdvancedFields" class="mt-8">
      <button
        type="button"
        class="inline-flex items-center gap-2 text-sm font-medium text-primary-600"
        @click="showAdvancedFields = !showAdvancedFields"
      >
        <BaseIcon
          :name="showAdvancedFields ? 'ChevronUpIcon' : 'ChevronDownIcon'"
          class="h-4 w-4"
        />
        {{
          showAdvancedFields
            ? $t(translationKey('hide_advanced_settings'))
            : $t(translationKey('show_advanced_settings'))
        }}
      </button>

      <div v-if="showAdvancedFields" class="mt-4 rounded-lg border border-line-default p-4">
        <h3 class="text-sm font-semibold text-heading">
          {{ $t(translationKey('advanced_settings')) }}
        </h3>

        <div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
          <template v-if="currentDriver === 'smtp'">
            <BaseInputGroup
              :label="$t(translationKey('scheme'))"
              :content-loading="isFetchingInitialData"
            >
              <BaseMultiselect
                v-model="mailConfig.mail_scheme"
                :content-loading="isFetchingInitialData"
                :options="smtpSchemeOptions"
                label="label"
                value-prop="value"
              />
            </BaseInputGroup>

            <BaseInputGroup
              :label="$t(translationKey('url'))"
              :content-loading="isFetchingInitialData"
            >
              <BaseInput
                v-model.trim="mailConfig.mail_url"
                :content-loading="isFetchingInitialData"
                type="text"
              />
            </BaseInputGroup>

            <BaseInputGroup
              :label="$t(translationKey('timeout'))"
              :content-loading="isFetchingInitialData"
              :error="getFieldError('mail_timeout')"
            >
              <BaseInput
                v-model.trim="mailConfig.mail_timeout"
                :content-loading="isFetchingInitialData"
                :invalid="v$.mail_timeout?.$error"
                type="text"
                @input="v$.mail_timeout?.$touch()"
              />
            </BaseInputGroup>

            <BaseInputGroup
              :label="$t(translationKey('local_domain'))"
              :content-loading="isFetchingInitialData"
            >
              <BaseInput
                v-model.trim="mailConfig.mail_local_domain"
                :content-loading="isFetchingInitialData"
                type="text"
              />
            </BaseInputGroup>
          </template>

          <template v-if="currentDriver === 'sendmail'">
            <BaseInputGroup
              :label="$t(translationKey('sendmail_path'))"
              :content-loading="isFetchingInitialData"
            >
              <BaseInput
                v-model.trim="mailConfig.mail_sendmail_path"
                :content-loading="isFetchingInitialData"
                type="text"
              />
            </BaseInputGroup>
          </template>

          <template v-if="currentDriver === 'mailgun'">
            <BaseInputGroup
              :label="$t(translationKey('mailgun_scheme'))"
              :content-loading="isFetchingInitialData"
            >
              <BaseMultiselect
                v-model="mailConfig.mail_mailgun_scheme"
                :content-loading="isFetchingInitialData"
                :options="mailgunSchemeOptions"
                label="label"
                value-prop="value"
                :can-clear="false"
                :can-deselect="false"
              />
            </BaseInputGroup>
          </template>

          <template v-if="currentDriver === 'postmark'">
            <BaseInputGroup
              :label="$t(translationKey('postmark_message_stream_id'))"
              :content-loading="isFetchingInitialData"
            >
              <BaseInput
                v-model.trim="mailConfig.mail_postmark_message_stream_id"
                :content-loading="isFetchingInitialData"
                type="text"
              />
            </BaseInputGroup>
          </template>
        </div>
      </div>
    </div>

    <div class="mt-8 flex">
      <BaseButton
        :disabled="isSaving"
        :content-loading="isFetchingInitialData"
        :loading="isSaving"
        variant="primary"
        type="submit"
      >
        <template #left="slotProps">
          <BaseIcon v-if="!isSaving" :name="submitIcon" :class="slotProps.class" />
        </template>
        {{ $t(submitLabel) }}
      </BaseButton>
      <slot />
    </div>
  </form>
</template>
