"use client";

import { useEffect, useState, type ReactNode } from "react";
import { Modal } from "./modal";
import { CoverArt } from "./cover-art";
import { toast } from "./toast";

type Track = { position: number; title: string; lengthMs: number | null };

// Session-scoped, per-release-group tracklist cache: reopening the same album modal is
// instant with zero network. Complements the server-side ApiCache (which spans sessions).
const trackCache = new Map<string, Track[]>();

function fmt(ms: number | null): string {
  if (ms == null) return "";
  const s = Math.round(ms / 1000);
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
}

export type AlbumInfo = {
  rgMbid: string | null;
  album: string;
  artist: string;
  year?: string | null;
  type?: string | null;
  /** When present, the modal shows a Follow button + links the artist name to their page. */
  artistMbid?: string | null;
  /** Real on-disk audio filenames (Library items). When present, the modal shows THESE —
   *  revealing incomplete downloads / edition mismatches — instead of MusicBrainz's listing. */
  trackNames?: string[] | null;
};

/** Parse on-disk "## Title.ext" filenames into display tracks (sorted order = position). */
function parseOnDisk(names: string[]): Track[] {
  return names.map((name, i) => {
    const stem = name.replace(/\.[^.]+$/, ""); // drop extension
    const title = stem.replace(/^\s*\d+(?:[-.\s]+\d+)?[\s._-]+/, "").trim() || stem; // drop leading NN / N-NN
    return { position: i + 1, title, lengthMs: null };
  });
}

/** Reusable album modal: cover art + tracklist (fetched from MB by release-group MBID) with
 *  slots for a status chip and context-specific action buttons. */
export function AlbumModal({
  open,
  onClose,
  album,
  status,
  actions,
}: {
  open: boolean;
  onClose: () => void;
  album: AlbumInfo;
  status?: ReactNode;
  actions?: ReactNode;
}) {
  const [tracks, setTracks] = useState<Track[] | "loading" | "error">("loading");
  const onDisk = (album.trackNames?.length ?? 0) > 0;
  // null = unknown/loading; only meaningful when album.artistMbid is set.
  const [followed, setFollowed] = useState<boolean | null>(null);

  useEffect(() => {
    if (!open || !album.artistMbid) {
      setFollowed(false);
      return;
    }
    let cancelled = false;
    setFollowed(null);
    fetch(`/api/artists?mbid=${encodeURIComponent(album.artistMbid)}`)
      .then((r) => (r.ok ? r.json() : { followed: false }))
      .then((d) => !cancelled && setFollowed(!!d.followed))
      .catch(() => !cancelled && setFollowed(false));
    return () => {
      cancelled = true;
    };
  }, [open, album.artistMbid]);

  async function follow() {
    if (!album.artistMbid) return;
    try {
      const res = await fetch("/api/artists", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ mbid: album.artistMbid, name: album.artist }),
      });
      if (res.ok || res.status === 409) {
        setFollowed(true);
        toast(`Following ${album.artist}`);
      } else {
        toast(`Couldn't follow ${album.artist}`);
      }
    } catch {
      toast(`Couldn't follow ${album.artist}`);
    }
  }

  const titleNode: ReactNode = album.artistMbid ? (
    <span className="modal-artist">
      <a href={`/discover/artist/${album.artistMbid}`}>{album.artist}</a>
      <button
        type="button"
        className="btn sm ghost"
        onClick={follow}
        disabled={followed !== false}
        aria-label={followed ? "following" : "follow artist"}
      >
        {followed === null ? "…" : followed ? "Following" : "Follow"}
      </button>
    </span>
  ) : (
    album.artist
  );

  useEffect(() => {
    if (!open) return;
    // Prefer the REAL on-disk tracks (Library items) — no network, and reveals mismatches.
    if (album.trackNames?.length) {
      setTracks(parseOnDisk(album.trackNames));
      return;
    }
    const rgMbid = album.rgMbid;
    if (!rgMbid) {
      setTracks("error");
      return;
    }
    const hit = trackCache.get(rgMbid);
    if (hit) {
      setTracks(hit);
      return;
    }
    setTracks("loading");
    let cancelled = false;
    fetch(`/api/mb/release-groups/${rgMbid}/tracks`)
      .then((r) => (r.ok ? r.json() : Promise.reject()))
      .then((d) => {
        if (cancelled) return;
        trackCache.set(rgMbid, d.tracks);
        setTracks(d.tracks);
      })
      .catch(() => !cancelled && setTracks("error"));
    return () => {
      cancelled = true;
    };
  }, [open, album.rgMbid]);

  return (
    <Modal open={open} onClose={onClose} title={titleNode} wide>
      <div className="album-modal-head">
        <CoverArt rgMbid={album.rgMbid} alt={album.album} size={250} className="album-modal-cover" />
        <div>
          <h2 className="album-modal-title">{album.album}</h2>
          <div className="album-modal-meta">
            {album.type ? <span>{album.type}</span> : null}
            {album.year ? (
              <>
                <span className="dot">·</span>
                <span>{album.year}</span>
              </>
            ) : null}
          </div>
          {status ? <div className="album-modal-status">{status}</div> : null}
          {actions ? <div className="actions album-modal-actions">{actions}</div> : null}
        </div>
      </div>
      <div className="album-modal-tracks">
        {Array.isArray(tracks) ? (
          <p className="tracks-source">
            {onDisk ? `On disk · ${tracks.length} tracks` : "MusicBrainz tracklist"}
          </p>
        ) : null}
        {tracks === "loading" ? <p className="muted-note">Loading tracks…</p> : null}
        {tracks === "error" ? (
          <p className="muted-note">No tracklist available.</p>
        ) : null}
        {Array.isArray(tracks) ? (
          <ol className="tracks">
            {tracks.map((t) => (
              <li key={t.position}>
                <span className="tnum">{t.position}</span>
                <span className="tname">{t.title}</span>
                {t.lengthMs != null ? <span>{fmt(t.lengthMs)}</span> : null}
              </li>
            ))}
          </ol>
        ) : null}
      </div>
    </Modal>
  );
}
