// Styles
import './VProgressLinear.sass'

// Composables
import { useBackgroundColor, useTextColor } from '@/composables/color'
import { makeComponentProps } from '@/composables/component'
import { useIntersectionObserver } from '@/composables/intersectionObserver'
import { useRtl } from '@/composables/locale'
import { makeLocationProps, useLocation } from '@/composables/location'
import { useProxiedModel } from '@/composables/proxiedModel'
import { useResizeObserver } from '@/composables/resizeObserver'
import { makeRoundedProps, useRounded } from '@/composables/rounded'
import { makeTagProps } from '@/composables/tag'
import { makeThemeProps, provideTheme } from '@/composables/theme'
import { useToggleScope } from '@/composables/toggleScope'

// Utilities
import { computed, ref, shallowRef, Transition, watchEffect } from 'vue'
import { makeChunksProps, useChunks } from './chunks'
import { clamp, convertToUnit, genericComponent, propsFactory, useRender } from '@/util'

type VProgressLinearSlots = {
  default: { value: number, buffer: number }
}

export const makeVProgressLinearProps = propsFactory({
  absolute: Boolean,
  active: {
    type: Boolean,
    default: true,
  },
  bgColor: String,
  bgOpacity: [Number, String],
  bufferValue: {
    type: [Number, String],
    default: 0,
  },
  bufferColor: String,
  bufferOpacity: [Number, String],
  clickable: Boolean,
  color: String,
  height: {
    type: [Number, String],
    default: 4,
  },
  indeterminate: Boolean,
  max: {
    type: [Number, String],
    default: 100,
  },
  modelValue: {
    type: [Number, String],
    default: 0,
  },
  opacity: [Number, String],
  reverse: Boolean,
  stream: Boolean,
  striped: Boolean,
  roundedBar: Boolean,

  ...makeChunksProps(),
  ...makeComponentProps(),
  ...makeLocationProps({ location: 'top' } as const),
  ...makeRoundedProps(),
  ...makeTagProps(),
  ...makeThemeProps(),
}, 'VProgressLinear')

