"use client";

import { useMemo } from "react";
import type { SanityImageSource } from "@sanity/image-url";
import { IoPlay, IoPause } from "react-icons/io5";
import { usePlayer, type PlayerTrack } from "@/components/player/PlayerContext";
import { parseDuration } from "@/lib/duration";

export type TrackData = {
  workId?: string;
  workTitle?: string;
  composerName?: string;
  arrangerName?: string;
  movement?: string;
  displayTitle?: string;
  duration?: string;
  artist?: string;
  previewMp3Url?: string;
};

type Props = {
  tracks: TrackData[];
  albumCover?: SanityImageSource;
  albumArtist?: string;
  releaseSlug?: string;
};

/* ── Grouping types ── */

type MovementEntry = {
  movement?: string;
  duration?: string;
  flatIndex: number;
};

type WorkGroup = {
  workId?: string;
  workTitle?: string;
  arrangerName?: string;
  entries: MovementEntry[];
};

type ComposerBlock = {
  type: "composer";
  composerName: string;
  works: WorkGroup[];
};

type DisplayTitleBlock = {
  type: "displayTitle";
  title: string;
  duration?: string;
  flatIndex: number;
};

type TracklistBlock = ComposerBlock | DisplayTitleBlock;

/* ── Grouping logic ── */

function groupTracks(tracks: TrackData[]): TracklistBlock[] {
  const blocks: TracklistBlock[] = [];
  let currentComposer: ComposerBlock | null = null;
  let currentWork: WorkGroup | null = null;

  function flushWork() {
    if (currentWork && currentComposer) {
      currentComposer.works.push(currentWork);
      currentWork = null;
    }
  }

  function flushComposer() {
    flushWork();
    if (currentComposer) {
      blocks.push(currentComposer);
      currentComposer = null;
    }
  }

  for (let i = 0; i < tracks.length; i++) {
    const track = tracks[i];

    // Display title track (no work reference)
    if (!track.workId && track.displayTitle) {
      flushComposer();
      blocks.push({
        type: "displayTitle",
        title: track.displayTitle,
        duration: track.duration,
        flatIndex: i,
      });
      continue;
    }

    // Work-based track
    const composerName = track.composerName || "";

    const arrangerName = track.arrangerName || "";
    const prevArranger =
      currentWork?.arrangerName || currentComposer?.works.at(-1)?.arrangerName || "";

    if (
      !currentComposer ||
      currentComposer.composerName !== composerName ||
      arrangerName !== prevArranger
    ) {
      flushComposer();
      currentComposer = {
        type: "composer",
        composerName,
        works: [],
      };
    }

    if (!currentWork || currentWork.workId !== track.workId) {
      flushWork();
      currentWork = {
        workId: track.workId,
        workTitle: track.workTitle,
        arrangerName: track.arrangerName,
        entries: [],
      };
    }

    currentWork.entries.push({
      movement: track.movement,
      duration: track.duration,
      flatIndex: i,
    });
  }

  flushComposer();
  return blocks;
}

/* ── Duration helpers ── */

function formatDuration(totalSeconds: number): string {
  if (totalSeconds <= 0) return "";
  const h = Math.floor(totalSeconds / 3600);
  const m = Math.floor((totalSeconds % 3600) / 60);
  const s = totalSeconds % 60;
  const pad = (n: number) => n.toString().padStart(2, "0");
  return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
}

/* ── Build player-friendly flat track list ── */

export function buildPlayerTracks(
  tracks: TrackData[],
  albumCover?: SanityImageSource,
  albumArtist?: string,
  releaseSlug?: string,
): PlayerTrack[] {
  return tracks
    .filter((t) => t.previewMp3Url)
    .map((t) => {
      let title: string;
      let movement: string | undefined;
      if (t.displayTitle) {
        title = t.displayTitle;
      } else if (t.workTitle && t.movement) {
        title = t.workTitle;
        movement = t.movement;
      } else {
        title = t.workTitle || "(Untitled)";
      }

      let subtitle: string;
      if (t.composerName) {
        subtitle = t.arrangerName ? `${t.composerName} (arr. ${t.arrangerName})` : t.composerName;
      } else {
        subtitle = t.artist || albumArtist || "";
      }

      return {
        previewMp3Url: t.previewMp3Url!,
        title,
        movement,
        subtitle,
        albumCover,
        trackDuration: parseDuration(t.duration) || undefined,
        releaseSlug,
      };
    });
}

