<template>
  <div ref="wrapperRef" class="floating-window-wrapper">
    <div
      v-if="isDragging"
      class="floating-window__drag-overlay"
      @mousemove="onDrag"
      @mouseup="onDragEnd"
    />
    <div
      ref="windowRef"
      class="floating-window"
      :class="{
        'floating-window--expanded': isExpanded,
        'floating-window--dragging': isDragging,
      }"
      v-show="open"
      :style="windowStyle"
    >
      <div class="floating-window__header" @mousedown="onDragStart">
        <h3 class="floating-window__header__title">{{ title }}</h3>
        <div class="floating-window__header__buttons">
          <slot name="action"></slot>
          <BmsIconButton
            v-if="expandable"
            class="floating-window__expand-btn"
            :class="{ 'floating-window__expand-btn--expanded': isExpanded }"
            :tooltip-text="
              isExpanded ? 'Réduire la fenêtre' : 'Maximiser la fenêtre'
            "
            @click.prevent.stop="toggleExpand"
          >
            <Minimize2 v-if="isExpanded" />
            <Maximize2 v-else />
          </BmsIconButton>
          <BmsIconButton @click.prevent.stop="open = false">
            <X />
          </BmsIconButton>
        </div>
      </div>
      <div class="floating-window__content">
        <slot />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Maximize2, Minimize2, X } from 'lucide-vue-next';
import BmsIconButton from '../button/BmsIconButton.vue';
import { ref, computed, onMounted, watch, type CSSProperties } from 'vue';
import { useDebounceFn } from '@vueuse/core';

export type Placement =
  | 'top-left'
  | 'top-right'
  | 'bottom-left'
  | 'bottom-right'
  | 'center';

const props = withDefaults(
  defineProps<{
    title: string;
    defaultPlacement?: Placement;
    expandable?: boolean;
    startExpanded?: boolean; // @deprecated
    expanded?: boolean;
    expandedWidth?: string;
    expandedHeight?: string;
    expandTarget?: string;
    width?: string;
    height?: string;
  }>(),
  {
    defaultPlacement: 'center',
    expandable: false,
    startExpanded: false, // @deprecated
    expandedWidth: '100%',
    expandedHeight: '100%',
    expandTarget: undefined,
    width: '50%',
    height: '40%',
  },
);

const open = defineModel<boolean>({
  default: false,
});

const wrapperRef = ref<HTMLElement | null>(null);
const windowRef = ref<HTMLElement | null>(null);
const position = ref<{ x: number; y: number } | null>(null);
const computedSize = ref<{ width: number; height: number } | null>(null);
const isDragging = ref(false);
const dragOffset = ref({ x: 0, y: 0 });
const isExpanded = ref(false);
const positionBeforeExpand = ref<{ x: number; y: number } | null>(null);
const expandedStyle = ref<CSSProperties | null>(null);

function computeInitialPosition(): { x: number; y: number } | null {
  if (!wrapperRef.value || !windowRef.value) return null;

  const wrapperRect = wrapperRef.value.getBoundingClientRect();
  const windowRect = windowRef.value.getBoundingClientRect();
  const padding = 16;

  switch (props.defaultPlacement) {
    case 'top-left':
      return { x: wrapperRect.left + padding, y: wrapperRect.top + padding };
    case 'top-right':
      return {
        x: wrapperRect.right - windowRect.width - padding,
        y: wrapperRect.top + padding,
      };
    case 'bottom-left':
      return {
        x: wrapperRect.left + padding,
        y: wrapperRect.bottom - windowRect.height - padding,
      };
    case 'bottom-right':
      return {
        x: wrapperRect.right - windowRect.width - padding,
        y: wrapperRect.bottom - windowRect.height - padding,
      };
    case 'center':
    default:
      return null;
  }
}

function initPosition() {
  if (props.defaultPlacement !== 'center' && !position.value) {
    position.value = computeInitialPosition();
  }
}

const resizeCallack = useDebounceFn(() => {
  expandedStyle.value = computeExpandedStyle();
}, 100);

onMounted(() => {
  if (open.value) {
    initPosition();
  }
  if (props.expandTarget) {
    const targetEl = document.querySelector(props.expandTarget);
    if (targetEl) {
      const resizeObserver = new ResizeObserver(resizeCallack);
      resizeObserver.observe(targetEl);
    }
  }
  if (props.expandable && (props.expanded || props.startExpanded)) {
    toggleExpand();
  }
});

watch(open, (newVal) => {
  if (newVal) {
    requestAnimationFrame(initPosition);
  }
});

watch(
  () => props.expanded,
  () => {
    toggleExpand();
  },
);

defineExpose({
  isExpanded,
});

