<script lang="ts">
  import { onMount } from "svelte";
  import WaveformDisplay from "./lib/components/WaveformDisplay.svelte";
  import VUMeter from "./lib/components/VUMeter.svelte";
  import { engines } from "./lib/audio/engines/registry";
  import type { SynthEngine } from "./lib/audio/engines/SynthEngine";
  import type { EngineType } from "./lib/audio/engines/SynthEngine";
  import { AudioService } from "./lib/audio/services/AudioService";
  import { downloadWAV } from "./lib/audio/utils/WAVEncoder";
  import {
    loadVolume,
    saveVolume,
    loadDuration,
    saveDuration,
  } from "./lib/utils/settings";
  import { generateRandomColor } from "./lib/utils/colors";
  import {
    getRandomProcessor,
    getAllProcessors,
  } from "./lib/audio/processors/registry";
  import type { AudioProcessor } from "./lib/audio/processors/AudioProcessor";
  import { Sample } from "./lib/audio/engines/Sample";
  import { Input } from "./lib/audio/engines/Input";

  let currentEngineIndex = 0;
  let engine = engines[currentEngineIndex];
  let engineType: EngineType = engine.getType();

  const audioService = new AudioService();

  let currentParams: any = null;
  let currentBuffer: AudioBuffer | null = null;
  let duration = loadDuration();
  let volume = loadVolume();
  let playbackPosition = -1;
  let waveformColor = generateRandomColor();
  let showModal = true;
  let isProcessed = false;
  let showProcessorPopup = false;
  let popupTimeout: ReturnType<typeof setTimeout> | null = null;
  let isRecording = false;
  let isDragOver = false;

  const allProcessors = getAllProcessors();

  $: showDuration = engineType !== 'sample';
  $: showRandomButton = engineType === 'generative';
  $: showRecordButton = engineType === 'input';
  $: showFileDropZone = engineType === 'sample' && !currentBuffer;
  $: showMutateButton = engineType === 'generative' && !isProcessed && currentBuffer;

  onMount(() => {
    audioService.setVolume(volume);
    audioService.setPlaybackUpdateCallback((position) => {
      playbackPosition = position;
    });
    generateRandom();
  });

  function generateRandom() {
    currentParams = engine.randomParams();
    waveformColor = generateRandomColor();
    isProcessed = false;
    regenerateBuffer();
  }

  function mutate() {
    if (!currentParams) {
      generateRandom();
      return;
    }
    currentParams = engine.mutateParams(currentParams);
    waveformColor = generateRandomColor();
    regenerateBuffer();
  }

  function regenerateBuffer() {
    if (!currentParams) return;

    const sampleRate = audioService.getSampleRate();
    const data = engine.generate(currentParams, sampleRate, duration);
    currentBuffer = audioService.createAudioBuffer(data);
    audioService.play(currentBuffer);
  }

  function replaySound() {
    if (currentBuffer) {
      audioService.play(currentBuffer);
    }
  }

  function download() {
    if (!currentBuffer) return;
    downloadWAV(currentBuffer, "synth-sound.wav");
  }

  function processSound() {
    if (!currentBuffer) return;

    const processor = getRandomProcessor();
    applyProcessor(processor);
  }

  function processWithSpecificProcessor(processor: AudioProcessor) {
    if (!currentBuffer) return;

    hideProcessorPopup();
    applyProcessor(processor);
  }

  function handlePopupMouseEnter() {
    if (popupTimeout) {
      clearTimeout(popupTimeout);
    }
    showProcessorPopup = true;
    popupTimeout = setTimeout(() => {
      showProcessorPopup = false;
    }, 2000);
  }

  function handlePopupMouseLeave() {
    if (popupTimeout) {
      clearTimeout(popupTimeout);
    }
    popupTimeout = setTimeout(() => {
      showProcessorPopup = false;
    }, 200);
  }

  function hideProcessorPopup() {
    if (popupTimeout) {
      clearTimeout(popupTimeout);
    }
    showProcessorPopup = false;
  }

  async function applyProcessor(processor: AudioProcessor) {
    if (!currentBuffer) return;

    const leftChannel = currentBuffer.getChannelData(0);
    const rightChannel = currentBuffer.getChannelData(1);

    const [processedLeft, processedRight] = await processor.process(
      leftChannel,
      rightChannel,
    );

    currentBuffer = audioService.createAudioBuffer([
      processedLeft,
      processedRight,
    ]);
    isProcessed = true;
    audioService.play(currentBuffer);
  }

  function handleVolumeChange(event: Event) {
    const target = event.target as HTMLInputElement;
    volume = parseFloat(target.value);
    audioService.setVolume(volume);
    saveVolume(volume);
  }

  function handleDurationChange(event: Event) {
    const target = event.target as HTMLInputElement;
    duration = parseFloat(target.value);
    saveDuration(duration);
  }

  function switchEngine(index: number) {
    currentEngineIndex = index;
    engine = engines[index];
    engineType = engine.getType();
    currentBuffer = null;
    currentParams = null;
    isProcessed = false;

    if (engineType === 'generative') {
      generateRandom();
    }
  }

  async function handleFileInput(event: Event) {
    const input = event.target as HTMLInputElement;
    if (!input.files || input.files.length === 0) return;

    const file = input.files[0];
    await loadAudioFile(file);
  }

  async function loadAudioFile(file: File) {
    if (!(engine instanceof Sample)) return;

    try {
      await engine.loadFile(file);
      currentParams = engine.randomParams();
      waveformColor = generateRandomColor();
      isProcessed = false;
      regenerateBuffer();
    } catch (error) {
      console.error('Failed to load audio file:', error);
      alert(`Failed to load audio file: ${error}`);
    }
  }

  async function recordAudio() {
    if (!(engine instanceof Input)) return;
    if (isRecording) return;

    try {
      isRecording = true;
      await engine.record(duration);
      currentParams = engine.randomParams();
      waveformColor = generateRandomColor();
      isProcessed = false;
      regenerateBuffer();
    } catch (error) {
      console.error('Failed to record audio:', error);
      alert(`Failed to record audio: ${error}`);
    } finally {
      isRecording = false;
    }
  }

  function handleDrop(event: DragEvent) {
    event.preventDefault();
    isDragOver = false;

    if (!event.dataTransfer) return;
    const files = event.dataTransfer.files;
    if (files.length === 0) return;

    const file = files[0];
    if (!file.type.startsWith('audio/')) {
      alert('Please drop an audio file');
      return;
    }

    loadAudioFile(file);
  }

  function handleDragOver(event: DragEvent) {
    event.preventDefault();
    isDragOver = true;
  }

  function handleDragLeave(event: DragEvent) {
    event.preventDefault();
    isDragOver = false;
  }

  async function closeModal() {
    showModal = false;
    await audioService.initialize();
  }

  function handleKeydown(event: KeyboardEvent) {
    // Ignore if typing in an input
    if (event.target instanceof HTMLInputElement) return;

    const key = event.key.toLowerCase();

    // Close modal with Escape key
    if (key === "escape" && showModal) {
      closeModal();
      return;
    }

    switch (key) {
      case "m":
        mutate();
        break;
      case "r":
        generateRandom();
        break;
      case "p":
        processSound();
        break;
      case "s":
        download();
        break;
      case "arrowleft":
        event.preventDefault();
        const durationDecrement = event.shiftKey ? 1 : 0.05;
        duration = Math.max(0.05, duration - durationDecrement);
        saveDuration(duration);
        break;
      case "arrowright":
        event.preventDefault();
        const durationIncrement = event.shiftKey ? 1 : 0.05;
        duration = Math.min(32, duration + durationIncrement);
        saveDuration(duration);
        break;
      case "arrowdown":
        event.preventDefault();
        const volumeDecrement = event.shiftKey ? 0.2 : 0.05;
        volume = Math.max(0, volume - volumeDecrement);
        audioService.setVolume(volume);
        saveVolume(volume);
        break;
      case "arrowup":
        event.preventDefault();
        const volumeIncrement = event.shiftKey ? 0.2 : 0.05;
        volume = Math.min(1, volume + volumeIncrement);
        audioService.setVolume(volume);
        saveVolume(volume);
        break;
    }
  }