/* ── Build a map from flat track index → player playlist index ── */

function buildIndexMap(tracks: TrackData[]): Map<number, number> {
  const map = new Map<number, number>();
  let playerIdx = 0;
  for (let i = 0; i < tracks.length; i++) {
    if (tracks[i].previewMp3Url) {
      map.set(i, playerIdx);
      playerIdx++;
    }
  }
  return map;
}

/* ── Component ── */

function Duration({ value }: { value?: string }) {
  if (!value) return <span className="w-14 shrink-0" />;
  return (
    <span className="w-14 shrink-0 text-right text-lightsec tabular-nums dark:text-darksec">
      {value}
    </span>
  );
}

function TrackRow({
  children,
  duration,
  indent,
  playable,
  isCurrent,
  isCurrentPlaying,
  onPlay,
  onToggle,
}: {
  children: React.ReactNode;
  duration?: string;
  indent?: boolean;
  playable?: boolean;
  isCurrent?: boolean;
  isCurrentPlaying?: boolean;
  onPlay?: () => void;
  onToggle?: () => void;
}) {
  const handleClick = () => {
    if (isCurrent) {
      onToggle?.();
    } else if (playable) {
      onPlay?.();
    }
  };

  return (
    <div
      role={playable ? "button" : undefined}
      tabIndex={playable ? 0 : undefined}
      onClick={playable ? handleClick : undefined}
      onKeyDown={
        playable
          ? (e) => {
              if (e.key === "Enter" || e.key === " ") {
                e.preventDefault();
                handleClick();
              }
            }
          : undefined
      }
      className={[
        "group mb-1 flex items-start justify-between gap-4 leading-tight",
        indent ? "pl-4" : "",
        playable ? "cursor-pointer" : "",
        isCurrent ? "text-trptkblue dark:text-white" : "",
      ].join(" ")}
    >
      <span className="relative min-w-0">
        {playable && (
          <span
            className={[
              "absolute top-0.5 right-full mr-1.5 transition-opacity duration-200 ease-in-out",
              isCurrent ? "opacity-100" : "opacity-0 group-hover:opacity-50",
            ].join(" ")}
          >
            {isCurrent && isCurrentPlaying ? <IoPause size={14} /> : <IoPlay size={14} />}
          </span>
        )}
        {children}
      </span>
      <Duration value={duration} />
    </div>
  );
}

