import { useEffect, useState } from 'react';
import { defineStory, useBoolean, useSelect } from '@djangocfg/playground';
import { TooltipProvider } from '@djangocfg/ui-core/components';
import { decodePeaks } from './audio/decodePeaks';
import { PlayerProvider } from './context/PlayerProvider';
import { Player } from './Player';
import {
  useActivePlayer,
  useIsActivePlayer,
  useLastActivePlayer,
} from './hooks/useActivePlayer';
import { usePlayerPreferences } from './hooks/usePlayerPreferences';
import { Cover } from './parts/Cover/Cover';
import { LoopButton } from './parts/Controls/LoopButton';
import { PlayButton } from './parts/Controls/PlayButton';
import { VolumeControl } from './parts/Controls/VolumeControl';
import { Artist } from './parts/Meta/Artist';
import { TimeDisplay } from './parts/Meta/TimeDisplay';
import { Title } from './parts/Meta/Title';
import { Waveform } from './parts/Waveform/Waveform';
import type { WaveformMode } from './types';

export default defineStory({
  title: 'Tools/Audio Player',
  component: Player,
  description:
    'WebView-safe audio player. Static peaks waveform by default; clip-path playhead; one accent.',
});

// Local samples copied from @sources/. Vite serves them same-origin so
// crossOrigin="anonymous" + decodeAudioData work without CORS friction.
const SAMPLES = {
  short: '/audio/short.mp3',
  voice: '/audio/voice.mp3',
  long: '/audio/long.mp3',
} as const;

const COVER = 'data:image/svg+xml;utf8,' + encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
  <rect width="200" height="200" fill="#0f172a"/>
  <circle cx="100" cy="100" r="56" fill="none" stroke="#94a3b8" stroke-width="2"/>
  <circle cx="100" cy="100" r="6" fill="#94a3b8"/>