</script>

<svelte:window onkeydown={handleKeydown} />

<div class="container">
  <div class="top-bar">
    <div class="mode-buttons">
      {#each engines as engine, index}
        <button
          class="engine-button"
          class:active={currentEngineIndex === index}
          data-description={engine.getDescription()}
          onclick={() => switchEngine(index)}
        >
          {engine.getName()}
        </button>
      {/each}
    </div>
    <div class="controls-group">
      {#if showDuration}
        <div class="slider-control duration-slider">
          <label for="duration">Duration: {duration.toFixed(2)}s</label>
          <input
            id="duration"
            type="range"
            min="0.05"
            max="32"
            step="0.01"
            value={duration}
            oninput={handleDurationChange}
          />
        </div>
      {/if}
      <div class="slider-control">
        <label for="volume">Volume</label>
        <input
          id="volume"
          type="range"
          min="0"
          max="1"
          step="0.01"
          value={volume}
          oninput={handleVolumeChange}
        />
      </div>
    </div>
  </div>

  <div class="main-area">
    <div class="waveform-container">
      {#if showFileDropZone}
        <div
          class="file-drop-zone"
          class:drag-over={isDragOver}
          role="button"
          tabindex="0"
          ondrop={handleDrop}
          ondragover={handleDragOver}
          ondragleave={handleDragLeave}
        >
          <div class="drop-zone-content">
            <h2>Drop an audio file here</h2>
            <label for="file-input" class="file-input-label">
              <input
                id="file-input"
                type="file"
                accept="audio/*"
                onchange={handleFileInput}
                style="display: none;"
              />
              Choose a file
            </label>
          </div>
        </div>
      {:else}
        <WaveformDisplay
          buffer={currentBuffer}
          color={waveformColor}
          {playbackPosition}
          onclick={replaySound}
        />
      {/if}

      <div class="bottom-controls">
        {#if showRandomButton}
          <button onclick={generateRandom}>Random (R)</button>
        {/if}
        {#if showRecordButton}
          <button onclick={recordAudio} disabled={isRecording}>
            {isRecording ? 'Recording...' : 'Record'}
          </button>
        {/if}
        {#if showMutateButton}
          <button onclick={mutate}>Mutate (M)</button>
        {/if}
        {#if currentBuffer}
          <div
            class="process-button-container"
            role="group"
            onmouseenter={handlePopupMouseEnter}
            onmouseleave={handlePopupMouseLeave}
          >
            <button onclick={processSound}>Process (P)</button>
            {#if showProcessorPopup}
              <div class="processor-popup">
                {#each allProcessors as processor}
                  <button
                    class="processor-tile"
                    data-description={processor.getDescription()}
                    onclick={() => processWithSpecificProcessor(processor)}
                  >
                    {processor.getName()}
                  </button>
                {/each}
              </div>
            {/if}
          </div>
          <button onclick={download}>Download (D)</button>
        {/if}
      </div>
    </div>
    <div class="vu-meter-container">
      <VUMeter buffer={currentBuffer} {playbackPosition} />
    </div>
  </div>

  {#if showModal}
    <div
      class="modal-overlay"
      role="button"
      tabindex="0"
      onclick={closeModal}
      onkeydown={(e) => e.key === "Enter" && closeModal()}
    >
      <div
        class="modal-content"
        role="dialog"
        aria-labelledby="modal-title"
        tabindex="-1"
        onclick={(e) => e.stopPropagation()}
        onkeydown={(e) => e.stopPropagation()}
      >
        <h1 id="modal-title">Vending Machine</h1>
        <p class="description">
          Oh, looks like you found a sound vending machine. This one seems
          slightly broken and it seems that you can get sounds for free... Have
          fun!
        </p>
        <div class="modal-links">
          <p>
            Created by <a
              href="https://raphaelforment.fr"
              target="_blank"
              rel="noopener noreferrer">Raphaël Forment (BuboBubo)</a
            >
          </p>
          <p>
            Licensed under <a
              href="https://www.gnu.org/licenses/gpl-3.0.html"
              target="_blank"
              rel="noopener noreferrer">GPL 3.0</a
            >
          </p>
        </div>
        <button class="modal-close" onclick={closeModal}>Start</button>
      </div>
    </div>
  {/if}
</div>

<style>
  .container {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
  }

  .top-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    padding: 0.5rem;
    background-color: #1a1a1a;
    border-bottom: 1px solid #333;
  }

  .mode-buttons {
    display: flex;
    gap: 0.5rem;
  }

  .engine-button {
    opacity: 0.7;
    position: relative;
  }

  .engine-button.active {
    opacity: 1;
    border-color: #646cff;
  }

  .engine-button::after {
    content: attr(data-description);
    position: absolute;
    top: 100%;
    left: 0;
    padding: 0.5rem 0.75rem;
    background-color: #0a0a0a;
    border: 1px solid #444;
    color: #ccc;
    font-size: 0.85rem;
    width: 30vw;
    white-space: normal;
    word-wrap: break-word;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s;
    z-index: 1000;
  }

  .engine-button:hover::after {
    opacity: 1;
  }

  .controls-group {
    display: flex;
    gap: 1rem;
    align-items: center;
  }

  .slider-control {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  .slider-control label {
    font-size: 0.9rem;
    white-space: nowrap;
  }

  .slider-control input[type="range"] {
    width: 100px;
  }

  .duration-slider input[type="range"] {
    width: 300px;
  }

  .main-area {
    flex: 1;
    display: flex;
    background-color: #0a0a0a;
    overflow: hidden;
  }

  .waveform-container {
    flex: 1;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
  }

  .vu-meter-container {
    width: 5%;
    min-width: 40px;
    max-width: 80px;
    border-left: 1px solid #333;
  }

  .bottom-controls {
    position: absolute;
    bottom: 2rem;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 1rem;
  }

  input[type="range"] {
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    height: 20px;
    border-radius: 0;
  }

  input[type="range"]::-webkit-slider-track {
    background: #333;
    height: 4px;
    border: 1px solid #444;
    border-radius: 0;
  }

  input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 16px;
    height: 16px;
    background: #fff;
    border: 1px solid #000;
    border-radius: 0;
    cursor: pointer;
    margin-top: -6px;
  }

  input[type="range"]::-webkit-slider-thumb:hover {
    background: #ddd;
  }

  input[type="range"]::-moz-range-track {
    background: #333;
    height: 4px;
    border: 1px solid #444;
    border-radius: 0;
  }

  input[type="range"]::-moz-range-thumb {
    width: 16px;
    height: 16px;
    background: #fff;
    border: 1px solid #000;
    border-radius: 0;
    cursor: pointer;
  }

  input[type="range"]::-moz-range-thumb:hover {
    background: #ddd;
  }

  .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 2000;
  }

  .modal-content {
    background-color: #000;
    border: 2px solid #fff;
    padding: 2rem;
    max-width: 500px;
    width: 90%;
    color: #fff;
  }

  .modal-content h1 {
    margin: 0 0 1rem 0;
    font-size: 2rem;
    font-weight: bold;
  }

  .modal-content .description {
    margin: 0 0 1.5rem 0;
    line-height: 1.6;
    color: #ccc;
  }

  .modal-links {
    margin: 1.5rem 0;
    padding: 1rem 0;
    border-top: 1px solid #333;
    border-bottom: 1px solid #333;
  }

  .modal-links p {
    margin: 0.5rem 0;
    font-size: 0.9rem;
    color: #ccc;
  }

  .modal-links a {
    color: #646cff;
    text-decoration: none;
  }

  .modal-links a:hover {
    text-decoration: underline;
  }

  .modal-close {
    margin-top: 1rem;
    width: 100%;
    padding: 0.75rem;
    font-size: 1.1rem;
    background-color: #fff;
    color: #000;
    border: none;
    cursor: pointer;
    font-weight: bold;
  }

  .modal-close:hover {
    background-color: #ddd;
  }

  .process-button-container {
    position: relative;
  }

  .processor-popup {
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    background-color: #000;
    border: 2px solid #fff;
    padding: 0.75rem;
    z-index: 1000;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.5rem;
    width: 600px;
    margin-bottom: 0.5rem;
  }

  .processor-tile {
    background-color: #1a1a1a;
    border: 1px solid #444;
    padding: 0.6rem 0.4rem;
    text-align: center;
    cursor: pointer;
    transition: background-color 0.2s, border-color 0.2s;
    font-size: 0.85rem;
    color: #fff;
    position: relative;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .processor-tile:hover {
    background-color: #2a2a2a;
    border-color: #646cff;
  }

  .processor-tile::after {
    content: attr(data-description);
    position: absolute;
    bottom: 100%;
    left: 50%;
    transform: translateX(-50%);
    padding: 0.5rem 0.75rem;
    background-color: #0a0a0a;
    border: 1px solid #444;
    color: #ccc;
    font-size: 0.85rem;
    width: max-content;
    max-width: 300px;
    white-space: normal;
    word-wrap: break-word;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s;
    z-index: 1001;
    margin-bottom: 0.25rem;
  }

  .processor-tile:hover::after {
    opacity: 1;
  }

  .file-drop-zone {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px dashed #444;
    background-color: #0a0a0a;
    transition: all 0.2s;
  }

  .file-drop-zone.drag-over {
    border-color: #646cff;
    background-color: #1a1a1a;
  }

  .drop-zone-content {
    text-align: center;
    color: #ccc;
  }

  .drop-zone-content h2 {
    font-size: 1.5rem;
    margin-bottom: 1rem;
    color: #fff;
  }

  .drop-zone-content p {
    margin: 1rem 0;
    font-size: 1rem;
  }

  .file-input-label {
    display: inline-block;
    padding: 0.75rem 1.5rem;
    background-color: #646cff;
    color: #fff;
    border: 1px solid #646cff;
    cursor: pointer;
    transition: background-color 0.2s;
    font-size: 1rem;
  }

  .file-input-label:hover {
    background-color: #535bf2;
  }

  button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
</style>
