import { useEffect, useMemo, useRef, useState } from 'react';
import type { ImportedMediaAsset } from '../app/application-state';
import {
  assessBrowserPlayback,
  assessGeneratedResultPreview,
  isAudioMimeType,
  isVideoMimeType,
  mimeTypeFromFileName,
  type MediaProbe,
} from '../media';
import { renderWaveformToCanvas, type WaveformPeakData } from '../waveform';
import { Icon } from './Icon';

export interface PreviewExportResult {
  readonly name: string;
  readonly mimeType: string;
  readonly objectUrl: string;
  readonly probe?: MediaProbe;
}

export interface PreviewProxySettings {
  readonly maxDurationSeconds: number;
  readonly maxWidth: number;
}

export interface MediaPreviewProps {
  asset?: ImportedMediaAsset;
  result?: PreviewExportResult;
  onCreateProxy?: () => void;
  proxySettings?: PreviewProxySettings;
  onProxySettingsChange?: (settings: PreviewProxySettings) => void;
  proxyBusy?: boolean;
  waveformPeaks?: WaveformPeakData;
  currentTimeSeconds?: number;
  onCurrentTimeChange?: (timeSeconds: number) => void;
}

export function MediaPreview({
  asset,
  result,
  onCreateProxy,
  proxySettings,
  onProxySettingsChange,
  proxyBusy = false,
  waveformPeaks,
  currentTimeSeconds,
  onCurrentTimeChange,
}: MediaPreviewProps) {
  const [failedSourceUrl, setFailedSourceUrl] = useState<string>();
  const mediaElement = useRef<HTMLMediaElement>(null);
  const sourceUrl = result?.objectUrl ?? asset?.objectUrl;
  const playbackFailed = sourceUrl === failedSourceUrl;
  const fileMimeType = asset?.file.type;
  const declaredSourceMimeType =
    fileMimeType &&
    (isVideoMimeType(fileMimeType) || isAudioMimeType(fileMimeType))
      ? fileMimeType
      : asset
        ? mimeTypeFromFileName(asset.file.name)
        : undefined;
  const hasVideo = result
    ? result.mimeType.startsWith('video/')
    : asset?.probe
      ? asset.probe.streams.some(
          (stream) =>
            stream.type === 'video' && stream.disposition.attached_pic !== true
        )
      : declaredSourceMimeType
        ? isVideoMimeType(declaredSourceMimeType)
        : false;
  const hasAudio = result
    ? result.mimeType.startsWith('audio/')
    : asset?.probe
      ? asset.probe.streams.some((stream) => stream.type === 'audio')
      : declaredSourceMimeType
        ? isAudioMimeType(declaredSourceMimeType)
        : false;
  const hasImage = result?.mimeType.startsWith('image/') === true;
  const resultPlayback = useMemo(() => {
    if (!result) return undefined;
    const element = document.createElement(
      result.mimeType.startsWith('video/') ? 'video' : 'audio'
    );
    return assessGeneratedResultPreview(
      {
        fileName: result.name,
        mimeType: result.mimeType,
        ...(result.probe ? { probe: result.probe } : {}),
      },
      (mimeType) => element.canPlayType(mimeType)
    );
  }, [result]);

  const playback = useMemo(() => {
    if (!asset?.probe) return undefined;
    const element = document.createElement(hasVideo ? 'video' : 'audio');
    return assessBrowserPlayback(
      asset.probe,
      (mimeType) => element.canPlayType(mimeType),
      {
        fileName: asset.file.name,
        declaredMimeType: asset.file.type,
      }
    );
  }, [asset, hasVideo]);

  useEffect(() => {
    const element = mediaElement.current;
    if (
      element &&
      currentTimeSeconds !== undefined &&
      Number.isFinite(currentTimeSeconds) &&
      Math.abs(element.currentTime - currentTimeSeconds) > 0.05
    ) {
      element.currentTime = Math.max(0, currentTimeSeconds);
    }
  }, [currentTimeSeconds, sourceUrl]);

  const reportCurrentTime = () => {
    const value = mediaElement.current?.currentTime;
    if (value !== undefined && Number.isFinite(value)) {
      onCurrentTimeChange?.(value);
    }
  };

  if (!sourceUrl) {
    return (
      <div className="preview-empty">
        <div className="preview-empty__graphic" aria-hidden="true">
          <span />
          <Icon name="play" />
        </div>
        <p>Select a source to inspect and preview it.</p>
      </div>
    );
  }

  const needsProxy =
    !result &&
    (playbackFailed || playback?.support === 'unsupported') &&
    onCreateProxy;

  return (
    <div className="preview">
      <div className="preview__stage">
        {result && resultPlayback?.previewable === false ? (
          <div className="preview-unsupported" role="status">
            <Icon name="download" />
            <p>Download only: this browser cannot preview this result.</p>
            <small>{resultPlayback.reason}</small>
          </div>
        ) : hasImage && result ? (
          <img src={sourceUrl} alt={`Generated result: ${result.name}`} />
        ) : hasVideo ? (
          <video
            ref={(element) => {
              mediaElement.current = element;
            }}
            key={sourceUrl}
            controls
            playsInline
            preload="metadata"
            src={sourceUrl}
            onError={() => setFailedSourceUrl(sourceUrl)}
            onLoadedMetadata={reportCurrentTime}
            onTimeUpdate={reportCurrentTime}
          />
        ) : hasAudio ? (
          <div className="audio-preview">
            {!result && waveformPeaks ? (
              <AudioWaveformPreview
                peaks={waveformPeaks}
                currentTimeSeconds={currentTimeSeconds}
                sourceName={asset?.file.name ?? 'audio source'}
              />
            ) : (
              <div className="audio-preview__disc">
                <Icon name="audio" />
              </div>
            )}
            <audio
              ref={(element) => {
                mediaElement.current = element;
              }}
              key={sourceUrl}
              controls
              preload="metadata"
              src={sourceUrl}
              onError={() => setFailedSourceUrl(sourceUrl)}
              onLoadedMetadata={reportCurrentTime}
              onTimeUpdate={reportCurrentTime}
            />
          </div>
        ) : (
          <div className="preview-unsupported">
            <Icon name="file" />
            <p>This derivative has no browser preview.</p>
          </div>
        )}
      </div>
      <div className="preview__caption">
        <span className="status-dot status-dot--local" />
        <span>
          {result
            ? resultPlayback?.previewable === false
              ? `Download only · ${result.name}`
              : `Generated result · ${result.name}`
            : (playback?.reason ??
              (asset?.phase === 'probing'
                ? 'Local source preview · Media inspection continues in the background.'
                : 'Local source preview'))}
        </span>
        {needsProxy && !proxySettings ? (
          <button type="button" className="text-button" onClick={onCreateProxy}>
            Create short preview proxy
          </button>
        ) : null}
      </div>
      {needsProxy && proxySettings ? (
        <div
          className={`preview-proxy-controls${hasVideo ? '' : ' preview-proxy-controls--audio'}`}
        >
          <div>
            <strong>Create a disposable preview proxy</strong>
            <span>
              {hasVideo
                ? 'H.264/AAC MP4, cached locally by these bounded settings.'
                : 'AAC/M4A, cached locally by these bounded settings.'}
            </span>
          </div>
          <label>
            <span>Maximum seconds</span>
            <input
              type="number"
              min="1"
              max="300"
              step="1"
              value={proxySettings.maxDurationSeconds}
              disabled={proxyBusy}
              onChange={(event) => {
                const value = Number(event.currentTarget.value);
                if (Number.isFinite(value)) {
                  onProxySettingsChange?.({
                    ...proxySettings,
                    maxDurationSeconds: Math.min(300, Math.max(1, value)),
                  });
                }
              }}
            />
          </label>
          {hasVideo ? (
            <label>
              <span>Maximum width</span>
              <select
                value={proxySettings.maxWidth}
                disabled={proxyBusy}
                onChange={(event) =>
                  onProxySettingsChange?.({
                    ...proxySettings,
                    maxWidth: Number(event.currentTarget.value),
                  })
                }
              >
                <option value="480">480 px</option>
                <option value="720">720 px</option>
                <option value="960">960 px</option>
                <option value="1280">1280 px</option>
                <option value="1920">1920 px</option>
              </select>
            </label>
          ) : null}
          <button
            type="button"
            className="button button--secondary"
            disabled={proxyBusy}
            onClick={onCreateProxy}
          >
            <Icon name={proxyBusy ? 'queue' : 'play'} />
            {proxyBusy ? 'Queued or running…' : 'Create proxy'}
          </button>
        </div>
      ) : null}
    </div>
  );
}

