import './VNavigationDrawer.scss'
import { useSticky } from './sticky'
import { useTouch } from './touch'
import { makeLayoutItemProps, useLayoutItem } from '@/composables/layout'
import { useProxiedModel } from '@/composables/proxiedModel'
import { useRouter } from '@/composables/router'
import { useScopeId } from '@/composables/scopeId'
import { makeTagProps } from '@/composables/tag'
import { makeComponentProps } from '@/composables/component'
import {
  computed,
  nextTick,
  onBeforeMount,
  ref,
  shallowRef,
  toRef,
  Transition,
  watch,
  ExtractPropTypes,
  PropType,
  onMounted,
} from 'vue'
import { genericComponent, propsFactory, useRender } from '@/utils'
import { provideDefaults } from '@/composables/defaults'

export type USidebarSlots = {
  default: never
  prepend: never
  append: never
}

const locations = ['start', 'end', 'left', 'right', 'top', 'bottom'] as const

export const makeUSidebarProps = propsFactory(
  {
    variant: String,
    theme: String,
    disableResizeWatcher: Boolean,
    disableRouteWatcher: Boolean,
    expandOnHover: Boolean,
    floating: Boolean,
    transitionDuration: {
      type: Number,
      default: 500,
    },
    border: {
      type: Boolean,
      default: false,
    },
    elevation: {
      type: Boolean,
      default: false,
    },
    modelValue: {
      type: Boolean,
      default: false,
      required: false,
    },
    permanent: Boolean,
    rail: {
      type: Boolean as PropType<boolean | null>,
      default: null,
    },
    railWidth: {
      type: [Number, String],
      default: 56,
    },
    scrim: {
      type: [Boolean, String],
      default: true,
    },
    temporary: Boolean,
    touchless: Boolean,
    width: {
      type: [Number, String],
      default: 312,
    },
    location: {
      type: String as PropType<(typeof locations)[number]>,
      default: 'start',
      validator: (value: any) => locations.includes(value),
    },
    sticky: Boolean,

    ...makeComponentProps(),
    ...makeLayoutItemProps(),
    ...makeTagProps({ tag: 'nav' }),
  },
  'USidebar'
)

export type USidebarProps = ExtractPropTypes<typeof makeUSidebarProps>

