import Taro from "@tarojs/taro";
import {View, Image} from '@tarojs/components'
import {Component, ReactNode} from "react";
import indicator from './loading.svg'
import './style.scss'

// 动画状态
type Status =
  | 'dismissed'
  | 'forward'
  | 'reverse'
  | 'completed'

interface Props {
  enable?: boolean  // 控制显现
  overlay?: boolean  // 页面覆盖
  block?: boolean // 块级
}

interface State {
  status?: Status
  background?: Record<string, unknown>,
  rotation?: Record<string, unknown>,
}

type StateSetter = (state: State) => void

type Controller = {
  setTick: (enabled: boolean | undefined) => void
  clear: () => void
}

function createController(setState: StateSetter): Controller {
  const background = Taro.createAnimation({duration: 600})
  const rotation = Taro.createAnimation({duration: 600})
  let rotateTimer: ReturnType<typeof setTimeout> | undefined
  let status: Status | undefined

  const notifyListener = () => setState({
    status,
    background: background.export(),
    rotation: rotation.export(),
  })

  const clearAnimation = (notify = true) => {
    // 清空旋转动画定时器
    if (rotateTimer != null) {
      clearTimeout(rotateTimer)
      rotateTimer = undefined
    }

    // 清空动画
    background.step().export()
    rotation.step().export()

    // 通知 UI 刷新
    if (notify) {
      notifyListener()
    }
  }

  const setAnimation = (opacity: number) => {
    // 停止旋转动画
    if (rotateTimer != null) {
      clearTimeout(rotateTimer)
    }

    // 旋转动画定时器
    const rotate = () => {
      rotation.opacity(opacity).rotate(360).step({duration: 600})
      notifyListener()
      rotateTimer = setTimeout(rotate, 600)
    }

    // 背景动画
    background.backgroundColor(`rgba(255,255,255,${opacity})`).step()

    // 启动旋转动画
    rotate()
  }

  const onFinish = (opacity: number, nextStatus: Status) => {
    const lockStatus = status
    if (lockStatus === status) {
      background.backgroundColor(`rgba(255,255,255,${opacity})`).step({duration: 0})
      if (nextStatus === 'dismissed') {
        clearAnimation()
      }
      status = nextStatus
      notifyListener()
    }
  }

  const setStatus = (newStatus: Status, opacity: number) => {
    if (status !== newStatus) {
      status = newStatus
      setAnimation(opacity)
      if (status === 'reverse') {
        onFinish(0, 'dismissed')
      } else if (status === 'forward') {
        onFinish(1, 'completed')
      }
    }
  }

  const setTick = (enabled?: boolean) => {
    if (enabled !== true) {
      if (status === 'dismissed') {
        clearAnimation()
      } else if (status !== 'reverse') {
        setStatus('reverse', 0)
      }
    } else if (status === 'completed') {
      // ignore
    } else if (status !== 'forward') {
      setStatus('forward', 1)
    }
  }

  return {
    setTick,
    clear() {
      clearAnimation(false)
    },
  }
}

export default class Spin extends Component<Props, State> {
  constructor(props) {
    super(props)
    this.controller = createController(this.onAnimation)
    this.state = {}
  }

  controller: Controller

  onAnimation = (state: State) => {
    this.setState((s) => ({...s, ...state}))
  }

  componentDidMount(): void {
    this.controller.setTick(this.props.enable)
  }

  componentDidUpdate(): void {
    this.controller.setTick(this.props.enable)
  }

  componentWillUnmount(): void {
    this.controller.clear()
  }

  shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean {
    return nextProps.enable !== this.props.enable
      || nextProps.overlay !== this.props.overlay
      || nextState.status != this.state.status
  }

  render(): ReactNode {
    return (
      <View
        className={`spinner-wrapper ${this.state.status} ${this.props.overlay && 'is-fixed'} ${this.props.block && 'is-block'}`}>
        <View className={`spinner ${this.state.status}`}>
          <Image className="spinner-icon" src={indicator}/>
        </View>
      </View>
    )
  }
}