export function Tracklist({ tracks, albumCover, albumArtist, releaseSlug }: Props) {
  const { loadPlaylist, currentIndex, playlist, isPlaying, togglePlayback } = usePlayer();

  const validTracks = tracks.filter(Boolean);
  const blocks = useMemo(() => groupTracks(validTracks), [validTracks]);
  const totalSeconds = validTracks.reduce((sum, t) => sum + parseDuration(t.duration), 0);
  const totalTime = formatDuration(totalSeconds);

  const playerTracks = useMemo(
    () => buildPlayerTracks(validTracks, albumCover, albumArtist, releaseSlug),
    [validTracks, albumCover, albumArtist, releaseSlug],
  );

  const indexMap = useMemo(() => buildIndexMap(validTracks), [validTracks]);

  // Check if a flat track index is the currently loaded track (playing or paused)
  const isCurrentTrack = (flatIndex: number) => {
    if (currentIndex === null) return false;
    const playerIdx = indexMap.get(flatIndex);
    if (playerIdx === undefined) return false;
    return (
      playerIdx === currentIndex &&
      playlist[currentIndex]?.previewMp3Url === playerTracks[playerIdx]?.previewMp3Url
    );
  };

  const playTrack = (flatIndex: number) => {
    const playerIdx = indexMap.get(flatIndex);
    if (playerIdx !== undefined) {
      loadPlaylist(playerTracks, playerIdx);
    }
  };

  if (validTracks.length === 0) {
    return <p className="text-lightsec dark:text-darksec">No tracks available.</p>;
  }

  return (
    <div>
      {blocks.map((block, i) => {
        const prevBlock = i > 0 ? blocks[i - 1] : null;
        const isConsecutiveDisplayTitle =
          block.type === "displayTitle" && prevBlock?.type === "displayTitle";
        const blockMargin = i === 0 ? "" : isConsecutiveDisplayTitle ? "" : "mt-6";

        if (block.type === "displayTitle") {
          const playable = indexMap.has(block.flatIndex);
          return (
            <div key={i} className={blockMargin}>
              <TrackRow
                duration={block.duration}
                playable={playable}
                isCurrent={isCurrentTrack(block.flatIndex)}
                isCurrentPlaying={isCurrentTrack(block.flatIndex) && isPlaying}
                onPlay={() => playTrack(block.flatIndex)}
                onToggle={togglePlayback}
              >
                {block.title}
              </TrackRow>
            </div>
          );
        }

        return (
          <div key={i} className={blockMargin}>
            {block.composerName && (
              <p className="text-lightsec dark:text-darksec">
                {block.composerName}
                {block.works[0]?.arrangerName && (
                  <span className="ml-2 opacity-50">(arr. {block.works[0].arrangerName})</span>
                )}
              </p>
            )}
            <div className="">
              {block.works.map((work, wi) => {
                const showArranger =
                  work.arrangerName && work.arrangerName !== block.works[0]?.arrangerName;

                // Single entry for this work
                if (work.entries.length === 1) {
                  const entry = work.entries[0];
                  const playable = indexMap.has(entry.flatIndex);
                  return (
                    <div key={wi}>
                      {showArranger && (
                        <p className="mt-2 mb-1 leading-tight text-lightsec opacity-50 dark:text-darksec">
                          arr. {work.arrangerName}
                        </p>
                      )}
                      <TrackRow
                        duration={entry.duration}
                        playable={playable}
                        isCurrent={isCurrentTrack(entry.flatIndex)}
                        isCurrentPlaying={isCurrentTrack(entry.flatIndex) && isPlaying}
                        onPlay={() => playTrack(entry.flatIndex)}
                        onToggle={togglePlayback}
                      >
                        <span className="font-silkasb">{work.workTitle || "(Untitled)"}</span>
                        {entry.movement && <span>: {entry.movement}</span>}
                      </TrackRow>
                    </div>
                  );
                }

                // Multiple movements for this work
                return (
                  <div key={wi} className={wi > 0 ? "mt-3" : ""}>
                    {showArranger && (
                      <p className="mb-1 leading-tight text-lightsec opacity-50 dark:text-darksec">
                        arr. {work.arrangerName}
                      </p>
                    )}
                    <p className="mb-1 font-silkasb leading-tight">
                      {work.workTitle || "(Untitled)"}
                    </p>
                    {work.entries.map((entry, ei) => {
                      const playable = indexMap.has(entry.flatIndex);
                      return (
                        <TrackRow
                          key={ei}
                          duration={entry.duration}
                          indent
                          playable={playable}
                          isCurrent={isCurrentTrack(entry.flatIndex)}
                          isCurrentPlaying={isCurrentTrack(entry.flatIndex) && isPlaying}
                          onPlay={() => playTrack(entry.flatIndex)}
                          onToggle={togglePlayback}
                        >
                          {entry.movement || `${ei + 1}.`}
                        </TrackRow>
                      );
                    })}
                  </div>
                );
              })}
            </div>
          </div>
        );
      })}

      {totalTime && (
        <div className="mt-6 border-t border-lighttext/10 pt-6 dark:border-darktext/10">
          <div className="flex items-baseline justify-between gap-4">
            <span className="text-lightsec dark:text-darksec">Total playing time</span>
            <span className="w-14 shrink-0 text-right text-lightsec tabular-nums dark:text-darksec">
              {totalTime}
            </span>
          </div>
        </div>
      )}
    </div>
  );
}
