<!--
 * @Author: ChenChanghua
 * @Date: 2023-03-08 14:52:17
 * @Description: file content
-->
<script setup>
import { ref, inject, shallowRef, provide } from "vue";
import { ListWidget, HORIZONTAL, useFocusHub, getKeyFramesGroup } from "jsview";
import Item from "./Item.vue";
import CreepFocus from "./CreepFocus.vue";
import redCircle from "./assets/redCircle.png";

const DURATION = 200;
const props = defineProps({
  data: Object,
  query: Object,
  onAction: Object,
  onItemEdge: Function,
});
const focusHub = useFocusHub();
const folded = ref(true);
const creepFocus = ref(null);
const showFocus = ref(true);
const styleShell = getKeyFramesGroup();
const itemKeepScale = ref(false);
provide("itemKeepScale", itemKeepScale);

const focused = ref(false);
const width = ref(props.data.width);
const widgetRef = shallowRef(null);
const widgetLeft = ref(0);

/**
 * 关键代码
 * 焦点快速往复移动时, 复用一个子widget会导致slideToDiv焦点位置的偏移, 因此每次缩放时都创建新的widget
 */
const changeToken = ref(0);

const focusRectController = {
  onFocusChange: (rect) => {
    creepFocus.value?.changeRect(rect);
  },
};
const focusFrameController = inject("focusFrameController");

const provideData = () => {
  return props.data.childTab.list;
};

const measures = (data) => {
  return {
    width: data.width,
    height: data.height,
    marginRight: data.marginRight,
  };
};

// 注册回调
const onFocus = () => {
  focused.value = true;
  folded.value = true;
  if (props.data.expandWhenFocus) {
    expand();
  }
};

const foldSubTab = () => {
  if (!folded.value) {
    if (width.value == props.data.childTab.width) {
      changeToken.value++;
      width.value = props.data.width;
      let index =
        widgetRef.value?.getCurrentFocusIndex() ?? props.data.initFocus;

      const anchor = getAnchor(index);
      widgetLeft.value = -Math.round(
        (props.data.childTab.width - props.data.width) * anchor
      );

      //创建动画
      const anim = getClipAnimation(
        props.data.childTab.width,
        width.value,
        anchor
      );
      clipAnimStr.value = anim.clip;
      slideAnimStr.value = anim.slide;

      props.query.updateItemSize(
        props.query.index,
        {
          width: width.value,
          height: props.data.height,
        },
        {
          duration: DURATION,
          anchor: getAnchor(widgetRef.value.getCurrentFocusIndex()),
          onEnd: (cancel) => {
            if (!(focused.value && props.data.expandWhenFocus)) {
              folded.value = true;
            }
            console.log("fold anim end.", cancel);
          },
        }
      );
      focusFrameController.onItemSizeChange(false);
    }
    focusHub.returnFocusToParent();
  }
};

const onBlur = () => {
  if (!itemKeepScale.value) {
    showFocus.value = false;
  }
  focused.value = false;
  foldSubTab();
};

let animToken = 1;
const clipAnimStr = ref("");
const slideAnimStr = ref("");
const animationCache = {};
const getClipAnimation = (from, to, anchor) => {
  animToken++;
  const name = "foldableItemClipAnim" + animToken;
  if (animationCache.hasOwnProperty(name)) {
    styleShell.removeRule(name);
    delete animationCache[name];
  }
  const clipAnimStr = `@keyframes ${name} { from { width: ${from}; } to { width: ${to}; } }`;
  animationCache[name] = clipAnimStr;
  styleShell.insertRule(clipAnimStr);

  //平移动画
  const anchorPosition = Math.round((to - from) * anchor);
  const slideName = "foldableItemSlideAnim" + animToken;
  if (animationCache.hasOwnProperty(slideName)) {
    styleShell.removeRule(slideName);
    delete animationCache[slideName];
  }
  const slideAnimStr = `@keyframes ${slideName} { from { transform: translate3d(${-anchorPosition},0,0); } to { transform: translate3d(0,0,0); } }`;

  animationCache[slideName] = slideAnimStr;
  styleShell.insertRule(slideAnimStr);

  return {
    clip: `${name} ${DURATION / 1000}s`,
    slide: `${slideName} ${DURATION / 1000}s`,
  };
};

