<template>
  <div class="relative w-full h-full overflow-visible">
    <div class="absolute inset-0 flex items-center justify-center overflow-visible">
      <div :style="faceStyle" class="relative box-border">
        <div
          v-if="mainImageSrc"
          class="absolute inset-0 flex items-center justify-center overflow-hidden pointer-events-none"
          :style="{ opacity: mainImageOpacity }"
        >
          <img
            v-if="!mainImageUseMask"
            :src="mainImageSrc"
            class="block max-w-none max-h-none"
            alt=""
          />
          <ImageBg v-else :src="mainImageSrc" :imageStyle="mainImageStyle" />
        </div>

        <svg
          :viewBox="`0 0 ${faceSize} ${faceSize}`"
          class="block w-full h-full overflow-visible"
          xmlns="http://www.w3.org/2000/svg"
        >
          <line
            v-for="tick in minorTicks"
            :key="tick.key"
            :x1="tick.x1"
            :y1="tick.y1"
            :x2="tick.x2"
            :y2="tick.y2"
            :stroke="tick.stroke"
            :stroke-width="tick.strokeWidth"
            :stroke-linecap="tick.linecap"
          />
          <line
            v-for="tick in majorTicks"
            :key="tick.key"
            :x1="tick.x1"
            :y1="tick.y1"
            :x2="tick.x2"
            :y2="tick.y2"
            :stroke="tick.stroke"
            :stroke-width="tick.strokeWidth"
            :stroke-linecap="tick.linecap"
          />
          <text
            v-for="label in labels"
            :key="label.key"
            :x="label.x"
            :y="label.y"
            text-anchor="middle"
            dominant-baseline="middle"
            :fill="label.fill"
            :font-size="label.fontSize"
            :font-family="label.fontFamily"
            :letter-spacing="label.letterSpacing"
            :text-decoration="label.decoration"
          >
            {{ label.text }}
          </text>
          <line
            v-for="hand in lineHands"
            :key="hand.key"
            :x1="hand.x1"
            :y1="hand.y1"
            :x2="hand.x2"
            :y2="hand.y2"
            :stroke="hand.stroke"
            :stroke-width="hand.strokeWidth"
            stroke-linecap="round"
          />
        </svg>

        <div
          v-for="hand in imageHands"
          :key="hand.key"
          class="absolute pointer-events-none"
          :style="hand.style"
        >
          <img
            v-if="!hand.useMask"
            :src="hand.src"
            class="block w-full h-full"
            :style="{ opacity: hand.opacity }"
            alt=""
          />
          <ImageBg v-else :src="hand.src" :imageStyle="hand.imageStyle" />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, type CSSProperties } from 'vue'
import { useProjectStore } from '@/store/modules/project'
import { useWidgetStyle } from '../hooks/useWidgetStyle'
import ImageBg from '../ImageBg.vue'

type PointerType = 'image' | 'line'

type PointerImage = {
  image: string
  alpha: number
  recolor: string
  size: {
    width: number
    height: number
  }
  center: {
    x: number
    y: number
  }
}

type PointerLine = {
  color: string
  size: {
    width: number
    height: number
  }
}

type HandConfig = {
  time: number
  type: PointerType
  image: PointerImage
  line: PointerLine
}

type TickLine = {
  key: string
  x1: number
  y1: number
  x2: number
  y2: number
  stroke: string
  strokeWidth: number
  linecap: 'round' | 'butt'
}

type LineHand = {
  key: string
  x1: number
  y1: number
  x2: number
  y2: number
  stroke: string
  strokeWidth: number
}

type ImageHand = {
  key: string
  src: string
  useMask: boolean
  opacity: number
  imageStyle: CSSProperties
  style: CSSProperties
}

const props = defineProps<{
  width: number
  height: number
  styles: any[]
  part?: string
  state?: string
  enableText: boolean
  totalTicks: number
  majorTickInterval: number
  hourHand: HandConfig
  minuteHand: HandConfig
  secondHand: HandConfig
}>()

const projectStore = useProjectStore()

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

const getStyleConfig = (partName: string) => {
  const targetState = props.part === partName ? props.state : 'default'

  return (
    props.styles?.find(
      (item) => item.part?.name === partName && item.part?.state === targetState
    ) ||
    props.styles?.find((item) => item.part?.name === partName && item.part?.state === 'default')
  )
}

const indicatorStyleConfig = computed(() => getStyleConfig('indicator'))
const itemsStyleConfig = computed(() => getStyleConfig('items'))

const faceSize = computed(() => Math.max(0, Math.min(props.width || 0, props.height || 0)))
const center = computed(() => faceSize.value / 2)
const radius = computed(() => faceSize.value / 2)
const lineWidthLimit = computed(() => Math.max(0, radius.value))

const minorTickLength = computed(() =>
  Math.max(0, Number(itemsStyleConfig.value?.other?.length ?? 6))
)
const majorTickLength = computed(() =>
  Math.max(0, Number(indicatorStyleConfig.value?.other?.length ?? 10))
)

