<template>
  <div class="h-chart-configurable" :class="wrapperClassObject">
    <x-title v-if="chartTitle" v-bind="xTitleAttrs" :title="chartTitle" />
    <div class="chart-layout">
      <div v-if="showSideList" class="chart-side-list">
        <div v-if="sideListLabel" class="side-list-label">{{ sideListLabel }}</div>
        <div class="side-list-menu">
          <button
            v-for="option in sideListOptions"
            :key="option.value"
            type="button"
            class="side-list-item"
            :class="{ 'is-active': option.value === selectedSideListValue }"
            @click="handleListChange(option.value)"
          >
            <span class="item-label">{{ option.label }}</span>
            <span v-if="option.badge" class="item-badge">{{ option.badge }}</span>
          </button>
        </div>
      </div>
      <div class="chart-main">
        <div v-if="showCascadeList" class="chart-cascade-panel">
          <div v-if="sideListLabel" class="cascade-panel-label">{{ sideListLabel }}</div>
          <div class="cascade-columns">
            <div
              v-for="column in cascadeColumns"
              :key="column.field"
              class="cascade-column"
              :class="{ 'is-disabled': !column.options.length }"
            >
              <div class="cascade-column-header">
                <span class="column-title">{{ column.label }}</span>
                <span v-if="column.subtitle" class="column-subtitle">{{ column.subtitle }}</span>
              </div>
              <div class="cascade-option-list">
                <button
                  v-for="option in column.options"
                  :key="option.value"
                  type="button"
                  class="cascade-option"
                  :class="{ 'is-active': option.value === selectedCascadeValues[column.field] }"
                  :disabled="!column.options.length"
                  @click="handleCascadeChange(column.field, option.value)"
                >
                  <span class="option-label">{{ option.label }}</span>
                  <span v-if="option.badge" class="option-badge">{{ option.badge }}</span>
                </button>
              </div>
            </div>
          </div>
        </div>
        <div v-if="showRadioFilter" class="chart-toolbar">
          <span v-if="radioFilterLabel" class="toolbar-label">{{ radioFilterLabel }}</span>
          <div class="toolbar-radio-group">
            <label
              v-for="option in radioFilterOptions"
              :key="option.value"
              class="radio-pill"
              :class="{ 'is-active': option.value === selectedFilterValue }"
            >
              <input
                type="radio"
                class="radio-pill-input"
                :value="option.value"
                :checked="option.value === selectedFilterValue"
                @change="handleFilterChange(option.value)"
              />
              <span>{{ option.label }}</span>
            </label>
          </div>
        </div>
        <div ref="chartRef" class="chart-canvas" :style="chartStyle"></div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, watch, onMounted, computed, useAttrs, onBeforeUnmount, nextTick } from 'vue'
