<template>
  <teleport v-if="modelValue" to="body">
    <div class="tour-bg"></div>
  </teleport>
  <v-overlay
    v-bind="props"
    v-model="modelValue"
    :close-on-content-click="false"
    location-strategy="connected"
    no-click-animation
    persistent
    :scrim="false"
    :target="target"
  >
    <v-card :ref="(el) => (modal = (el as VCard)?.$el)" v-bind="props">
      <slot name="title" :step="currentStep">
        <v-card-title>
          <v-toolbar color="surface" density="compact">
            <span>{{ currentStep.title }}</span>
            <v-spacer />
            <v-btn density="comfortable" icon="close" variant="text" @click="modelValue = false" />
          </v-toolbar>
        </v-card-title>
      </slot>
      <slot name="text" :step="currentStep">
        <v-card-text>
          <p>{{ currentStep.text }}</p>
        </v-card-text>
      </slot>
      <slot name="actions" :step="currentStep">
        <v-card-actions class="px-4">
          <span class="text-primary">{{ step + 1 }} / {{ steps.length }}</span>
          <v-spacer />
          <v-btn v-if="hasPrevious" color="primary" variant="text" @click="previous">
            <span>{{ t('labels.previous') }}</span>
          </v-btn>
          <v-btn v-if="hasNext" color="primary" variant="flat" @click="next">
            <span>{{ t('labels.next') }}</span>
          </v-btn>
          <v-btn v-if="!hasNext" color="primary" variant="flat" @click="modelValue = false">
            <span>{{ t('labels.finish') }}</span>
          </v-btn>
        </v-card-actions>
      </slot>
    </v-card>
  </v-overlay>
</template>

<script setup lang="ts">
import { Step } from '@/components/intro/tour/step';
import { computed, ref, watch } from 'vue';
import { VCard } from 'vuetify/components';
import { useI18n } from 'vue-i18n';
import { useElementBounding } from '@vueuse/core';

interface Props {
  gap?: number;
  height?: string | number;
  maxHeight?: string | number;
  maxWidth?: string | number;
  minHeight?: string | number;
  minWidth?: string | number;
  steps?: Step[];
  width?: string | number;
}

const { t } = useI18n();

const props = withDefaults(defineProps<Props>(), {
  gap: 8,
  steps: () => []
});

const element = ref<HTMLElement | null>(null);
const { x: ex, y: ey, width: ew, height: eh } = useElementBounding(element);

const modal = ref<HTMLElement | null>(null);
const { width: mw, height: mh } = useElementBounding(modal);

const target = ref<[number, number]>([0, 0]);

const modelValue = defineModel<boolean>({ default: false });
const step = defineModel<number>('step', { default: 0 });

const currentStep = computed(() => {
  return props.steps[step.value];
});

const hasPrevious = computed(() => {
  return step.value > 0;
});

const hasNext = computed(() => {
  return step.value < props.steps.length - 1;
});

const left = computed(() => {
  return element.value !== null ? ex.value : document.documentElement.clientWidth / 2;
});

const top = computed(() => {
  return element.value !== null ? ey.value : document.documentElement.clientHeight / 2;
});

watch(modelValue, (value) => {
  if (value) {
    refresh();
  }
});

watch(currentStep, (step) => {
  element.value = _queryElement(step.element);
});

watch([element, currentStep, ex, ey, ew, eh, mw, mh], ([el, step, ex, ey, ew, eh, mw, mh]) => {
  target.value = _recalculate(el, ex, ey, ew, eh, mw, mh, step.position);
});

function _queryElement(selector?: string | HTMLElement): HTMLElement | null {
  if (selector) {
    return typeof selector === 'string' ? (document.querySelector(selector) as HTMLElement) : selector;
  } else {
    return null;
  }
}

function _recalculate(
  el: HTMLElement | null,
  ex: number,
  ey: number,
  ew: number,
  eh: number,
  mw: number,
  mh: number,
  pos?: string
): [number, number] {
  if (el) {
    switch (pos) {
      case 'top': {
        const x = Math.max(ex + ew / 2 - mw / 2, 0);
        const y = Math.max(ey - mh - props.gap, 0);
        return [x, y];
      }
      case 'right': {
        const x = Math.max(ex + ew + props.gap, 0);
        const y = Math.max(ey + eh / 2 - mh / 2, 0);
        return [x, y];
      }
      case 'bottom': {
        const x = Math.max(ex + ew / 2 - mw / 2, 0);
        const y = Math.max(ey + eh + props.gap, 0);
        return [x, y];
      }
      case 'left': {
        const x = Math.max(ex - mw - props.gap, 0);
        const y = Math.max(ey + eh / 2 - mh / 2, 0);
        return [x, y];
      }
    }
  }

  // default to center
  const x = document.documentElement.clientWidth / 2 - mw / 2;
  const y = document.documentElement.clientHeight / 2 - mh / 2;
  return [x, y];
}

function refresh(): void {
  const step = currentStep.value;
  element.value = _queryElement(step.element);
}

function previous(): void {
  if (hasPrevious.value) {
    step.value -= 1;
  }
}

function next(): void {
  if (hasNext.value) {
    step.value += 1;
  }
}

defineExpose({ next, previous, refresh });
</script>

<style scoped lang="scss">
.tour-bg {
  pointer-events: none;
  position: absolute;
  top: calc(v-bind('top') * 1px);
  left: calc(v-bind('left') * 1px);
  width: calc(v-bind('ew') * 1px);
  height: calc(v-bind('eh') * 1px);
  box-shadow: 0 0 0 9999px rgba(31, 31, 55, 0.9);
  border-radius: 8px;
  z-index: 999;
  transition:
    width 500ms,
    height 500ms,
    top 500ms,
    left 500ms;
}
</style>