export const VProgressLinear = genericComponent<VProgressLinearSlots>()({
  name: 'VProgressLinear',

  props: makeVProgressLinearProps(),

  emits: {
    'update:modelValue': (value: number) => true,
  },

  setup (props, { slots }) {
    const root = ref<HTMLElement>()

    const progress = useProxiedModel(props, 'modelValue')
    const { isRtl, rtlClasses } = useRtl()
    const { themeClasses } = provideTheme(props)
    const { locationStyles } = useLocation(props)
    const { textColorClasses, textColorStyles } = useTextColor(() => props.color)
    const {
      backgroundColorClasses,
      backgroundColorStyles,
    } = useBackgroundColor(() => props.bgColor || props.color)
    const {
      backgroundColorClasses: bufferColorClasses,
      backgroundColorStyles: bufferColorStyles,
    } = useBackgroundColor(() => props.bufferColor || props.bgColor || props.color)
    const {
      backgroundColorClasses: barColorClasses,
      backgroundColorStyles: barColorStyles,
    } = useBackgroundColor(() => props.color)
    const { roundedClasses, roundedStyles } = useRounded(props)
    const { intersectionRef, isIntersecting } = useIntersectionObserver()

    const max = computed(() => parseFloat(props.max))
    const height = computed(() => parseFloat(props.height))
    const normalizedBuffer = computed(() => clamp(parseFloat(props.bufferValue) / max.value * 100, 0, 100))
    const normalizedValue = computed(() => clamp(parseFloat(progress.value) / max.value * 100, 0, 100))
    const isReversed = computed(() => isRtl.value !== props.reverse)
    const transition = computed(() => props.indeterminate ? 'fade-transition' : 'slide-x-transition')

    const containerWidth = shallowRef(0)
    const { hasChunks, splitStyles, chunksMaskStyles, snapValueToChunk } = useChunks(
      props,
      containerWidth,
      normalizedValue,
      normalizedBuffer,
      isReversed
    )
    useToggleScope(hasChunks, () => {
      const { resizeRef } = useResizeObserver(entries => containerWidth.value = entries[0].contentRect.width)
      watchEffect(() => resizeRef.value = root.value)
    })

    const bufferWidth = computed(() => {
      return hasChunks.value
        ? snapValueToChunk(normalizedBuffer.value)
        : normalizedBuffer.value
    })

    const barWidth = computed(() => {
      return hasChunks.value
        ? snapValueToChunk(normalizedValue.value)
        : normalizedValue.value
    })

    function handleClick (e: MouseEvent) {
      if (!intersectionRef.value) return

      const { left, right, width } = intersectionRef.value.getBoundingClientRect()
      const value = isReversed.value ? (width - e.clientX) + (right - width) : e.clientX - left

      progress.value = Math.round(value / width * max.value)
    }

    watchEffect(() => {
      intersectionRef.value = root.value
    })

    function renderBackgroundBar () {
      return (
        <div
          class={[
            'v-progress-linear__background',
            backgroundColorClasses.value,
          ]}
          style={[
            backgroundColorStyles.value,
            {
              opacity: props.bgOpacity != null ? parseFloat(props.bgOpacity) : undefined,
              width: props.stream ? 0 : undefined,
            },
            props.indeterminate ? {} : splitStyles.value?.background,
          ]}
        />
      )
    }

    useRender(() => (
      <props.tag
        ref={ root }
        class={[
          'v-progress-linear',
          {
            'v-progress-linear--absolute': props.absolute,
            'v-progress-linear--active': props.active && isIntersecting.value,
            'v-progress-linear--reverse': isReversed.value,
            'v-progress-linear--rounded': props.rounded,
            'v-progress-linear--rounded-bar': props.roundedBar,
            'v-progress-linear--striped': props.striped,
            'v-progress-linear--clickable': props.clickable,
            'v-progress-linear--variant-split': props.variant === 'split',
          },
          roundedClasses.value,
          themeClasses.value,
          rtlClasses.value,
          props.class,
        ]}
        style={[
          {
            bottom: props.location === 'bottom' ? 0 : undefined,
            top: props.location === 'top' ? 0 : undefined,
            height: props.active ? convertToUnit(height.value) : 0,
            '--v-progress-linear-height': convertToUnit(height.value),
            '--v-progress-chunk-gap': convertToUnit(props.chunkGap),
            ...(props.absolute ? locationStyles.value : {}),
          },
          chunksMaskStyles.value,
          roundedStyles.value,
          props.style,
        ]}
        role="progressbar"
        aria-hidden={ props.active ? 'false' : 'true' }
        aria-valuemin="0"
        aria-valuemax={ props.max }
        aria-valuenow={ props.indeterminate ? undefined : Math.min(parseFloat(progress.value), max.value) }
        onClick={ props.clickable && handleClick }
      >
        { props.stream && (
          <div
            key="stream"
            class={[
              'v-progress-linear__stream',
              textColorClasses.value,
            ]}
            style={{
              ...textColorStyles.value,
              [isReversed.value ? 'left' : 'right']: convertToUnit(-height.value),
              borderTop: `${convertToUnit(height.value / 2)} dotted`,
              opacity: props.bufferOpacity != null ? parseFloat(props.bufferOpacity) : undefined,
              top: `calc(50% - ${convertToUnit(height.value / 4)})`,
              width: convertToUnit(100 - normalizedBuffer.value, '%'),
              '--v-progress-linear-stream-to': convertToUnit(height.value * (isReversed.value ? 1 : -1)),
            }}
          />
        )}

        { (props.variant !== 'split' || !props.indeterminate) && renderBackgroundBar() }

        <div
          class={[
            'v-progress-linear__buffer',
            bufferColorClasses.value,
          ]}
          style={[
            bufferColorStyles.value,
            {
              opacity: props.bufferOpacity != null ? parseFloat(props.bufferOpacity) : undefined,
              width: convertToUnit(bufferWidth.value, '%'),
            },
            splitStyles.value?.buffer,
          ]}
        />

        <Transition name={ transition.value }>
          { !props.indeterminate ? (
            <div
              class={[
                'v-progress-linear__determinate',
                barColorClasses.value,
              ]}
              style={[
                barColorStyles.value,
                { width: convertToUnit(barWidth.value, '%') },
                splitStyles.value?.bar,
              ]}
            />
          ) : (
            <div class="v-progress-linear__indeterminate">
              { props.variant === 'split' && (
                <>
                  { renderBackgroundBar() }
                  { renderBackgroundBar() }
                  { renderBackgroundBar() }
                </>
              )}
              {['long', 'short'].map(bar => (
                <div
                  key={ bar }
                  class={[
                    'v-progress-linear__indeterminate',
                    bar,
                    barColorClasses.value,
                  ]}
                  style={ barColorStyles.value }
                />
              ))}
            </div>
          )}
        </Transition>

        { slots.default && (
          <div class="v-progress-linear__content">
            { slots.default({ value: normalizedValue.value, buffer: normalizedBuffer.value }) }
          </div>
        )}
      </props.tag>
    ))

    return {}
  },
})

export type VProgressLinear = InstanceType<typeof VProgressLinear>
