<script setup lang="ts">
import { AlertCircle, AlertTriangle, CheckCircle, X } from 'lucide-vue-next';
import {
  type Component,
  computed,
  onMounted,
  onUnmounted,
  provide,
  ref,
} from 'vue';
import BmsButton from '../button/BmsButton.vue';
import BmsIconButton from '../button/BmsIconButton.vue';
import BmsLoader from '../feedback/BmsLoader.vue';
import BmsOverlay from './BmsOverlay.vue';
import { StatusType, ModalActionButton } from '@/models';
import { onClickOutside } from '@vueuse/core';
import { v4 as uuid } from 'uuid';

interface Props {
  modelValue: any;
  backdropDismiss?: boolean;
  loading?: boolean;
  loadingText?: string;
  closable?: boolean;
  title?: string;
  submitLabel?: string;
  cancelLabel?: string;
  buttons?: ModalActionButton[];
  type?: StatusType;
  priority?: boolean;
  submitAction?: () => Promise<boolean>;
  cancelAction?: () => Promise<boolean>;
}

const emit = defineEmits(['update:modelValue', 'submit', 'close']);
defineExpose({ close: dismissModal });

const props = withDefaults(defineProps<Props>(), {
  modelValue: null,
  backdropDismiss: true,
  closable: true,
  submitLabel: 'Ok',
  cancelLabel: 'Annuler',
  loading: false,
  type: StatusType.Default,
  priority: false,
});

provide('bms-in-modal', true);

const innerModal = ref(null);
const id = ref(uuid());
onClickOutside(innerModal, (event: PointerEvent) => {
  if (
    event.target &&
    (event.target as HTMLElement)
      .getAttribute('data-testid')
      ?.includes(id.value)
  ) {
    //prevent overlay over modal and modal in modal
    backdropClick();
  }
});

const isModalClosable = computed<boolean>(() => {
  return props.closable && !props.loading;
});

const buttons = computed<ModalActionButton[]>(() => {
  return (
    props.buttons || [
      {
        label: props.cancelLabel,
        role: 'cancel',
        action: props.cancelAction || (async () => true),
        mode: StatusType.Default,
      },
      {
        label: props.submitLabel,
        role: 'submit',
        action: props.submitAction || (async () => true),
        mode:
          props.type === StatusType.Danger
            ? StatusType.Danger
            : StatusType.Default,
      },
    ]
  );
});

const titleComponent = computed<Component>(() => {
  switch (props.type) {
    case StatusType.Success:
      return CheckCircle;
    case StatusType.Warning:
      return AlertTriangle;
    case StatusType.Danger:
      return AlertCircle;
    default:
      return CheckCircle;
  }
});

function submitModal() {
  emit('submit');
  dismissModal();
}

function onKeyPress(e: KeyboardEvent) {
  if (props.modelValue && props.backdropDismiss && e.key === 'Escape') {
    dismissModal();
    return;
  }
  if (props.modelValue && e.key === 'Enter') {
    submitModal();
    return;
  }
}

async function callButtonAction(button: ModalActionButton) {
  if (button?.action && !button.disabled) {
    const shouldClose = await button.action();

    if (shouldClose) {
      button.role === 'cancel' ? dismissModal() : submitModal();
    }
  }
}

function dismissModal() {
  if (!isModalClosable.value) {
    return;
  }

  emit('update:modelValue', undefined);
  emit('close');
}

function backdropClick() {
  if (!props.backdropDismiss) {
    return;
  }

  dismissModal();
}

onMounted(() => {
  document.addEventListener('keydown', onKeyPress);
});

onUnmounted(() => {
  document.removeEventListener('keydown', onKeyPress);
});
</script>

