import {
  Component,
  ElementRef,
  OnDestroy,
  ViewChild,
  computed,
  effect,
  input,
  output,
  signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgIcon, provideIcons } from '@ng-icons/core';
import type WaveSurfer from 'wavesurfer.js';
import {
  lucideChevronDown,
  lucideChevronUp,
  lucideDownload,
  lucidePause,
  lucidePlay,
  lucideVolume2,
  lucideVolumeX
} from '@ng-icons/lucide';

const AUDIO_PLAYER_VOLUME_STORAGE_KEY = 'metoyou_audio_player_volume';
const DEFAULT_AUDIO_PLAYER_VOLUME_PERCENT = 50;

function getInitialSharedAudioPlayerVolume(): number {
  if (typeof window === 'undefined')
    return DEFAULT_AUDIO_PLAYER_VOLUME_PERCENT;

  try {
    const raw = window.localStorage.getItem(AUDIO_PLAYER_VOLUME_STORAGE_KEY);
    const parsed = Number(raw);

    if (Number.isFinite(parsed) && parsed > 0 && parsed <= 100)
      return parsed;
  } catch { /* ignore storage access issues */ }

  return DEFAULT_AUDIO_PLAYER_VOLUME_PERCENT;
}

function persistSharedAudioPlayerVolume(volumePercent: number): void {
  if (typeof window === 'undefined')
    return;

  try {
    window.localStorage.setItem(AUDIO_PLAYER_VOLUME_STORAGE_KEY, String(volumePercent));
  } catch { /* ignore storage access issues */ }
}

@Component({
  selector: 'app-chat-audio-player',
  standalone: true,
  imports: [CommonModule, NgIcon],
  viewProviders: [
    provideIcons({
      lucideChevronDown,
      lucideChevronUp,
      lucideDownload,
      lucidePause,
      lucidePlay,
      lucideVolume2,
      lucideVolumeX
    })
  ],
  templateUrl: './chat-audio-player.component.html',
  styleUrl: './chat-audio-player.component.scss'
})
/* eslint-disable @typescript-eslint/member-ordering */
export class ChatAudioPlayerComponent implements OnDestroy {
  src = input.required<string>();
  filename = input.required<string>();
  sizeLabel = input<string>('');
  downloadRequested = output<undefined>();

  @ViewChild('audioEl') audioRef?: ElementRef<HTMLAudioElement>;
  @ViewChild('waveformContainer') waveformContainer?: ElementRef<HTMLDivElement>;

  isPlaying = signal(false);
  isMuted = signal(false);
  waveformExpanded = signal(false);
  waveformLoading = signal(false);
  waveformUnavailable = signal(false);
  currentTimeSeconds = signal(0);
  durationSeconds = signal(0);
  volumePercent = signal(getInitialSharedAudioPlayerVolume());
  private lastNonZeroVolume = signal(getInitialSharedAudioPlayerVolume());
  private waveSurfer: WaveSurfer | null = null;

  progressPercent = computed(() => {
    const duration = this.durationSeconds();

    if (duration <= 0)
      return 0;

    return (this.currentTimeSeconds() / duration) * 100;
  });
  seekTrackBackground = computed(() => this.buildSliderBackground(this.progressPercent()));
  waveformToggleIcon = computed(() => this.waveformExpanded() ? 'lucideChevronUp' : 'lucideChevronDown');
  displayVolumePercent = computed(() => this.isMuted() ? 0 : this.volumePercent());
  volumeTrackBackground = computed(() => {
    const volume = Math.max(0, Math.min(100, this.displayVolumePercent()));

    return this.buildSliderBackground(volume);
  });

  constructor() {
    effect(() => {
      void this.src();
      const storedVolume = getInitialSharedAudioPlayerVolume();

      this.destroyWaveSurfer();
      this.waveformExpanded.set(false);
      this.waveformLoading.set(false);
      this.waveformUnavailable.set(false);
      this.currentTimeSeconds.set(0);
      this.durationSeconds.set(0);
      this.isPlaying.set(false);
      this.isMuted.set(false);
      this.volumePercent.set(storedVolume);
      this.lastNonZeroVolume.set(storedVolume);

      requestAnimationFrame(() => {
        const audio = this.audioRef?.nativeElement;

        if (!audio)
          return;

        audio.load();
        this.applyAudioVolume(storedVolume);
      });
    });
  }