interface AudioWaveformPreviewProps {
  readonly peaks: WaveformPeakData;
  readonly currentTimeSeconds?: number;
  readonly sourceName: string;
}

function AudioWaveformPreview({
  peaks,
  currentTimeSeconds,
  sourceName,
}: AudioWaveformPreviewProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const stageRef = useRef<HTMLDivElement>(null);
  const [renderRevision, setRenderRevision] = useState(0);

  useEffect(() => {
    const stage = stageRef.current;
    if (stage === null || typeof ResizeObserver === 'undefined') return;
    const observer = new ResizeObserver(() =>
      setRenderRevision((revision) => revision + 1)
    );
    observer.observe(stage);
    return () => observer.disconnect();
  }, []);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (canvas === null) return;
    const style = getComputedStyle(canvas);
    try {
      renderWaveformToCanvas(canvas, peaks, {
        width: Math.max(1, Math.round(canvas.clientWidth || 640)),
        height: 112,
        devicePixelRatio: Math.min(
          4,
          Math.max(1, window.devicePixelRatio || 1)
        ),
        waveColor:
          style.getPropertyValue('--audio-waveform-wave').trim() || '#6fd7ca',
        backgroundColor:
          style.getPropertyValue('--audio-waveform-background').trim() ||
          '#101b22',
        centerLineColor:
          style.getPropertyValue('--audio-waveform-center').trim() ||
          'rgba(255, 255, 255, 0.22)',
        accessibleLabel: `Bounded waveform preview for ${sourceName}.`,
      });
    } catch {
      // Audio controls remain usable if Canvas is unavailable.
    }
  }, [peaks, renderRevision, sourceName]);

  const playheadFraction =
    currentTimeSeconds === undefined || peaks.durationSeconds <= 0
      ? undefined
      : Math.min(1, Math.max(0, currentTimeSeconds / peaks.durationSeconds));

  return (
    <div className="audio-preview__waveform" ref={stageRef}>
      <canvas ref={canvasRef} />
      {playheadFraction !== undefined ? (
        <span
          className="audio-preview__playhead"
          style={{ left: `${String(playheadFraction * 100)}%` }}
          aria-hidden="true"
        />
      ) : null}
    </div>
  );
}