</svg>
`);

const Frame = ({ children, max = 'max-w-xl' }: { children: React.ReactNode; max?: string }) => (
  <div className={`mx-auto w-full ${max} p-6`}>{children}</div>
);

function Section({ title, hint, children }: { title: string; hint?: string; children: React.ReactNode }) {
  return (
    <section className="space-y-2">
      <header>
        <h3 className="text-sm font-medium text-foreground">{title}</h3>
        {hint && <p className="text-xs text-muted-foreground">{hint}</p>}
      </header>
      {children}
    </section>
  );
}

export const Default = () => (
  <Frame>
    <Player src={SAMPLES.short} title="Stereo demo" artist="wavesurfer samples" />
  </Frame>
);

export const WithCover = () => (
  <Frame>
    <Player src={SAMPLES.short} title="With cover" artist="wavesurfer samples" cover={COVER} />
  </Frame>
);

export const Compact = () => (
  <Frame max="max-w-md">
    <Player src={SAMPLES.short} title="Compact" variant="compact" />
  </Frame>
);

export const Bars = () => (
  <Frame>
    <Player
      src={SAMPLES.short}
      title="Bars decoration"
      artist="No audio coupling"
      waveform={{ mode: 'bars', height: 28 }}
    />
  </Frame>
);

export const Live = () => (
  <Frame>
    <Player
      src={SAMPLES.voice}
      title="Live analyser"
      artist="Reads from AnalyserNode at 30 Hz"
      waveform={{ mode: 'live', height: 48 }}
    />
  </Frame>
);

export const NoWaveform = () => (
  <Frame>
    <Player src={SAMPLES.short} title="Minimal" artist="No waveform" waveform={{ mode: 'none' }} />
  </Frame>
);

export const CustomLayout = () => (
  <Frame max="max-w-2xl">
    <p className="mb-4 text-xs text-muted-foreground">
      Slot composition: drop <code className="rounded bg-muted px-1">PlayerProvider</code> and
      arrange the parts you want yourself. Big cover left (spans two rows), meta + timer right,
      waveform + controls full-width below.
    </p>
    <CustomLayoutDemo />
  </Frame>
);

export const ReactiveCover = () => (
  <Frame>
    <Player
      src={SAMPLES.voice}
      title="Subtle reactive cover"
      artist="Tiny scale on the cover"
      cover={COVER}
      reactiveCover="subtle"
      waveform={{ mode: 'live' }}
    />
  </Frame>
);

export const ErrorState = () => (
  <Frame>
    <Player
      src="/audio/this-does-not-exist.mp3"
      title="Broken source"
      artist="Demonstrates error state"
    />
  </Frame>
);

export const Exclusive = () => {
  const active = useActivePlayer();
  return (
    <Frame max="max-w-2xl">
      <p className="mb-4 text-xs text-muted-foreground">
        Three players, <code className="rounded bg-muted px-1">exclusive</code> on (default).
        Press play on one — the others pause automatically (same tab + cross-tab via{' '}
        <code className="rounded bg-muted px-1">BroadcastChannel</code>).
        <br />
        Active id: <code className="rounded bg-muted px-1">{active ?? '∅'}</code>
      </p>
      <div className="space-y-3">
        <Player src={SAMPLES.short} title="Track A · stereo" artist="wavesurfer" />
        <Player src={SAMPLES.voice} title="Track B · librivox" artist="voice" />
        <Player src={SAMPLES.long} title="Track C · phonograph" artist="deepnote" />
      </div>
    </Frame>
  );
};

// One-stop demo of every prop / mode / hook. Long but scrollable; each section
// is independent so you can grab a single example as a reference.
export const Showcase = () => {
  return (
    <div className="mx-auto w-full max-w-3xl space-y-8 p-6">
      <Section title="1 · Default" hint="Static peaks waveform, container query auto-switches to compact below 480 px / on phones.">
        <Player src={SAMPLES.short} title="Stereo demo" artist="wavesurfer" />
      </Section>

      <Section title="2 · With cover" hint="Cover uses <img loading='lazy' decoding='async'>; placeholder otherwise.">
        <Player src={SAMPLES.short} title="With cover" artist="wavesurfer" cover={COVER} />
      </Section>

      <Section title="3 · Compact (forced)" hint="Single-row layout. Always picked when variant='compact'.">
        <Player src={SAMPLES.short} title="Compact" variant="compact" />
      </Section>

      <Section title="4 · Skip controls" hint="onPrev / onNext render the SkipBack / SkipForward buttons (and wire MediaSession).">
        <Player
          src={SAMPLES.short}
          title="With prev / next"
          artist="onPrev / onNext"
          cover={COVER}
          onPrev={() => console.log('prev')}
          onNext={() => console.log('next')}
        />
      </Section>

      <Section title="5 · Live analyser" hint="mode='live' — AnalyserNode @ 30 Hz pushed into LevelsStore; canvas paints from rAF.">
        <Player
          src={SAMPLES.voice}
          title="Live waveform"
          artist="AnalyserNode at 30 Hz"
          waveform={{ mode: 'live', height: 48 }}
        />
      </Section>

      <Section title="6 · Bars (decoration)" hint="mode='bars' — CSS-only equalizer animation, no audio coupling.">
        <Player
          src={SAMPLES.short}
          title="Bars"
          artist="No audio coupling"
          waveform={{ mode: 'bars', height: 28 }}
        />
      </Section>

      <Section title="7a · Progress bar (no animation)" hint="mode='progress' — thin scrubber, no waveform/animation. Same click + drag + hover-tip + playhead pipeline.">
        <Player
          src={SAMPLES.short}
          title="Plain progress"
          artist="No waveform animation"
          waveform={{ mode: 'progress', height: 4 }}
        />
      </Section>

      <Section title="7b · No waveform" hint="mode='none' — bare meta + controls. Use when even the scrubber gets in the way.">
        <Player src={SAMPLES.short} title="Minimal" waveform={{ mode: 'none' }} />
      </Section>

      <Section title="8 · Subtle reactive cover" hint="reactiveCover='subtle' — compositor-only scale tied to a low-pass envelope.">
        <Player
          src={SAMPLES.voice}
          title="Reactive cover"
          artist="scale(1.00 — 1.03)"
          cover={COVER}
          reactiveCover="subtle"
          waveform={{ mode: 'live' }}
        />
      </Section>

      <Section title="9 · Pre-computed peaks" hint="peaks prop seeds the cache — skips the OfflineAudioContext decode entirely.">
        <PrecomputedPeaksDemo />
      </Section>

      <Section title="10 · seekStartsPlayback={false}" hint="Click on the waveform seeks but does not start playback. Useful in embeds.">
        <Player
          src={SAMPLES.short}
          title="Click only seeks"
          artist="No autoplay on click"
          seekStartsPlayback={false}
        />
      </Section>

      <Section title="11 · Persistent volume sync" hint="Two uncontrolled players read / write the same persisted prefs (also synced across tabs).">
        <PreferencesSyncDemo />
      </Section>

      <Section title="12 · Active-player coordination" hint="exclusive (default) pauses sibling players. Active id + 'last active' badges from useActivePlayer / useLastActivePlayer.">
        <ActivePlayerDemo />
      </Section>

      <Section title="13 · Custom toolbar via useIsActivePlayer" hint="Uses Player's id+`useIsActivePlayer` to outline the playing card.">
        <ActiveOutlineDemo />
      </Section>

      <Section title="14 · Custom layout (slot composition)" hint="Drop <PlayerProvider> + import the parts you want. Build any grid.">
        <CustomLayoutDemo />
      </Section>

      <Section title="15 · Error state" hint="Bad src triggers MediaError → ErrorState with retry.">
        <Player src="/audio/this-does-not-exist.mp3" title="Broken source" artist="See error pane" />
      </Section>
    </div>
  );
};

function PrecomputedPeaksDemo() {
  const [peaks, setPeaks] = useState<Float32Array | null>(null);
  useEffect(() => {
    let alive = true;
    decodePeaks(SAMPLES.short).then((p) => alive && setPeaks(p)).catch(() => undefined);
    return () => {
      alive = false;
    };
  }, []);
  if (!peaks) {
    return (
      <div className="rounded-md border border-border/60 bg-muted/30 px-3 py-4 text-xs text-muted-foreground">
        Decoding peaks once (so the player below mounts with them already in hand)…
      </div>
    );
  }
  return (
    <Player
      src={SAMPLES.short}
      title="Pre-computed peaks"
      artist={`Float32Array · ${peaks.length} buckets`}
      peaks={peaks}
    />
  );
}

function PreferencesSyncDemo() {
  const prefs = usePlayerPreferences();
  return (
    <div className="space-y-3">
      <div className="rounded-md border border-border/60 bg-muted/30 px-3 py-2 text-xs">
        Stored prefs:{' '}
        <code className="text-foreground">
          volume {Math.round(prefs.volume * 100)}% · {prefs.muted ? 'muted' : 'unmuted'}
        </code>
      </div>
      <Player src={SAMPLES.short} title="Player A" artist="uncontrolled volume" />
      <Player src={SAMPLES.voice} title="Player B" artist="uncontrolled volume" />
    </div>
  );
}

function ActivePlayerDemo() {
  const active = useActivePlayer();
  const last = useLastActivePlayer();
  return (
    <div className="space-y-3">
      <div className="grid grid-cols-2 gap-2 text-xs">
        <div className="rounded border border-border/60 bg-card px-2 py-1.5">
          Active: <code className="text-foreground">{active ?? '∅'}</code>
        </div>
        <div className="rounded border border-border/60 bg-card px-2 py-1.5">
          Last: <code className="text-foreground">{last ?? '∅'}</code>
        </div>
      </div>
      <Player src={SAMPLES.short} title="Track A" artist="exclusive default" />
      <Player src={SAMPLES.voice} title="Track B" artist="exclusive default" />
      <Player src={SAMPLES.long} title="Track C" artist="exclusive default" />
    </div>
  );
}

function ActiveOutlineCard({
  id,
  src,
  title,
}: {
  id: string;
  src: string;
  title: string;
}) {
  const isActive = useIsActivePlayer(id);
  return (
    <div
      className={`rounded-lg p-px transition-colors ${
        isActive ? 'bg-primary/60' : 'bg-transparent'
      }`}
    >
      <Player src={src} title={title} artist={isActive ? 'now playing' : 'idle'} ariaLabel={id} />
    </div>
  );
}

function ActiveOutlineDemo() {
  // We pass a stable `ariaLabel` as the id we read back from the bus.
  // useActivePlayer returns React's useId-generated value, so a real consumer
  // would key off that instead — this is just to demonstrate the pattern.
  return (
    <div className="space-y-3">
      <p className="text-xs text-muted-foreground">
        The currently playing card gets a primary ring. Implementation reads
        <code className="rounded bg-muted px-1">useIsActivePlayer(id)</code>.
      </p>
      <ActiveOutlineCard id="card-a" src={SAMPLES.short} title="Card A" />
      <ActiveOutlineCard id="card-b" src={SAMPLES.voice} title="Card B" />
    </div>
  );
}

function CustomLayoutDemo() {
  return (
    <TooltipProvider delayDuration={400}>
    <PlayerProvider src={SAMPLES.long} title="Custom layout demo" artist="phonograph" cover={COVER}>
      <div className="grid grid-cols-[96px_1fr_auto] gap-4 rounded-lg border border-border/60 bg-card p-4">
        {/* Left: large cover spanning two rows */}
        <div className="row-span-2">
          <Cover size={96} alt="Custom layout cover" />
        </div>

        {/* Top right: meta + timer */}
        <div className="min-w-0">
          <Title />
          <Artist />
        </div>
        <TimeDisplay />

        {/* Bottom: full-width waveform + controls */}
        <div className="col-span-2 space-y-3">
          <Waveform height={48} />
          <div className="flex items-center justify-between gap-2">
            <PlayButton />
            <div className="flex items-center gap-1">
              <VolumeControl />
              <LoopButton />
            </div>
          </div>
        </div>
      </div>
    </PlayerProvider>
    </TooltipProvider>
  );
}

export const Interactive = () => {
  const [sampleKey] = useSelect('sample', {
    options: ['short', 'voice', 'long'] as const,
    defaultValue: 'short',
    label: 'Sample',
  });
  const [mode] = useSelect('waveformMode', {
    options: ['peaks', 'live', 'bars', 'progress', 'none'] as const,
    defaultValue: 'peaks',
    label: 'Waveform mode',
  });
  const [variant] = useSelect('variant', {
    options: ['auto', 'default', 'compact'] as const,
    defaultValue: 'default',
    label: 'Variant',
  });
  const [withCover] = useBoolean('withCover', { defaultValue: true, label: 'Show cover' });
  const [reactive] = useBoolean('reactive', {
    defaultValue: false,
    label: 'Reactive cover (subtle)',
  });

  const [userSrc, setUserSrc] = useState<string | null>(null);
  const [userName, setUserName] = useState<string>('');

  useEffect(() => {
    return () => {
      if (userSrc?.startsWith('blob:')) URL.revokeObjectURL(userSrc);
    };
  }, [userSrc]);

  const src = userSrc ?? SAMPLES[sampleKey];

  return (
    <Frame>
      <div className="mb-4 flex flex-wrap items-center gap-3">
        <label className="inline-flex cursor-pointer items-center gap-2 rounded-md border border-border/60 bg-card px-3 py-1.5 text-xs hover:bg-accent">
          <span>Drop / pick a file</span>
          <input
            type="file"
            accept="audio/*"
            className="hidden"
            onChange={(e) => {
              const file = e.target.files?.[0];
              if (!file) return;
              if (userSrc?.startsWith('blob:')) URL.revokeObjectURL(userSrc);
              setUserSrc(URL.createObjectURL(file));
              setUserName(file.name);
            }}
          />
        </label>
        {userSrc && (
          <button
            type="button"
            onClick={() => {
              if (userSrc.startsWith('blob:')) URL.revokeObjectURL(userSrc);
              setUserSrc(null);
              setUserName('');
            }}
            className="rounded-md border border-border/60 px-2 py-1 text-xs text-muted-foreground hover:bg-accent"
          >
            Reset to sample
          </button>
        )}
        <span className="text-xs text-muted-foreground">
          {userSrc ? userName : `sample: ${sampleKey}`}
        </span>
      </div>
      <Player
        key={src}
        src={src}
        title={userSrc ? userName : 'Local sample'}
        artist={userSrc ? 'Your file (blob URL)' : 'wavesurfer / phonograph'}
        cover={withCover ? COVER : undefined}
        variant={variant}
        waveform={{ mode: mode as WaveformMode }}
        reactiveCover={reactive ? 'subtle' : false}
      />
    </Frame>
  );
};