const getAnchor = (index) => {
  let anchor = 0;
  const l = props.data.childTab.list;
  if (index <= 0) {
    anchor = 0;
  } else if (index > l.length - 1) {
    anchor = 1;
  } else {
    let focusMiddle = 0;
    for (let i = 0; i <= index - 1; ++i) {
      focusMiddle += l[i].width + l[i].marginRight;
    }
    focusMiddle += l[index].width / 2;
    const scaleRate = props.data.childTab.width / props.data.width;
    const targetPercent = focusMiddle / props.data.childTab.width; // 居中目标位置的百分比
    /**
     * anchor计算说明
     * 考虑[0,1]的线段, 以a(0 <= a <= 1)为锚点, 放大n倍(n > 1)
     * 坐标的映射关系为 x -> n(x - a) + a
     * 此处我们想要的是目标焦点的中线对齐0.5, 即targetPercent放大后的位置是0.5
     * 所以方程为 scaleRate * (targetPercent - anchor) + anchor = 0.5
     */
    anchor = (scaleRate * targetPercent - 0.5) / (scaleRate - 1);
  }
  return anchor;
};

const expand = () => {
  if (folded.value) {
    changeToken.value++;

    itemKeepScale.value = false;
    showFocus.value = true;
    widgetLeft.value = 0;
    /**
     * 动画未结束时, widgetLeft不为0, 此时又获焦那一帧同时设置的widgetLeft值未同步到div, slideToDiv获取的就不是展开后item的正确位置
     */
    folded.value = false;
    width.value = props.data.childTab.width;
    //为了居中而计算锚点
    const anchor = getAnchor(props.data.initFocus);
    //创建动画
    const anim = getClipAnimation(props.data.width, width.value, anchor);
    clipAnimStr.value = anim.clip;
    slideAnimStr.value = anim.slide;

    props.query.updateItemSize(
      props.query.index,
      {
        width: width.value,
        height: props.data.height,
      },
      {
        anchor,
        duration: DURATION,
        onEnd: () => {
          console.log("unfold anim end.");
        },
      }
    );

    focusHub.setFocus(props.data.name + changeToken.value);
    focusFrameController.onItemSizeChange(true);

    // focusFrameController.focusVisible(false);
  }
};

const onClick = () => {
  if (!props.data.expandWhenFocus) {
    expand();
  }
};
props.onAction.register("onFocus", onFocus);
props.onAction.register("onBlur", onBlur);
props.onAction.register("onClick", onClick);

const onKeyDown = (ev) => {
  if (ev.keyCode === 10000 || ev.keyCode === 8 || ev.keyCode === 27) {
    if (!props.data.expandWhenFocus) {
      itemKeepScale.value = true;
      foldSubTab();
    }
    return true;
  }
  return false;
};
</script>

<template>
  <div
    v-if="folded"
    :style="{
      fontSize: 40,
      width: data.width,
      height: data.height,
      color: '#FFFFFF',
      textAlign: 'center',
      lineHeight: data.height,
      transform: focused ? 'scale3d(1.1,1.1,1)' : 'scale3d(1,1,1)',
    }"
  >
    {{ data.content }}
  </div>
  <div
    v-else
    :style="{
      width: width,
      height: data.height,
      overflow: 'hidden',
      animation: clipAnimStr,
    }"
  >
    <jsv-focus-block
      :onAction="{
        onKeyDown,
      }"
      :style="{
        left: widgetLeft,
        width: data.childTab.width,
        height: data.height,
        backgroundColor: '#00990099',
        borderRadius: 10,
        animation: slideAnimStr,
      }"
    >
      <list-widget
        :key="changeToken"
        ref="widgetRef"
        :name="data.name + changeToken"
        :width="data.childTab.width"
        :height="data.height"
        :provideData="provideData"
        :direction="HORIZONTAL"
        :measures="measures"
        :onEdge="onItemEdge"
        :initFocusId="data.initFocus"
      >
        <template #renderItem="{ data, onAction, query }">
          <item
            :focusRectController="focusRectController"
            :data="data"
            :onAction="onAction"
            :query="query"
          />
        </template>
        <template #background>
          <creep-focus v-if="showFocus" ref="creepFocus" :imgUrl="redCircle" :id="'child' + props.query.index">
          </creep-focus>
        </template>
      </list-widget>
    </jsv-focus-block>
  </div>
</template>