const minorTickWidth = computed(() =>
  Math.max(0, Math.min(Number(styleMap.value?.itemsStyle?.line?.width ?? 2), lineWidthLimit.value))
)
const majorTickWidth = computed(() =>
  Math.max(
    0,
    Math.min(Number(styleMap.value?.indicatorStyle?.line?.width ?? 2), lineWidthLimit.value)
  )
)

const minorTickColor = computed(() =>
  String(styleMap.value?.itemsStyle?.line?.color ?? '#212121FF')
)
const majorTickColor = computed(() =>
  String(styleMap.value?.indicatorStyle?.line?.color ?? '#212121FF')
)
const minorTickLinecap = computed(() =>
  styleMap.value?.itemsStyle?.line?.radius ? ('round' as const) : ('butt' as const)
)
const majorTickLinecap = computed(() =>
  styleMap.value?.indicatorStyle?.line?.radius ? ('round' as const) : ('butt' as const)
)

const labelColor = computed(() => String(styleMap.value?.indicatorStyle?.color ?? '#212121FF'))
const labelFontSize = computed(() =>
  Number.parseFloat(String(styleMap.value?.indicatorStyle?.fontSize ?? 14))
)
const labelFontFamily = computed(() =>
  String(styleMap.value?.indicatorStyle?.fontFamily ?? 'sans-serif')
)
const labelLetterSpacing = computed(() =>
  String(styleMap.value?.indicatorStyle?.letterSpacing ?? '0px')
)
const labelDecoration = computed(() =>
  String(styleMap.value?.indicatorStyle?.textDecoration ?? 'none')
)

const uniqueTickCount = computed(() => {
  const total = Math.max(0, Math.floor(Number(props.totalTicks) || 0))
  if (total <= 1) return total
  return total - 1
})

const normalizedMajorTickInterval = computed(() =>
  Math.max(0, Math.floor(Number(props.majorTickInterval) || 0))
)

const hasIncompleteLeadingMajor = computed(() => {
  if (normalizedMajorTickInterval.value <= 0) return 0
  return uniqueTickCount.value % normalizedMajorTickInterval.value
})

const tickStepAngle = computed(() => {
  if (uniqueTickCount.value <= 0) return 0
  return 360 / uniqueTickCount.value
})

const outerTickRadius = computed(() => {
  const maxLineWidth = Math.max(minorTickWidth.value, majorTickWidth.value)
  return Math.max(0, radius.value - maxLineWidth / 2 - 2)
})

const polarPoint = (distance: number, angleDeg: number) => {
  const rad = (angleDeg * Math.PI) / 180
  return {
    x: center.value + Math.cos(rad) * distance,
    y: center.value + Math.sin(rad) * distance
  }
}

const tickMeta = computed(() => {
  const list: { index: number; angle: number; isMajor: boolean }[] = []
  for (let index = 0; index < uniqueTickCount.value; index++) {
    list.push({
      index,
      angle: -90 + tickStepAngle.value * index,
      isMajor:
        normalizedMajorTickInterval.value > 0 &&
        index % normalizedMajorTickInterval.value === 0 &&
        !(hasIncompleteLeadingMajor.value > 0 && index === 0)
    })
  }
  return list
})

const createTickLine = (
  index: number,
  angle: number,
  length: number,
  width: number,
  stroke: string,
  linecap: 'round' | 'butt'
): TickLine => {
  const start = polarPoint(outerTickRadius.value, angle)
  const end = polarPoint(Math.max(0, outerTickRadius.value - length), angle)

  return {
    key: `tick-${index}`,
    x1: start.x,
    y1: start.y,
    x2: end.x,
    y2: end.y,
    stroke,
    strokeWidth: width,
    linecap
  }
}

const majorTicks = computed(() => {
  if (majorTickWidth.value <= 0 || majorTickLength.value <= 0) return []

  return tickMeta.value
    .filter((item) => item.isMajor)
    .map((item) =>
      createTickLine(
        item.index,
        item.angle,
        majorTickLength.value,
        majorTickWidth.value,
        majorTickColor.value,
        majorTickLinecap.value
      )
    )
})

const minorTicks = computed(() => {
  if (minorTickWidth.value <= 0 || minorTickLength.value <= 0) return []

  return tickMeta.value
    .filter((item) => !item.isMajor)
    .map((item) =>
      createTickLine(
        item.index,
        item.angle,
        minorTickLength.value,
        minorTickWidth.value,
        minorTickColor.value,
        minorTickLinecap.value
      )
    )
})

const labels = computed(() => {
  if (!props.enableText || normalizedMajorTickInterval.value <= 0) return []

  const labelRadius = Math.max(
    0,
    outerTickRadius.value - majorTickLength.value - labelFontSize.value * 0.9 - 0
  )

  const majorLabelTicks = tickMeta.value.filter((item) => item.isMajor)
  const labelCount = majorLabelTicks.length

  if (labelCount <= 0) return []

  return majorLabelTicks.map((item, index) => {
      const point = polarPoint(labelRadius, item.angle)
      const text =
        hasIncompleteLeadingMajor.value > 0
          ? String(index + 1)
          : index === 0
            ? String(labelCount)
            : String(index)

      return {
        key: `label-${item.index}`,
        x: point.x,
      y: point.y,
      text,
      fill: labelColor.value,
      fontSize: labelFontSize.value,
      fontFamily: labelFontFamily.value,
      letterSpacing: labelLetterSpacing.value,
      decoration: labelDecoration.value
    }
  })
})