  ngOnDestroy(): void {
    this.destroyWaveSurfer();
  }

  togglePlayback(): void {
    const audio = this.audioRef?.nativeElement;

    if (!audio)
      return;

    if (audio.paused || audio.ended) {
      void audio.play().catch(() => {
        this.isPlaying.set(false);
      });

      return;
    }

    audio.pause();
  }

  toggleWaveform(): void {
    const nextExpanded = !this.waveformExpanded();

    this.waveformExpanded.set(nextExpanded);

    if (nextExpanded) {
      this.waveformUnavailable.set(false);
      requestAnimationFrame(() => {
        void this.ensureWaveformLoaded();
      });
    }
  }

  onLoadedMetadata(): void {
    const audio = this.audioRef?.nativeElement;

    if (!audio)
      return;

    this.applyAudioVolume(this.volumePercent());
    this.durationSeconds.set(Number.isFinite(audio.duration) ? audio.duration : 0);
    this.currentTimeSeconds.set(audio.currentTime || 0);

    if (this.waveformExpanded() && !this.waveSurfer && !this.waveformLoading()) {
      requestAnimationFrame(() => {
        void this.ensureWaveformLoaded();
      });
    }
  }

  onTimeUpdate(): void {
    const audio = this.audioRef?.nativeElement;

    if (!audio)
      return;

    this.currentTimeSeconds.set(audio.currentTime || 0);
  }

  onPlay(): void {
    this.isPlaying.set(true);
  }

  onPause(): void {
    this.isPlaying.set(false);
  }

  onSeek(event: Event): void {
    const audio = this.audioRef?.nativeElement;

    if (!audio)
      return;

    const nextTime = Number((event.target as HTMLInputElement).value);

    if (!Number.isFinite(nextTime))
      return;

    audio.currentTime = nextTime;
    this.currentTimeSeconds.set(nextTime);
  }

  onVolumeInput(event: Event): void {
    const audio = this.audioRef?.nativeElement;

    if (!audio)
      return;

    const nextVolume = Math.max(0, Math.min(100, Number((event.target as HTMLInputElement).value)));

    if (nextVolume <= 0) {
      audio.volume = 0;
      audio.muted = true;
      this.isMuted.set(true);
      return;
    }

    audio.volume = nextVolume / 100;
    audio.muted = false;

    this.volumePercent.set(nextVolume);
    this.lastNonZeroVolume.set(nextVolume);
    this.isMuted.set(false);
    this.setSharedVolume(nextVolume);
  }

  onVolumeChange(): void {
    const audio = this.audioRef?.nativeElement;

    if (!audio)
      return;

    this.isMuted.set(audio.muted || audio.volume === 0);

    if (!audio.muted && audio.volume > 0) {
      const volume = Math.round(audio.volume * 100);

      this.volumePercent.set(volume);
      this.lastNonZeroVolume.set(volume);
      this.setSharedVolume(volume);
    }
  }

  toggleMute(): void {
    const audio = this.audioRef?.nativeElement;

    if (!audio)
      return;

    if (audio.muted || audio.volume === 0) {
      const restoredVolume = Math.max(this.lastNonZeroVolume(), 1);

      audio.muted = false;
      audio.volume = restoredVolume / 100;
      this.volumePercent.set(restoredVolume);
      this.isMuted.set(false);
      this.setSharedVolume(restoredVolume);
      return;
    }

    audio.muted = true;
    this.isMuted.set(true);
  }

  requestDownload(): void {
    this.downloadRequested.emit(undefined);
  }