<template>
  <Teleport to="body">
    <Transition :duration="300" name="content-fade">
      <BmsOverlay
        :active="!!props.modelValue"
        data-testid="backdrop-modal"
        :priority="priority"
      >
        <div class="ui-modal" :data-testid="`modal-${id}`" :class="props.type">
          <article class="ui-modal__inner" ref="innerModal">
            <header class="ui-modal__header">
              <div class="ui-modal__header-inner">
                <slot name="header">
                  <h1 class="ui-modal__title">
                    <component
                      v-if="props.type !== StatusType.Default"
                      :is="titleComponent"
                      class="title-indicator"
                      :size="20"
                    />
                    <slot name="title" :modal-data="props.modelValue">
                      {{ title }}
                    </slot>
                  </h1></slot
                >
              </div>

              <BmsIconButton
                :disabled="!isModalClosable"
                @click="dismissModal"
                data-testid="close-modal"
              >
                <X />
              </BmsIconButton>
            </header>

            <main class="ui-modal__content">
              <slot :modal-data="props.modelValue"></slot>
            </main>

            <footer class="ui-modal__footer">
              <slot name="footer" :modal-data="props.modelValue">
                <div class="ui-modal__buttons">
                  <slot
                    name="buttons"
                    :close="dismissModal"
                    :modal-data="props.modelValue"
                  >
                    <BmsButton
                      v-for="button in buttons"
                      :key="button.label + button.role"
                      :type="button.role === 'cancel' ? 'secondary' : 'primary'"
                      :disabled="!isModalClosable || button.disabled"
                      :mode="button.mode"
                      @click="callButtonAction(button)"
                      :data-testid="button.role + '-button'"
                      >{{ button.label }}</BmsButton
                    >
                  </slot>
                </div>
              </slot>
            </footer>

            <Transition name="fade">
              <div v-if="loading" class="ui-modal__loader">
                <BmsLoader :size="64" />
                <p v-if="loadingText">{{ loadingText }}</p>
              </div>
            </Transition>
          </article>
        </div>
      </BmsOverlay>
    </Transition>
  </Teleport>
</template>

<style lang="scss" scoped>
.ui-modal {
  --modal-header-border-size: 1px;
  --modal-header-color: var(--bms-grey-10);

  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--bms-z-index-fixed);

  &.Default {
    --modal-header-border-size: 1px;
    --modal-header-color: var(--bms-grey-10);
  }

  &.success {
    --modal-header-border-size: 4px;
    --modal-header-color: var(--bms-success-color);
  }

  &.warning {
    --modal-header-border-size: 4px;
    --modal-header-color: var(--bms-warning-color);
  }

  &.danger {
    --modal-header-border-size: 4px;
    --modal-header-color: var(--bms-danger-color);
  }

  &__loader {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    gap: 1em;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: var(--bms-white);
    z-index: calc(var(--bms-z-index-fixed) + 5);

    p {
      margin: 0;
      font-size: 0.88em;
    }
  }

  &__title {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin: 0;
    font-size: 1.5em;
    font-weight: bold;

    .title-indicator {
      color: var(--modal-header-color);
    }
  }

  &__inner {
    position: relative;
    pointer-events: all;
    background: var(--bms-white);
    min-width: 32em;
    max-width: 80vw;
    padding: 1em;
    border-radius: var(--bms-border-radius-large);
  }

  &__content {
    margin-top: 1rem;
    max-height: 60vh;
    overflow: auto;
  }

  &__header,
  &__footer {
    margin: -1em;
    position: sticky;
    background: var(--bms-white);
    z-index: var(--bms-z-index-fixed);
  }

  &__header {
    padding: 1em 1em 0.5em 1em;
    border-bottom: var(--modal-header-border-size) solid
      var(--modal-header-color);
    margin-bottom: 0;
    top: -1em;
    display: flex;

    &-inner {
      flex: 1 1 auto;
    }
  }

  &__footer {
    padding: 1em;
    margin-top: 0;
    bottom: -1em;
  }

  &__buttons {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 1em;
  }
}

.content-fade-enter-active,
.content-fade-leave-active {
  transition: opacity 0.3s ease;
}

.content-fade-enter-from,
.content-fade-leave-to {
  opacity: 0;
}

.content-fade-enter-active .ui-modal,
.content-fade-leave-active .ui-modal {
  transition: transform 0.3s ease;
}

.content-fade-enter-from .ui-modal,
.content-fade-leave-to .ui-modal {
  transform: scale(0.8);
}
</style>
