import { useCallback, useMemo, useRef, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Loader2, Play, Shuffle, ThumbsDown, Square } from 'lucide-react';
import { advanceVibe, endVibeSession, startVibeSession, vibeErrorMessage } from '../services/vibeSession';
import { trackService } from '../services/trackService';
import { usePlaybackStore } from '../store/usePlaybackStore';
import { useVibeStore } from '../store/useVibeStore';
import { TrackRow } from '../components/TrackRow';
import { PageContainer } from '../components/PageContainer';
import { PageHeader } from '../components/PageHeader';
import type { Track } from '../types';
import { VibeTimeline } from '../components/VibeTimeline';
import { VibeAura } from '../components/VibeAura';

const SEED_LIST_SIZE = 50;

export default function Vibe() {
  const { currentTrack, queue } = usePlaybackStore();
  const {
    activeSessionId,
    buffer,
    initialBatchStatus,
    profile,
  } = useVibeStore();

  const [starting, setStarting] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [empty, setEmpty] = useState(false);
  const startingRef = useRef(false);

  // The sample is drawn in the database and is uniform over everything
  // playable, so Surprise me still reaches the whole library. Reading all 5000
  // tracks to display fifty of them was megabytes of JSON for a phone to parse
  // before this page could paint.
  const { data: seeds, isLoading: libraryLoading } = useQuery({
    queryKey: ['library-seed'],
    queryFn: () => trackService.listSeedTracks(SEED_LIST_SIZE),
    enabled: !activeSessionId,
  });
  const seedTracks = useMemo(() => seeds?.tracks ?? [], [seeds]);
  const eligibleCount = seeds?.total ?? 0;

  const startSession = useCallback(
    async (seed: Track) => {
      if (startingRef.current) return;
      startingRef.current = true;
      setStarting(true);
      setError(null);
      setEmpty(false);
      try {
        const result = await startVibeSession(seed);
        if (result.tracks.length === 0) {
          setEmpty(true);
          setError('Could not load recommendations for this vibe. Please try another seed.');
        }
      } catch (startError) {
        setError(vibeErrorMessage(startError));
      } finally {
        startingRef.current = false;
        setStarting(false);
      }
    },
    []
  );

  const startFromCurrent = useCallback(() => {
    if (currentTrack) void startSession(currentTrack);
  }, [currentTrack, startSession]);

  const surpriseMe = useCallback(() => {
    if (seedTracks.length === 0) return;
    const seed = seedTracks[Math.floor(Math.random() * seedTracks.length)];
    void startSession(seed);
  }, [seedTracks, startSession]);

  const handleDislike = useCallback(() => {
    if (currentTrack) {
      void advanceVibe('disliked').catch((feedbackError) => setError(vibeErrorMessage(feedbackError)));
    }
  }, [currentTrack]);

  const handleEnd = useCallback(() => {
    void endVibeSession().catch((endError) => setError(vibeErrorMessage(endError)));
    setEmpty(false);
  }, []);

  // The aura shows the profile as light, which is not readable. These are the
  // same numbers the director steers on (Ethos law 5 — show the machinery).
  const profileMeta = useMemo(() => {
    if (initialBatchStatus === 'loading') return 'planning…';
    const pct = (value: number | undefined, fallback: number) =>
      `${Math.round(Math.min(1, Math.max(0, value ?? fallback)) * 100)}%`;
    const parts = [
      `energy ${pct(profile.energy, 0.5)}`,
      `discovery ${pct(profile.noveltyHunger ?? profile.explorationCoefficient, 0.3)}`,
    ];
    const goal = profile.sessionGoal;
    if (goal?.target) parts.push(`goal ${goal.progress ?? 0}/${goal.target}`);
    return parts.join(' · ');
  }, [profile, initialBatchStatus]);

  // Read what is actually queued rather than the plan preview: the seed plays
  // first and is not a plan item, so the preview alone would misreport what's next.
  const upcoming = useMemo(() => {
    const index = queue.findIndex((track) => track.id === currentTrack?.id);
    return index >= 0 ? queue.slice(index + 1) : buffer;
  }, [queue, currentTrack, buffer]);

  // ---- Start screen (no active session) ----
  if (!activeSessionId) {
    return (
      <PageContainer>
        {/* Three stacked paragraphs of explanation used to sit here. A seed, a
            shuffle and a track list say the same thing by being used. */}
        <PageHeader
          title="Start a Vibe"
          subtitle="Pick a seed. Playback starts at once and the queue re-plans as you keep or skip."
          meta={eligibleCount ? `${eligibleCount.toLocaleString()} tracks eligible` : undefined}
        />

        {error && (
          <div className="rounded-lg border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-300">
            {error}
          </div>
        )}

        <div className="grid gap-2 sm:grid-cols-2">
          {currentTrack && (
            <button
              onClick={startFromCurrent}
              disabled={starting}
              className="flex h-16 w-full items-center gap-3 rounded-lg border border-accent/50 bg-accent/10 px-3 text-left transition-colors hover:bg-accent/20 disabled:opacity-60"
            >
              <Play size={18} className="flex-none text-accent" />
              <div className="min-w-0 flex-1">
                <div className="truncate text-sm font-medium text-text">Vibe from &ldquo;{currentTrack.title}&rdquo;</div>
                <div className="truncate text-xs text-secondary">{currentTrack.artist}</div>
              </div>
            </button>
          )}

          <button
            onClick={surpriseMe}
            disabled={starting || libraryLoading || seedTracks.length === 0}
            className="flex h-16 w-full items-center gap-3 rounded-lg border border-border bg-surface0/60 px-3 text-left transition-colors hover:bg-surface1 disabled:opacity-60"
          >
            <Shuffle size={18} className="flex-none text-text" />
            <div className="min-w-0 flex-1">
              <div className="text-sm font-medium text-text">Surprise me</div>
              <div className="truncate text-xs text-secondary">
                {libraryLoading
                  ? 'reading library…'
                  : seedTracks.length === 0
                    ? 'No eligible tracks to seed from.'
                    : 'Random seed from the whole library.'}
              </div>
            </div>
            {starting && <Loader2 size={18} className="flex-none animate-spin text-muted" />}
          </button>
        </div>

        {!libraryLoading && seedTracks.length > 0 && (
          <div className="space-y-2">
            <h2 className="text-[11px] font-semibold uppercase tracking-[0.1em] text-muted">Or pick a seed track</h2>
            {/* Two columns of the sampled 50 — one 320px-tall scroller showed 6
                of them and left the rest behind a scrollbar. */}
            <ul className="track-list grid rounded-lg border border-border bg-surface0/30 px-1 sm:grid-cols-2 sm:gap-x-4 sm:[&>*:nth-child(2)]:border-t-0">
              {seedTracks.map((track, index) => (
                <li key={track.id}>
                  <TrackRow
                    track={track}
                    queue={seedTracks}
                    index={index}
                    showActions={false}
                    onSelect={startSession}
                  />
                </li>
              ))}
            </ul>
          </div>
        )}
      </PageContainer>
    );
  }

  // ---- Active session ----
  return (
    <PageContainer width="sm" className="relative isolate py-6">
      <VibeAura profile={profile} ambient />
      <div className="relative z-10 space-y-6">
        <PageHeader
          title="Vibing"
          // The buffer count belongs to Up next, and the plan revision is a
          // debugging number. The live profile does belong here.
          meta={profileMeta}
          // Both verbs sit in one cluster. Dislike used to live a row down,
          // which put 200px of empty header between the two things to press.
          actions={
            <>
              {currentTrack && (
                <button
                  onClick={handleDislike}
                  className="flex items-center gap-2 rounded-lg border border-border bg-surface0/60 px-3 py-2 text-sm text-text transition-colors hover:border-red/60 hover:text-red"
                >
                  <ThumbsDown size={16} />
                  Dislike &amp; skip
                </button>
              )}
              <button
                onClick={handleEnd}
                className="flex items-center gap-2 rounded-lg border border-border px-3 py-2 text-sm text-text transition-colors hover:border-red/60 hover:text-red"
              >
                <Square size={14} />
                End Vibe
              </button>
            </>
          }
        />

        {(empty || initialBatchStatus === 'exhausted' || initialBatchStatus === 'failed') && (
          <div className="rounded-lg border border-border bg-surface0 p-4 text-sm text-muted">
            {initialBatchStatus === 'failed'
              ? 'Could not load recommendations for this Vibe. Try starting a different one.'
              : 'No recommendations came back for this seed yet. Try ending and starting a different vibe.'}
          </div>
        )}

        {error && (
          <div className="rounded-lg border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-300">
            {error}
          </div>
        )}

        {/* Keep is gone: letting a track finish already reports `completed`,
            which the director weighs the same as an explicit keep. */}
        <VibeTimeline currentTrack={currentTrack} upcoming={upcoming} />
      </div>
    </PageContainer>
  );
}
