<script lang="ts">
  import { onDestroy, onMount } from 'svelte';
  import { createAudioEngine } from './lib/audio/engine';
  import {
    isEditableTarget,
    keyCode,
    keyLabel,
    shouldDeleteSelected,
    shouldOverrideSelected,
    shouldRecordMarker,
  } from './lib/keyboard';
  import { downloadProject, exportAudio, loadProject } from './lib/project';
  import { openProject, openVideo } from './lib/platform/files';
  import {
    addMarker,
    app,
    deleteMarkers,
    nudgeSelected,
    selectAll,
    setActiveSwitch,
    setFps,
    setPreviewSpeed,
    setVideoUrl,
    snapToFrame,
    updateMarker,
  } from './lib/store.svelte';
  import Timeline from './lib/timeline/Timeline.svelte';
  import { SWITCH_OPTIONS } from './lib/types';
  import { attachVideoPlayer, type VideoController } from './lib/video/player';

  let videoEl: HTMLVideoElement | undefined = $state();
  let timeline: Timeline | undefined = $state();
  let controller: VideoController | null = null;
  let audioEngine = createAudioEngine();
  let statusMessage = $state('');
  let lastScheduledTime = -1;
  const pendingPresses = new Map<string, string>();

  function rescheduleAudio() {
    if (!videoEl) return;
    audioEngine.reschedule(
      videoEl.currentTime,
      app.markers,
      !videoEl.paused,
      app.previewSpeed,
    );
    lastScheduledTime = videoEl.currentTime;
  }

  function initVideo() {
    if (!videoEl) return;
    controller?.destroy();
    void audioEngine.init(videoEl).then(() => rescheduleAudio());
    controller = attachVideoPlayer(videoEl);
    queueMicrotask(() => timeline?.focus());
  }

  $effect(() => {
    if (app.videoUrl && videoEl) initVideo();
  });

  $effect(() => {
    if (!videoEl) return;
    const t = app.currentTime;
    const playing = app.isPlaying;
    void app.markers;
    void app.activeSwitch;
    void app.previewSpeed;
    if (playing && Math.abs(t - lastScheduledTime) > 0.25) {
      rescheduleAudio();
    }
  });

  $effect(() => {
    const speed = app.previewSpeed;
    controller?.setPlaybackRate(speed);
    if (app.isPlaying) rescheduleAudio();
  });

  onMount(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      if (isEditableTarget(event.target)) return;

      if (event.ctrlKey && event.code === 'Space') {
        event.preventDefault();
        controller?.togglePlayPause();
        rescheduleAudio();
        return;
      }

      if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
        event.preventDefault();
        selectAll();
        return;
      }

      if (shouldDeleteSelected(event, app.selectedIds.size > 0)) {
        event.preventDefault();
        deleteMarkers(new Set(app.selectedIds));
        rescheduleAudio();
        return;
      }

      if (['ArrowLeft', 'ArrowRight'].includes(event.key) && app.selectedIds.size > 0) {
        event.preventDefault();
        nudgeSelected(event.key === 'ArrowLeft' ? -1 : 1);
        rescheduleAudio();
        return;
      }

      if (shouldOverrideSelected(event, app.selectedIds.size > 0)) {
        const label = keyLabel(event);
        if (!label) return;
        event.preventDefault();
        for (const id of app.selectedIds) updateMarker(id, { key: label });
        return;
      }

      if (!app.videoUrl || !videoEl) return;

      if (shouldRecordMarker(event)) {
        const label = keyLabel(event);
        if (!label) return;
        event.preventDefault();
        const code = keyCode(event);
        const time = snapToFrame(videoEl.currentTime);
        const marker = addMarker(time, label, code);
        pendingPresses.set(code, marker.id);
        audioEngine.playKey(code, label, 'press');
        rescheduleAudio();
      }
    };

    const onKeyUp = (event: KeyboardEvent) => {
      if (isEditableTarget(event.target)) return;
      const code = keyCode(event);
      const markerId = pendingPresses.get(code);
      if (!markerId || !videoEl) return;

      event.preventDefault();
      const label = keyLabel(event);
      pendingPresses.delete(code);
      const releaseTime = snapToFrame(videoEl.currentTime);
      updateMarker(markerId, { releaseTime });
      if (label) audioEngine.playKey(code, label, 'release');
      rescheduleAudio();
    };

    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
    return () => {
      window.removeEventListener('keydown', onKeyDown);
      window.removeEventListener('keyup', onKeyUp);
    };
  });

  onDestroy(() => {
    controller?.destroy();
    audioEngine.destroy();
  });

  async function handleOpenVideo() {
    try {
      const result = await openVideo();
      if (!result) return;
      setVideoUrl(result.url, result.name);
      statusMessage = `Loaded video: ${result.name}`;
    } catch (error) {
      statusMessage = error instanceof Error ? error.message : 'Failed to open video';
    }
  }

  async function handleOpenProject() {
    try {
      const text = await openProject();
      if (!text) return;
      loadProject(JSON.parse(text));
      statusMessage = 'Loaded project';
      rescheduleAudio();
    } catch (error) {
      statusMessage = error instanceof Error ? error.message : 'Failed to load project';
    }
  }

  async function handleExportAudio() {
    if (!app.duration) {
      statusMessage = 'Load a video before exporting audio';
      return;
    }
    try {
      statusMessage = 'Exporting audio...';
      await exportAudio(app.duration, audioEngine.getSampleCache() ?? undefined);
      statusMessage = 'Audio export complete';
    } catch (error) {
      statusMessage = error instanceof Error ? error.message : 'Export failed';
    }
  }
