<script lang="ts">
  import { onMount } from 'svelte';

  interface Props {
    buffer: AudioBuffer | null;
    color?: string;
    playbackPosition?: number;
    cuePoint?: number;
    selectionStart?: number | null;
    selectionEnd?: number | null;
    onselectionchange?: (start: number | null, end: number | null) => void;
    onclick?: (timeOffset: number) => void;
  }

  let {
    buffer,
    color = '#646cff',
    playbackPosition = 0,
    cuePoint = 0,
    selectionStart = null,
    selectionEnd = null,
    onselectionchange,
    onclick
  }: Props = $props();

  let canvas: HTMLCanvasElement;
  let isDragging = $state(false);
  let dragStartX = $state(0);
  let hasMoved = $state(false);

  onMount(() => {
    const resizeObserver = new ResizeObserver(() => {
      if (canvas) {
        updateCanvasSize();
        draw();
      }
    });

    resizeObserver.observe(canvas.parentElement!);

    return () => resizeObserver.disconnect();
  });

  $effect(() => {
    buffer;
    color;
    playbackPosition;
    cuePoint;
    selectionStart;
    selectionEnd;
    draw();
  });

  function updateCanvasSize() {
    const parent = canvas.parentElement!;
    canvas.width = parent.clientWidth;
    canvas.height = parent.clientHeight;
  }

  function handleClick(event: MouseEvent) {
    if (hasMoved) return;

    const rect = canvas.getBoundingClientRect();
    const x = event.clientX - rect.left;

    if (buffer && selectionStart !== null && selectionEnd !== null) {
      const selStart = Math.min(selectionStart, selectionEnd);
      const selEnd = Math.max(selectionStart, selectionEnd);
      const width = canvas.width;
      const startX = (selStart / buffer.length) * width;
      const endX = (selEnd / buffer.length) * width;

      if (x < startX || x > endX) {
        if (onselectionchange) {
          onselectionchange(null, null);
        }
        return;
      }
    }

    if (onclick && buffer) {
      const timeOffset = (x / canvas.width) * (buffer.length / buffer.sampleRate);
      onclick(timeOffset);
    }
  }

  function handleMouseDown(event: MouseEvent) {
    if (!buffer) return;

    isDragging = true;
    hasMoved = false;
    const rect = canvas.getBoundingClientRect();
    dragStartX = event.clientX - rect.left;

    const sample = Math.floor((dragStartX / canvas.width) * buffer.length);
    if (onselectionchange) {
      onselectionchange(sample, sample);
    }
  }

  function handleMouseMove(event: MouseEvent) {
    if (!isDragging || !buffer) return;

    const rect = canvas.getBoundingClientRect();
    const currentX = event.clientX - rect.left;
    const clampedX = Math.max(0, Math.min(canvas.width, currentX));

    if (Math.abs(clampedX - dragStartX) > 2) {
      hasMoved = true;
    }

    const endSample = Math.floor((clampedX / canvas.width) * buffer.length);

    if (onselectionchange && selectionStart !== null) {
      onselectionchange(selectionStart, endSample);
    }
  }

  function handleMouseUp() {
    if (isDragging && selectionStart !== null && selectionEnd !== null) {
      if (Math.abs(selectionEnd - selectionStart) < buffer!.sampleRate * 0.01) {
        if (onselectionchange) {
          onselectionchange(null, null);
        }
      }
    }
    isDragging = false;
  }

  function handleTouchStart(event: TouchEvent) {
    if (!buffer || event.touches.length !== 1) return;

    event.preventDefault();
    isDragging = true;
    hasMoved = false;
    const rect = canvas.getBoundingClientRect();
    dragStartX = event.touches[0].clientX - rect.left;

    const sample = Math.floor((dragStartX / canvas.width) * buffer.length);
    if (onselectionchange) {
      onselectionchange(sample, sample);
    }
  }

  function handleTouchMove(event: TouchEvent) {
    if (!isDragging || !buffer || event.touches.length !== 1) return;

    event.preventDefault();
    const rect = canvas.getBoundingClientRect();
    const currentX = event.touches[0].clientX - rect.left;
    const clampedX = Math.max(0, Math.min(canvas.width, currentX));

    if (Math.abs(clampedX - dragStartX) > 2) {
      hasMoved = true;
    }

    const endSample = Math.floor((clampedX / canvas.width) * buffer.length);

    if (onselectionchange && selectionStart !== null) {
      onselectionchange(selectionStart, endSample);
    }
  }

  function handleTouchEnd() {
    if (isDragging && selectionStart !== null && selectionEnd !== null) {
      if (Math.abs(selectionEnd - selectionStart) < buffer!.sampleRate * 0.01) {
        if (onselectionchange) {
          onselectionchange(null, null);
        }
      }
    }
    isDragging = false;
  }

  function draw() {
    if (!canvas) return;

    const ctx = canvas.getContext('2d')!;
    const width = canvas.width;
    const height = canvas.height;

    ctx.fillStyle = '#0a0a0a';
    ctx.fillRect(0, 0, width, height);

    if (!buffer) {
      ctx.fillStyle = '#666';
      ctx.font = '24px system-ui';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText('No waveform generated', width / 2, height / 2);
      return;
    }

    const numChannels = buffer.numberOfChannels;
    const channelHeight = height / numChannels;
    const step = Math.ceil(buffer.length / width);

    for (let channel = 0; channel < numChannels; channel++) {
      const data = new Float32Array(buffer.length);
      buffer.copyFromChannel(data, channel);

      const channelTop = channel * channelHeight;
      const channelCenter = channelTop + channelHeight / 2;
      const amp = channelHeight / 2;

      ctx.strokeStyle = color;
      ctx.lineWidth = 2;
      ctx.beginPath();

      for (let i = 0; i < width; i++) {
        const index = i * step;
        const value = data[index] || 0;
        const y = channelCenter - value * amp;

        if (i === 0) {
          ctx.moveTo(i, y);
        } else {
          ctx.lineTo(i, y);
        }
      }

      ctx.stroke();

      if (channel < numChannels - 1) {
        ctx.strokeStyle = '#333';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(0, channelTop + channelHeight);
        ctx.lineTo(width, channelTop + channelHeight);
        ctx.stroke();
      }
    }

    if (buffer && selectionStart !== null && selectionEnd !== null) {
      const selStart = Math.min(selectionStart, selectionEnd);
      const selEnd = Math.max(selectionStart, selectionEnd);
      const startX = (selStart / buffer.length) * width;
      const endX = (selEnd / buffer.length) * width;

      ctx.fillStyle = 'rgba(100, 108, 255, 0.2)';
      ctx.fillRect(startX, 0, endX - startX, height);

      ctx.strokeStyle = 'rgba(100, 108, 255, 0.8)';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(startX, 0);
      ctx.lineTo(startX, height);
      ctx.moveTo(endX, 0);
      ctx.lineTo(endX, height);
      ctx.stroke();
    }

    if (playbackPosition >= 0 && buffer) {
      const duration = buffer.length / buffer.sampleRate;
      const x = (playbackPosition / duration) * width;

      ctx.strokeStyle = '#fff';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, height);
      ctx.stroke();
    } else if (cuePoint > 0 && buffer) {
      const duration = buffer.length / buffer.sampleRate;
      const x = (cuePoint / duration) * width;

      ctx.strokeStyle = '#fff';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, height);
      ctx.stroke();
    }
  }
</script>

<svelte:window onmouseup={handleMouseUp} ontouchend={handleTouchEnd} />

<canvas
  bind:this={canvas}
  onclick={handleClick}
  onmousedown={handleMouseDown}
  onmousemove={handleMouseMove}
  ontouchstart={handleTouchStart}
  ontouchmove={handleTouchMove}
  style="cursor: {isDragging ? 'text' : 'pointer'}; touch-action: none;"
></canvas>

<style>
  canvas {
    width: 100%;
    height: 100%;
  }
</style>