const isTransparentColor = (color?: string) => {
  if (!color) return true
  const normalized = color.trim().toLowerCase()
  return normalized === '#00000000' || normalized.endsWith('00')
}

const getImageSrc = (imageId?: string) => {
  if (!imageId) return ''
  const image = projectStore.project?.resources.images.find((item) => item.id === imageId)
  if (!image) return ''
  return `local:///${(projectStore.projectPath + image.path).replaceAll('\\', '/')}`
}

const mainImageSrc = computed(() => String(styleMap.value?.mainStyle?.imageSrc ?? ''))
const mainImageStyle = computed(
  () =>
    ({
      backgroundColor: styleMap.value?.mainStyle?.imageStyle?.backgroundColor,
      opacity: styleMap.value?.mainStyle?.imageStyle?.opacity
    }) as CSSProperties
)
const mainImageOpacity = computed(() => Number(mainImageStyle.value.opacity ?? 1))
const mainImageUseMask = computed(() => {
  return !isTransparentColor(String(mainImageStyle.value.backgroundColor ?? ''))
})

const faceStyle = computed(() => {
  const mainStyle = styleMap.value?.mainStyle || {}
  const {
    imageSrc: _imageSrc,
    imageStyle: _imageStyle,
    line: _line,
    curve: _curve,
    ...style
  } = mainStyle

  return {
    ...style,
    width: `${faceSize.value}px`,
    height: `${faceSize.value}px`,
    borderRadius: '50%',
    overflow: 'hidden'
  }
})

const getHourAngle = (value: number) => ((value % 12) / 12) * 360 - 90
const getMinuteSecondAngle = (value: number) => ((value % 60) / 60) * 360 - 90
const getHandTime = (hand: HandConfig) =>
  hand.time ?? (hand.type === 'image' ? (hand as any)?.image?.time : (hand as any)?.line?.time) ?? 0

const createLineHand = (key: string, hand: HandConfig, angle: number) => {
  if (hand.type !== 'line') return null

  const strokeWidth = Math.max(
    0,
    Math.min(Number(hand.line?.size?.width ?? 0), lineWidthLimit.value)
  )
  const length = Math.max(0, Number(hand.line?.size?.height ?? 0))
  const point = polarPoint(length, angle)

  return {
    key,
    x1: center.value,
    y1: center.value,
    x2: point.x,
    y2: point.y,
    stroke: String(hand.line?.color ?? '#212121FF'),
    strokeWidth
  }
}

const isLineHand = (hand: LineHand | null): hand is LineHand => !!hand

const lineHands = computed(() => {
  const result = [
    createLineHand('hour-line', props.hourHand, getHourAngle(Number(getHandTime(props.hourHand)))),
    createLineHand(
      'minute-line',
      props.minuteHand,
      getMinuteSecondAngle(Number(getHandTime(props.minuteHand)))
    ),
    createLineHand(
      'second-line',
      props.secondHand,
      getMinuteSecondAngle(Number(getHandTime(props.secondHand)))
    )
  ]

  return result.filter(isLineHand)
})

const createImageHand = (key: string, hand: HandConfig, angle: number) => {
  if (hand.type !== 'image') return null

  const src = getImageSrc(hand.image?.image)
  if (!src) return null

  const width = Math.max(1, Number(hand.image?.size?.width ?? 1))
  const height = Math.max(1, Number(hand.image?.size?.height ?? 1))
  const centerX = Number(hand.image?.center?.x ?? 0)
  const centerY = Number(hand.image?.center?.y ?? 0)
  const recolor = String(hand.image?.recolor ?? '#00000000')
  const opacity = Math.max(0, Math.min(1, Number(hand.image?.alpha ?? 255) / 255))

  return {
    key,
    src,
    useMask: !isTransparentColor(recolor),
    opacity,
    imageStyle: {
      backgroundColor: recolor,
      opacity
    } as CSSProperties,
    style: {
      left: `${center.value - centerX}px`,
      top: `${center.value - centerY}px`,
      width: `${width}px`,
      height: `${height}px`,
      transform: `rotate(${angle}deg)`,
      transformOrigin: `${centerX}px ${centerY}px`
    } as CSSProperties
  }
}

const isImageHand = (hand: ImageHand | null): hand is ImageHand => !!hand

const imageHands = computed(() => {
  const result = [
    createImageHand(
      'hour-image',
      props.hourHand,
      getHourAngle(Number(getHandTime(props.hourHand)))
    ),
    createImageHand(
      'minute-image',
      props.minuteHand,
      getMinuteSecondAngle(Number(getHandTime(props.minuteHand)))
    ),
    createImageHand(
      'second-image',
      props.secondHand,
      getMinuteSecondAngle(Number(getHandTime(props.secondHand)))
    )
  ]

  return result.filter(isImageHand)
})
</script>
