<template>
    <div class="image-melody">
        <img ref="image" hidden />

        <transition name="fade" mode="out-in">
            <div
                v-if="step === 0"
                :class="['drop-zone', { active: isOverDropZone }]"
                ref="dropZone"
                key="drop"
                @click="open"
            >
                <svg-util-spinner v-if="uploading" />
                <p v-else>Drop PNG or JPG</p>
            </div>

            <div v-else-if="step === 1" class="images" key="images">
                <canvas ref="viewCanvas" />
                <canvas ref="processCanvas" />
            </div>
        </transition>

        <div v-if="processedData" class="audio">
            <div class="controls">
                <btn @click="onPlayClick" class="play">Play</btn>
                <btn @click="onStopClick">Stop</btn>
            </div>

            <button @click="onResetClick" class="reset">Reset</button>
        </div>
    </div>
</template>

<script setup>
import {useDropZone, useFileDialog} from '@vueuse/core'
import resizeToFit from 'intrinsic-scale'
import { ref, watch } from 'vue'
import Btn from '@shared/Btn.vue'
import * as Tone from 'tone'

const CANVAS_SIZE = 500
const IMAGE_RESOLUTION = 10

const dropZone = ref()
const step = ref(0)
const uploading = ref()
const imageData = ref()
const image = ref()
const viewCanvas = ref()
const processCanvas = ref()
const processedData = ref()

// Image Upload
const uploadImage = async (files) => {
  if (!files?.[0]) return

  uploading.value = true
  await new Promise((res) => {
      const reader = new FileReader()

      reader.onload = (e) => {
        imageData.value = e.target.result
        res()
      }

      reader.readAsDataURL(files[0])
  })
}
const { isOverDropZone } = useDropZone(dropZone, {
    onDrop: uploadImage,
    dataTypes: ['image/jpeg', 'image/png'],
})
const { open, onChange } = useFileDialog( {
  accept: ['image/jpeg', 'image/png'],
})
onChange(uploadImage)

const drawImage = (canvas, pixels) => {
    canvas.width = CANVAS_SIZE
    canvas.height = CANVAS_SIZE

    const ctx = canvas.getContext('2d')

    const { x, y, width, height } = resizeToFit(
        'cover',
        { width: image.value.naturalWidth, height: image.value.naturalHeight },
        { width: canvas.width, height: canvas.height }
    )

    if (pixels) {
        const IMAGE_RESOLUTION = 5
        const scaleX = pixels.width / IMAGE_RESOLUTION
        const scaleY = pixels.height / IMAGE_RESOLUTION

        // Iterate over each pixel in the target resolution
        const processed = []
        for (let y = 0; y < IMAGE_RESOLUTION; y++) {
            for (let x = 0; x < IMAGE_RESOLUTION; x++) {
                // Calculate the corresponding source pixel position
                const sourceX = Math.floor(x * scaleX)
                const sourceY = Math.floor(y * scaleY)
                const index = (sourceY * pixels.width + sourceX * pixels.height) * 4

                // Extract the RGBA values from the source image
                const r = pixels.data[index]
                const g = pixels.data[index + 1]
                const b = pixels.data[index + 2]
                const a = pixels.data[index + 3] / 255 // alpha needs to be in the range 0-1

                // Set the fill style to the current pixel's color
                ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`

                // Add to processed array
                processed.push(Math.round((r + g + b) / 3))

                // Draw the pixel onto the canvas at the target resolution
                const size = CANVAS_SIZE / IMAGE_RESOLUTION
                ctx.fillRect(x * size, y * size, size, size)
            }
        }

        processedData.value = processed
    } else {
        ctx.drawImage(image.value, x, y, width, height)
    }

    return ctx
}

watch(imageData, async () => {
    if (!imageData.value) return

    image.value.src = imageData.value

    await new Promise((res) => (image.value.onload = res))

    uploading.value = false
    step.value = 1

    await new Promise((res) => setTimeout(res, CANVAS_SIZE))

    const viewCtx = drawImage(viewCanvas.value)
    const pixels = viewCtx.getImageData(0, 0, CANVAS_SIZE, CANVAS_SIZE)
    const processCtx = drawImage(processCanvas.value, pixels)
})

// AUDIO
const loop = ref()
const mapToNotes = (values, scale) => {
    const clippedValues = values.slice(0, scale.length)

    const sorted = clippedValues
        .map((v, i) => ({ index: i, value: v }))
        .sort((a, b) => a.value - b.value)
        .map((v) => v.index)

    return sorted.map((i) => scale[i])
}
const onPlayClick = () => {
    if (loop.value) return

    //create a synth and connect it to the main output (your speakers)
    const synth = new Tone.Synth().toDestination()
    const now = Tone.now()

    const notes = mapToNotes(processedData.value, [
        'C4',
        'D4',
        'E4',
        'G4',
        'A4',
    ])
    let noteIndex = 0

    loop.value = new Tone.Loop((time) => {
        synth.triggerAttackRelease(notes[noteIndex], '8n')
        noteIndex = (noteIndex + 1) % notes.length
    }, '4n').start(0)

    Tone.getTransport().start()
}
const onStopClick = () => {
    loop.value?.dispose()
    loop.value = null
    Tone.getTransport().stop()
}
const onResetClick = () => {
    onStopClick()

    step.value = 0
    processedData.value = null
}
</script>

<style lang="scss">
.image-melody {
    height: 100vh;
    display: flex;
    flex-direction: column;
    gap: rs(50px);
    justify-content: center;
    align-items: center;

    .drop-zone {
        width: rs(500px);
        height: rs(500px);
        background: var(--theme-layout);
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: rs(10px);
        cursor: pointer;

        &.active {
            opacity: 0.7;
        }
    }

    .images {
        grid-template-columns: 1fr 1fr;
        display: grid;
        border-radius: rs(10px);
        overflow: hidden;

        canvas {
            width: rs(500px);
            height: rs(500px);
            background: var(--theme-layout);

            &:first-child {
                border-right: 1px solid var(--theme-grey);
            }
        }
    }

    .audio {
        width: rs(300px);
        text-align: center;

        .controls {
            display: flex;
            justify-content: center;
            gap: rs(8px);
        }

        .reset {
            text-decoration: underline;
            margin-top: rs(16px);
        }
    }
}
</style>
