<template>
  <div
    class="relative w-full h-full box-border overflow-hidden lvgl-roller"
    :style="styleMap?.mainStyle"
  >
    <ImageBg
      v-if="styleMap?.mainStyle?.imageSrc"
      :src="styleMap.mainStyle.imageSrc"
      :imageStyle="styleMap.mainStyle.imageStyle"
    />

    <div class="absolute left-0 right-0 pointer-events-none" :style="indicatorInlineStyle">
      <div class="w-full h-full" :style="styleMap?.selectedStyle">
        <ImageBg
          v-if="styleMap?.selectedStyle?.imageSrc"
          :src="styleMap.selectedStyle.imageSrc"
          :imageStyle="styleMap.selectedStyle.imageStyle"
        />
      </div>
    </div>

    <div class="absolute left-0 right-0 top-0 bottom-0">
      <div
        class="w-full flex flex-col items-stretch"
        :style="{
          transform: `translateY(${offsetY}px)`,
          transition: 'transform 200ms ease-out'
        }"
      >
        <div
          v-for="(item, index) in displayOptions"
          :key="`${index}-${item.raw}`"
          class="flex h-40px items-center justify-center box-border"
        >
          <span
            class="truncate w-full text-center"
            :style="getOptionTextStyle(item, index === effectiveSelectedIndex)"
          >
            {{ item.text }}
          </span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, type CSSProperties } from 'vue'
import { useWidgetStyle } from '../hooks/useWidgetStyle'
import ImageBg from '../ImageBg.vue'
import { useLanguage } from '../hooks/useLanguage'

const ITEM_HEIGHT = 40

const props = defineProps<{
  width: number
  height: number
  styles: any[]
  state?: string
  part?: string
  options: string[]
  visibleRowCount?: number
  selected: number
  direction: 'infinite' | 'normal'
}>()
const { resolveText, getResolvedFontStyle } = useLanguage()

const styleMap = useWidgetStyle({
  widget: 'lv_roller',
  props
})

const normalizedOptions = computed(() => {
  const options = Array.isArray(props.options) && props.options.length ? props.options : ['']

  return options.map((raw) => ({
    raw,
    text: resolveText(raw).text,
    style: getResolvedFontStyle(raw)
  }))
})

const infiniteSideCopies = computed(() => {
  const optionCount = normalizedOptions.value.length
  if (props.direction !== 'infinite' || optionCount <= 1) {
    return 0
  }

  const visibleCount = Math.max(1, Math.ceil((props.height || ITEM_HEIGHT) / ITEM_HEIGHT))
  return Math.max(1, Math.ceil(visibleCount / optionCount) + 1)
})

const displayOptions = computed(() => {
  const opts = normalizedOptions.value
  if (props.direction !== 'infinite' || opts.length <= 1) {
    return opts
  }

  return Array.from({ length: infiniteSideCopies.value * 2 + 1 }, () => opts).flat()
})

const effectiveSelectedIndex = computed(() => {
  const optionCount = normalizedOptions.value.length
  const selected = props.selected ?? 0

  if (!optionCount) {
    return 0
  }

  if (props.direction !== 'infinite' || optionCount === 1) {
    return Math.max(0, Math.min(selected, optionCount - 1))
  }

  const normalized = ((selected % optionCount) + optionCount) % optionCount
  return infiniteSideCopies.value * optionCount + normalized
})

const centerTop = computed(() => {
  return (props.height || ITEM_HEIGHT) / 2 - ITEM_HEIGHT / 2
})

const selectedTextStyle = computed<CSSProperties>(() => {
  const selectedStyle = styleMap.value?.selectedStyle
  if (!selectedStyle) {
    return {}
  }

  return {
    color: selectedStyle.color,
    fontSize: selectedStyle.fontSize,
    fontFamily: selectedStyle.fontFamily,
    fontStyle: selectedStyle.fontStyle,
    fontWeight: selectedStyle.fontWeight,
    textAlign: selectedStyle.textAlign,
    textDecoration: selectedStyle.textDecoration,
    letterSpacing: selectedStyle.letterSpacing,
    lineHeight: selectedStyle.lineHeight
  }
})

const getOptionTextStyle = (
  item: { raw: string; text: string; style: CSSProperties },
  isSelected: boolean
) => {
  return isSelected ? { ...selectedTextStyle.value, ...item.style } : item.style
}

const offsetY = computed(() => {
  return centerTop.value - effectiveSelectedIndex.value * ITEM_HEIGHT
})

const indicatorInlineStyle = computed(() => {
  return {
    top: `${centerTop.value}px`,
    height: `${ITEM_HEIGHT}px`
  }
})
</script>

<style scoped>
.lvgl-roller {
  user-select: none;
}
</style>
