import { DualAxes, Plot } from '@antv/g2plot'

/**
 * 使用 Map 来存储实例，键为 chart.container 对象
 */
export const CAROUSEL_MANAGER_INSTANCES = new Map<string, ChartCarouselTooltip>()
/**
 * 支持的图表类型
 */
const CHART_CATEGORY = {
  COLUMN: ['bar', 'bar-stack', 'bar-group', 'bar-group-stack', 'percentage-bar-stack'],
  LINE: ['line', 'area', 'area-stack'],
  MIX: ['chart-mix', 'chart-mix-group', 'chart-mix-stack', 'chart-mix-dual-line'],
  PIE: ['pie', 'pie-donut']
}

/**
 * 判断是否为柱状图
 * @param chartType
 */
export function isColumn(chartType: string) {
  return CHART_CATEGORY.COLUMN.includes(chartType)
}

/**
 * 判断是否为折线图
 * @param chartType
 */
export function isLine(chartType: string) {
  return CHART_CATEGORY.LINE.includes(chartType)
}

/**
 * 判断是否为饼图
 * @param chartType
 */
export function isPie(chartType: string) {
  return CHART_CATEGORY.PIE.includes(chartType)
}

/**
 * 判断是否为组合图
 * @param chartType
 */
export function isMix(chartType: string) {
  return CHART_CATEGORY.MIX.includes(chartType)
}

export function isSupport(chartType: string) {
  return Object.values(CHART_CATEGORY).some(category => category.includes(chartType))
}

// 轮播配置默认值
const DEFAULT_CAROUSEL_CONFIG: Required<CarouselConfig> = {
  xField: '',
  duration: 2000,
  interval: 2000,
  loop: true
}

type CarouselConfig = {
  xField: string
  duration?: number
  interval?: number
  loop?: boolean
}

/**
 * 图表轮播提示管理类
 * */
class ChartCarouselTooltip {
  private plot: Plot | DualAxes
  private config: Required<CarouselConfig>
  private currentIndex = 0
  private values: string[] = []
  // 合并定时器管理
  private timers = { interval: null, carousel: null }
  private states = { paused: false, destroyed: false }
  // 图表可视性变化
  private observers: Map<string, IntersectionObserver> = new Map()
  // 图表元素大小变化
  private resizeObservers: Map<string, ResizeObserver> = new Map()
  // 图表是否在可视范围内
  private chartIsVisible: boolean

  private constructor(plot: Plot | DualAxes, private chart: Chart, config: CarouselConfig) {
    this.plot = plot
    this.config = { ...DEFAULT_CAROUSEL_CONFIG, ...config }
    this.init()
  }

  /**
   * 创建或更新实例
   * */
  static manage(plot: Plot | DualAxes, chart: Chart, config: CarouselConfig) {
    if (!isSupport(chart.type)) return null
    const container = chart.container
    let instance = CAROUSEL_MANAGER_INSTANCES.get(container)

    CAROUSEL_MANAGER_INSTANCES.forEach(instance => {
      if (container.includes('viewDialog')) {
        instance.paused()
      }
    })

    if (instance) {
      instance.update(plot, chart, config)
      return instance
    }
    if (isSupport(chart.type)) {
      instance = new this(plot, chart, config)
      CAROUSEL_MANAGER_INSTANCES.set(container, instance)
    }

    return instance
  }

  /**
   * 销毁实例
   * @param container
   */
  static destroyByContainer(container: string) {
    const instance = CAROUSEL_MANAGER_INSTANCES.get(container)
    if (instance) {
      instance.destroy()
    }
  }

  /**
   * 通过容器DOM获取对应实例
   * */
  static getInstanceByContainer(container: string) {
    const instance = CAROUSEL_MANAGER_INSTANCES.get(container)
    if (instance) {
      return instance
    }
    return null
  }

