<template>
  <q-scroll-area
    ref="scrollAreaRef"
    :style="scrollStyle"
    :vertical-offset="verticalOffset"
    :thumb-style="thumbStyle"
    @scroll="onScroll"
    class="shadow-scroll position-relative w100"
    >
    <q-resize-observer @resize="onInnerResize" />
    
    <div :style="topSpacerStyle" />
    <slot />
    <div :style="{ height: bottom + 'px' }" />
  </q-scroll-area>
</template>

<script setup lang="ts">
  import { ref, computed, watch } from 'vue'
  import { QScrollArea } from 'quasar'

  interface Props {
    height: number
    topOffset?: number
    bottomOffset?: number
    scrollPosition?: number
    offset?: number
    scrollHeaderSizeFull?: number
    scrollHeaderSizeSticky?: number
  }

  const props = withDefaults(defineProps<Props>(), {
    scrollPosition: 0,
    offset: 16,
    scrollHeaderSizeFull: 0,
    scrollHeaderSizeSticky: 0
  })

  const emit = defineEmits([
    'updateScrollPosition',
    'resizeScrollArea'
  ])

  const scrollAreaRef = ref<QScrollArea | null>(null)
  const currentScroll = ref(0)
  const innerHeight = ref(0)

  const top = computed(() => props.topOffset || props.offset)
  const bottom = computed(() => props.bottomOffset || props.offset)

  const offsetBefore = computed(() => {
    if (props.scrollHeaderSizeFull && props.scrollHeaderSizeSticky) {
      return Math.max(
        props.scrollHeaderSizeFull - currentScroll.value,
        props.scrollHeaderSizeSticky
      )
    }
    return 0
  })

  const verticalOffset = computed<[number, number]>(() => [
    top.value + offsetBefore.value,
    bottom.value
  ])

  const thumbStyle = {
    borderRadius: '3px',
    background: '#999',
    width: '6px',
    opacity: '0.4'
  }

  const scrollStyle = computed(() => {
    const isHeaderSticky = props.scrollHeaderSizeFull && props.scrollHeaderSizeSticky
      ? (props.scrollHeaderSizeFull - currentScroll.value <= props.scrollHeaderSizeSticky)
      : true

    return {
      height: props.height + 'px',
      '--top-height': top.value + 'px',
      '--bottom-height': bottom.value + 'px',
      '--offset-before': offsetBefore.value + 'px',
      '--top-shadow-visibility': isHeaderSticky ? 'visible' : 'hidden'
    }
  })

  const topSpacerStyle = computed(() => ({
    height: top.value + 'px',
    visibility: offsetBefore.value === 0 ? 'visible' as const : 'hidden' as const,
    marginTop: offsetBefore.value === 0 ? '0px' : `-${top.value}px`
  }))

  function onScroll ({ verticalPosition }: { verticalPosition: number }) {
    currentScroll.value = verticalPosition
    emit('updateScrollPosition', verticalPosition)
  }

  function onInnerResize (size: { height: number }) {
    innerHeight.value = size.height
    emit('resizeScrollArea', {
      height: props.height,
      innerHeight: size.height,
      topOffset: top.value,
      bottomOffset: bottom.value
    })
  }

  watch(() => props.scrollPosition, (newPos) => {
    if (newPos !== currentScroll.value) {
      scrollAreaRef.value?.setScrollPosition('vertical', newPos)
    }
  })

  watch([top, bottom, () => props.height], () => {
    emit('resizeScrollArea', {
      height: props.height,
      innerHeight: innerHeight.value,
      topOffset: top.value,
      bottomOffset: bottom.value
    })
  })
</script>

<style scoped>
  .shadow-scroll:before,
  .shadow-scroll:after {
    content: "";
    position: absolute;
    left: 0;
    width: 100%;
    pointer-events: none;
    z-index: 95;
    transition: opacity 0.2s ease;
  }

  .shadow-scroll:before {
    top: var(--offset-before);
    height: var(--top-height);
    background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
    visibility: var(--top-shadow-visibility);
  }

  .shadow-scroll:after {
    bottom: 0;
    height: var(--bottom-height);
    background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
  }

  .shadow-scroll :deep(.q-scrollarea__content) {
    max-width: 100%;
  }
</style>
