import { onUnmounted, ref, Ref, watch } from "vue"
import { Tween, Easing } from '@tweenjs/tween.js'
import { inRevise, startAnimation } from "@/utils/shared.ts"
import { Color, RGB } from "three"
import { DC } from "@/deconstruction"
import { Shape } from "konva/lib/Shape"

export const animation = <T extends object>(origin: T, target: T, update?: (data: T) => void) => {
  let isStop = false
  const stop = () => {
    tw.stop()
    isStop = true
  }

  const tw = new Tween(origin)
  .to(target, 300)
  .easing(easing)
  .start()
  .onComplete(() => {
    stop()
  })
  if (update) {
    tw.onUpdate(() => {
      update && update(origin)
    })
  }

  const start = () => {
    requestAnimationFrame(() => {
      tw.update()
      isStop || start()
    })
  }
  start()
  return stop
}

const pickColors = <T extends object>(origin: T): Record<string, RGB>  => {
  const originColors = {} as any
  for (const [key, val] of Object.entries(origin)) {
    if (typeof val === 'string') {
      if (val.startsWith('#') || val.startsWith('rgb')) {
        originColors[key] = new Color(val).getRGB({} as any)
      } 
    }
  }
  return originColors
}
const resumeColors = <T extends object>(origin: T, colors: Record<string, RGB>) => {
  for (const [key, val] of Object.entries(colors)) {
    (origin as any)[key] = `#${new Color(val.r, val.g, val.b).getHexString()}`
  }
}

const easing = Easing.Quadratic.InOut
const animationProperty = <T extends object>(origin: T, target: T, update?: (data: T) => void) => {
  let isStop = false
  const stop = () => {
    numTw.stop()
    colorTw.stop()
    isStop = true
  }
  const oColors = pickColors(origin);
  const tColors = pickColors(target)
  const tOrigin = {...origin}
  const tTarget = {...target}

  for (const key in oColors) {
    if (!(key in tColors)) {
      ;(origin as any)[key] = null
      delete oColors[key]
    }
  }

  for (const key in tOrigin) {
    if (typeof tOrigin[key] === 'string') {
      delete tOrigin[key]
    }
  }
  for (const key in tTarget) {
    if (typeof tTarget[key] === 'string') {
      delete tTarget[key]
    }
  }

  let count = 0
  const tw = (origin: any, target: any) => {
    ++count
    return new Tween(origin)
      .to(target, 300)
      .easing(easing)
      .start()
      .onComplete(() => {
        if (--count === 0) stop()
      })
  }

  const numTw = tw(tOrigin, tTarget)
    .onUpdate(() => {
      Object.assign(origin, tOrigin)
      update && update(origin)
    })
  
  const colorTw = tw(oColors, tColors)
    .onUpdate(() => {
      resumeColors(origin, oColors)
      update && update(origin)
    })

  const start = () => {
    requestAnimationFrame(() => {
      numTw.update()
      colorTw.update()
      isStop || start()
    })
  }
  start()
  return stop
}

export const useAniamtion = <T extends object>(data: Ref<T>) => {
  const atData = ref(JSON.parse(JSON.stringify(data.value))) as Ref<T>
  let animationStop: () => void
  let isPause = false
  watch(data, (newData) => {
    if (!inRevise(newData, atData.value)) return;
    animationStop! && animationStop()
    if (isPause) {
      atData.value = newData
    } else {
      animationStop = animationProperty(atData.value, newData)
    }
  })

  onUnmounted(() => {
    animationStop && animationStop()
  })
  return [
    atData, () => isPause = true, () => isPause = false
  ] as const
}

type DA = DC<Shape> | undefined
export const useDashAnimation = (shapes: Ref<DA | DA[]>) => {
  watch(
    () => (Array.isArray(shapes.value) ? shapes.value : [shapes.value]).map((i) => i),
    (shapes, _, onCleanup) => {
      for (const shape of shapes) {
        if (!shape) continue;
        const $shape = shape.getNode();
        let i = 0;
        onCleanup(
          startAnimation(() => {
            $shape.dashOffset(i--);
          })
        );
      }
    }
  );
}