<script>
import {connectASR_WebSocket, getASR_Token} from '@/api/diagnosis';
import {generateUUID} from '@/utils';

let websocket;
let dispose = () => {};

let onAudioConnect = () => {};
let onAudioCleanup = () => {};
let onSocketCleanup = () => {};

export default {
  name: 'SoundRecorder',
  props: {
    enabled: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      taskId: '',

      recording: false,
      loading: false,
      proof: {token: '', appKey: ''},
    };
  },
  beforeMount() {
    dispose = (event) => {
      switch (event.type) {
        case 'open':
          return this.startTranscription(this.taskId);
        case 'close': {
          websocket = null;
          this.recording = false;
          this.loading = false;
          break;
        }
        case 'message':
          return this.analysis(JSON.parse(event.data));
        case 'audioprocess':
          return this.send(event.inputBuffer.getChannelData(0));
      }
      console.log(event, 'log:');
    };
  },
  beforeDestroy() {
    this.stop();
    this.disconnect();
  },
  watch: {
    enabled(value) {
      if (!value) this.stop();
    },
  },
  methods: {
    handle() {
      if (this.loading) return;
      try { if (this.recording) this.stop(); else this.start(); } catch (e) {
        this.$message.warning(`出错了，请重试`);
        this.disconnect();
        console.log('[sr-ars]', e);
      }
    },
    async connect() {
      if (!this.proof || !this.proof.token) this.proof = await getASR_Token();
      websocket = await connectASR_WebSocket(this.proof);
      websocket.addEventListener('open', dispose);
      websocket.addEventListener('close', dispose);
      websocket.addEventListener('message', dispose);
      websocket.addEventListener('error', dispose);
      onSocketCleanup = () => {
        websocket.removeEventListener('open', dispose);
        websocket.removeEventListener('close', dispose);
        websocket.removeEventListener('message', dispose);
        websocket.removeEventListener('error', dispose);
        onSocketCleanup = () => {};
      };
    },
    async disconnect() {
      this.loading = false;
      this.recording = false;
      onSocketCleanup();
    },
    async start() {
      if (onAudioCleanup) onAudioCleanup();

      this.loading = true;
      this.taskId = generateUUID();

      const stream = await navigator.mediaDevices.getUserMedia({audio: true});
      const context = new window.AudioContext({sampleRate: 16000});
      const input = context.createMediaStreamSource(stream);
      const processor = context.createScriptProcessor(2048, 1, 1);
      processor.addEventListener('audioprocess', dispose);

      onAudioCleanup = () => {
        this.loading = false;
        processor.removeEventListener('audioprocess', dispose);
        processor.disconnect();
        input.disconnect();
        stream.getTracks().forEach(track => track.stop());
        context.close();
        onAudioCleanup = () => {};
      };

      onAudioConnect = () => {
        this.loading = false;
        input.connect(processor);
        processor.connect(context.destination);
        onAudioConnect = () => {};
      };

      if (!websocket || websocket.readyState !== WebSocket.OPEN) await this.connect();
      else if (this.recording) onAudioConnect();
    },
    async stop() {
      if (!websocket || websocket.readyState !== WebSocket.OPEN) return;
      websocket.send(JSON.stringify({
        header: {
          appkey: this.proof.appKey,
          namespace: 'SpeechTranscriber',
          name: 'StopTranscription',
          task_id: this.taskId,
          message_id: generateUUID(),
        },
      }));
    },

    startTranscription(taskId = generateUUID()) {
      if (!websocket || websocket.readyState !== WebSocket.OPEN) return;
      websocket.send(JSON.stringify({
        header: {
          appkey: this.proof.appKey,
          namespace: 'SpeechTranscriber',
          name: 'StartTranscription',
          task_id: taskId,
          message_id: generateUUID(),
        },
        payload: {
          'format': 'pcm',
          'sample_rate': 16000,
          'enable_intermediate_result': true,
          'enable_punctuation_prediction': true,
          'enable_inverse_text_normalization': true,
        },
      }));
    },
    send(data) {
      if (!websocket || websocket.readyState !== WebSocket.OPEN) return;
      const inputData16 = new Int16Array(data.length);
      for (let i = 0; i < data.length; ++i) {
        inputData16[i] = Math.max(-1, Math.min(1, data[i])) * 0x7FFF; // PCM 16-bit
      }
      websocket.send(inputData16.buffer);
      console.log('[sr-ars] 发送音频数据块');
    },
    analysis(message) {
      switch (message.header.name) {
        case 'TranscriptionStarted': {
          this.recording = true;
          onAudioConnect();
          break;
        }
        case 'TranscriptionCompleted': {
          this.recording = false;
          onAudioCleanup();
          break;
        }
        case 'SentenceBegin': {
          const {index, time} = message.payload;
          console.log(`[sr-ars]:message: 一句话开始：[${index}(${time} - )]`);
          this.$emit('begin');
          break;
        }
        case 'SentenceEnd': {
          const {index, time, result, begin_time} = message.payload;
          console.log(`[sr-ars]:message: 一句话结束：${result} [${index}(${begin_time} - ${time})]`);
          this.$emit('finish', result);
          break;
        }
        case 'TranscriptionResultChanged': {
          const {index, time, result} = message.payload;
          console.log(`[sr-ars]:message: 识别结果变化：${result} [${index}(${time})]`);
          this.$emit('change', result);
          break;
        }
        default: {
          console.log('[sr-ars]:message', message);
        }
      }
    },
  },
};
</script>

<template>
  <div style="z-index: 11;">
    <slot>
      <div class="sr-wrapper" :class="{recording, loading}" @click="handle()">
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">

          <circle v-if="loading" cx="12" cy="12" r="5" fill="none" stroke="white" stroke-width="1"></circle>
          <rect v-if="!loading" x="9" y="6" :width="recording? 10 : 6" height="10" :rx="recording ? 5 : 3"
                fill="white"/>
          <path v-if="!loading"
                d="M6.75 12.75V13C6.75 15.8995 9.1005 18.25 12 18.25C14.8995 18.25 17.25 15.8995 17.25 13V12.75"
                stroke="white" stroke-width="1.5" stroke-linecap="round"/>
        </svg>
      </div>
    </slot>
  </div>
</template>

<style scoped lang="scss">
.sr-wrapper {
  position: relative;
  width: 24px;
  height: 24px;
  background: #FF3359;
  border-radius: 50%;
  transform: scale(1.5);
  cursor: pointer;
  box-shadow: 0 4px 50px #FF3359;

  &.recording {
    svg {
      rect {
        transform: translate(-2px, 1px);
        animation: recording 2s infinite 0.1s;
      }

      path {
        transform: scale(0.7);
        opacity: 0;
      }

    }
  }

  &.loading {
    cursor: not-allowed;

    svg {
      animation: loading-rotate 2s linear infinite;
    }
  }

  svg {
    rect {
      stroke-width: 0;
      stroke: white;
      transition: all 0.2s ease;
      transform-origin: center;
    }

    circle {
      stroke-linecap: round;
      animation: loading-dash 1.5s ease-in-out infinite;
    }

    path {
      transition: all 0.2s ease;
      transform-origin: center;
    }
  }

  @keyframes recording {
    50% {
      stroke-width: 2;
    }
  }
}
</style>