const windowStyle = computed<CSSProperties>(() => {
  if (isExpanded.value && expandedStyle.value) {
    return expandedStyle.value;
  }

  const sizeStyle: CSSProperties = {};
  if (computedSize.value) {
    sizeStyle.width = `${computedSize.value.width}px`;
    sizeStyle.height = `${computedSize.value.height}px`;
  } else {
    if (props.width) {
      sizeStyle.width = props.width;
    }
    if (props.height) {
      sizeStyle.height = props.height;
    }
  }

  if (!position.value) {
    return sizeStyle;
  }

  return {
    position: 'absolute',
    left: `${position.value.x}px`,
    top: `${position.value.y}px`,
    ...sizeStyle,
  };
});

function computeExpandedStyle(): CSSProperties {
  if (props.expandTarget) {
    const targetEl = document.querySelector(props.expandTarget);
    if (targetEl) {
      const targetRect = targetEl.getBoundingClientRect();
      return {
        position: 'fixed',
        left: `${targetRect.left}px`,
        top: `${targetRect.top}px`,
        width:
          props.expandedWidth === '100%'
            ? `${targetRect.width}px`
            : props.expandedWidth,
        height:
          props.expandedHeight === '100%'
            ? `${targetRect.height}px`
            : props.expandedHeight,
        maxWidth: 'none',
        maxHeight: 'none',
      };
    }
  }

  return {
    position: 'absolute',
    width: props.expandedWidth,
    height: props.expandedHeight,
    maxWidth: 'none',
    maxHeight: 'none',
  };
}

function toggleExpand() {
  if (isExpanded.value) {
    expandedStyle.value = null;
    position.value = positionBeforeExpand.value;
    isExpanded.value = false;
  } else {
    positionBeforeExpand.value = position.value;
    expandedStyle.value = computeExpandedStyle();
    isExpanded.value = true;
  }
}

function onDragStart(event: MouseEvent) {
  if (
    (event.target as HTMLElement).closest('.floating-window__header__buttons')
  ) {
    return;
  }

  if (isExpanded.value) {
    return;
  }

  isDragging.value = true;

  if (windowRef.value) {
    const rect = windowRef.value.getBoundingClientRect();
    dragOffset.value = {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top,
    };

    if (!computedSize.value) {
      computedSize.value = { width: rect.width, height: rect.height };
    }

    if (!position.value) {
      position.value = { x: rect.left, y: rect.top };
    }
  }

  document.addEventListener('mousemove', onDrag);
  document.addEventListener('mouseup', onDragEnd);
}

function onDrag(event: MouseEvent) {
  if (!isDragging.value) return;

  position.value = {
    x: event.clientX - dragOffset.value.x,
    y: event.clientY - dragOffset.value.y,
  };
}

function onDragEnd() {
  isDragging.value = false;
  document.removeEventListener('mousemove', onDrag);
  document.removeEventListener('mouseup', onDragEnd);
}
</script>

<style scoped lang="scss">
.floating-window-wrapper {
  --breakpoint: 900px;
  container-name: floating-window;
  container-type: inline-size;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  pointer-events: none;

  .floating-window__drag-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z-index: var(--bms-z-index-modal);
    pointer-events: all;
    cursor: grabbing;
  }

  .floating-window {
    background-color: var(--bms-white);
    border-radius: var(--bms-border-radius-large);
    border: 1px solid var(--bms-border-color);
    pointer-events: all;
    display: grid;
    grid-template-rows: auto 1fr;
    box-shadow: var(--bms-box-shadow-elevated);
    z-index: var(--bms-z-index-modal);
    transition:
      left 0.3s cubic-bezier(0.4, 0, 0.2, 1),
      top 0.3s cubic-bezier(0.4, 0, 0.2, 1),
      width 0.3s cubic-bezier(0.4, 0, 0.2, 1),
      height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
      border-radius 0.3s cubic-bezier(0.4, 0, 0.2, 1),
      box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);

    &--expanded {
      border-radius: 0;
      box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);

      .floating-window__header {
        cursor: default;

        &:active {
          cursor: default;
        }
      }
    }

    &--dragging {
      transition: none;
    }

    &__header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-bottom: 1px solid var(--bms-border-color);
      padding: 0.5em;
      cursor: grab;
      user-select: none;

      &:active {
        cursor: grabbing;
      }

      &__title {
        margin: 0;
      }

      &__buttons {
        display: flex;
        align-items: center;
        gap: 0.5em;
      }
    }
    &__content {
      overflow-y: auto;
      height: auto;
      scrollbar-width: thin;
    }
  }
}

@container floating-window (min-width:900px) {
  .floating-window {
    max-width: 80%;
  }
}
</style>