import * as echarts from 'echarts/core'
import { BarChart, LineChart, PieChart, CustomChart } from 'echarts/charts'
import { TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import { getConfigByName, runLogic } from '@vue2-client/services/api/common'
import XTitle from '../XTitle/XTitle.vue'
// 注册常用图表类型，避免每次重复导入
echarts.use([BarChart, LineChart, PieChart, CustomChart, TooltipComponent, LegendComponent, GridComponent, CanvasRenderer])

const props = defineProps({
  // 配置名称（用于查询配置）
  queryParamsName: {
    type: String,
    default: ''
  },
  // 服务名
  serviceName: {
    type: String,
    default: 'af-his'
  },
  // 固定查询参数
  fixedQueryForm: {
    type: Object,
    default: () => ({ condition: '1=1' })
  },
  config: {
    type: Object,
    default: null
  }
})

const emit = defineEmits(['dataLoaded', 'error', 'update:fixedQueryForm'])

const chartRef = ref(null)
let chartInstance = null
const chartConfig = ref(null)
const chartData = ref([])
const loading = ref(false)
let resizeObserver = null
const activePointIndex = ref(-1)
const activeBarIndex = ref(-1)
const selectedFilterValue = ref('')
const selectedSideListValue = ref('')
const selectedCascadeValues = ref({})

// 记录上一次的 queryParamsName，用于比较变化
const lastQueryParamsName = ref('')

const chartTitle = computed(() => {
  if (props.config?.xtitle) return props.config.xtitle
  if (chartConfig.value?.xtitle) return chartConfig.value.xtitle
  return ''
})
const xTitleAttrs = computed(() => {
  if (props.config?.xtitleAttrs) return props.config.xtitleAttrs
  if (chartConfig.value?.xtitleAttrs) return chartConfig.value.xtitleAttrs
  return {}
})
const chartStyle = computed(() => {
  const target = props.config ?? chartConfig.value ?? {}
  if (target.style && typeof target.style === 'object') return target.style
  return {}
})
const radioFilterConfig = computed(() => props.config?.radioFilter || chartConfig.value?.radioFilter || null)
const radioFilterOptions = computed(() => {
  const options = radioFilterConfig.value?.options
  return Array.isArray(options) ? options : []
})
const radioFilterLabel = computed(() => radioFilterConfig.value?.label || '')
const showRadioFilter = computed(() => radioFilterOptions.value.length > 0)
const filterFieldName = computed(() => radioFilterConfig.value?.field || radioFilterConfig.value?.prop || 'dimension')
const filterQueryParams = computed(() => {
  if (!showRadioFilter.value) return {}
  const optionValue = selectedFilterValue.value
  if (optionValue === undefined || optionValue === null || optionValue === '') return {}
  return { [filterFieldName.value]: optionValue }
})
const sideListConfig = computed(() => props.config?.sideList || chartConfig.value?.sideList || null)
const isCascadeMode = computed(() => sideListConfig.value?.mode === 'cascade')
const sideListOptions = ref([])

// 异步获取sideList options
const loadSideListOptions = async () => {
  const config = sideListConfig.value
  if (!config) {
    sideListOptions.value = []
    return
  }

  // 如果配置了sideListLogic，则通过runLogic获取options
  if (config.sideListLogic) {
    try {
      const result = await runLogic(config.sideListLogic, {
        config,
        chartConfig: chartConfig.value,
        props
      })
      sideListOptions.value = Array.isArray(result) ? result : []
    } catch (error) {
      console.error('获取sideList options失败:', error)
      sideListOptions.value = []
      emit('error', error)
    }
  } else {
    // 使用静态options
    const options = config.options
    sideListOptions.value = Array.isArray(options) ? options : []
  }
}
const sideListLabel = computed(() => sideListConfig.value?.label || sideListConfig.value?.title || '')
const cascadeLevels = computed(() => {
  if (!isCascadeMode.value) return []
  const levels = sideListConfig.value?.levels
  return Array.isArray(levels) ? levels : []
})
const pickFirstArrayFromObject = (source) => {
  if (!source || typeof source !== 'object') return []
  for (const value of Object.values(source)) {
    if (Array.isArray(value)) return value
  }
  return []
}
const normalizeOptionList = (options) => {
  if (!options) return []
  if (Array.isArray(options)) return options
  return []
}
const cascadeColumns = computed(() => {
  if (!isCascadeMode.value) return []
  const columns = []
  cascadeLevels.value.forEach((level, idx) => {
    const field = level?.field || level?.prop || `cascade-field-${idx}`
    const optionsSource = level?.options
    let scopedOptions = normalizeOptionList(optionsSource)
    if (!Array.isArray(optionsSource) && optionsSource && typeof optionsSource === 'object') {
      if (idx === 0) {
        const firstArray = optionsSource.default || optionsSource.list || pickFirstArrayFromObject(optionsSource)
        scopedOptions = normalizeOptionList(firstArray)
      } else {
        const parentField = columns[idx - 1]?.field
        const parentValue = parentField ? selectedCascadeValues.value[parentField] : undefined
        const mappedOptions = (parentValue && optionsSource[parentValue]) || optionsSource.default || pickFirstArrayFromObject(optionsSource)
        scopedOptions = normalizeOptionList(mappedOptions)
      }
    }
    columns.push({
      label: level?.label || level?.title || '',
      subtitle: level?.subtitle || '',
      field,
      defaultValue: level?.defaultValue,
      options: scopedOptions
    })
  })
  return columns
})
const showSideList = computed(() => !isCascadeMode.value && sideListOptions.value.length > 0)
const showCascadeList = computed(() => isCascadeMode.value && cascadeColumns.value.some(column => column.options.length))
const sideListFieldName = computed(() => sideListConfig.value?.field || sideListConfig.value?.prop || 'category')
const cascadeQueryParams = computed(() => {
  if (!isCascadeMode.value) return {}
  return cascadeColumns.value.reduce((params, column) => {
    const field = column.field
    if (!field) return params
    const value = selectedCascadeValues.value[field]
    if (value !== undefined && value !== null && value !== '') {
      params[field] = value
    }
    return params
  }, {})
})
const listQueryParams = computed(() => {
  if (isCascadeMode.value) return cascadeQueryParams.value
  if (!showSideList.value) return {}
  const optionValue = selectedSideListValue.value
  if (optionValue === undefined || optionValue === null || optionValue === '') return {}
  return { [sideListFieldName.value]: optionValue }
})

// eslint-disable-next-line no-unused-vars
const cascadeSelectedKeys = computed(() => {
  if (!isCascadeMode.value) return []
  return cascadeColumns.value
    .map(column => column.field && selectedCascadeValues.value[column.field])
    .filter(value => value !== undefined && value !== null && value !== '')
})
const resolveStaticDataset = (config) => {
  const dataset = config?.dataset
  if (Array.isArray(dataset)) return dataset
  if (dataset && typeof dataset === 'object') {
    const pickFirstArray = (source) => {
      if (!source || typeof source !== 'object') return null
      for (const value of Object.values(source)) {
        if (Array.isArray(value)) return value
        if (value && typeof value === 'object') {
          const nested = pickFirstArray(value)
          if (nested) return nested
        }
      }
      return null
    }
    const pickByKeys = (source, keys, allowObjectReturn = false) => {
      if (!source || typeof source !== 'object') return null
      for (const key of keys) {
        if (!key) continue
        const scoped = source[key]
        if (scoped === undefined) continue
        if (Array.isArray(scoped)) return scoped
        if (allowObjectReturn && scoped && typeof scoped === 'object') return scoped
      }
      return null
    }
    const pickNestedByKeys = (source, keys) => {
      if (!source || typeof source !== 'object' || !keys.length) return null
      let scoped = source
      for (const key of keys) {
        if (!key) continue
        if (!scoped || typeof scoped !== 'object') return null
        if (!(key in scoped)) return null
        scoped = scoped[key]
      }
      return scoped
    }
    let scopedDataset = dataset
    // 先处理时间粒度筛选（radioFilter），因为 dataset 的第一层可能是时间粒度
    const filterKeys = [
      selectedFilterValue.value,
      radioFilterConfig.value?.defaultValue
    ].filter(Boolean)
    if (filterKeys.length) {
      const filterScoped = pickByKeys(dataset, filterKeys, true)
      if (Array.isArray(filterScoped)) return filterScoped
      if (filterScoped && typeof filterScoped === 'object') scopedDataset = filterScoped
    }
    if (Array.isArray(scopedDataset)) return scopedDataset
    // 再处理级联筛选或侧边列表筛选
    if (isCascadeMode.value) {
      const cascadeKeys = cascadeColumns.value
        .map(column => column.field && selectedCascadeValues.value[column.field])
        .filter(value => value !== undefined && value !== null && value !== '')
      if (cascadeKeys.length) {
        const cascadeScoped = pickNestedByKeys(scopedDataset, cascadeKeys)
        if (Array.isArray(cascadeScoped)) return cascadeScoped
        if (cascadeScoped && typeof cascadeScoped === 'object') scopedDataset = cascadeScoped
      }
    } else {
      const listKeys = [
        selectedSideListValue.value,
        sideListConfig.value?.defaultValue
      ].filter(Boolean)
      if (listKeys.length) {
        // 优先在已按时间粒度过滤后的 scopedDataset 中取
        const listScoped = pickByKeys(scopedDataset, listKeys, true)
        if (Array.isArray(listScoped)) return listScoped
        if (listScoped && typeof listScoped === 'object') {
          // 在侧边列表命中的作用域内，再次按时间粒度过滤（处理 { dept: { week: [...] } } 场景）
          let scopedAfterList = listScoped
          if (filterKeys.length) {
            const filterScopedInner = pickByKeys(scopedAfterList, filterKeys, true)
            if (Array.isArray(filterScopedInner)) return filterScopedInner
            if (filterScopedInner && typeof filterScopedInner === 'object') scopedAfterList = filterScopedInner
          }
          scopedDataset = scopedAfterList
        } else {
          // 如果时间粒度在第二层（如 { dept: { week: [...] } }），尝试反向嵌套取值
          const nestedOrder = pickNestedByKeys(dataset, [...listKeys, ...filterKeys])
          if (Array.isArray(nestedOrder)) return nestedOrder
          if (nestedOrder && typeof nestedOrder === 'object') scopedDataset = nestedOrder
          else {
            const reverseNested = pickNestedByKeys(dataset, [...filterKeys, ...listKeys])
            if (Array.isArray(reverseNested)) return reverseNested
            if (reverseNested && typeof reverseNested === 'object') scopedDataset = reverseNested
          }
        }
      }
    }
    if (Array.isArray(scopedDataset)) return scopedDataset
    const firstArray = pickFirstArray(scopedDataset)
    if (Array.isArray(firstArray)) return firstArray
  }
  return null
}
const buildQueryPayload = () => ({
  ...(props.fixedQueryForm || {}),
  ...listQueryParams.value,
  ...filterQueryParams.value
})

const attrs = useAttrs()
const wrapperClassObject = computed(() => {
  const a = attrs
  const classes = {}

  // 通用布尔样式开关（以存在/空字符串/'true' 为真）
  const booleanStyleKeys = [
    'show-border',
    'border-hide-left',
    'border-hide-right'
  ]
  for (const key of booleanStyleKeys) {
    const val = a[key]
    const truthy = val === true || val === '' || val === 'true'
    if (truthy) classes[`h-chart-${key}`] = true
  }

  return classes
})

const ensureFilterSelection = () => {
  const options = radioFilterOptions.value
  if (!options.length) {
    if (selectedFilterValue.value !== '') selectedFilterValue.value = ''
    return
  }
  const exists = options.some(option => option.value === selectedFilterValue.value)
  if (exists) return
  const fallback = radioFilterConfig.value?.defaultValue ?? options[0]?.value ?? ''
  selectedFilterValue.value = fallback
}
const ensureListSelection = () => {
  const options = sideListOptions.value
  if (!options.length) {
    if (selectedSideListValue.value !== '') selectedSideListValue.value = ''
    return
  }
  const exists = options.some(option => option.value === selectedSideListValue.value)
  if (exists) return
  const fallback = sideListConfig.value?.defaultValue ?? options[0]?.value ?? ''
  selectedSideListValue.value = fallback
}
const ensureCascadeSelections = () => {
  if (!isCascadeMode.value) {
    if (Object.keys(selectedCascadeValues.value).length) {
      selectedCascadeValues.value = {}
    }
    return
  }
  const nextSelections = { ...selectedCascadeValues.value }
  let changed = false
  cascadeColumns.value.forEach(column => {
    const field = column.field
    if (!field) return
    const options = column.options || []
    if (!options.length) {
      if (nextSelections[field]) {
        delete nextSelections[field]
        changed = true
      }
      return
    }
    const exists = options.some(option => option.value === nextSelections[field])
    if (exists) return
    const fallback = column.defaultValue ?? options[0]?.value ?? ''
    if (fallback !== undefined && fallback !== null && fallback !== '') {
      if (nextSelections[field] !== fallback) {
        nextSelections[field] = fallback
        changed = true
      }
    } else if (nextSelections[field]) {
      delete nextSelections[field]
      changed = true
    }
  })
  if (changed) {
    selectedCascadeValues.value = { ...nextSelections }
  }
}

// 监听sideList配置变化，重新加载options
watch(sideListConfig, () => {
  loadSideListOptions()
}, { immediate: true, deep: true })

watch([radioFilterOptions, () => radioFilterConfig.value?.defaultValue], () => {
  ensureFilterSelection()
}, { immediate: true })
watch(
  () => ({
    options: sideListOptions.value,
    defaultValue: sideListConfig.value?.defaultValue,
    cascadeMode: isCascadeMode.value
  }),
  () => {
    if (isCascadeMode.value) return
    ensureListSelection()
  },
  { immediate: true, deep: true }
)
watch(
  () => cascadeColumns.value.map(column => ({
    field: column.field,
    defaultValue: column.defaultValue,
    optionValues: column.options?.map(option => option.value)
  })),
  () => {
    ensureCascadeSelections()
  },
  { immediate: true, deep: true }
)

const handleFilterChange = (value) => {
  if (value === selectedFilterValue.value) return
  selectedFilterValue.value = value
  const config = chartConfig.value || props.config
  if (config) {
    loadChartData(config)
  }
}
const handleListChange = (value) => {
  if (value === selectedSideListValue.value) return
  selectedSideListValue.value = value
  const config = chartConfig.value || props.config
  if (config) {
    loadChartData(config)
  }
}
const handleCascadeChange = (field, value) => {
  if (!isCascadeMode.value || !field) return
  if (selectedCascadeValues.value[field] === value) return
  const updatedSelections = { ...selectedCascadeValues.value, [field]: value }
  const startIndex = cascadeColumns.value.findIndex(column => column.field === field)
  if (startIndex >= 0) {
    for (let idx = startIndex + 1; idx < cascadeColumns.value.length; idx++) {
      const childField = cascadeColumns.value[idx].field
      if (childField && updatedSelections[childField] !== undefined) {
        delete updatedSelections[childField]
      }
    }
  }
  selectedCascadeValues.value = updatedSelections
  ensureCascadeSelections()
  const config = chartConfig.value || props.config
  if (config) {
    loadChartData(config)
  }
}

// 常用图表预设，统一处理 dataset → ECharts option 的映射
const presetResolvers = {
  bar: ({ dataset = [], config = {}, state = {} }) => {
    const activeIndex = typeof state.activeBarIndex === 'number' ? state.activeBarIndex : -1
    if (!dataset.length) {
      return {
        xAxis: { type: 'category', data: [] },
        yAxis: { type: 'value' },
        series: [{ type: 'bar', data: [] }]
      }
    }

    const barColor = config.barColor || '#3362DA'
    const barHighlightColor = config.barHighlightColor || '#4C7CFF'
    const valueSuffix = config.valueSuffix || ''

    const tooltipFormatter = (params = []) => {
      if (!params.length) return ''
      const point = params.find(item => item.seriesIndex === 0) || params[0]
      const value = typeof point.data === 'object' ? point.data?.value : point.data
      return `${point.axisValue}<br/>${value}${valueSuffix}`
    }

    return {
      animationDuration: 420,
      animationEasing: 'cubicOut',
      animationDelay: (_, idx) => idx * 40,
      tooltip: {
        trigger: 'axis',
        backgroundColor: '#1F5BFF',
        borderWidth: 0,
        textStyle: { color: '#fff' },
        axisPointer: {
          type: 'shadow',
          shadowStyle: {
            color: 'rgba(51, 98, 218, 0.08)'
          }
        },
        formatter: tooltipFormatter
      },
      grid: {
        top: 40,
        left: 60,
        right: 40,
        bottom: 40,
        containLabel: true
      },
      xAxis: {
        type: 'category',
        data: dataset.map(item => item.label),
        axisLine: { lineStyle: { color: '#C8D2E8' } },
        axisLabel: { color: '#5C6C8C', fontWeight: 500 },
        axisTick: { show: false },
        splitLine: { show: false }
      },
      yAxis: {
        type: 'value',
        min: 0,
        max: config.yMax ?? 100,
        minInterval: 1,
        axisLine: { show: false },
        axisTick: { show: false },
        axisLabel: {
          color: '#5C6C8C',
          fontWeight: 500,
          formatter: value => `${value}${valueSuffix}`
        },
        splitLine: {
          show: true,
          lineStyle: { type: 'dashed', color: '#E0E6F1' }
        }
      },
      series: [
        {
          type: 'bar',
          data: dataset.map((item, idx) => ({
            value: item.value,
            barWidth: idx === activeIndex ? 10 : 5
          })),
          itemStyle: {
            color: barColor,
            borderRadius: [6, 6, 0, 0]
          },
          label: {
            show: true,
            position: 'top',
            color: barColor,
            fontWeight: 600,
            formatter: params => {
              const value = typeof params.data === 'object' ? params.data?.value : params.data
              return `${value}${valueSuffix}`
            }
          },
          emphasis: {
            itemStyle: {
              color: barHighlightColor,
              shadowBlur: 18,
              shadowColor: 'rgba(76, 124, 255, 0.45)'
            },
            label: {
              color: barHighlightColor
            }
          }
        }
      ]
    }
  },
  line: ({ dataset = [], config = {}, state = {} }) => {
    if (!dataset.length) {
      return {
        xAxis: { type: 'category', data: [] },
        yAxis: { type: 'value' },
        series: [{ type: 'line', data: [] }]
      }
    }

    const highlightColor = config.highlightColor || '#1F5BFF'
    const lineColor = config.lineColor || '#1F66FF'
    const areaStart = config.areaStartColor || 'rgba(31, 91, 255, 0.25)'
    const areaEnd = config.areaEndColor || 'rgba(31, 91, 255, 0.02)'
    const valueSuffix = config.valueSuffix || ''
    const isSmoothLine = typeof config.smooth === 'boolean' ? config.smooth : false
    const baseSymbolSize = Number(config.symbolSize) || 12
    const activeSymbolSize = Number(config.activeSymbolSize) || baseSymbolSize + 4
    const inactivePointFill = config.inactivePointFill || '#fff'
    const pointBorderWidth = Number(config.pointBorderWidth) || 4
    const activeIndex = typeof state.activePointIndex === 'number' ? state.activePointIndex : -1

    const seriesData = dataset.map((item, idx) => ({
      value: item.value,
      label: item.label,
      symbol: 'circle',
      symbolSize: idx === activeIndex ? activeSymbolSize : baseSymbolSize,
      itemStyle: {
        color: idx === activeIndex ? highlightColor : inactivePointFill,
        borderColor: idx === activeIndex ? highlightColor : lineColor,
        borderWidth: pointBorderWidth
      }
    }))

    return {
      animationDuration: 420,
      animationEasing: 'cubicOut',
      tooltip: {
        trigger: 'axis',
        backgroundColor: '#1F5BFF',
        borderWidth: 0,
        textStyle: { color: '#fff' },
        axisPointer: {
          type: 'line',
          lineStyle: {
            color: highlightColor,
            width: 2
          }
        },
        formatter: (params = []) => {
          if (!params.length) return ''
          const point = params[0]
          return `${point.axisValue}<br/>${point.data.value}${valueSuffix}`
        }
      },
      grid: {
        top: 40,
        left: 60,
        right: 40,
        bottom: 40,
        containLabel: true
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: dataset.map(item => item.label),
        axisLine: { lineStyle: { color: '#C8D2E8' } },
        axisLabel: { color: '#5C6C8C', fontWeight: 500 },
        axisTick: { show: false },
        splitLine: { show: false }
      },
      yAxis: {
        type: 'value',
        minInterval: 1,
        axisLine: { show: false },
        axisTick: { show: false },
        axisLabel: { color: '#5C6C8C', fontWeight: 500 },
        splitLine: {
          show: true,
          lineStyle: { type: 'dashed', color: '#E0E6F1' }
        }
      },
      series: [
        {
          type: 'line',
          smooth: isSmoothLine,
          showSymbol: true,
          symbol: 'circle',
          data: seriesData.map((point, idx) => ({
            value: point.value,
            symbolSize: point.symbolSize,
            itemStyle: point.itemStyle,
            label: {
              show: true,
              fontWeight: idx === activeIndex ? 600 : 500,
              color: idx === activeIndex ? highlightColor : lineColor,
              formatter: `${point.value}${valueSuffix}`,
              position: 'top'
            }
          })),
          lineStyle: {
            width: 3,
            color: lineColor
          },
          emphasis: {
            focus: 'series',
            scale: true
          },
          areaStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: areaStart },
              { offset: 1, color: areaEnd }
            ])
          }
        }
      ]
    }
  },
  pie: ({ dataset }) => ({
    animationDuration: 420,
    animationEasing: 'cubicOut',
    tooltip: {
      trigger: 'item',
      formatter: '{a} <br/>{b}: {c} ({d}%)'
    },
    series: [
      {
        type: 'pie',
        radius: '55%',
        startAngle: 180,
        hoverOffset: 1,
        data: dataset.map(item => ({ name: item.label, value: item.value })),
        itemStyle: {
          borderColor: '#fff',
          borderWidth: 1.5
        },
        emphasis: {
          scale: true,
          scaleSize: 1,
          itemStyle: {
            shadowBlur: 12,
            shadowColor: 'rgba(0, 0, 0, 0.22)'
          },
          label: {
            show: true,
            fontWeight: 'bold'
          }
        }
      }
    ]
  }),
  // 范围折线图：显示高低两条线及差值连线
  'range-line': ({ dataset = [], config = {} }) => {
    if (!dataset.length) {
      return {
        xAxis: { type: 'category', data: [] },
        yAxis: { type: 'value' },
        series: []
      }
    }

    // 配置项
    const highField = config.highField || 'high'
    const lowField = config.lowField || 'low'
    const highName = config.highName || '流入'
    const lowName = config.lowName || '流出'
    const highColor = config.highColor || '#1F66FF'
    const lowColor = config.lowColor || '#A8C4FF'
    const rangeLineColor = config.rangeLineColor || '#1F66FF'
    const rangeLineType = config.rangeLineType || 'solid'
    const valueSuffix = config.valueSuffix || ''
    const showRangeLines = config.showRangeLines !== false
    const isSmoothLine = typeof config.smooth === 'boolean' ? config.smooth : false
    const baseSymbolSize = Number(config.symbolSize) || 10
    const pointBorderWidth = Number(config.pointBorderWidth) || 3

    // 提取数据
    const labels = dataset.map(item => item.label)
    const highData = dataset.map(item => Number(item[highField] || item.high || 0))
    const lowData = dataset.map(item => Number(item[lowField] || item.low || 0))

    // 构建系列
    const series = [
      // 高值折线
      {
        name: highName,
        type: 'line',
        smooth: isSmoothLine,
        showSymbol: true,
        symbol: 'circle',
        symbolSize: baseSymbolSize,
        z: 10,
        data: highData,
        lineStyle: {
          width: 2,
          color: highColor
        },
        itemStyle: {
          color: '#fff',
          borderColor: highColor,
          borderWidth: pointBorderWidth
        },
        label: {
          show: true,
          position: 'top',
          color: highColor,
          fontWeight: 500,
          formatter: params => `${params.value}${valueSuffix}`
        },
        emphasis: {
          scale: true,
          itemStyle: {
            color: highColor,
            borderColor: highColor
          }
        }
      },
      // 低值折线
      {
        name: lowName,
        type: 'line',
        smooth: isSmoothLine,
        showSymbol: true,
        symbol: 'circle',
        symbolSize: baseSymbolSize,
        z: 10,
        data: lowData,
        lineStyle: {
          width: 2,
          color: lowColor
        },
        itemStyle: {
          color: '#fff',
          borderColor: lowColor,
          borderWidth: pointBorderWidth
        },
        label: {
          show: true,
          position: 'bottom',
          color: lowColor,
          fontWeight: 500,
          formatter: params => `${params.value}${valueSuffix}`
        },
        emphasis: {
          scale: true,
          itemStyle: {
            color: lowColor,
            borderColor: lowColor
          }
        }
      }
    ]

    // 添加差值连线（使用 custom series）
    if (showRangeLines) {
      series.push({
        name: '差值连线',
        type: 'custom',
        z: 5,
        renderItem: (params, api) => {
          const xValue = api.value(0)
          const highValue = api.value(1)
          const lowValue = api.value(2)

          const highPoint = api.coord([xValue, highValue])
          const lowPoint = api.coord([xValue, lowValue])

          return {
            type: 'line',
            shape: {
              x1: highPoint[0],
              y1: highPoint[1],
              x2: lowPoint[0],
              y2: lowPoint[1]
            },
            style: {
              stroke: rangeLineColor,
              lineWidth: 1,
              lineDash: rangeLineType === 'dashed' ? [4, 4] : null
            }
          }
        },
        encode: {
          x: 0,
          y: [1, 2]
        },
        data: dataset.map((item, idx) => [idx, highData[idx], lowData[idx]])
      })
    }

    return {
      animationDuration: 420,
      animationEasing: 'cubicOut',
      tooltip: {
        trigger: 'axis',
        backgroundColor: 'rgba(255, 255, 255, 0.96)',
        borderColor: '#E5E9F0',
        borderWidth: 1,
        textStyle: { color: '#333' },
        axisPointer: {
          type: 'line',
          lineStyle: {
            color: rangeLineColor,
            width: 1,
            type: 'dashed'
          }
        },
        formatter: (params = []) => {
          if (!params.length) return ''
          const label = params[0].axisValue
          let html = `<div style="font-weight:600;margin-bottom:6px">${label}</div>`
          params.forEach(p => {
            if (p.seriesName === '差值连线') return
            const marker = `<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${p.color};margin-right:6px"></span>`
            html += `<div>${marker}${p.seriesName}: ${p.value}${valueSuffix}</div>`
          })
          // 计算差值
          const highVal = params.find(p => p.seriesName === highName)?.value || 0
          const lowVal = params.find(p => p.seriesName === lowName)?.value || 0
          const diff = highVal - lowVal
          html += `<div style="margin-top:4px;padding-top:4px;border-top:1px dashed #E5E9F0;color:#666">净额: ${diff}${valueSuffix}</div>`
          return html
        }
      },
      legend: {
        show: true,
        top: 0,
        right: 20,
        itemWidth: 16,
        itemHeight: 8,
        textStyle: { color: '#5C6C8C' },
        data: [highName, lowName]
      },
      grid: {
        top: 60,
        left: 60,
        right: 40,
        bottom: 40,
        containLabel: true
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: labels,
        axisLine: { lineStyle: { color: '#C8D2E8' } },
        axisLabel: { color: '#5C6C8C', fontWeight: 500 },
        axisTick: { show: false },
        splitLine: { show: false }
      },
      yAxis: {
        type: 'value',
        minInterval: 1,
        axisLine: { show: false },
        axisTick: { show: false },
        axisLabel: {
          color: '#5C6C8C',
          fontWeight: 500,
          formatter: value => `${value}${valueSuffix}`
        },
        splitLine: {
          show: true,
          lineStyle: { type: 'dashed', color: '#E0E6F1' }
        }
      },
      series
    }
  }
}

