<template>
  <h3> AudioTagWebAudio </h3>
  <button @click="fetchAudio">Fetch </button> <br> fetched:{{ readyState }}<br><br>
  <button v-if="readyState" @click="startAudio"> Play </button>
  <button @click="mute">Mute</button>
  <button @click="unmute">Unmute</button>
  <p v-if="audioNodes.length > 0">
  <!-- eslint-disable-next-line vue/require-v-for-key -->
  </p><div v-for="node in audioNodes">
    duration: {{ node.buffer?.duration }}
    volume: {{ safeVolume }}
    length: {{ node.buffer?.length }}
    channels: {{ node.buffer?.numberOfChannels }}
    duration: {{ node.buffer?.duration }}
  </div>
</template>
<script lang="ts">
import type { Logger } from 'pino'
import { defineComponent, ref, watch, computed } from 'vue'
import { createAudioSource } from '~/lib/AudioFunctions'
import { ensureAudio, useAudioStore } from '~/stores/audio'

export default defineComponent({
  name: 'AudioTagWebAudio',
  props: {
    src: {
      type: String,
      required: true
    },
    volume: {
      type: Number,
      default: 0.0
    },
    play: {
      type: Boolean,
      default: false
    },
    masterGain: {
      type: GainNode,
      default: null,
      require: true
    }
  },
  emits: ['canplay'],
  setup (props, { emit: $emit }) {
    const logger = useNuxtApp().$logger as Logger
    const ctx = useAudioStore().getContext()

    const readyState = ref(false)
    const fadingState = ref(false)
    const audioNodes = ref([] as Array<AudioBufferSourceNode>)

    let gainNode: GainNode | null = null
    let audioElement: AudioBufferSourceNode | null = null

    const safeVolume = computed((): number => {
      const volumeVal = props.volume as number
      if (volumeVal >= 0 && volumeVal <= 1.2589254117941673) { return volumeVal }
      return Math.abs(volumeVal - 0) < Math.abs(volumeVal - 1) ? 0 : 1.2589254117941673
    })

    onMounted(() => {
      fetchAudio()
    })
    onBeforeUnmount(() => {
      if (audioElement instanceof AudioBufferSourceNode) {
        try {
          if (audioElement.hasStarted) {
            audioElement.stop()
          }
        } catch (e) {
          logger.warn('Audio element could not be stopped cleanly:', e)
        }

        try {
          audioElement.disconnect()
        } catch (e) {
          logger.warn('Audio element could not be disconnected:', e)
        }

        audioElement = null // ✅ freigeben
      }

      if (gainNode instanceof GainNode) {
        try {
          gainNode.gain.cancelScheduledValues(ctx.currentTime)
          gainNode.disconnect()
        } catch (e) {
          logger.warn('Gain node cleanup failed:', e)
        }

        gainNode = null // ✅ freigeben
      }
    })

    const emitReady = () => {
      $emit('canplay')
      readyState.value = true
    }
    const mute = (value: any) => {
      gainNode?.gain.setValueAtTime(0, ctx.currentTime)
    }

    const unmute = (value: any) => {
      gainNode?.gain.setValueAtTime(1, ctx.currentTime)
    }
    const connectGainNode = (source:AudioBufferSourceNode) => {
      gainNode = ctx.createGain()
      gainNode.gain.setValueAtTime(0, ctx.currentTime)
      source.connect(gainNode)
      gainNode.connect(props.masterGain)
    }
    const fetchAudio = async () => {
      audioElement = null
      audioElement = await createAudioSource(ctx, props.src)

      audioNodes.value.push(audioElement)
      if (audioElement instanceof AudioBufferSourceNode) {
        connectGainNode(audioElement)
        emitReady()
      }
    }
    const recreateSourceNode = () => {
      if (!ctx || !audioElement?.buffer || !gainNode) {
        logger.error('Cannot recreate source node: missing context, buffer, or gain node.')
        return
      }

      // Erstelle neue AudioBufferSourceNode
      const newSource = ctx.createBufferSource()
      newSource.buffer = audioElement.buffer
      newSource.playbackRate.value = audioElement.playbackRate.value || 1

      // Optional: Übertrage weitere Parameter falls nötig (looping, detune, etc.)
      newSource.loop = audioElement.loop ?? false
      // Verbinde mit GainNode
      newSource.connect(gainNode)
      // Ersetze die alte Referenz
      audioElement = newSource
      // Reset hasStarted
      newSource.hasStarted = false
    }
    const startAudio = async () => {
      await ensureAudio()
      if (props.play === false) {
        return
      }

      if (gainNode instanceof GainNode && audioElement instanceof AudioBufferSourceNode) {
        if ((audioElement as any).hasStarted) {
          recreateSourceNode()
        }
        gainNode.gain.setValueAtTime(0, ctx.currentTime)
        audioElement.playbackRate.value = 1
        try {
          (audioElement as any).hasStarted = true
          audioElement.start()
        } catch (error) {
          (audioElement as any).hasStarted = false
          audioElement.playbackRate.value = 1
        }
        gainNode.gain.linearRampToValueAtTime(safeVolume.value, ctx.currentTime + 5)
      } else {
        logger.error('Missing required audioNodes.')
      }
    }
    const stopAudio = () => {
      if (gainNode instanceof GainNode && audioElement instanceof AudioBufferSourceNode) {
        try {
          // Sanftes Fade-Out
          const currentTime = ctx.currentTime
          gainNode.gain.cancelScheduledValues(currentTime)
          gainNode.gain.setValueAtTime(gainNode.gain.value, currentTime)
          gainNode.gain.linearRampToValueAtTime(0, currentTime + 0.5) // 0.5 Sek. Fade-Out

          // Stoppen nach dem Fade-Out
          setTimeout(() => {
            try {
              if (audioElement instanceof AudioBufferSourceNode && audioElement.hasStarted) {
                audioElement?.stop()
                const audioElementObj = audioElement as any
              }
            } catch (error) {
              logger.error('Error stopping audioElement:', { error })
            }
          }, 500)
        } catch (error) {
          logger.warn('Error during stopAudio:', error)
        }
      } else {
        logger.error('Missing required audioNodes for stopping.')
      }
    }

    watch(() => props.play, () => {
      if (props.play && audioElement && readyState.value) {
        try {
          startAudio()
        } catch (error) {
          logger.warn('Error while start audio', error)
        }
      } else if (audioElement && !props.play) {
        stopAudio()
      }
    })
    watch(() => props.src, async () => {
      if (props.src === '') {
        logger.warn('Audio-Source is empty. Please check your props.')
        return
      }
      await fetchAudio()
    })
    watch(() => props.volume, () => {
      const bla = 0
      if (!readyState.value) {
        logger.warn('Audio is not yet ready for playing.')
        return
      }
      if (props.play) {
        if (gainNode instanceof GainNode) {
          gainNode.gain.exponentialRampToValueAtTime(safeVolume.value, ctx.currentTime + 15)
        }
      }
    })

    return {
      safeVolume,
      emitReady,
      fetchAudio,
      readyState,
      fadingState,
      startAudio,
      gainNode,
      audioNodes,
      mute,
      unmute
    }
  }
})
</script>
