import { ref, Ref, watch, onUnmounted, computed, onMounted } from 'vue';
import tickWav from './audio/tick.wav';
import tockWav from './audio/tock.wav';

/*  播放相关 */
export default function useMetronome(
  beatVal: Ref<string>,
  beatSymbol: Ref<string>
) {
  /* 音量 */
  const volumeNum = ref(100);
  watch(volumeNum, () => {
    changeVolume(volumeNum.value / 100);
  });
  /* 播放状态 */
  const playState = ref<'play' | 'pause'>('pause');
  /* 速度 */
  const speedNum = ref(90);
  /* 音频hooks */
  const { start, stop, changeVolume } = useHandleAudio([tickWav, tockWav]);
  onUnmounted(() => {
    pausePlay();
  });
  // 开始播放
  async function startPlay() {
    (await start(computeTimeArr.value, {
      volume: volumeNum.value / 100,
      playbackRate: speedNum.value / 60
    })) && (playState.value = 'play');
  }
  //暂停播放
  function pausePlay() {
    stop();
    playState.value = 'pause';
  }
  const computeTimeArr = computed(() => {
    if (beatSymbol.value === '1') {
      return beatVal.value.split('-');
    }
    return beatSymbol.value.split('-');
  });

  return {
    volumeNum,
    playState,
    speedNum,
    startPlay,
    pausePlay
  };
}

import Crunker from 'crunker';
function useHandleAudio(files: [File | Blob | string, File | Blob | string]) {
  const crunker = new Crunker();
  async function handleBatetimeToAudio(
    files: [File | Blob | string, File | Blob | string],
    timeArr: string[],
    playbackRate: number
  ) {
    try {
      const buffersArr = await crunker.fetchAudio(...files);
      const tickAudioBuff = buffersArr[0];
      const tockAudioBuff = buffersArr[1];
      let mergeAudio: AudioBuffer | undefined;
      /* 处理音频合并 */
      timeArr.map((time, index) => {
        const timeNum = Number(time);
        let nowBuff =
          index === 0 && timeNum !== 0 ? tickAudioBuff : tockAudioBuff;
        /*  当速度过快时候 响的时候大于整个拍子时候 对响进行裁剪  当间隔小于响的时候也进行裁剪 */
        if (
          1 / playbackRate - nowBuff.duration * timeArr.length <= 0 ||
          (timeNum || 1) / playbackRate - nowBuff.duration <= 0
        ) {
          nowBuff = crunker.sliceAudio(
            nowBuff,
            0,
            nowBuff.duration / playbackRate,
            0,
            0.12
          );
        }
        mergeAudio
          ? (mergeAudio = crunker.concatAudio([mergeAudio, nowBuff]))
          : (mergeAudio = nowBuff);
        mergeAudio = crunker.padAudio(
          mergeAudio,
          mergeAudio.duration - 0.01, // 预留0.01的安全距离 crunker这里有bug duration时长精确度不够
          (timeNum || 1) / playbackRate - nowBuff.duration
        );
      });
      return mergeAudio;
    } catch (err) {
      console.log(err);
      return undefined;
    }
  }

  const audioCtx = crunker.context;
  let audioSourceNode: AudioBufferSourceNode | null;
  let audioGainNode: GainNode | null;
  async function start(
    timeArr: string[],
    opt: { volume: number; playbackRate: number }
  ) {
    const buffer = await handleBatetimeToAudio(
      files,
      timeArr,
      opt.playbackRate
    );
    if (buffer) {
      audioSourceNode = audioCtx.createBufferSource();
      audioSourceNode.buffer = buffer;
      audioGainNode = audioCtx.createGain();
      audioSourceNode.connect(audioGainNode);
      audioGainNode.connect(audioCtx.destination);
      audioGainNode.gain.value = opt.volume;
      audioSourceNode.loop = true;
      audioSourceNode.start();
      return true;
    } else {
      return false;
    }
  }
  function stop() {
    audioSourceNode?.stop();
    audioSourceNode = null;
    audioGainNode = null;
  }
  function changeVolume(volume: number) {
    audioGainNode && (audioGainNode.gain.value = volume);
  }
  return {
    start,
    stop,
    changeVolume
  };
}

// 缓存
const localStorageName = 'metronomePos';
export function getCachePos(
  useId: string
): null | undefined | Record<string, any> {
  const localCachePos = localStorage.getItem(localStorageName);
  if (localCachePos) {
    try {
      return JSON.parse(localCachePos)[useId + localStorageName];
    } catch {
      return null;
    }
  }
  return null;
}
export function setCachePos(useId: string, pos: Record<string, any>) {
  const localCachePos = localStorage.getItem(localStorageName);
  let cachePosObj: Record<string, any> = {};
  if (localCachePos) {
    try {
      cachePosObj = JSON.parse(localCachePos);
    } catch {
      //
    }
  }
  cachePosObj[useId + localStorageName] = pos;
  localStorage.setItem(localStorageName, JSON.stringify(cachePosObj));
}