  private async ensureWaveformLoaded(): Promise<void> {
    if (this.waveformLoading() || this.waveSurfer)
      return;

    const source = this.src();
    const audio = this.audioRef?.nativeElement;
    const waveformContainer = this.waveformContainer?.nativeElement;

    if (!source || !audio || !waveformContainer)
      return;

    this.waveformLoading.set(true);
    this.waveformUnavailable.set(false);

    try {
      await this.ensureAudioMetadata(audio);

      await this.waitForNextPaint();

      if (!this.waveformExpanded()) {
        this.waveformLoading.set(false);
        return;
      }

      const { default: WaveSurfer } = await import('wavesurfer.js');

      this.waveSurfer = WaveSurfer.create({
        barGap: 2,
        barRadius: 999,
        barWidth: 3,
        container: waveformContainer,
        cursorWidth: 0,
        dragToSeek: true,
        height: 56,
        hideScrollbar: true,
        interact: true,
        media: audio,
        normalize: true,
        progressColor: this.resolveThemeColor('--primary', 'hsl(262 83% 58%)', 0.9),
        waveColor: this.resolveThemeColor('--foreground', 'hsl(215 16% 47%)', 0.22)
      });

      this.waveSurfer.on('error', () => {
        this.waveformLoading.set(false);
        this.waveformUnavailable.set(true);
        this.destroyWaveSurfer();
      });

      this.waveSurfer.on('ready', () => {
        this.waveformLoading.set(false);
        this.waveformUnavailable.set(false);
      });
    } catch {
      this.destroyWaveSurfer();
      this.waveformLoading.set(false);
      this.waveformUnavailable.set(true);
    }
  }

  private ensureAudioMetadata(audio: HTMLAudioElement): Promise<void> {
    if (audio.readyState >= HTMLMediaElement.HAVE_METADATA)
      return Promise.resolve();

    return new Promise((resolve, reject) => {
      const handleLoadedMetadata = (): void => {
        cleanup();
        resolve();
      };
      const handleCanPlay = (): void => {
        cleanup();
        resolve();
      };
      const handleError = (): void => {
        cleanup();
        reject(new Error('Failed to load audio metadata'));
      };
      const cleanup = (): void => {
        audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
        audio.removeEventListener('canplay', handleCanPlay);
        audio.removeEventListener('error', handleError);
      };

      audio.addEventListener('loadedmetadata', handleLoadedMetadata, { once: true });
      audio.addEventListener('canplay', handleCanPlay, { once: true });
      audio.addEventListener('error', handleError, { once: true });
      audio.load();
    });
  }

  private destroyWaveSurfer(): void {
    if (!this.waveSurfer)
      return;

    this.waveSurfer.destroy();
    this.waveSurfer = null;
  }

  private resolveThemeColor(cssVarName: string, fallback: string, alpha: number): string {
    if (typeof window === 'undefined')
      return fallback;

    const rawValue = window.getComputedStyle(document.documentElement)
      .getPropertyValue(cssVarName)
      .trim();

    if (!rawValue)
      return fallback;

    return `hsl(${rawValue} / ${alpha})`;
  }

  private applyAudioVolume(volumePercent: number): void {
    const audio = this.audioRef?.nativeElement;

    if (!audio)
      return;

    audio.volume = this.isMuted() ? 0 : volumePercent / 100;
    audio.muted = this.isMuted();
  }

  private setSharedVolume(volumePercent: number): void {
    persistSharedAudioPlayerVolume(volumePercent);
  }

  private buildSliderBackground(fillPercent: number): string {
    return [
      'linear-gradient(90deg, ',
      'hsl(var(--primary)) 0%, ',
      `hsl(var(--primary)) ${fillPercent}%, `,
      `hsl(var(--secondary)) ${fillPercent}%, `,
      'hsl(var(--secondary)) 100%)'
    ].join('');
  }

  private waitForNextPaint(): Promise<void> {
    return new Promise((resolve) => {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => resolve());
      });
    });
  }

  formatTime(seconds: number): string {
    if (!Number.isFinite(seconds) || seconds < 0)
      return '0:00';

    const totalSeconds = Math.floor(seconds);
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const remainingSeconds = totalSeconds % 60;

    if (hours > 0) {
      return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
    }

    return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
  }
}