// 颜色工具函数：将十六进制颜色转换为 RGB
const hexToRgb = (hex) => {
  // 移除 # 号
  const cleanHex = hex.replace('#', '')
  // 处理 3 位和 6 位十六进制
  const fullHex = cleanHex.length === 3
    ? cleanHex.split('').map(char => char + char).join('')
    : cleanHex
  const r = parseInt(fullHex.substring(0, 2), 16)
  const g = parseInt(fullHex.substring(2, 4), 16)
  const b = parseInt(fullHex.substring(4, 6), 16)
  return { r, g, b }
}

// 颜色工具函数：将 RGB 转换为十六进制颜色
const rgbToHex = (r, g, b) => {
  const toHex = (n) => {
    const hex = Math.round(n).toString(16)
    return hex.length === 1 ? '0' + hex : hex
  }
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`
}

// 生成颜色渐变数组：基于单个颜色生成变淡的颜色数组
const generateColorGradient = (baseColor, count, fadeRatio = 0.15) => {
  if (!baseColor) return []
  if (count <= 0) return []
  if (count === 1) {
    return Array.isArray(baseColor) ? baseColor : [baseColor]
  }

  // 如果已经是数组且长度大于1，直接返回
  if (Array.isArray(baseColor) && baseColor.length > 1) {
    return baseColor
  }

  // 获取基础颜色（如果是数组，取第一个）
  const color = Array.isArray(baseColor) ? baseColor[0] : baseColor

  // 转换为 RGB
  const baseRgb = hexToRgb(color)
  const white = { r: 255, g: 255, b: 255 }

  // 生成渐变数组
  const colors = []
  for (let i = 0; i < count; i++) {
    // 第一个颜色保持原色不变，后续逐渐向白色混合
    if (i === 0) {
      colors.push(color)
      continue
    }
    // 计算混合比例：fadeRatio 控制每次变淡的比例（0-1之间，值越大变淡越快）
    const ratio = i * fadeRatio
    const mixRatio = Math.min(ratio, 0.9) // 最多混合到90%，避免完全变白
    const r = baseRgb.r + (white.r - baseRgb.r) * mixRatio
    const g = baseRgb.g + (white.g - baseRgb.g) * mixRatio
    const b = baseRgb.b + (white.b - baseRgb.b) * mixRatio

    colors.push(rgbToHex(r, g, b))
  }

  return colors
}

// 数据转换：将后端返回的数据转换为图表需要的 dataset 格式
const transformData = (rawData, dataMapping) => {
  // 增强数据提取：支持从包装对象中提取数组
  let dataArray = rawData

  // 如果 rawData 是对象但不是数组，尝试提取数组数据
  if (rawData && typeof rawData === 'object' && !Array.isArray(rawData)) {
    // 尝试常见的包装字段
    dataArray = rawData.data || rawData.result || rawData.list || rawData.items || rawData.records

    // 如果还是找不到数组，检查是否所有值都是数组
    if (!Array.isArray(dataArray)) {
      const arrayValues = Object.values(rawData).filter(val => Array.isArray(val))
      if (arrayValues.length === 1) {
        dataArray = arrayValues[0]
      }
    }
  }

  // 最终检查：必须是数组
  if (!dataArray || !Array.isArray(dataArray)) {
    console.warn('HChart: transformData 接收到的数据不是数组格式:', rawData)
    return []
  }

  // 如果配置了数据映射规则，使用映射规则
  if (dataMapping) {
    const { labelField = 'label', valueField = 'value' } = dataMapping
    return dataArray.map(item => ({
      label: item[labelField] || item.name || item.label || '',
      value: Number(item[valueField] || item.value || 0)
    }))
  }

  // 默认处理：尝试自动识别
  return dataArray.map(item => {
    if (typeof item === 'object' && item !== null) {
      return {
        label: item.label || item.name || item.text || item.x || String(item[Object.keys(item)[0]] || ''),
        value: Number(item.value || item.count || item.y || item[Object.keys(item)[1]] || 0)
      }
    }
    return { label: String(item), value: 0 }
  })
}

// 获取配置和数据
const fetchConfigAndData = async () => {
  if (!props.queryParamsName && !props.config) {
    console.warn('HChart: queryParamsName 或 config 必须提供一个')
    return
  }

  try {
    loading.value = true

    // 如果直接提供了 config，直接使用
    if (props.config) {
      chartConfig.value = props.config
      await loadChartData(props.config)
      return
    }

    // 否则通过 queryParamsName 查询配置
    getConfigByName(props.queryParamsName, props.serviceName, async (res) => {
      if (!res) {
        console.error('HChart: 未能获取到配置内容')
        emit('error', new Error('未能获取到配置内容'))
        return
      }

      chartConfig.value = res
      await loadChartData(res)
    })
  } catch (error) {
    console.error('HChart: 获取配置或数据时发生错误:', error)
    emit('error', error)
  } finally {
    loading.value = false
  }
}

// 加载图表数据
const loadChartData = async (config) => {
  if (!config) return
  ensureListSelection()
  ensureCascadeSelections()
  ensureFilterSelection()

  try {
    // 如果配置中直接提供了 dataset（数组或映射），直接使用
    const staticDataset = resolveStaticDataset(config)
    if (staticDataset) {
      chartData.value = staticDataset
      activePointIndex.value = -1
      activeBarIndex.value = -1
      renderChart()
      emit('dataLoaded', chartData.value)
      return
    }

    // 如果配置中提供了 data（logicName），通过 runLogic 获取数据
    if (config.data) {
      const queryPayload = buildQueryPayload()
      const result = await runLogic(config.data, queryPayload, props.serviceName)

      // 转换数据格式
      const transformedData = transformData(result, config.dataMapping)
      if (transformedData.length === 0) {
        console.warn('HChart: 数据转换后为空数组，原始数据:', result)
      }
      chartData.value = transformedData
      activePointIndex.value = -1
      activeBarIndex.value = -1
      renderChart()
      emit('dataLoaded', transformedData)
    } else {
      // 没有数据源，使用空数据
      chartData.value = []
      activePointIndex.value = -1
      activeBarIndex.value = -1
      renderChart()
    }
  } catch (error) {
    console.error('HChart: 加载数据时发生错误:', error)
    emit('error', error)
  }
}

// 渲染图表
const handleChartClick = (params) => {
  const config = chartConfig.value || props.config
  if (!config || params?.componentType !== 'series') return
  if (config.type === 'line' && params.seriesType === 'line') {
    if (typeof params.dataIndex !== 'number') return
    if (activePointIndex.value === params.dataIndex) return
    activePointIndex.value = params.dataIndex
    renderChart()
    return
  }
  if (config.type === 'bar' && params.seriesType === 'bar') {
    if (typeof params.dataIndex !== 'number') return
    const nextIndex = activeBarIndex.value === params.dataIndex ? -1 : params.dataIndex
    if (activeBarIndex.value === nextIndex) return
    activeBarIndex.value = nextIndex
    renderChart()
  }
}

const renderChart = () => {
  if (!chartRef.value) return
  nextTick(() => {
    if (!chartInstance) {
      chartInstance = echarts.init(chartRef.value)
      // 监听窗口大小变化
      resizeObserver = new ResizeObserver(() => {
        chartInstance?.resize()
      })
      resizeObserver.observe(chartRef.value)
      chartInstance.on('click', handleChartClick)
    }

    const config = chartConfig.value || props.config
    if (!config || !config.type) return

    const resolver = presetResolvers[config.type]
    if (!resolver) {
      console.warn(`HChart: 不支持的图表类型 ${config.type}`)
      return
    }

    const preset = resolver({
      dataset: chartData.value,
      config,
      state: {
        activePointIndex: activePointIndex.value,
        activeBarIndex: activeBarIndex.value
      }
    })

    // 处理颜色配置：如果只有一个颜色，生成渐变数组
    let processedColor = config.options?.color
    if (processedColor) {
      const dataCount = chartData.value.length || 12 // 默认生成12个颜色
      // 如果只有一个颜色（字符串或长度为1的数组），生成渐变
      if (typeof processedColor === 'string' || (Array.isArray(processedColor) && processedColor.length === 1)) {
        processedColor = generateColorGradient(processedColor, dataCount)
      }
    }
    // 深度合并 series 配置
    let finalOptions = {
      legend: config.legend || {},
      grid: config.grid || {},
      ...preset
    }

    // 如果 options 中有 series，进行深度合并
    if (config.options?.series && preset.series) {
      // 分离需要特殊处理的配置项
      const { xAxis: customXAxis, yAxis: customYAxis, series: customSeries, color: _, ...restOptions } = config.options
      const normalizedCustomSeries = Array.isArray(customSeries) ? customSeries : [customSeries]

      finalOptions = {
        ...finalOptions,
        ...restOptions,
        // 应用处理后的颜色
        ...(processedColor && { color: processedColor }),
        // 对于有 xAxis 的图表类型（bar, line），智能合并 xAxis
        ...(preset.xAxis && {
          xAxis: {
            ...preset.xAxis,
            ...customXAxis,
            // 如果用户配置的 data 为空数组或未定义，使用预设的 data
            data: (customXAxis?.data && customXAxis.data.length > 0)
              ? customXAxis.data
              : preset.xAxis.data
          }
        }),
        // 对于有 yAxis 的图表类型，合并 yAxis
        ...(preset.yAxis && {
          yAxis: {
            ...preset.yAxis,
            ...customYAxis
          }
        }),
        series: preset.series.map((seriesItem, idx) => ({
          ...seriesItem,
          ...(normalizedCustomSeries[idx] || {}),
          data: seriesItem.data
        }))
      }
    } else {
      // 没有自定义 series 的情况
      const { xAxis: customXAxis, yAxis: customYAxis, color: _, ...restOptions } = config.options || {}

      finalOptions = {
        ...finalOptions,
        ...restOptions,
        // 应用处理后的颜色
        ...(processedColor && { color: processedColor }),
        // 对于有 xAxis 的图表类型，智能合并
        ...(preset.xAxis && {
          xAxis: {
            ...preset.xAxis,
            ...customXAxis,
            // 如果用户配置的 data 为空数组或未定义，使用预设的 data
            data: (customXAxis?.data && customXAxis.data.length > 0)
              ? customXAxis.data
              : preset.xAxis.data
          }
        }),
        // 对于有 yAxis 的图表类型，合并 yAxis
        ...(preset.yAxis && {
          yAxis: {
            ...preset.yAxis,
            ...customYAxis
          }
        })
      }
    }
    chartInstance.setOption(finalOptions, true)
  })
}

// 监听 queryParamsName 变化，只有在名称变化时才重新获取配置
watch(
  () => props.queryParamsName,
  (newName, oldName) => {
    if (newName !== oldName) {
      fetchConfigAndData()
    }
  }
)

// 暴露给外部的方法
const refresh = () => {
  fetchConfigAndData()
}

const reload = (newQueryForm) => {
  if (newQueryForm) {
    emit('update:fixedQueryForm', { ...props.fixedQueryForm, ...newQueryForm })
  } else {
    fetchConfigAndData()
  }
}

defineExpose({
  refresh,
  reload,
  chartInstance
})

onMounted(() => {
  // 初始化时记录 queryParamsName
  lastQueryParamsName.value = props.queryParamsName
  fetchConfigAndData()
})

onBeforeUnmount(() => {
  // 清理图表实例和观察器
  if (chartInstance) {
    chartInstance.off('click', handleChartClick)
    chartInstance.dispose()
    chartInstance = null
  }
  if (resizeObserver) {
    resizeObserver.disconnect()
    resizeObserver = null
  }
})
</script>

<style scoped lang="less">
.h-chart-configurable {
  display: flex;
  flex-direction: column;
  transition: transform 0.22s ease, box-shadow 0.22s ease;

  &.h-chart-show-border {
    border: 1px solid #E5E9F0;
  }
  &.h-chart-border-hide-right {
    border-right: 0px;
  }
  &.h-chart-border-hide-left {
    border-left: 0px;
  }
}

.h-chart-configurable.hoverable:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12);
}

.chart-layout {
  display: flex;
  gap: 16px;
}

.chart-side-list {
  width: 180px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  border-right: 1px solid #ECF0F7;
  padding-right: 12px;
}

.side-list-label {
  font-size: 14px;
  color: #4A5875;
  font-weight: 600;
  margin-bottom: 8px;
}

.side-list-menu {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 280px;
  overflow-y: auto;
  padding-right: 4px;
}

.side-list-item {
  width: 100%;
  text-align: left;
  border: 0;
  background: transparent;
  padding: 8px 10px;
  border-radius: 8px;
  font-size: 14px;
  color: #4A5875;
  cursor: pointer;
  transition: all 0.15s ease;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.side-list-item .item-label {
  flex: 1;
}

.side-list-item .item-badge {
  font-size: 12px;
  color: #9AA7C5;
}

.side-list-item.is-active {
  background: rgba(31, 91, 255, 0.08);
  color: #1F5BFF;
  font-weight: 600;
}

.chart-main {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.chart-cascade-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 4px;
  width: 100%;
}

.cascade-panel-label {
  font-size: 14px;
  color: #4A5875;
  font-weight: 600;
}

.cascade-columns {
  display: flex;
  gap: 16px;
  width: 100%;
  flex-wrap: wrap;
  overflow-x: auto;
  padding-bottom: 4px;
}

.cascade-column {
  flex: 1 1 180px;
  min-width: 180px;
  border-right: 1px solid #ECF0F7;
  padding-right: 12px;
}

.cascade-column:last-child {
  border-right: none;
  padding-right: 0;
}

.cascade-column.is-disabled {
  opacity: 0.6;
}

.cascade-column-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  color: #5C6C8C;
  font-weight: 600;
  margin-bottom: 6px;
}

.column-subtitle {
  font-size: 12px;
  color: #9AA7C5;
  font-weight: 400;
}

.cascade-option-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.cascade-option {
  width: 100%;
  text-align: left;
  border: 0;
  background: transparent;
  padding: 8px 10px;
  border-radius: 8px;
  font-size: 13px;
  color: #4A5875;
  cursor: pointer;
  transition: all 0.15s ease;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.cascade-option .option-label {
  flex: 1;
}

.cascade-option .option-badge {
  font-size: 12px;
  color: #9AA7C5;
}

.cascade-option.is-active {
  background: rgba(31, 91, 255, 0.08);
  color: #1F5BFF;
  font-weight: 600;
}

.cascade-option:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.chart-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 4px;
}

.toolbar-label {
  font-size: 14px;
  color: #5C6C8C;
  font-weight: 500;
}

.toolbar-radio-group {
  display: inline-flex;
  gap: 8px;
}

.radio-pill {
  display: inline-flex;
  align-items: center;
  padding: 4px 14px;
  border-radius: 16px;
  border: 1px solid #D5DFF2;
  font-size: 13px;
  color: #5C6C8C;
  cursor: pointer;
  transition: all 0.18s ease;
  user-select: none;
}

.radio-pill-input {
  display: none;
}

.radio-pill.is-active {
  background-color: rgba(31, 91, 255, 0.08);
  border-color: #1F5BFF;
  color: #1F5BFF;
  font-weight: 600;
}

.chart-canvas {
  width: 100%;
  margin-top: 10px;
  min-height: 280px;
}
</style>