</script>

<div class="app">
  <header class="toolbar">
    <div class="file-actions">
      <button type="button" onclick={handleOpenVideo}>Open Video</button>
      <button type="button" onclick={handleOpenProject}>Open Project</button>
      <button type="button" onclick={() => void downloadProject()}>Save Project</button>
      <button type="button" onclick={handleExportAudio}>Export Audio</button>
    </div>
    <div class="meta">
      {#if app.videoName}
        <span>{app.videoName}</span>
      {/if}
      {#if app.duration}
        <label class="fps-control">
          FPS
          <input
            type="number"
            min="1"
            max="240"
            value={app.fps}
            onchange={(event) => {
              const value = Number(event.currentTarget.value);
              if (value >= 1 && value <= 240) setFps(value);
            }}
          />
        </label>
      {/if}
      {#if statusMessage}
        <span class="status">{statusMessage}</span>
      {/if}
    </div>
  </header>

  <section class="player-section">
    {#if app.videoUrl}
      <!-- svelte-ignore a11y_media_has_caption -->
      <video
        bind:this={videoEl}
        src={app.videoUrl}
        controls={false}
        onplay={rescheduleAudio}
        onpause={rescheduleAudio}
        onseeked={rescheduleAudio}
      ></video>
    {:else}
      <div class="placeholder">Open an MP4 video to begin annotating key presses</div>
    {/if}
  </section>

  <section class="transport">
    <button
      type="button"
      class="play-btn"
      disabled={!app.videoUrl}
      onkeydown={(event) => {
        if (event.code === 'Space' && !event.ctrlKey) event.preventDefault();
      }}
      onclick={() => {
        controller?.togglePlayPause();
        rescheduleAudio();
      }}
    >
      {app.isPlaying ? 'Pause' : 'Play'}
    </button>
    <span class="shortcut-hint">Ctrl+Space</span>

    <label class="speed-control">
      Preview
      <input
        type="range"
        min="25"
        max="200"
        step="5"
        value={Math.round(app.previewSpeed * 100)}
        disabled={!app.videoUrl}
        oninput={(event) => {
          setPreviewSpeed(Number(event.currentTarget.value) / 100);
        }}
      />
      <span class="speed-value">{Math.round(app.previewSpeed * 100)}%</span>
    </label>

    <label class="switch-select">
      Switch
      <select
        value={app.activeSwitch}
        onchange={(event) => {
          setActiveSwitch(event.currentTarget.value as typeof app.activeSwitch);
          void audioEngine.preloadActiveSwitch().then(() => rescheduleAudio());
        }}
      >
        {#each SWITCH_OPTIONS as option}
          <option value={option.id}>{option.label}</option>
        {/each}
      </select>
    </label>
  </section>

  <Timeline
    bind:this={timeline}
    onSeek={(time) => {
      controller?.seek(time);
      rescheduleAudio();
    }}
    onReschedule={rescheduleAudio}
  />
</div>

<style>
  :global(body) {
    margin: 0;
    background: #111;
    color: #eee;
    font-family: Inter, system-ui, sans-serif;
  }

  .app {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1rem;
    box-sizing: border-box;
  }

  .toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    justify-content: space-between;
  }

  .file-actions {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
  }

  button,
  select {
    background: #2d3748;
    color: #edf2f7;
    border: 1px solid #4a5568;
    border-radius: 6px;
    padding: 0.45rem 0.75rem;
    cursor: pointer;
  }

  button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }

  .meta {
    display: flex;
    gap: 1rem;
    font-size: 0.85rem;
    color: #a0aec0;
  }

  .status {
    color: #68d391;
  }

  .player-section {
    background: #000;
    border-radius: 8px;
    overflow: hidden;
    min-height: 320px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  video {
    width: 100%;
    max-height: 60vh;
    display: block;
    background: #000;
  }

  .placeholder {
    color: #718096;
    padding: 3rem;
    text-align: center;
  }

  .transport {
    display: flex;
    align-items: center;
    gap: 0.75rem;
  }

  .play-btn {
    min-width: 5rem;
  }

  .shortcut-hint {
    font-size: 0.8rem;
    color: #a0aec0;
    padding: 0.2rem 0.5rem;
    border: 1px dashed #4a5568;
    border-radius: 4px;
  }

  .speed-control {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9rem;
    margin-left: auto;
  }

  .speed-control input[type='range'] {
    width: 8rem;
    accent-color: #68d391;
  }

  .speed-value {
    min-width: 2.75rem;
    text-align: right;
    color: #a0aec0;
    font-size: 0.85rem;
  }

  .switch-select {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9rem;
  }

  .fps-control {
    display: flex;
    align-items: center;
    gap: 0.35rem;
  }

  .fps-control input {
    width: 4rem;
    background: #2d3748;
    color: #edf2f7;
    border: 1px solid #4a5568;
    border-radius: 4px;
    padding: 0.2rem 0.35rem;
  }
</style>