export const USidebar = genericComponent<USidebarSlots>()({
  name: 'USidebar',

  props: makeUSidebarProps(),

  emits: {
    'update:modelValue': (val: boolean) => true,
    'update:rail': (val: boolean) => true,
  },

  setup(props, { attrs, emit, slots }) {
    const router = useRouter()
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    const isActive = useProxiedModel(props, 'modelValue', null, (v) => !!v)
    const { scopeId } = useScopeId()
    const rootEl = ref<HTMLElement>()
    const isHovering = shallowRef(false)

    const screenWidth = ref(0)

    const bgColor = computed(() => {
      let bgColor = ''
      if (
        props.theme === 'light' &&
        (props.variant === 'gray' || props.variant === 'primary')
      ) {
        bgColor = 'white'
      } else if (props.theme === 'dark' && props.variant === 'primary') {
        bgColor = 'primary-700'
      } else if (props.theme === 'dark' && props.variant === 'gray') {
        bgColor = 'gray-900'
      }
      return bgColor
    })

    onMounted(() => {
      screenWidth.value = window.innerWidth
    })

    const mobile = computed(() => {
      return screenWidth.value < 350 ? true : false
    })

    const width = computed(() => {
      return props.rail && props.expandOnHover && isHovering.value
        ? Number(props.width)
        : Number(props.rail ? props.railWidth : props.width)
    })

    const location = computed(() => {
      return props.location as 'left' | 'right' | 'bottom'
    })

    const isTemporary = computed(
      () => !props.permanent && (mobile.value || props.temporary)
    )
    const isSticky = computed(
      () => props.sticky && !isTemporary.value && location.value !== 'bottom'
    )

    if (props.expandOnHover && props.rail != null) {
      watch(isHovering, (val) => emit('update:rail', !val))
    }

    if (!props.disableResizeWatcher) {
      watch(
        isTemporary,
        (val) => !props.permanent && nextTick(() => (isActive.value = !val))
      )
    }

    if (!props.disableRouteWatcher && router) {
      watch(
        router.currentRoute,
        () => isTemporary.value && (isActive.value = false)
      )
    }

    watch(
      () => props.permanent,
      (val) => {
        if (val) isActive.value = true
      }
    )

    onBeforeMount(() => {
      if (props.modelValue != null || isTemporary.value) return

      isActive.value = props.permanent || !mobile.value
    })

    const { isDragging, dragProgress, dragStyles } = useTouch({
      isActive,
      isTemporary,
      width,
      touchless: toRef(props, 'touchless'),
      position: location,
    })

    const layoutSize = computed(() => {
      const size = isTemporary.value
        ? 0
        : props.rail && props.expandOnHover
        ? Number(props.railWidth)
        : width.value

      return isDragging.value ? size * dragProgress.value : size
    })

    const { layoutItemStyles, layoutItemScrimStyles } = useLayoutItem({
      id: props.name,
      order: computed(() => parseInt(props.order, 10)),
      position: location,
      layoutSize,
      elementSize: width,
      active: computed(() => isActive.value || isDragging.value),
      disableTransitions: computed(() => isDragging.value),
      absolute: computed(
        () =>
          // eslint-disable-next-line @typescript-eslint/no-use-before-define
          props.absolute ||
          (isSticky.value && typeof isStuck.value !== 'string')
      ),
    })

    const { isStuck, stickyStyles } = useSticky({
      rootEl,
      isSticky,
      layoutItemStyles,
    })

    const scrimStyles = computed(() => ({
      ...(isDragging.value
        ? {
            opacity: dragProgress.value * 0.2,
            transition: 'none',
          }
        : undefined),
      ...layoutItemScrimStyles.value,
    }))

    provideDefaults({
      VList: {
        bgColor: 'transparent',
      },
    })

    function onMouseenter() {
      isHovering.value = true
    }
    function onMouseleave() {
      isHovering.value = false
    }

    const flexContainerClasses = computed(() => {
      let classes = ''
      if (props.location === 'top') {
        classes = 'flex justify-between items-center h-fit'
      } else {
        classes = 'flex flex-col justify-between h-full'
      }
      return classes
    })

    const borderClasses = computed(() => {
      let border = ''
      if (!props.border) {
        return null
      } else if (
        props.border &&
        props.location === 'left' &&
        props.theme === 'light'
      ) {
        border = `border-gray-200 border-r-1`
      } else if (
        props.border &&
        props.location === 'right' &&
        props.theme === 'light'
      ) {
        border = `border-gray-200 border-l-1`
      } else if (
        props.border &&
        props.location === 'top' &&
        props.theme === 'light'
      ) {
        border = `border-gray-200 border-b-1`
      }
      return border
    })

    const elevationClasses = computed(() => {
      let shadow = ''
      if (!props.elevation) {
        return null
      } else if (props.elevation && props.temporary) {
        shadow = `shadow-xl shadow-gray-900`
      }
      return shadow
    })

    useRender(() => {
      return (
        <>
          <props.tag
            ref={rootEl}
            onMouseenter={onMouseenter}
            onMouseleave={onMouseleave}
            class={[
              `sidebar-drawer transition-all duration-${props.transitionDuration} ease-out`,
              `sidebar-drawer--${location.value}`,
              elevationClasses.value,
              {
                'sidebar--expand-on-hover': props.expandOnHover,
                'sidebar--floating': props.floating,
                'sidebar--is-hovering': isHovering.value,
                'sidebar--rail': props.rail,
                'sidebar--temporary': isTemporary.value,
                'sidebar--active': isActive.value,
                'sidebar--sticky': isSticky.value,
              },
              `bg-${bgColor.value}`,
              props.class,
              props.location === 'top' ? 'h-fit' : 'h-full',
            ]}
            style={[
              layoutItemStyles.value,
              dragStyles.value,
              stickyStyles.value,
              props.style,
            ]}
            {...scopeId}
            {...attrs}
          >
            {slots.prepend && (
              <div class="sidebar-drawer__prepend">{slots.prepend?.()}</div>
            )}

            <div
              class={[
                flexContainerClasses.value,
                'sidebar-drawer__content',
                borderClasses.value,
                //elevationClasses.value,
              ]}
            >
              {slots.default?.()}
            </div>

            {slots.append && (
              <div class="sidebar-drawer__append">{slots.append?.()}</div>
            )}
          </props.tag>

          <Transition name="fade" appear>
            {isTemporary.value &&
              (isDragging.value || isActive.value) &&
              !!props.scrim && (
                <div
                  class={['sidebar-drawer__scrim', `bg-gray-700`, `fade`]}
                  style={[scrimStyles.value]}
                  onClick={() => (isActive.value = false)}
                  {...scopeId}
                />
              )}
          </Transition>
        </>
      )
    })

    return {
      isStuck,
      isHovering,
      isActive,
    }
  },
})

export type USidebar = InstanceType<typeof USidebar>
