<template>
  <div :style="styleMap?.mainStyle" class="relative w-full h-full box-border overflow-visible">
    <div
      v-if="mainBackgroundImageSrc"
      class="absolute pointer-events-none flex items-center justify-center overflow-hidden"
      :style="backgroundImageWrapStyle"
    >
      <ImageBg
        v-if="mainBackgroundUseMask"
        :src="mainBackgroundImageSrc"
        :imageStyle="mainBackgroundMaskStyle"
      />
      <img
        v-else
        :src="mainBackgroundImageSrc"
        alt=""
        class="block max-w-none max-h-none"
        :style="{ opacity: mainBackgroundOpacity }"
      />
    </div>

    <svg
      :viewBox="`0 0 ${width} ${height}`"
      class="block w-full h-full overflow-visible"
      xmlns="http://www.w3.org/2000/svg"
    >
      <defs>
        <pattern
          v-if="mainCurveImageSrc"
          :id="mainCurvePatternId"
          patternUnits="userSpaceOnUse"
          :x="curvePatternBox.x"
          :y="curvePatternBox.y"
          :width="curvePatternBox.size"
          :height="curvePatternBox.size"
        >
          <image
            :href="mainCurveImageSrc"
            :x="curvePatternBox.x"
            :y="curvePatternBox.y"
            :width="curvePatternBox.size"
            :height="curvePatternBox.size"
            preserveAspectRatio="xMidYMid meet"
            :opacity="mainCurveOpacity"
          />
        </pattern>
      </defs>

      <path
        v-if="mainCurveWidth > 0 && mainArcPath"
        :d="mainArcPath"
        fill="none"
        :stroke="mainCurveStroke"
        :stroke-width="mainCurveWidth"
        :stroke-linecap="mainCurveLinecap"
      />

      <path
        v-for="section in sectionCurves"
        :key="section.key"
        :d="section.path"
        fill="none"
        :stroke="section.stroke"
        :stroke-width="section.strokeWidth"
        :stroke-linecap="mainCurveLinecap"
      />

      <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 labelItems"
        :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="needle in lineNeedles"
        :key="needle.key"
        :x1="needle.x1"
        :y1="needle.y1"
        :x2="needle.x2"
        :y2="needle.y2"
        :stroke="needle.stroke"
        :stroke-width="needle.strokeWidth"
        :stroke-linecap="needle.linecap"
      />
    </svg>

    <div
      v-for="needle in imageNeedles"
      :key="needle.key"
      class="absolute pointer-events-none"
      :style="needle.style"
    >
      <img
        v-if="!needle.useMask"
        :src="needle.src"
        class="block w-full h-full"
        :style="{ opacity: needle.opacity }"
        alt=""
      />
      <ImageBg v-else :src="needle.src" :imageStyle="needle.imageStyle" />
    </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'
import type { BaseMeterNeedle, BaseMeterSection } from './types'

const props = defineProps<{
  id?: string
  width: number
  height: number
  styles: any[]
  state?: string
  part?: string
  mode: 'round_inner' | 'round_outer'
  tick: number
  mainTick: number
  rangeStart: number
  rangeEnd: number
  angleRange: number
  rotationAngle: number
  enableText: boolean
  labels: string
  needles: BaseMeterNeedle[]
  sections: BaseMeterSection[]
}>()

const projectStore = useProjectStore()