  /**
   * 通过chart.id销毁对应实例
   * 关闭放大图表弹窗，销毁对应实例
   * 重启图表自身轮播
   * */
  static closeEnlargeDialogDestroy(id?: string) {
    // 首先，暂停并删除包含 'viewDialog' 的实例
    CAROUSEL_MANAGER_INSTANCES?.forEach((instance, key) => {
      if (instance.chart.id === id && instance.chart.container.includes('viewDialog')) {
        const dialogInstance = CAROUSEL_MANAGER_INSTANCES.get(key)
        if (dialogInstance) {
          dialogInstance.destroy()
        }
      }
    })
    setTimeout(() => {
      // 然后，恢复
      CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
        if (instance.chartIsVisible) {
          instance.resume()
        }
      })
    }, 400)
  }

  /**
   * 暂停轮播
   * @param id
   */
  static paused(id?: string) {
    CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
      if (id && instance.chart.id === id) {
        setTimeout(() => instance.paused(), 200)
      }
      if (!id) {
        setTimeout(() => instance.paused(), 200)
      }
    })
  }

  /**
   * @param id
   */
  static resume(id?: string) {
    CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
      if (instance.chart.id === id) {
        instance.paused()
        setTimeout(() => instance.resume(), 500)
      }
      if (!id) {
        setTimeout(() => instance.resume(), 200)
      }
    })
  }

  /**
   * 初始化核心逻辑
   * */
  private init() {
    this.values = [].concat(this.getUniqueValues())
    if (!this.values.length) return
    this.chartIsVisible = true
    this.states.paused = false
    this.states.destroyed = false
    this.bindEventListeners()
    this.startCarousel()
  }

  /**
   * 获取图表唯一值集合
   * */
  private getUniqueValues() {
    const data =
      this.plot instanceof DualAxes
        ? [...this.plot.options.data[0], ...this.plot.options.data[1]]
        : this.plot.options.data

    return [...new Set(data.map(item => item[this.config.xField]))]
  }

  /**
   * 启动轮播
   * */
  private startCarousel() {
    if (!this.shouldStart()) {
      this.stop()
      return
    }
    // 定义启动嵌套定时器的函数
    const startNestedTimers = () => {
      // 重置当前索引
      this.currentIndex = 0
      // 定义递归处理数据数组的函数
      const processArray = () => {
        if (this.states.paused || this.states.destroyed || !this.isElementFullyVisible()) return
        // 获取当前需要显示的值
        const currentValue = this.values[this.currentIndex]
        // 计算 Tooltip 显示的位置
        const point = this.calculatePosition(currentValue)
        // 高亮当前数据点
        this.highlightElement(currentValue)
        if (point) {
          // 显示 Tooltip，并设置其位置为顶部
          this.plot.chart.showTooltip(point)
          this.plot.chart.getController('tooltip').update()
        }
        // 更新索引，指向下一个数据点
        this.currentIndex++
        if (this.currentIndex > this.values.length) {
          this.currentIndex = 0
          this.hideTooltip()
          this.plot.chart.showTooltip({ x: 0, y: 0 })
          this.plot.chart.getController('tooltip').update()
          this.unHighlightPoint(currentValue)
          this.timers.interval = setTimeout(() => processArray(), this.config.interval)
        } else {
          // 如果未遍历完，继续处理下一个数据点
          this.timers.carousel = setTimeout(() => processArray(), this.config.duration)
        }
      }
      processArray()
    }
    this.stop()
    startNestedTimers()
  }

  /**
   *  判断是否满足启动条件' */
  private shouldStart() {
    return (
      this.chart.customAttr?.tooltip?.show &&
      this.chart.customAttr?.tooltip?.carousel?.enable &&
      this.values.length > 0 &&
      this.chartIsVisible &&
      !this.hasParentWithSwitchHidden(this.plot.chart.ele)
    )
  }

  /**
   *  判断图表是否在可视范围内
   *  */
  private isElementFullyVisible(): boolean {
    // 全屏
    const isFullscreen = document.fullscreenElement !== null
    // 新页面或公共连接
    const isNewPagePublicLink = document
      .getElementById('enlarge-inner-content-' + this.chart.id)
      ?.getBoundingClientRect()
    const isMobileEdit = document.getElementsByClassName('panel-mobile')?.length > 0
    const isMobileList = document.getElementsByClassName('mobile-com-list')?.length > 0
    if (isMobileList) {
      return false
    }
    const rect = this.plot.chart.ele.getBoundingClientRect()
    return (
      rect.top >= (isFullscreen || isNewPagePublicLink || isMobileEdit ? 0 : 64) &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    )
  }
  /**
   *  计算元素位置（核心定位逻辑）
   *  */
  private calculatePosition(value: string) {
    const view = this.plot.chart.views?.[0] || this.plot.chart
    // 饼图特殊处理
    if (CHART_CATEGORY.PIE.includes(this.chart.type)) {
      return this.getPieTooltipPosition(view, value)
    }
    if (this.plot instanceof DualAxes) {
      return this.getDualAxesTooltipPosition(view, value)
    }
    const types = view
      .scale()
      .getGeometries()
      .map(item => item.type)
    let point = { x: 0, y: 0 }
    if (!types.length) return point
    types.forEach(type => {
      if (type === 'interval' || type === 'point') {
        point = view
          .scale()
          .getGeometries()
          .find(item => item.type === type)
          .elements.find(item => item.data.field === value && (item.model.x || item.model.y))?.model
      }
    })
    // 处理柱状图和折线图,柱状图固定y轴位置
    const y = CHART_CATEGORY.COLUMN.includes(this.chart.type) ? 0 : [].concat(point?.y)?.[0]
    return { x: [].concat(point?.x)?.[0], y: y }
  }

  /**
   *  计算饼图元素位置
   *  */
  private getPieTooltipPosition(view, value: string) {
    const piePoint = view
      .scale()
      .getGeometries()[0]
      ?.elements.find(item => item.data.field === value)
      ?.getModel()
    if (!piePoint) {
      return { x: 0, y: 0 }
    }
    const coordinates = [
      { x: [].concat(piePoint.x)[0], y: piePoint.y[0] },
      { x: piePoint.x[0], y: piePoint.y[1] },
      { x: piePoint.x[1], y: piePoint.y[0] },
      { x: piePoint.x[1], y: piePoint.y[1] }
    ]
    const index = coordinates.findIndex(coord => {
      const items = this.plot.chart.getTooltipItems(coord)
      return items.some(item => item.data.field === value)
    })
    if (index !== -1) {
      return coordinates[index]
    } else {
      return {
        x: piePoint.x[0],
        y: piePoint.y[0]
      }
    }
  }

  /**
   * 获取双轴图表的 Tooltip 位置
   * @param view
   * @param value
   * @private
   */
  private getDualAxesTooltipPosition(view, value: string) {
    const xScale = view.getXScale()
    if (!xScale) return { x: 0, y: 0 }
    const values = xScale.values
    if (values.length < 2) {
      const point = view
        .getGeometries()?.[0]
        .elements[view.getGeometries()?.[0].elements?.length - 1].getModel()
      return point || { x: 0, y: 0 }
    }
    const [rangeStart, rangeEnd] = xScale.range
    const totalMonths = values.length
    const bandWidth = (rangeEnd - rangeStart) / totalMonths
    const index = values.indexOf(value)
    const xPos = rangeStart + bandWidth * (index + 0.5)
    return view.getCoordinate().convert({ x: xPos, y: 0 })
  }

  /**
   * 高亮指定元素
   * */
  private highlightElement(value: string) {
    if (CHART_CATEGORY.LINE.includes(this.chart.type)) return
    this.unHighlightPoint(value)
    this.plot.setState(
      this.getHighlightType(),
      (data: any) => data[this.config.xField] === value,
      true
    )
  }

  /**
   * 取消高亮
   * **/
  private unHighlightPoint(value?: string) {
    if (CHART_CATEGORY.LINE.includes(this.chart.type)) return
    this.plot.setState(
      this.getHighlightType(),
      (data: any) => data[this.config.xField] !== value,
      false
    )
  }
  private getHighlightType() {
    return 'active'
  }

  /**
   *  隐藏工具提示
   *  */
  private hideTooltip() {
    const container = this.getTooltipContainer()
    if (container) {
      container.style.display = 'none'
    }
  }

  /**
   *  获取工具提示容器
   *  */
  private getTooltipContainer() {
    const tooltipCtl = this.plot.chart.getController('tooltip')
    if (!tooltipCtl) {
      return
    }
    return tooltipCtl.tooltip?.cfg?.container
  }

  /**
   * 判断元素是否有父元素包含 'switch-hidden' 类
   * @param element
   */
  public hasParentWithSwitchHidden(element: HTMLElement) {
    let parent = element.parentElement
    while (parent) {
      if (parent.classList.contains('switch-hidden')) {
        return true
      }
      parent = parent.parentElement
    }
    return false
  }

  /**
   *  绑定事件监听
   *  */
  private bindEventListeners() {
    // 定义图表元素ID前缀数组
    // 图表在不同的显示页面可能有不同的ID前缀
    const chartElementIds = ['container-canvas-%s-common']
    let chartElement = null

    const sprintf = (fmt: string, ...args: unknown[]) => {
      let i = 0
      return fmt.replace(/%s/g, () => String(args[i++]))
    }
    // 查找图表元素
    for (const idPrefix of chartElementIds) {
      chartElement = document.getElementById(sprintf(idPrefix, this.chart.id))
      if (chartElement) break
    }
    // 绑定鼠标进入和离开事件
    const addMouseEvent = el => {
      el?.addEventListener('mouseenter', () => this.paused())
      el?.addEventListener('mouseleave', ev => {
        setTimeout(() => {
          // 获取鼠标位置
          const mouseX = ev.clientX
          const mouseY = ev.clientY
          // 获取div的边界信息
          const rect = el.getBoundingClientRect()
          // 判断鼠标位置是否在div内
          const isInside =
            mouseX >= rect.left + 10 &&
            mouseX <= rect.right - 10 &&
            mouseY >= rect.top + 10 &&
            mouseY <= rect.bottom - 10
          console.log(isInside)
          if (!isInside) {
            this.paused()
            this.resume()
          }
        }, 300)
      })
    }
    addMouseEvent(chartElement)
    const showViewDialog = document.getElementById(
      'container-viewDialog-' + this.chart.id + '-common'
    )
    if (showViewDialog && this.chart.container?.startsWith('container-viewDialog-')) {
      addMouseEvent(showViewDialog)
    }
    // 定义鼠标滚轮事件处理函数
    const handleMouseWheel = this.debounce(() => {
      CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
        // 如果元素有父元素不包含 'switch-hidden' 类，则继续轮播
        if (!this.hasParentWithSwitchHidden(instance.plot.chart.ele)) {
          instance.paused()
          instance.resume()
        }
      })
    }, 0)
    // 定义 touchmove 事件处理函数（移动端）
    const handleTouchMove = (event: TouchEvent) => {
      handleMouseWheel(event)
    }
    // 获取目标元素，优先全屏预览
    const targetDiv =
      document.getElementById('de-preview-content') ||
      document.getElementById('preview-canvas-main') ||
      document.getElementById('dv-main-center') ||
      document.getElementById('edit-canvas-main') ||
      document.getElementById('canvas-mark-line') ||
      document.getElementById('de-canvas-canvas-main')
    // 绑定目标元素的事件
    if (targetDiv) {
      targetDiv.removeEventListener('wheel', handleMouseWheel)
      targetDiv.addEventListener('wheel', handleMouseWheel)
      //移除和添加 touchmove 事件监听器（移动端）
      targetDiv.removeEventListener('touchmove', handleTouchMove)
      targetDiv.addEventListener('touchmove', handleTouchMove)
    }
    // 页面可见性控制
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'hidden') {
        CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
          instance.paused()
        })
      } else if (this.chartIsVisible) {
        CAROUSEL_MANAGER_INSTANCES?.forEach(instance => {
          instance.resume()
        })
      }
    })
    // 元素可视性观察（交叉观察器）
    this.setupIntersectionObserver()
    // 元素大小观察（大小观察器）
    this.setupResizeObserver()
  }

  /**
   * 设置暂停状态
   * */
  private setPaused(state: boolean) {
    this.states.paused = state
    state ? this.stop() : this.startCarousel()
  }
  /**
   * 设置交叉观察器
   * */
  private setupIntersectionObserver() {
    setTimeout(() => {
      // 监听元素可见性变化,全部可见时开始轮播
      if (!this.observers.get(this.plot.chart.ele.id)) {
        this.observers.set(
          this.plot.chart.ele.id,
          new IntersectionObserver(
            entries => {
              entries.forEach(entry => {
                if (entry.intersectionRatio < 0.7) {
                  this.paused()
                  this.chartIsVisible = false
                } else {
                  this.paused()
                  this.chartIsVisible = true
                  this.resume()
                }
              })
            },
            { threshold: [0.7] }
          )
        )
        this.observers.get(this.plot.chart.ele.id).observe(this.plot.chart.ele)
      }
    }, 100)
  }

  /**
   * 设置元素大小观察器
   * 当元素全部可见时
   * 图表的最外层元素
   * @private
   */
  private setupResizeObserver() {
    // 放大图表弹窗不需要监听
    if (this.plot.chart.ele.id.includes('viewDialog')) return
    // 创建防抖回调函数
    const debouncedCallback = (entries: ResizeObserverEntry[]) => {
      for (const entry of entries) {
        if (entry.target) {
          this.debounce(() => {
            this.paused()
            this.resume()
          }, 200)
        }
      }
    }
    // 监听元素大小, 发生变化时重新轮播
    if (!this.resizeObservers.get(this.plot.chart.ele.id)) {
      this.resizeObservers.set(this.plot.chart.ele.id, new ResizeObserver(debouncedCallback))
      this.resizeObservers.get(this.plot.chart.ele.id).observe(this.plot.chart.ele)
    }
  }

  /**
   * 更新配置
   * */
  private update(plot: Plot | DualAxes, chart: Chart, config: CarouselConfig) {
    this.stop()
    this.plot = plot
    this.chart = chart
    this.config = { ...this.config, ...config }
    this.currentIndex = 0
    this.init()
  }

  /**
   * 停止定时器
   * @private
   */
  private stop() {
    clearTimeout(this.timers.interval)
    clearTimeout(this.timers.carousel)
    this.timers = { interval: null, carousel: null }
  }

  /**
   * 销毁实例
   * */
  destroy() {
    this.stop()
    this.clearObserver()
    this.states.destroyed = true
    CAROUSEL_MANAGER_INSTANCES.delete(this.chart.container)
  }
  /**
   * 清除观察器
   * */
  clearObserver() {
    const observer = this.observers.get(this.plot.chart.ele.id)
    if (observer) {
      observer.disconnect()
      this.observers.delete(this.plot.chart.ele.id)
    }
    const resizeObservers = this.resizeObservers.get(this.plot.chart.ele.id)
    if (resizeObservers) {
      resizeObservers.disconnect()
      this.resizeObservers.delete(this.plot.chart.ele.id)
    }
  }
  /** 暂停 */
  paused() {
    this.hideTooltip()
    this.unHighlightPoint()
    this.setPaused(true)
  }

  /** 恢复 */
  resume() {
    this.setPaused(false)
  }

  /**
   * 防抖
   */
  private debounce(func: (...args: any[]) => void, delay: number): (...args: any[]) => void {
    let timeout: number | null = null
    return (...args: any[]) => {
      if (timeout) clearTimeout(timeout)
      timeout = window.setTimeout(() => {
        func(...args)
      }, delay)
    }
  }
}

export default ChartCarouselTooltip