const styleMap = useWidgetStyle({
  widget: 'base_meter',
  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 clamp = (value: number, min: number, max: number) => {
  return Math.min(max, Math.max(min, 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 width = computed(() => Math.max(1, Number(props.width) || 1))
const height = computed(() => Math.max(1, Number(props.height) || 1))
const minSide = computed(() => Math.min(width.value, height.value))
const center = computed(() => ({
  x: width.value / 2,
  y: height.value / 2
}))
const halfMin = computed(() => minSide.value / 2)
const maxLineWidth = computed(() => Math.max(0, halfMin.value))

const mainCurveWidth = computed(() =>
  clamp(Number(styleMap.value?.mainStyle?.curve?.width ?? 2), 0, maxLineWidth.value)
)
const mainCurveColor = computed(() =>
  String(styleMap.value?.mainStyle?.curve?.color ?? '#212121FF')
)
const mainCurveLinecap = computed(() =>
  styleMap.value?.mainStyle?.curve?.radius ? ('round' as const) : ('butt' as const)
)
const mainCurveImageSrc = computed(() => String(styleMap.value?.mainStyle?.curve?.imageSrc ?? ''))
const mainCurveOpacity = computed(() => Number(styleMap.value?.mainStyle?.curve?.opacity ?? 1))
const mainCurvePatternId = computed(() => `base-meter-curve-${props.id || 'default'}`)

const mainTextColor = computed(() => String(styleMap.value?.mainStyle?.color ?? '#212121FF'))
const mainFontSize = computed(() =>
  Number.parseFloat(String(styleMap.value?.mainStyle?.fontSize ?? '14'))
)
const mainFontFamily = computed(() => String(styleMap.value?.mainStyle?.fontFamily ?? 'sans-serif'))
const mainLetterSpacing = computed(() => String(styleMap.value?.mainStyle?.letterSpacing ?? '0px'))
const mainTextDecoration = computed(() =>
  String(styleMap.value?.mainStyle?.textDecoration ?? 'none')
)

const mainBackgroundImageSrc = computed(() => String(styleMap.value?.mainStyle?.imageSrc ?? ''))
const mainBackgroundMaskStyle = computed(
  () =>
    ({
      backgroundColor: styleMap.value?.mainStyle?.imageStyle?.backgroundColor,
      opacity: styleMap.value?.mainStyle?.imageStyle?.opacity
    }) as CSSProperties
)
const mainBackgroundOpacity = computed(() => Number(mainBackgroundMaskStyle.value.opacity ?? 1))
const mainBackgroundUseMask = computed(() => {
  return !isTransparentColor(String(mainBackgroundMaskStyle.value.backgroundColor ?? ''))
})
const backgroundImageWrapStyle = computed(() => {
  return {
    left: '-100px',
    top: '-100px',
    width: `${width.value + 200}px`,
    height: `${height.value + 200}px`
  }
})

const indicatorLength = computed(() =>
  Math.max(0, Number(indicatorStyleConfig.value?.other?.length ?? 10))
)
const itemsLength = computed(() => Math.max(0, Number(itemsStyleConfig.value?.other?.length ?? 5)))
const indicatorLineWidth = computed(() =>
  clamp(Number(styleMap.value?.indicatorStyle?.line?.width ?? 2), 0, maxLineWidth.value)
)
const itemsLineWidth = computed(() =>
  clamp(Number(styleMap.value?.itemsStyle?.line?.width ?? 2), 0, maxLineWidth.value)
)
const indicatorLineColor = computed(() =>
  String(styleMap.value?.indicatorStyle?.line?.color ?? '#212121FF')
)
const itemsLineColor = computed(() =>
  String(styleMap.value?.itemsStyle?.line?.color ?? '#212121FF')
)
const indicatorLinecap = computed(() =>
  styleMap.value?.indicatorStyle?.line?.radius ? ('round' as const) : ('butt' as const)
)
const itemsLinecap = computed(() =>
  styleMap.value?.itemsStyle?.line?.radius ? ('round' as const) : ('butt' as const)
)

const outerReserve = computed(() => {
  if (props.mode === 'round_outer') {
    return 0
  }
  return 8
})

const curveRadius = computed(() => {
  const radius = halfMin.value - outerReserve.value - mainCurveWidth.value / 2
  return Math.max(0, radius)
})

const curvePatternBox = computed(() => {
  return {
    x: center.value.x - minSide.value / 2,
    y: center.value.y - minSide.value / 2,
    size: minSide.value
  }
})

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

const describeArc = (radius: number, startAngle: number, sweepAngle: number) => {
  if (radius <= 0 || sweepAngle <= 0) return ''

  let normalizedSweep = sweepAngle % 360
  if (normalizedSweep === 0) {
    normalizedSweep = 359.999
  }
  if (normalizedSweep < 0) {
    normalizedSweep += 360
  }

  const start = polarPoint(radius, startAngle)
  const end = polarPoint(radius, startAngle + normalizedSweep)
  const largeArcFlag = normalizedSweep > 180 ? '1' : '0'

  return ['M', start.x, start.y, 'A', radius, radius, 0, largeArcFlag, 1, end.x, end.y].join(' ')
}

const mainArcPath = computed(() =>
  describeArc(
    curveRadius.value,
    Number(props.rotationAngle) || 0,
    clamp(Number(props.angleRange) || 0, 0, 360)
  )
)
const mainCurveStroke = computed(() => {
  return mainCurveImageSrc.value ? `url(#${mainCurvePatternId.value})` : mainCurveColor.value
})

const normalizedSections = computed(() => {
  return (props.sections || []).map((section) => {
    const start = Math.min(Number(section.start) || 0, Number(section.end) || 0)
    const end = Math.max(Number(section.start) || 0, Number(section.end) || 0)

    return {
      ...section,
      start,
      end
    }
  })
})

const rangeMin = computed(() =>
  Math.min(Number(props.rangeStart) || 0, Number(props.rangeEnd) || 0)
)
const rangeMax = computed(() =>
  Math.max(Number(props.rangeStart) || 0, Number(props.rangeEnd) || 0)
)
const rangeSpan = computed(() => (Number(props.rangeEnd) || 0) - (Number(props.rangeStart) || 0))

const normalizeValue = (value: number) => {
  if (rangeSpan.value === 0) return 0
  return clamp((value - (Number(props.rangeStart) || 0)) / rangeSpan.value, 0, 1)
}

const valueAt = (t: number) => {
  return (Number(props.rangeStart) || 0) + rangeSpan.value * t
}

const angleAt = (t: number) => {
  return (Number(props.rotationAngle) || 0) + clamp(Number(props.angleRange) || 0, 0, 360) * t
}

const valueToAngle = (value: number) => angleAt(normalizeValue(value))

const getSectionForValue = (value: number) => {
  return normalizedSections.value.find((section) => value >= section.start && value <= section.end)
}

const sectionCurves = computed(() => {
  return normalizedSections.value
    .map((section, index) => {
      const startValue = clamp(section.start, rangeMin.value, rangeMax.value)
      const endValue = clamp(section.end, rangeMin.value, rangeMax.value)
      const startT = normalizeValue(startValue)
      const endT = normalizeValue(endValue)
      const pathStart = Math.min(startT, endT)
      const pathEnd = Math.max(startT, endT)
      const startAngle = angleAt(pathStart)
      const path = describeArc(
        curveRadius.value,
        startAngle,
        clamp(Number(props.angleRange) || 0, 0, 360) * (pathEnd - pathStart)
      )
      const strokeWidth = clamp(Number(section.curve.width) || 0, 0, 100)

      return {
        key: `section-curve-${index}`,
        path,
        stroke: String(section.curve.color || '#FF00FFFF'),
        strokeWidth
      }
    })
    .filter((section) => section.path && section.strokeWidth > 0)
})

const tickCount = computed(() => Math.max(2, Math.min(1000, Math.floor(Number(props.tick) || 41))))
const majorTickGap = computed(() =>
  Math.max(1, Math.min(tickCount.value - 1, Math.floor(Number(props.mainTick) || 8)))
)

const majorIndices = computed(() => {
  const set = new Set<number>()
  const lastIndex = tickCount.value - 1

  for (let index = 0; index <= lastIndex; index += majorTickGap.value) {
    set.add(index)
  }

  return set
})

const tickPositions = computed(() => {
  const list: { index: number; value: number; angle: number; isMajor: boolean }[] = []
  const lastIndex = tickCount.value - 1

  for (let index = 0; index < tickCount.value; index++) {
    const t = lastIndex <= 0 ? 0 : index / lastIndex
    list.push({
      index,
      value: valueAt(t),
      angle: angleAt(t),
      isMajor: majorIndices.value.has(index)
    })
  }

  return list
})

const createTickLine = (
  prefix: string,
  index: number,
  angle: number,
  length: number,
  widthValue: number,
  color: string,
  linecap: 'round' | 'butt'
) => {
  const tickStartRadius =
    props.mode === 'round_outer'
      ? curveRadius.value + mainCurveWidth.value / 2
      : curveRadius.value - mainCurveWidth.value / 2
  const tickEndRadius =
    props.mode === 'round_outer' ? tickStartRadius + length : tickStartRadius - length
  const start = polarPoint(Math.max(0, tickStartRadius), angle)
  const end = polarPoint(Math.max(0, tickEndRadius), angle)

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

const majorTicks = computed(() => {
  return tickPositions.value
    .filter((tick) => tick.isMajor)
    .map((tick) => {
      const section = getSectionForValue(tick.value)
      const color = String(section?.line?.color || indicatorLineColor.value)
      const strokeWidth = clamp(
        Number(section?.line?.width ?? indicatorLineWidth.value),
        0,
        maxLineWidth.value
      )

      return createTickLine(
        'major-tick',
        tick.index,
        tick.angle,
        indicatorLength.value,
        strokeWidth,
        color,
        indicatorLinecap.value
      )
    })
    .filter((tick) => tick.strokeWidth > 0)
})

const minorTicks = computed(() => {
  return tickPositions.value
    .filter((tick) => !tick.isMajor)
    .map((tick) => {
      const section = getSectionForValue(tick.value)
      const color = String(section?.line?.color || itemsLineColor.value)
      const strokeWidth = clamp(
        Number(section?.line?.width ?? itemsLineWidth.value),
        0,
        maxLineWidth.value
      )

      return createTickLine(
        'minor-tick',
        tick.index,
        tick.angle,
        itemsLength.value,
        strokeWidth,
        color,
        itemsLinecap.value
      )
    })
    .filter((tick) => tick.strokeWidth > 0)
})

const formatLabelValue = (value: number) => {
  return String(Math.round(value))
}

const labelTexts = computed(() => {
  const raw = (props.labels || '').trim()
  if (raw) {
    return raw
      .split(',')
      .map((item) => item.trim())
      .filter(Boolean)
  }

  return tickPositions.value
    .filter((tick) => tick.isMajor)
    .map((tick) => formatLabelValue(tick.value))
})

const labelRadius = computed(() => {
  if (props.mode === 'round_outer') {
    return (
      curveRadius.value +
      mainCurveWidth.value / 2 +
      indicatorLength.value +
      mainFontSize.value / 2 +
      6
    )
  }

  return Math.max(
    0,
    curveRadius.value -
      mainCurveWidth.value / 2 -
      indicatorLength.value -
      mainFontSize.value / 2 -
      6
  )
})

const labelItems = computed(() => {
  if (!props.enableText) return []

  const majorTicksOnly = tickPositions.value.filter((tick) => tick.isMajor)

  return majorTicksOnly
    .map((tick, index) => {
      const text = labelTexts.value[index]
      if (!text) return null

      const point = polarPoint(labelRadius.value, tick.angle)
      const section = getSectionForValue(tick.value)

      return {
        key: `label-${tick.index}`,
        x: point.x,
        y: point.y,
        text,
        fill: String(section?.text || mainTextColor.value),
        fontSize: mainFontSize.value,
        fontFamily: mainFontFamily.value,
        letterSpacing: mainLetterSpacing.value,
        decoration: mainTextDecoration.value
      }
    })
    .filter((item): item is NonNullable<typeof item> => !!item)
})

const createLineNeedle = (needle: BaseMeterNeedle, index: number) => {
  if (needle.type !== 'line') return null

  const angle = valueToAngle(Number(needle.value) || 0)
  const point = polarPoint(Math.max(0, Number(needle.line.size.height) || 0), angle)
  const strokeWidth = clamp(Number(needle.line.size.width) || 0, 0, maxLineWidth.value)

  return {
    key: `line-needle-${index}`,
    x1: center.value.x,
    y1: center.value.y,
    x2: point.x,
    y2: point.y,
    stroke: String(needle.line.color || '#212121FF'),
    strokeWidth,
    linecap: needle.line.round ? ('round' as const) : ('butt' as const)
  }
}

const lineNeedles = computed(() => {
  return (props.needles || [])
    .map((needle, index) => createLineNeedle(needle, index))
    .filter((needle): needle is NonNullable<typeof needle> => !!needle && needle.strokeWidth > 0)
})

const createImageNeedle = (needle: BaseMeterNeedle, index: number) => {
  if (needle.type !== 'image' || !needle.image.image) return null
  const src = getImageSrc(needle.image.image)
  if (!src) return null

  const angle = valueToAngle(Number(needle.value) || 0)
  const widthValue = Math.max(1, Number(needle.image.size.width) || 1)
  const heightValue = Math.max(1, Number(needle.image.size.height) || 1)
  const centerX = Number(needle.image.center.x) || 0
  const centerY = Number(needle.image.center.y) || 0
  const recolor = String(needle.image.recolor || '#00000000')
  const opacity = clamp(Number(needle.image.alpha ?? 255), 0, 255) / 255

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

const imageNeedles = computed(() => {
  return (props.needles || [])
    .map((needle, index) => createImageNeedle(needle, index))
    .filter((needle): needle is NonNullable<typeof needle> => !!needle)
})
</script>
