"use client";

import { useState, useRef, useEffect, useCallback, useMemo } from "react";

type BeatportImage = { uri?: string; dynamic_uri?: string; };
type Artist = { id: number; name: string; slug?: string; image?: BeatportImage; url?: string; };
type Release = { id: number; name: string; slug: string; artists: Artist[]; remixers: Artist[]; label: { id: number; name: string; slug?: string }; catalog_number: string; publish_date: string; new_release_date?: string; track_count: number; image?: BeatportImage; price?: any; url?: string; upc?: string; bpm_range?: { min: number | null; max: number | null }; exclusive?: boolean; is_explicit?: boolean; is_hype?: boolean; encoded_date?: string; };
type Track = { id: number; name: string; mix_name?: string; artists: Artist[]; remixers: Artist[]; release: Release & { label?: any }; genre?: { name: string }; sub_genre?: { name: string }; bpm?: number; key?: { name: string; camelot_number?: number; camelot_letter?: string }; length?: string; length_ms?: number; publish_date?: string; new_release_date?: string; sample_url?: string; price?: any; };
type Discography = { tracks: Track[]; releases: Release[]; };
type TokenStatus = { hasToken: boolean; issued_at: number | null; expires_at: number | null; expires_in: number | null; remaining_seconds: number; isExpired: boolean; access_preview: string | null; refresh_preview: string | null; config?: ConfigStatus; error?: string; };
type ConfigStatus = { configured: boolean; hasUserFile: boolean; source: string; client_id: string; client_id_masked: string | null; username: string; username_masked: string | null; hasPassword: boolean; configPath: string; client_id_full?: string; cacheStats?: any; };

function getImageUrl(img?: BeatportImage, w = 300, h = 300): string {
  if (!img) return "";
  if (img.dynamic_uri) return img.dynamic_uri.replace("{w}", String(w)).replace("{h}", String(h));
  return img.uri || "";
}
function formatDate(dateStr?: string): string {
  if (!dateStr) return "—";
  try { const d = new Date(dateStr); if (isNaN(d.getTime())) return dateStr; return d.toLocaleDateString("ru-RU", { year: "numeric", month: "short", day: "numeric" }); } catch { return dateStr; }
}
function formatDateTime(ts: number | null): string {
  if (!ts) return "—";
  try { return new Date(ts).toLocaleString("ru-RU", { hour: "2-digit", minute: "2-digit", second: "2-digit", day: "2-digit", month: "short" }); } catch { return "—"; }
}
function formatRemaining(sec: number): string {
  if (sec <= 0) return "истёк";
  const m = Math.floor(sec / 60); const s = sec % 60;
  if (sec >= 3600) { const h = Math.floor(sec / 3600); const mm = Math.floor((sec % 3600) / 60); return `${h}ч ${mm}м ${s}с`; }
  return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
}
function formatDurationMs(totalMs?: number): string {
  if (!totalMs || totalMs <= 0) return "—";
  const totalSeconds = Math.floor(totalMs / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  if (hours > 0) return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
  return `${minutes}:${String(seconds).padStart(2, "0")}`;
}
function formatDurationShort(totalMs?: number): string {
  if (!totalMs || totalMs <= 0) return "—";
  const totalSeconds = Math.floor(totalMs / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  if (hours > 0) return `${hours}ч ${minutes}м`;
  return `${minutes} мин`;
}
function getBeatportReleaseUrl(release: Release): string {
  if (release.slug) return `https://www.beatport.com/release/${release.slug}/${release.id}`;
  return `https://www.beatport.com/release/_/${release.id}`;
}
async function fetchJsonSafe(input: RequestInfo, init?: RequestInit): Promise<any> {
  const res = await fetch(input, init);
  const contentType = res.headers.get("content-type") || "";
  const text = await res.text();
  if (!contentType.includes("application/json")) {
    const trimmed = text.trim();
    if (trimmed.startsWith("<!") || trimmed.startsWith("<html") || trimmed.startsWith("<!DOCTYPE")) {
      throw new Error(`Сервер вернул HTML вместо JSON (статус ${res.status}). Попробуйте перевыпустить токен.`);
    }
  }
  try {
    const json = JSON.parse(text);
    if (!res.ok) throw new Error(json.error || `Ошибка ${res.status}: ${text.slice(0, 400)}`);
    return json;
  } catch (e: any) {
    if (e.message && (e.message.startsWith("Сервер вернул HTML") || e.message.startsWith("Ошибка"))) throw e;
    if (text.trim().startsWith("<")) throw new Error(`Ошибка: сервер вернул HTML (<!doctype ...). Статус ${res.status}. Проверьте конфигурацию и токен.`);
    throw new Error(`Не удалось распарсить ответ (статус ${res.status}): ${text.slice(0, 400)}`);
  }
}
function exportTracksToCSV(tracks: Track[], artistName: string) {
  const headers = ["Название", "Микс", "Артисты", "Ремиксеры", "Релиз", "Жанр", "BPM", "Ключ", "Длина", "Дата", "Роль", "Sample URL"];
  const rows = tracks.map(t => [
    `"${(t.name || "").replace(/"/g, '""')}"`,
    `"${(t.mix_name || "").replace(/"/g, '""')}"`,
    `"${t.artists.map(a => a.name).join(", ").replace(/"/g, '""')}"`,
    `"${t.remixers.map(a => a.name).join(", ").replace(/"/g, '""')}"`,
    `"${(t.release?.name || "").replace(/"/g, '""')}"`,
    t.genre?.name || "",
    t.bpm || "",
    t.key?.name || "",
    t.length || "",
    t.publish_date || "",
    t.remixers.some(r => r.name === artistName) ? "Ремиксер" : (t.artists[0]?.name === artistName ? "Автор #1" : "Участник"),
    t.sample_url || ""
  ].join(","));
  const csv = [headers.join(","), ...rows].join("\n");
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = `${artistName}_tracks_${new Date().toISOString().slice(0,10)}.csv`;
  a.click();
  URL.revokeObjectURL(url);
}

// Debounce hook
function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

export default function Page() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 400);
  const [artists, setArtists] = useState<Artist[]>([]);
  const [selectedArtist, setSelectedArtist] = useState<Artist | null>(null);
  const [artistDetails, setArtistDetails] = useState<Artist | null>(null);
  const [discography, setDiscography] = useState<Discography | null>(null);
  const [activeTab, setActiveTab] = useState<"releases" | "allReleases" | "tracks">("releases");

  const [isSearching, setIsSearching] = useState(false);
  const [isLoadingDiscography, setIsLoadingDiscography] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [searchError, setSearchError] = useState<string | null>(null);

  const [playingTrack, setPlayingTrack] = useState<Track | null>(null);
  const audioRef = useRef<HTMLAudioElement>(null);

  const [tokenStatus, setTokenStatus] = useState<TokenStatus | null>(null);
  const [tokenLoading, setTokenLoading] = useState(false);
  const [tokenError, setTokenError] = useState<string | null>(null);
  const [autoRefresh, setAutoRefresh] = useState(false);
  const autoRefreshRef = useRef(autoRefresh);
  autoRefreshRef.current = autoRefresh;

  const [configStatus, setConfigStatus] = useState<ConfigStatus | null>(null);
  const [configForm, setConfigForm] = useState({ client_id: "", username: "", password: "" });
  const [showConfigForm, setShowConfigForm] = useState(false);
  const [configLoading, setConfigLoading] = useState(false);
  const [configError, setConfigError] = useState<string | null>(null);
  const [configSuccess, setConfigSuccess] = useState<string | null>(null);

  // Audit improvements
  const [threshold, setThreshold] = useState(0.5); // 50% rule configurable
  const [recentSearches, setRecentSearches] = useState<string[]>([]);
  const [trackSortKey, setTrackSortKey] = useState<keyof Track | "role" | "duration" | "genre" | "bpm" | "date">("date");
  const [trackSortDir, setTrackSortDir] = useState<"asc" | "desc">("desc");
  const [releaseSortKey, setReleaseSortKey] = useState<"date" | "name" | "duration" | "participation">("date");
  const [releaseSortDir, setReleaseSortDir] = useState<"asc" | "desc">("desc");
  const [trackPage, setTrackPage] = useState(1);
  const tracksPerPage = 50;
  const [trackFilterGenre, setTrackFilterGenre] = useState<string>("");
  const [trackFilterBpm, setTrackFilterBpm] = useState<{ min?: number; max?: number }>({});
  const [isExportingXlsx, setIsExportingXlsx] = useState(false);

  // Release modal
  const [selectedReleaseModal, setSelectedReleaseModal] = useState<Release | null>(null);
  const [releaseModalDetails, setReleaseModalDetails] = useState<any>(null);
  const [isReleaseModalLoading, setIsReleaseModalLoading] = useState(false);
  const [releaseModalError, setReleaseModalError] = useState<string | null>(null);

  const loading = isSearching || isLoadingDiscography;

  // Load persisted settings
  useEffect(() => {
    const savedAuto = localStorage.getItem("bp_auto_refresh");
    if (savedAuto !== null) setAutoRefresh(savedAuto === "true");
    const savedThresh = localStorage.getItem("bp_threshold");
    if (savedThresh) { const v = parseFloat(savedThresh); if (!isNaN(v)) setThreshold(v); }
    const savedRecent = localStorage.getItem("bp_recent");
    if (savedRecent) { try { setRecentSearches(JSON.parse(savedRecent)); } catch {} }
    // URL params for shareable links
    const params = new URLSearchParams(window.location.search);
    const qParam = params.get("q");
    const artistParam = params.get("artist");
    if (qParam) setQuery(qParam);
    if (artistParam) {
      // will trigger search effect
    }
  }, []);

  useEffect(() => { localStorage.setItem("bp_auto_refresh", String(autoRefresh)); }, [autoRefresh]);
  useEffect(() => { localStorage.setItem("bp_threshold", String(threshold)); }, [threshold]);
  useEffect(() => { localStorage.setItem("bp_recent", JSON.stringify(recentSearches.slice(0, 10))); }, [recentSearches]);

  // Keyboard shortcuts: "/" focuses search, Esc closes modals/player
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if (e.key === "/" && !(e.target instanceof HTMLInputElement)) {
        e.preventDefault();
        document.getElementById("search-input")?.focus();
      }
      if (e.key === "Escape") {
        setShowConfigForm(false);
        setPlayingTrack(null);
        setSelectedReleaseModal(null);
        setReleaseModalDetails(null);
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, []);

  // Update URL for shareable links
  useEffect(() => {
    const params = new URLSearchParams();
    if (query) params.set("q", query);
    if (selectedArtist) params.set("artist", String(selectedArtist.id));
    const newUrl = params.toString() ? `?${params.toString()}` : window.location.pathname;
    window.history.replaceState(null, "", newUrl);
  }, [query, selectedArtist]);

  const fetchTokenStatus = useCallback(async (silent = false) => {
    if (!silent) setTokenError(null);
    try {
      const data = await fetchJsonSafe("/api/beatport/token");
      setTokenStatus(data);
      if (data.config) setConfigStatus(data.config);
      return data as TokenStatus;
    } catch (e: any) {
      if (!silent) setTokenError(e.message);
      setTokenStatus((prev) => prev || { hasToken: false, issued_at: null, expires_at: null, expires_in: null, remaining_seconds: 0, isExpired: true, access_preview: null, refresh_preview: null, error: e.message });
      return null;
    }
  }, []);

  const fetchConfigStatus = useCallback(async (silent = false) => {
    if (!silent) setConfigError(null);
    try {
      const data = await fetchJsonSafe("/api/beatport/config");
      setConfigStatus(data);
      return data as ConfigStatus;
    } catch (e: any) {
      if (!silent) setConfigError(e.message);
      return null;
    }
  }, []);

  useEffect(() => {
    fetchTokenStatus(true);
    fetchConfigStatus(true);
    const interval = setInterval(() => fetchTokenStatus(true), 3000);
    return () => clearInterval(interval);
  }, [fetchTokenStatus, fetchConfigStatus]);

  useEffect(() => {
    const interval = setInterval(async () => {
      if (!autoRefreshRef.current) return;
      if (!tokenStatus?.hasToken) return;
      if (tokenStatus.remaining_seconds <= 60 && tokenStatus.remaining_seconds > 0) {
        try {
          setTokenLoading(true);
          await fetchJsonSafe("/api/beatport/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "refresh" }) });
          await fetchTokenStatus(true);
        } catch {} finally { setTokenLoading(false); }
      } else if (tokenStatus.isExpired) {
        try { setTokenLoading(true); await fetchJsonSafe("/api/beatport/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "refresh" }) }); await fetchTokenStatus(true); } catch {} finally { setTokenLoading(false); }
      }
    }, 5000);
    return () => clearInterval(interval);
  }, [tokenStatus, fetchTokenStatus]);

  useEffect(() => { if (configStatus && !configStatus.configured) setShowConfigForm(true); }, [configStatus]);

  const handleRefreshToken = async (mode: "refresh" | "full" = "refresh") => {
    setTokenLoading(true); setTokenError(null);
    try {
      const data = await fetchJsonSafe("/api/beatport/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode }) });
      if (data.status) { setTokenStatus(data.status); if (data.status.config) setConfigStatus(data.status.config); } else await fetchTokenStatus(true);
    } catch (e: any) { setTokenError(e.message); } finally { setTokenLoading(false); }
  };

  const handleSaveConfig = async (e?: React.FormEvent) => {
    if (e) e.preventDefault();
    setConfigLoading(true); setConfigError(null); setConfigSuccess(null);
    try {
      const data = await fetchJsonSafe("/api/beatport/config", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(configForm) });
      setConfigStatus(data.config);
      if (data.tokenStatus) setTokenStatus(data.tokenStatus);
      if (data.tokenError) { setConfigError(data.tokenError); setConfigSuccess(data.message); } else { setConfigSuccess(data.message || "Сохранено"); }
      setConfigForm(p => ({ ...p, password: "" }));
      await fetchTokenStatus(true);
      if (!data.tokenError) setTimeout(() => setShowConfigForm(false), 1200);
    } catch (err: any) { setConfigError(err.message); } finally { setConfigLoading(false); }
  };

  const handleDeleteConfig = async () => {
    if (!confirm("Удалить персональную конфигурацию?")) return;
    setConfigLoading(true); setConfigError(null);
    try { const data = await fetchJsonSafe("/api/beatport/config", { method: "DELETE" }); setConfigStatus(data.config); setConfigSuccess("Удалено"); await fetchTokenStatus(true); } catch (e: any) { setConfigError(e.message); } finally { setConfigLoading(false); }
  };

  const openConfigForm = () => {
    if (configStatus?.client_id_full) setConfigForm(p => ({ ...p, client_id: configStatus.client_id_full || "" }));
    else if (configStatus?.source === "user_file") setConfigForm(p => ({ ...p, client_id: (configStatus as any).client_id_full || configStatus.client_id || "" }));
    if (configStatus?.username && configStatus.username !== "musinjector") setConfigForm(p => ({ ...p, username: configStatus.username }));
    setShowConfigForm(true);
  };

  const handleSearch = async (e?: React.FormEvent, overrideQuery?: string) => {
    if (e) e.preventDefault();
    const q = (overrideQuery ?? query).trim();
    if (!q) { setSearchError("Введите имя исполнителя"); return; }
    setSearchError(null); setError(null); setArtists([]); setSelectedArtist(null); setArtistDetails(null); setDiscography(null); setIsSearching(true);
    try {
      const data = await fetchJsonSafe(`/api/beatport/search?q=${encodeURIComponent(q)}`);
      const found: Artist[] = data.artists || [];
      setArtists(found);
      setRecentSearches(prev => [q, ...prev.filter(x => x !== q)].slice(0, 8));
      if (found.length === 0) setSearchError("Не найдено"); else if (found.length === 1) handleSelectArtist(found[0]);
    } catch (err: any) { setSearchError(err.message || "Ошибка поиска"); } finally { setIsSearching(false); }
  };

  // Auto-search via debounce: triggers automatically when user types 2+ chars
  const lastAutoSearchedRef = useRef<string>("");
  useEffect(() => {
    if (debouncedQuery.length >= 2 && debouncedQuery !== lastAutoSearchedRef.current && selectedArtist === null) {
      lastAutoSearchedRef.current = debouncedQuery;
      handleSearch(undefined, debouncedQuery);
    }
    // Reset auto-search state when query is cleared
    if (debouncedQuery.length < 2) {
      lastAutoSearchedRef.current = "";
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedQuery]);
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const artistIdParam = params.get("artist");
    if (artistIdParam && !selectedArtist && !isLoadingDiscography) {
      // Try to load artist by ID if we have it
      // For simplicity, if artistId param exists, fetch details and discography directly
      const id = parseInt(artistIdParam, 10);
      if (!isNaN(id)) {
        // Create minimal artist object
        const fakeArtist: Artist = { id, name: `Artist ${id}`, slug: "", image: undefined };
        handleSelectArtist(fakeArtist);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const handleSelectArtist = async (artist: Artist) => {
    setSelectedArtist(artist); setArtistDetails(artist); setDiscography(null); setActiveTab("releases"); setIsLoadingDiscography(true); setError(null); setTrackPage(1);
    try {
      const [detailsRes, discoRes] = await Promise.all([
        fetchJsonSafe(`/api/beatport/artist/${artist.id}`),
        fetchJsonSafe(`/api/beatport/artist/${artist.id}/discography`),
      ]);
      setArtistDetails(detailsRes);
      setDiscography(discoRes.tracks && discoRes.releases ? discoRes : { tracks: [], releases: discoRes.releases || [] });
    } catch (err: any) {
      const msg = err.message || "Ошибка загрузки";
      setError(msg.includes("HTML") ? "Сервер вернул HTML. Проверьте токен." : msg);
    } finally { setIsLoadingDiscography(false); }
  };

  const handleExportXLSX = async () => {
    if (!artistDetails || !discography || !stats) return;
    setIsExportingXlsx(true);
    try {
      const { exportDiscographyToXLSX } = await import("@/lib/xlsxExport");
      await exportDiscographyToXLSX(
        artistDetails,
        discography,
        releaseDurations,
        stats,
        overallDurations,
        threshold,
        configStatus?.source || "unknown"
      );
    } catch (e: any) {
      console.error("XLSX export failed", e);
      alert(`Ошибка экспорта XLSX: ${e.message}`);
    } finally {
      setIsExportingXlsx(false);
    }
  };

  const openReleaseModal = async (release: Release) => {
    setSelectedReleaseModal(release);
    setReleaseModalDetails(null);
    setReleaseModalError(null);
    setIsReleaseModalLoading(true);
    try {
      const data = await fetchJsonSafe(`/api/beatport/release/${release.id}`);
      setReleaseModalDetails(data);
    } catch (e: any) {
      setReleaseModalError(e.message || "Не удалось загрузить релиз");
    } finally {
      setIsReleaseModalLoading(false);
    }
  };

  const closeReleaseModal = () => {
    setSelectedReleaseModal(null);
    setReleaseModalDetails(null);
    setReleaseModalError(null);
  };

  const releaseDurations = useMemo(() => {
    if (!discography) return new Map<number, { totalMs: number; count: number }>();
    const map = new Map<number, { totalMs: number; count: number }>();
    for (const track of discography.tracks) {
      const rid = track.release?.id;
      if (!rid) continue;
      const prev = map.get(rid) || { totalMs: 0, count: 0 };
      let ms = track.length_ms || 0;
      if (!ms && track.length) {
        const parts = track.length.split(":").map(Number);
        if (parts.length === 2) ms = (parts[0] * 60 + parts[1]) * 1000;
        else if (parts.length === 3) ms = (parts[0] * 3600 + parts[1] * 60 + parts[2]) * 1000;
      }
      map.set(rid, { totalMs: prev.totalMs + (ms || 0), count: prev.count + 1 });
    }
    return map;
  }, [discography]);

  const getReleaseParticipation = useCallback((release: Release) => {
    const info = releaseDurations.get(release.id);
    const count = info?.count || 0;
    const total = release.track_count || 0;
    const rate = total > 0 ? count / total : 0;
    return { count, total, rate, totalMs: info?.totalMs || 0 };
  }, [releaseDurations]);

  const stats = useMemo(() => {
    if (!discography || !selectedArtist) return null;
    const artistId = selectedArtist.id;
    const releasesAuthor1Raw = discography.releases.filter((r) => r.artists[0]?.id === artistId);
    const releasesAuthor1 = releasesAuthor1Raw.filter((release) => {
      const info = releaseDurations.get(release.id);
      const c = info?.count || 0; const t = release.track_count || 0;
      if (t === 0) return c > 0;
      return c / t >= threshold;
    });
    const releasesExcludedBy50 = releasesAuthor1Raw.filter((r) => !releasesAuthor1.includes(r));
    const tracksAsArtist = discography.tracks.filter((t) => t.artists.some((a) => a.id === artistId));
    const tracksAsRemixer = discography.tracks.filter((t) => t.remixers.some((a) => a.id === artistId));
    const tracksAuthor1 = discography.tracks.filter((t) => t.artists[0]?.id === artistId);
    return {
      releasesAuthor1Count: releasesAuthor1.length,
      releasesAuthor1CountRaw: releasesAuthor1Raw.length,
      releasesExcludedCount: releasesExcludedBy50.length,
      releasesAuthor1,
      releasesAuthor1Raw,
      releasesExcludedBy50,
      allReleasesCount: discography.releases.length,
      tracksCount: discography.tracks.length,
      asArtist: tracksAsArtist.length,
      asRemixer: tracksAsRemixer.length,
      author1Tracks: tracksAuthor1.length,
    };
  }, [discography, selectedArtist, releaseDurations, threshold]);

  const overallDurations = useMemo(() => {
    if (!discography || !stats) return { author1TotalMs: 0, allTotalMs: 0, tracksTotalMs: 0, excludedTotalMs: 0 };
    let author1TotalMs = 0, allTotalMs = 0, tracksTotalMs = 0, excludedTotalMs = 0;
    for (const t of discography.tracks) tracksTotalMs += t.length_ms || 0;
    for (const r of stats.releasesAuthor1) { const d = releaseDurations.get(r.id); if (d) author1TotalMs += d.totalMs; }
    for (const r of stats.releasesExcludedBy50) { const d = releaseDurations.get(r.id); if (d) excludedTotalMs += d.totalMs; }
    for (const r of discography.releases) { const d = releaseDurations.get(r.id); if (d) allTotalMs += d.totalMs; }
    return { author1TotalMs, allTotalMs, tracksTotalMs, excludedTotalMs };
  }, [discography, stats, releaseDurations]);

  // Filtered and sorted tracks with genre/BPM filter + sorting + pagination
  const filteredTracks = useMemo(() => {
    if (!discography) return [];
    let list = [...discography.tracks];
    if (trackFilterGenre) list = list.filter(t => (t.genre?.name || "").toLowerCase().includes(trackFilterGenre.toLowerCase()) || (t.sub_genre?.name || "").toLowerCase().includes(trackFilterGenre.toLowerCase()));
    if (trackFilterBpm.min) list = list.filter(t => (t.bpm || 0) >= trackFilterBpm.min!);
    if (trackFilterBpm.max) list = list.filter(t => (t.bpm || 9999) <= trackFilterBpm.max!);
    // Sorting
    list.sort((a, b) => {
      let cmp = 0;
      switch (trackSortKey) {
        case "bpm": cmp = (a.bpm || 0) - (b.bpm || 0); break;
        case "genre": cmp = (a.genre?.name || "").localeCompare(b.genre?.name || ""); break;
        case "duration": cmp = (a.length_ms || 0) - (b.length_ms || 0); break;
        case "date": cmp = (a.publish_date || "").localeCompare(b.publish_date || ""); break;
        case "role": {
          const roleOrder = (t: Track) => t.remixers.some(r => r.id === selectedArtist?.id) ? 2 : t.artists[0]?.id === selectedArtist?.id ? 0 : 1;
          cmp = roleOrder(a) - roleOrder(b);
          break;
        }
        default: cmp = a.name.localeCompare(b.name);
      }
      return trackSortDir === "asc" ? cmp : -cmp;
    });
    return list;
  }, [discography, trackFilterGenre, trackFilterBpm, trackSortKey, trackSortDir, selectedArtist]);

  const paginatedTracks = useMemo(() => {
    const start = (trackPage - 1) * tracksPerPage;
    return filteredTracks.slice(start, start + tracksPerPage);
  }, [filteredTracks, trackPage]);

  const totalTrackPages = Math.max(1, Math.ceil(filteredTracks.length / tracksPerPage));

  // Sorted releases
  const sortedReleasesAuthor1 = useMemo(() => {
    if (!stats) return [];
    let list = [...stats.releasesAuthor1];
    list.sort((a, b) => {
      let cmp = 0;
      switch (releaseSortKey) {
        case "name": cmp = a.name.localeCompare(b.name); break;
        case "duration": {
          const da = releaseDurations.get(a.id)?.totalMs || 0;
          const db = releaseDurations.get(b.id)?.totalMs || 0;
          cmp = da - db;
          break;
        }
        case "participation": {
          const ra = getReleaseParticipation(a).rate;
          const rb = getReleaseParticipation(b).rate;
          cmp = ra - rb;
          break;
        }
        case "date":
        default: cmp = (a.publish_date || "").localeCompare(b.publish_date || "");
      }
      return releaseSortDir === "asc" ? cmp : -cmp;
    });
    return list;
  }, [stats, releaseSortKey, releaseSortDir, releaseDurations, getReleaseParticipation]);

  const sortedAllReleases = useMemo(() => {
    if (!discography) return [];
    let list = [...discography.releases];
    list.sort((a, b) => {
      let cmp = 0;
      switch (releaseSortKey) {
        case "name": cmp = a.name.localeCompare(b.name); break;
        case "duration": cmp = (releaseDurations.get(a.id)?.totalMs || 0) - (releaseDurations.get(b.id)?.totalMs || 0); break;
        case "participation": cmp = getReleaseParticipation(a).rate - getReleaseParticipation(b).rate; break;
        case "date":
        default: cmp = (a.publish_date || "").localeCompare(b.publish_date || "");
      }
      return releaseSortDir === "asc" ? cmp : -cmp;
    });
    return list;
  }, [discography, releaseSortKey, releaseSortDir, releaseDurations, getReleaseParticipation]);

  useEffect(() => { if (playingTrack && audioRef.current) audioRef.current.play().catch(() => {}); }, [playingTrack]);

  const getRoleBadge = (track: Track, artistId: number) => {
    if (track.remixers.some((r) => r.id === artistId)) return { label: "Ремиксер", className: "bg-purple-600/20 text-purple-300 border-purple-500/30" };
    if (track.artists[0]?.id === artistId) return { label: "Автор #1", className: "bg-[#FF6B00]/15 text-[#FF8A33] border-[#FF6B00]/30" };
    if (track.artists.some((a) => a.id === artistId)) return { label: "Артист", className: "bg-zinc-700/50 text-zinc-300 border-zinc-600/30" };
    return { label: "—", className: "bg-zinc-800 text-zinc-500" };
  };

  const getReleaseBadge = (release: Release, artistId: number) => {
    const participation = getReleaseParticipation(release);
    const isFirst = release.artists[0]?.id === artistId;
    if (isFirst) {
      if (participation.rate < threshold && participation.total > 0) return { label: `Автор #1 • ${Math.round(participation.rate * 100)}% <${Math.round(threshold*100)}%`, className: "bg-amber-500/20 text-amber-300 border-amber-500/30" };
      return { label: "Автор #1", className: "bg-[#FF6B00]/20 text-[#FF8A33] border-[#FF6B00]/30" };
    }
    return { label: "Участник", className: "bg-zinc-700/40 text-zinc-400 border-zinc-600/20" };
  };

  const tokenProgress = (() => {
    if (!tokenStatus || !tokenStatus.hasToken || !tokenStatus.expires_in) return 0;
    const total = tokenStatus.expires_in; const remaining = tokenStatus.remaining_seconds; const elapsed = total - remaining;
    return Math.min(100, Math.max(0, (elapsed / total) * 100));
  })();

  const uniqueGenres = useMemo(() => {
    if (!discography) return [];
    const set = new Set<string>();
    discography.tracks.forEach(t => { if (t.genre?.name) set.add(t.genre.name); });
    return Array.from(set).sort();
  }, [discography]);

  return (
    <div className="min-h-screen bg-[#09090b] text-zinc-100 selection:bg-[#FF6B00]/30">
      <div className="fixed top-0 left-0 w-full h-[2px] z-[100] pointer-events-none">
        {loading && (<div className="h-full w-full relative overflow-hidden"><div className="absolute inset-0 bg-[#FF6B00]/20" /><div className="absolute top-0 left-0 h-full w-1/3 bg-[#FF6B00]" style={{ animation: "progressIndeterminate 1.2s infinite linear" }} /></div>)}
      </div>

      <header className="sticky top-0 z-40 backdrop-blur-xl bg-[#09090b]/80 border-b border-zinc-800/80">
        <div className="mx-auto max-w-[1400px] px-4 sm:px-6 py-4 flex items-center justify-between gap-4">
          <div className="flex items-center gap-3">
            <div className="h-9 w-9 rounded-xl bg-[#FF6B00] grid place-items-center font-black text-black tracking-tighter">B</div>
            <div><h1 className="font-semibold tracking-tight leading-none">Beatport Discography Searcher</h1><p className="text-[11px] text-zinc-500 tracking-wide uppercase">Universal • Cached • Audited</p></div>
          </div>
          <div className="flex items-center gap-2">
            {configStatus && (<div className={`hidden sm:flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-[10px] ${configStatus.source === "user_file" ? "bg-emerald-500/10 border-emerald-500/20 text-emerald-300" : configStatus.source === "env" ? "bg-blue-500/10 border-blue-500/20 text-blue-300" : "bg-amber-500/10 border-amber-500/20 text-amber-300"}`}><span className={`h-1.5 w-1.5 rounded-full ${configStatus.source === "user_file" ? "bg-emerald-500" : configStatus.source === "env" ? "bg-blue-500" : "bg-amber-500"}`} />{configStatus.source === "user_file" ? "ваш аккаунт" : configStatus.source === "env" ? "env" : "демо"}</div>)}
            {tokenStatus && (<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-zinc-900 border border-zinc-800 text-[11px]"><span className={`h-2 w-2 rounded-full ${!tokenStatus.hasToken ? "bg-zinc-600" : tokenStatus.isExpired ? "bg-red-500" : tokenStatus.remaining_seconds < 60 ? "bg-amber-500 animate-pulse" : "bg-emerald-500"}`} /><span className="text-zinc-400">{!tokenStatus.hasToken ? "нет токена" : tokenStatus.isExpired ? "истёк" : `${formatRemaining(tokenStatus.remaining_seconds)}`}</span></div>)}
            <button onClick={openConfigForm} className="h-8 px-3 rounded-full bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-[11px] text-zinc-400 hover:text-zinc-200 transition">⚙️ Настроить</button>
          </div>
        </div>
      </header>

      {configStatus && configStatus.source === "default" && !showConfigForm && (
        <div className="mx-auto max-w-[1400px] px-4 sm:px-6 mt-4">
          <div className="rounded-2xl bg-gradient-to-r from-amber-500/10 via-orange-500/10 to-[#FF6B00]/10 border border-amber-500/20 p-4 flex flex-col sm:flex-row gap-3 sm:items-center justify-between">
            <div className="flex gap-3"><div className="h-10 w-10 rounded-xl bg-amber-500/20 grid place-items-center text-amber-300">⚠️</div><div><div className="font-medium text-sm text-amber-200">Используется демо-конфигурация</div><div className="text-xs text-zinc-400 mt-1 max-w-2xl">Сейчас сервис работает на встроенных учетных данных. Для продакшн введите свои Beatport данные. Кэш: search {configStatus.cacheStats?.search?.size || 0}, artist {configStatus.cacheStats?.artist?.size || 0}, disco {configStatus.cacheStats?.discography?.size || 0}</div></div></div>
            <button onClick={openConfigForm} className="self-start sm:self-center h-10 px-5 rounded-full bg-amber-500 hover:bg-amber-400 text-black font-semibold text-[13px] transition flex-shrink-0">Ввести свои данные</button>
          </div>
        </div>
      )}

      {showConfigForm && (
        <div className="fixed inset-0 z-[80] bg-black/70 backdrop-blur-sm grid place-items-center p-4" onClick={() => setShowConfigForm(false)}>
          <div className="w-full max-w-lg rounded-[20px] bg-zinc-900 border border-zinc-800 p-6 shadow-2xl max-h-[90vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
            <div className="flex items-start justify-between gap-4"><div><h2 className="text-lg font-semibold">Настройка Beatport API</h2><p className="text-xs text-zinc-500 mt-1">Универсальный вариант для любого пользователя.</p></div><button onClick={() => setShowConfigForm(false)} className="h-8 w-8 rounded-full bg-zinc-800 hover:bg-zinc-700 grid place-items-center text-zinc-400">✕</button></div>
            <div className="mt-4 p-3 rounded-xl bg-blue-500/10 border border-blue-500/20 text-[11px] text-blue-200"><div className="font-medium">Где взять данные?</div><ul className="mt-1 list-disc list-inside space-y-1 text-blue-200/80"><li>Client ID — публичный, из <code className="px-1 py-0.5 rounded bg-blue-900/30">api.beatport.com/v4/docs/</code></li><li>Логин/Пароль — Beatport аккаунт</li><li>Файлы: <code>{configStatus?.configPath}</code> + <code>~/.beatport/config.json</code></li></ul></div>
            <form onSubmit={handleSaveConfig} className="mt-5 space-y-4">
              <div><label className="text-[11px] font-medium uppercase tracking-wide text-zinc-400">Client ID</label><input value={configForm.client_id} onChange={(e) => setConfigForm(p => ({ ...p, client_id: e.target.value }))} placeholder="0GIvkClt..." className="mt-1 w-full h-11 px-4 rounded-xl bg-zinc-950 border border-zinc-800 focus:border-[#FF6B00]/50 focus:outline-none focus:ring-2 focus:ring-[#FF6B00]/20 text-[13px] font-mono" required /></div>
              <div><label className="text-[11px] font-medium uppercase tracking-wide text-zinc-400">Логин</label><input value={configForm.username} onChange={(e) => setConfigForm(p => ({ ...p, username: e.target.value }))} placeholder="username" className="mt-1 w-full h-11 px-4 rounded-xl bg-zinc-950 border border-zinc-800 focus:border-[#FF6B00]/50 focus:outline-none text-[13px]" required /></div>
              <div><label className="text-[11px] font-medium uppercase tracking-wide text-zinc-400">Пароль</label><input type="password" value={configForm.password} onChange={(e) => setConfigForm(p => ({ ...p, password: e.target.value }))} placeholder="••••••••" className="mt-1 w-full h-11 px-4 rounded-xl bg-zinc-950 border border-zinc-800 focus:border-[#FF6B00]/50 focus:outline-none text-[13px]" required /></div>
              {configError && (<div className="p-2.5 rounded-xl bg-red-500/10 border border-red-500/20 text-[12px] text-red-300">{configError}</div>)}
              {configSuccess && (<div className="p-2.5 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-[12px] text-emerald-300">{configSuccess}</div>)}
              <div className="flex gap-2 pt-2"><button type="submit" disabled={configLoading} className="flex-1 h-11 rounded-full bg-[#FF6B00] hover:bg-[#ff7e26] disabled:opacity-50 text-black font-semibold text-[13px] flex items-center justify-center gap-2">{configLoading ? (<><span className="h-4 w-4 border-2 border-black/30 border-t-black rounded-full animate-spin" /> Сохранение...</>) : ("💾 Сохранить и получить токен")}</button><button type="button" onClick={() => setShowConfigForm(false)} className="h-11 px-6 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-zinc-300 text-[13px]">Отмена</button></div>
              {configStatus?.hasUserFile && (<button type="button" onClick={handleDeleteConfig} className="w-full h-10 rounded-full bg-red-500/10 hover:bg-red-500/20 border border-red-500/20 text-red-300 text-[12px] mt-2">Удалить персональную конфигурацию</button>)}
            </form>
          </div>
        </div>
      )}

      <div className="mx-auto max-w-[1400px] px-4 sm:px-6 mt-4">
        <div className="rounded-2xl bg-zinc-900/70 border border-zinc-800 p-4 flex flex-col lg:flex-row gap-4 lg:items-center justify-between backdrop-blur">
          <div className="flex-1 min-w-0">
            <div className="flex items-center gap-3 flex-wrap"><div className="text-xs font-semibold uppercase tracking-wide text-zinc-400">Токен Beatport</div>{tokenStatus?.hasToken ? (<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] border ${tokenStatus.isExpired ? "bg-red-500/10 text-red-300 border-red-500/20" : tokenStatus.remaining_seconds < 60 ? "bg-amber-500/10 text-amber-300 border-amber-500/20" : "bg-emerald-500/10 text-emerald-300 border-emerald-500/20"}`}><span className={`h-1.5 w-1.5 rounded-full ${tokenStatus.isExpired ? "bg-red-500" : tokenStatus.remaining_seconds < 60 ? "bg-amber-500" : "bg-emerald-500"}`} />{tokenStatus.isExpired ? "Истёк" : tokenStatus.remaining_seconds < 60 ? "Скоро истечёт" : "Активен"}</span>) : (<span className="px-2 py-0.5 rounded-full text-[10px] bg-zinc-800 text-zinc-500 border border-zinc-700">Нет токена</span>)}</div>
            <div className="mt-2 grid grid-cols-1 sm:grid-cols-3 gap-3 text-[12px]">
              <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-2.5"><div className="text-[10px] uppercase tracking-wide text-zinc-500">Выпущен</div><div className="mt-1 font-medium text-zinc-200">{formatDateTime(tokenStatus?.issued_at || null)}</div></div>
              <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-2.5"><div className="text-[10px] uppercase tracking-wide text-zinc-500">Истекает</div><div className="mt-1 font-medium text-zinc-200">{formatDateTime(tokenStatus?.expires_at || null)}</div></div>
              <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-2.5"><div className="text-[10px] uppercase tracking-wide text-zinc-500">Осталось</div><div className="mt-1 font-mono font-medium flex items-center gap-2"><span className={`${tokenStatus?.remaining_seconds && tokenStatus.remaining_seconds < 60 ? "text-amber-300" : tokenStatus?.isExpired ? "text-red-300" : "text-emerald-300"}`}>{tokenStatus ? formatRemaining(tokenStatus.remaining_seconds) : "—"}</span>{tokenStatus?.expires_in && (<span className="text-[10px] text-zinc-500">из {tokenStatus.expires_in}с</span>)}</div></div>
            </div>
            {tokenStatus?.hasToken && tokenStatus.expires_in ? (<div className="mt-3 h-1.5 w-full rounded-full bg-zinc-800 overflow-hidden"><div className="h-full bg-gradient-to-r from-[#FF6B00] to-amber-500 transition-all duration-1000" style={{ width: `${tokenProgress}%` }} /></div>) : null}
            {tokenError && (<div className="mt-2 text-[11px] text-red-300 bg-red-500/10 border border-red-500/20 rounded-lg px-2.5 py-1.5">Ошибка токена: {tokenError}</div>)}
          </div>
          <div className="flex flex-col gap-3 lg:items-end flex-shrink-0">
            <div className="flex flex-wrap gap-2">
              <button onClick={() => handleRefreshToken("refresh")} disabled={tokenLoading} className="h-10 px-5 rounded-full bg-[#FF6B00] hover:bg-[#ff7e26] disabled:opacity-50 text-black font-semibold text-[13px] flex items-center gap-2 transition">{tokenLoading ? (<><span className="h-4 w-4 border-2 border-black/30 border-t-black rounded-full animate-spin" />Обновление...</>) : (<><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 12a9 9 0 1 1-9-9c2.5 0 4.8 1 6.5 2.6L21 8V3h-5l2.4 2.4A7 7 0 1 0 21 12Z" /></svg>Перевыпустить token</>)}</button>
              <button onClick={() => handleRefreshToken("full")} disabled={tokenLoading} className="h-10 px-4 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-zinc-300 text-[13px]">Full login</button>
            </div>
            <label className="flex items-center gap-2 cursor-pointer group select-none"><div className="relative"><input type="checkbox" checked={autoRefresh} onChange={(e) => setAutoRefresh(e.target.checked)} className="sr-only" /><div className={`h-5 w-9 rounded-full transition ${autoRefresh ? "bg-[#FF6B00]" : "bg-zinc-700"}`}><div className={`h-4 w-4 rounded-full bg-white transition-transform translate-y-0.5 ${autoRefresh ? "translate-x-4" : "translate-x-0.5"}`} /></div></div><span className="text-[12px] text-zinc-400 group-hover:text-zinc-200 transition">Авто-перевыпуск за 1 мин</span></label>
          </div>
        </div>
      </div>

      <main className="mx-auto max-w-[1400px] px-4 sm:px-6 py-6 sm:py-8">
        <div className="max-w-3xl mx-auto">
          <form onSubmit={handleSearch} className="relative group">
            <div className="relative flex items-center">
              <div className="absolute left-4 text-zinc-500"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg></div>
              <input id="search-input" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Введите имя исполнителя — например, Delerium ( / для фокуса )" className="w-full h-[52px] pl-12 pr-[140px] rounded-full bg-zinc-900 border border-zinc-800 focus:border-[#FF6B00]/50 focus:outline-none focus:ring-4 focus:ring-[#FF6B00]/10 text-[15px] placeholder:text-zinc-600 transition-all" />
              <button type="submit" disabled={isSearching} className="absolute right-1.5 h-[40px] px-6 rounded-full bg-[#FF6B00] hover:bg-[#ff7e26] text-black font-semibold text-[14px] disabled:opacity-60 flex items-center gap-2">{isSearching ? (<><span className="h-4 w-4 border-2 border-black/30 border-t-black rounded-full animate-spin" />Поиск</>) : ("Найти")}</button>
            </div>
          </form>

          {recentSearches.length > 0 && !selectedArtist && (
            <div className="mt-3 flex flex-wrap gap-1.5 items-center">
              <span className="text-[11px] text-zinc-500 mr-1">Недавние:</span>
              {recentSearches.map(r => (<button key={r} onClick={() => { setQuery(r); handleSearch(undefined, r); }} className="px-2.5 py-1 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-[11px] text-zinc-400 hover:text-zinc-200 transition">{r}</button>))}
              <button onClick={() => setRecentSearches([])} className="px-2 py-1 rounded-full bg-zinc-900 text-[10px] text-zinc-600 hover:text-zinc-400">очистить</button>
            </div>
          )}

          {searchError && (<div className="mt-4 p-3 rounded-xl bg-amber-500/10 border border-amber-500/20 text-amber-200 text-sm text-center">{searchError}</div>)}

          {artists.length > 1 && !selectedArtist && (
            <div className="mt-8">
              <h2 className="text-sm font-medium text-zinc-400 mb-3">Найдено артистов: {artists.length}</h2>
              <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
                {artists.map((artist) => (
                  <button key={artist.id} onClick={() => handleSelectArtist(artist)} className="group flex items-center gap-4 p-3 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-left transition">
                    <div className="h-14 w-14 rounded-xl overflow-hidden bg-zinc-800 flex-shrink-0">{getImageUrl(artist.image, 100, 100) ? (<img loading="lazy" src={getImageUrl(artist.image, 100, 100)} alt={artist.name} className="h-full w-full object-cover group-hover:scale-105 transition duration-300" />) : (<div className="h-full w-full grid place-items-center text-zinc-600">?</div>)}</div>
                    <div className="min-w-0"><div className="font-medium truncate">{artist.name}</div><div className="text-xs text-zinc-500">ID: {artist.id}</div></div>
                    <div className="ml-auto text-zinc-600 group-hover:text-[#FF6B00] transition"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m9 18 6-6-6-6" /></svg></div>
                  </button>
                ))}
              </div>
            </div>
          )}

          {artists.length === 0 && !isSearching && !selectedArtist && (
            <div className="mt-16 text-center">
              <div className="inline-flex h-20 w-20 rounded-3xl bg-zinc-900 border border-zinc-800 items-center justify-center mb-4"><span className="text-3xl">🎧</span></div>
              <h3 className="font-medium text-zinc-300">Найдите дискографию любого артиста Beatport</h3>
              <p className="text-sm text-zinc-500 mt-2 max-w-md mx-auto">Кэширование, правило {Math.round(threshold*100)}%, длительность, экспорт CSV, пагинация.</p>
              <div className="mt-6 flex flex-wrap justify-center gap-2">
                {["Delerium", "Boris Brejcha", "Charlotte de Witte", "Adam Beyer"].map((name) => (<button key={name} onClick={() => { setQuery(name); handleSearch(undefined, name); }} className="px-3 py-1.5 rounded-full bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-xs text-zinc-400 hover:text-zinc-200 transition">{name}</button>))}
              </div>
            </div>
          )}
        </div>

        {selectedArtist && (
          <div className="mt-8">
            {isLoadingDiscography && (
              <div className="py-20 text-center">
                <div className="inline-flex flex-col items-center gap-4"><div className="h-12 w-12 rounded-full border-2 border-zinc-800 border-t-[#FF6B00] animate-spin" /><div><div className="font-medium">Загрузка дискографии...</div><div className="text-sm text-zinc-500 mt-1 max-w-sm">10–30 сек, кэшируется на 15 мин.</div></div></div>
              </div>
            )}

            {error && !isLoadingDiscography && (
              <div className="max-w-3xl mx-auto p-4 rounded-2xl bg-red-500/10 border border-red-500/20 text-red-200 text-sm text-center">
                <div className="font-medium">Ошибка загрузки</div><div className="mt-1 text-red-300/80">{error}</div>
                <button onClick={() => handleRefreshToken("refresh")} className="mt-3 px-4 py-2 rounded-full bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 text-red-200 text-xs transition">Перевыпустить токен</button>
              </div>
            )}

            {artistDetails && discography && !isLoadingDiscography && stats && (
              <>
                <div className="rounded-[24px] bg-gradient-to-b from-zinc-900 to-zinc-950 border border-zinc-800 p-6 sm:p-8">
                  <div className="flex flex-col sm:flex-row gap-6">
                    <div className="h-28 w-28 sm:h-32 sm:w-32 rounded-[20px] overflow-hidden bg-zinc-800 flex-shrink-0 border border-zinc-700">
                      {getImageUrl(artistDetails.image, 300, 300) ? (<img loading="lazy" src={getImageUrl(artistDetails.image, 300, 300)} alt={artistDetails.name} className="h-full w-full object-cover" />) : (<div className="h-full w-full grid place-items-center text-3xl">🎵</div>)}
                    </div>
                    <div className="min-w-0 flex-1">
                      <h2 className="text-3xl sm:text-4xl font-semibold tracking-tight flex items-center gap-3">{artistDetails.name}<button onClick={() => { navigator.clipboard.writeText(String(artistDetails.id)); }} className="px-2 py-1 rounded-full bg-zinc-800 border border-zinc-700 text-[10px] text-zinc-500 hover:text-zinc-300" title="Копировать ID">ID {artistDetails.id} 📋</button></h2>
                      <div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-zinc-500">
                        <span className="px-2 py-1 rounded-full bg-zinc-800 border border-zinc-700">ID {artistDetails.id}</span>
                        <button onClick={() => { setSelectedArtist(null); setArtistDetails(null); setDiscography(null); setArtists([]); setQuery(""); }} className="ml-2 px-2.5 py-1 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-zinc-300 transition">← Новый поиск</button>
                        <button onClick={() => { if (discography) exportTracksToCSV(discography.tracks, artistDetails.name); }} className="px-2.5 py-1 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-zinc-400 text-[11px] transition">⬇️ CSV ({discography.tracks.length})</button>
                        <button onClick={handleExportXLSX} disabled={isExportingXlsx} className="px-3 py-1 rounded-full bg-[#FF6B00] hover:bg-[#ff7e26] disabled:opacity-50 border border-[#FF6B00] text-black font-semibold text-[11px] transition flex items-center gap-1">
                          {isExportingXlsx ? (<><span className="h-3 w-3 border-2 border-black/30 border-t-black rounded-full animate-spin" /> XLSX...</>) : (<>📊 XLSX MAX ({discography.tracks.length} треков, {discography.releases.length} релизов, 7 листов)</>)}
                        </button>
                      </div>

                      <div className="mt-4 flex flex-wrap items-center gap-3 p-3 rounded-xl bg-zinc-900 border border-zinc-800">
                        <div className="text-[11px] text-zinc-400">Порог собственных релизов:</div>
                        <input type="range" min={0} max={100} step={5} value={threshold*100} onChange={(e) => setThreshold(parseInt(e.target.value)/100)} className="w-32 accent-[#FF6B00]" />
                        <span className="text-[11px] font-mono px-2 py-1 rounded-full bg-zinc-800 border border-zinc-700 text-[#FF8A33]">{Math.round(threshold*100)}%</span>
                        <span className="text-[10px] text-zinc-500">релизы где артист участвует менее порога → только All Releases</span>
                      </div>

                      <div className="mt-6 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
                        <div className="rounded-2xl bg-zinc-900 border border-zinc-800 p-3"><div className="text-[11px] uppercase tracking-wide text-zinc-500">Релизов (≥{Math.round(threshold*100)}%)</div><div className="mt-1 text-xl font-semibold text-[#FF8A33]">{stats.releasesAuthor1Count}</div><div className="mt-1 text-[10px] text-zinc-500">сырых: {stats.releasesAuthor1CountRaw} • искл. {stats.releasesExcludedCount}</div><div className="mt-1 text-[11px] text-zinc-500 flex items-center gap-1"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>{formatDurationShort(overallDurations.author1TotalMs)}</div></div>
                        <div className="rounded-2xl bg-zinc-900 border border-zinc-800 p-3"><div className="text-[11px] uppercase tracking-wide text-zinc-500">Всего релизов</div><div className="mt-1 text-xl font-semibold">{stats.allReleasesCount}</div><div className="mt-1 text-[11px] text-zinc-500 flex items-center gap-1"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>{formatDurationShort(overallDurations.allTotalMs)}</div></div>
                        <div className="rounded-2xl bg-zinc-900 border border-zinc-800 p-3"><div className="text-[11px] uppercase tracking-wide text-zinc-500">Треков</div><div className="mt-1 text-xl font-semibold">{stats.tracksCount}</div><div className="mt-1 text-[11px] text-zinc-500">{formatDurationMs(overallDurations.tracksTotalMs)}</div></div>
                        <div className="rounded-2xl bg-zinc-900 border border-zinc-800 p-3"><div className="text-[11px] uppercase tracking-wide text-zinc-500">Как артист</div><div className="mt-1 text-xl font-semibold">{stats.asArtist}</div></div>
                        <div className="rounded-2xl bg-zinc-900 border border-zinc-800 p-3"><div className="text-[11px] uppercase tracking-wide text-zinc-500">Как ремиксер</div><div className="mt-1 text-xl font-semibold text-purple-300">{stats.asRemixer}</div></div>
                        <div className="rounded-2xl bg-zinc-900 border border-zinc-800 p-3"><div className="text-[11px] uppercase tracking-wide text-zinc-500">Исключено &lt;{Math.round(threshold*100)}%</div><div className="mt-1 text-xl font-semibold text-amber-300">{stats.releasesExcludedCount}</div><div className="mt-1 text-[10px] text-zinc-500">{formatDurationShort(overallDurations.excludedTotalMs)} в All</div></div>
                      </div>

                      {stats.releasesExcludedCount > 0 && (
                        <div className="mt-4 p-3 rounded-xl bg-amber-500/10 border border-amber-500/20 text-[11px] text-amber-200"><b>Правило {Math.round(threshold*100)}%:</b> {stats.releasesExcludedCount} релизов где артист первый, но участие &lt;{Math.round(threshold*100)}% треков — перенесены в All Releases.</div>
                      )}
                    </div>
                  </div>
                </div>

                <div className="mt-8 flex flex-wrap gap-2 border-b border-zinc-800 pb-4 items-center">
                  <button onClick={() => setActiveTab("releases")} className={`px-5 h-10 rounded-full text-sm font-medium border transition ${activeTab === "releases" ? "bg-[#FF6B00] text-black border-[#FF6B00]" : "bg-zinc-900 text-zinc-400 border-zinc-800 hover:border-zinc-700 hover:text-zinc-200"}`}>Releases (≥{Math.round(threshold*100)}%) <span className={`ml-2 px-2 py-0.5 rounded-full text-xs ${activeTab === "releases" ? "bg-black/15" : "bg-zinc-800"}`}>{stats.releasesAuthor1Count} • {formatDurationShort(overallDurations.author1TotalMs)}</span></button>
                  <button onClick={() => setActiveTab("allReleases")} className={`px-5 h-10 rounded-full text-sm font-medium border transition ${activeTab === "allReleases" ? "bg-[#FF6B00] text-black border-[#FF6B00]" : "bg-zinc-900 text-zinc-400 border-zinc-800 hover:border-zinc-700 hover:text-zinc-200"}`}>All Releases <span className={`ml-2 px-2 py-0.5 rounded-full text-xs ${activeTab === "allReleases" ? "bg-black/15" : "bg-zinc-800"}`}>{stats.allReleasesCount}</span></button>
                  <button onClick={() => setActiveTab("tracks")} className={`px-5 h-10 rounded-full text-sm font-medium border transition ${activeTab === "tracks" ? "bg-[#FF6B00] text-black border-[#FF6B00]" : "bg-zinc-900 text-zinc-400 border-zinc-800 hover:border-zinc-700 hover:text-zinc-200"}`}>Tracks <span className={`ml-2 px-2 py-0.5 rounded-full text-xs ${activeTab === "tracks" ? "bg-black/15" : "bg-zinc-800"}`}>{filteredTracks.length} • {formatDurationMs(overallDurations.tracksTotalMs)}</span></button>

                  {activeTab === "releases" && (
                    <div className="ml-auto flex items-center gap-2 text-[11px]">
                      <span className="text-zinc-500">Сортировка:</span>
                      <select value={releaseSortKey} onChange={(e) => setReleaseSortKey(e.target.value as any)} className="h-8 px-2 rounded-full bg-zinc-900 border border-zinc-800 text-zinc-300">
                        <option value="date">Дата</option><option value="name">Имя</option><option value="duration">Длительность</option><option value="participation">Участие %</option>
                      </select>
                      <button onClick={() => setReleaseSortDir(d => d === "asc" ? "desc" : "asc")} className="h-8 w-8 rounded-full bg-zinc-900 border border-zinc-800 grid place-items-center">{releaseSortDir === "asc" ? "↑" : "↓"}</button>
                    </div>
                  )}
                </div>

                <div className="mt-6">
                  {activeTab === "releases" && (
                    <>
                      {stats.releasesAuthor1Count === 0 ? (
                        <div className="py-16 text-center text-zinc-500 rounded-2xl bg-zinc-900/50 border border-zinc-800">Релизов не найдено (все отфильтрованы по правилу {Math.round(threshold*100)}%). Смотрите All Releases.</div>
                      ) : (
                        <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
                          {sortedReleasesAuthor1.map((release) => {
                            const dur = releaseDurations.get(release.id);
                            const part = getReleaseParticipation(release);
                            return (
                              <div key={release.id} onClick={() => openReleaseModal(release)} className="group cursor-pointer rounded-2xl overflow-hidden bg-zinc-900 border border-zinc-800 hover:border-zinc-700 transition">
                                <div className="aspect-square overflow-hidden bg-zinc-800 relative">
                                  {getImageUrl(release.image, 400, 400) ? (<img loading="lazy" src={getImageUrl(release.image, 400, 400)} alt={release.name} className="h-full w-full object-cover group-hover:scale-105 transition duration-500" />) : <div className="h-full w-full grid place-items-center text-zinc-600">no cover</div>}
                                  <div className="absolute top-2 left-2 px-2 py-1 rounded-full text-[10px] font-medium border bg-[#FF6B00]/20 text-[#FF8A33] border-[#FF6B00]/30">Автор #1 • {Math.round(part.rate * 100)}%</div>
                                  {dur && dur.totalMs > 0 && (<div className="absolute bottom-2 right-2 px-2 py-1 rounded-full text-[10px] font-mono font-medium bg-black/70 text-white border border-white/10 backdrop-blur flex items-center gap-1"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>{formatDurationMs(dur.totalMs)}</div>)}
                                </div>
                                <div className="p-3">
                                  <div className="font-medium text-sm leading-tight line-clamp-2 group-hover:text-[#FF8A33] transition">{release.name}</div>
                                  <div className="mt-1 text-[11px] text-zinc-500 truncate">{release.artists.map((a) => a.name).join(", ")}</div>
                                  <div className="mt-2 flex flex-wrap gap-1.5 text-[10px]"><span className="px-2 py-1 rounded-full bg-zinc-800 text-zinc-400 border border-zinc-700/50">{release.label?.name || "—"}</span><span className="px-2 py-1 rounded-full bg-emerald-500/10 text-emerald-300 border border-emerald-500/20">{part.count}/{part.total} • {Math.round(part.rate * 100)}%</span></div>
                                  <div className="mt-2 flex items-center justify-between text-[11px] text-zinc-500"><span>{formatDate(release.publish_date)}</span><span>{release.track_count} тр • {formatDurationMs(dur?.totalMs)}</span></div>
                                </div>
                              </div>
                            );
                          })}
                        </div>
                      )}
                    </>
                  )}

                  {activeTab === "allReleases" && discography && (
                    <>
                      {discography.releases.length === 0 ? (
                        <div className="py-16 text-center text-zinc-500 rounded-2xl bg-zinc-900/50 border border-zinc-800">Релизов не найдено</div>
                      ) : (
                        <>
                          <div className="mb-4 flex flex-wrap items-center gap-2">
                            <span className="text-[11px] text-zinc-500">Сортировка:</span>
                            <select value={releaseSortKey} onChange={(e) => setReleaseSortKey(e.target.value as any)} className="h-8 px-2 rounded-full bg-zinc-900 border border-zinc-800 text-zinc-300 text-[11px]">
                              <option value="date">Дата</option><option value="name">Имя</option><option value="duration">Длительность</option><option value="participation">Участие %</option>
                            </select>
                            <button onClick={() => setReleaseSortDir(d => d === "asc" ? "desc" : "asc")} className="h-8 w-8 rounded-full bg-zinc-900 border border-zinc-800 grid place-items-center text-[11px]">{releaseSortDir === "asc" ? "↑" : "↓"}</button>
                          </div>
                          <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
                            {sortedAllReleases.map((release) => {
                              const badge = getReleaseBadge(release, selectedArtist!.id);
                              const dur = releaseDurations.get(release.id);
                              const part = getReleaseParticipation(release);
                              const isExcluded = stats.releasesExcludedBy50.some(r => r.id === release.id);
                              return (
                                <div key={release.id} onClick={() => openReleaseModal(release)} className={`group cursor-pointer rounded-2xl overflow-hidden bg-zinc-900 border hover:border-zinc-700 transition ${isExcluded ? "border-amber-500/30" : "border-zinc-800"}`}>
                                  <div className="aspect-square overflow-hidden bg-zinc-800 relative">
                                    {getImageUrl(release.image, 400, 400) ? (<img loading="lazy" src={getImageUrl(release.image, 400, 400)} alt={release.name} className="h-full w-full object-cover group-hover:scale-105 transition duration-500" />) : <div className="h-full w-full grid place-items-center text-zinc-600">no cover</div>}
                                    <div className={`absolute top-2 left-2 px-2 py-1 rounded-full text-[10px] font-medium border backdrop-blur ${badge.className}`}>{badge.label}</div>
                                    {dur && dur.totalMs > 0 && (<div className="absolute bottom-2 right-2 px-2 py-1 rounded-full text-[10px] font-mono font-medium bg-black/70 text-white border border-white/10 backdrop-blur flex items-center gap-1"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>{formatDurationMs(dur.totalMs)}</div>)}
                                  </div>
                                  <div className="p-3">
                                    <div className="font-medium text-sm leading-tight line-clamp-2 group-hover:text-[#FF8A33] transition">{release.name}</div>
                                    <div className="mt-1 text-[11px] text-zinc-500 truncate">{release.artists.map((a) => a.name).join(", ") || "Various Artists"}</div>
                                    <div className="mt-2 flex flex-wrap gap-1.5 text-[10px]">
                                      <span className="px-2 py-1 rounded-full bg-zinc-800 text-zinc-400 border border-zinc-700/50">{release.label?.name}</span>
                                      <span className={`px-2 py-1 rounded-full border ${part.rate >= threshold ? "bg-emerald-500/10 text-emerald-300 border-emerald-500/20" : part.rate > 0 ? "bg-amber-500/10 text-amber-300 border-amber-500/20" : "bg-zinc-800 text-zinc-500 border-zinc-700/30"}`}>{part.count}/{part.total} • {Math.round(part.rate * 100)}%</span>
                                    </div>
                                    <div className="mt-2 flex items-center justify-between text-[11px] text-zinc-500"><span>{formatDate(release.publish_date)}</span><span>{release.track_count} tr • {dur ? `${dur.count} артиста` : "—"}</span></div>
                                    {isExcluded && (<div className="mt-2 p-1.5 rounded-lg bg-amber-500/10 border border-amber-500/20 text-[10px] text-amber-300">Исключён из Releases: {part.count}/{part.total} ({Math.round(part.rate * 100)}% &lt; {Math.round(threshold*100)}%)</div>)}
                                  </div>
                                </div>
                              );
                            })}
                          </div>
                        </>
                      )}
                    </>
                  )}

                  {activeTab === "tracks" && (
                    <>
                      {filteredTracks.length === 0 ? (
                        <div className="py-16 text-center text-zinc-500 rounded-2xl bg-zinc-900/50 border border-zinc-800">Треков не найдено (фильтры).</div>
                      ) : (
                        <>
                          <div className="mb-4 flex flex-wrap gap-3 items-center p-3 rounded-xl bg-zinc-900 border border-zinc-800">
                            <div className="flex items-center gap-2">
                              <span className="text-[11px] text-zinc-500">Жанр:</span>
                              <select value={trackFilterGenre} onChange={(e) => { setTrackFilterGenre(e.target.value); setTrackPage(1); }} className="h-8 px-2 rounded-full bg-zinc-950 border border-zinc-800 text-[11px] text-zinc-300">
                                <option value="">Все</option>{uniqueGenres.map(g => (<option key={g} value={g}>{g}</option>))}
                              </select>
                            </div>
                            <div className="flex items-center gap-2">
                              <span className="text-[11px] text-zinc-500">BPM:</span>
                              <input type="number" placeholder="мин" value={trackFilterBpm.min || ""} onChange={(e) => setTrackFilterBpm(p => ({ ...p, min: e.target.value ? parseInt(e.target.value) : undefined }))} className="w-16 h-8 px-2 rounded-full bg-zinc-950 border border-zinc-800 text-[11px]" />
                              <span className="text-zinc-600">—</span>
                              <input type="number" placeholder="макс" value={trackFilterBpm.max || ""} onChange={(e) => setTrackFilterBpm(p => ({ ...p, max: e.target.value ? parseInt(e.target.value) : undefined }))} className="w-16 h-8 px-2 rounded-full bg-zinc-950 border border-zinc-800 text-[11px]" />
                            </div>
                            <div className="flex items-center gap-2">
                              <button onClick={handleExportXLSX} disabled={isExportingXlsx} className="h-8 px-3 rounded-full bg-[#FF6B00] hover:bg-[#ff7e26] disabled:opacity-50 text-black font-semibold text-[11px] flex items-center gap-1">
                                {isExportingXlsx ? "⏳ XLSX..." : `📊 XLSX MAX (${filteredTracks.length})`}
                              </button>
                              <button onClick={() => { if (discography) exportTracksToCSV(filteredTracks, artistDetails?.name || "tracks"); }} className="h-8 px-2 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-[11px] text-zinc-400">CSV</button>
                            </div>
                            <div className="flex items-center gap-2 ml-auto">
                              <span className="text-[11px] text-zinc-500">Сорт:</span>
                              <select value={trackSortKey} onChange={(e) => setTrackSortKey(e.target.value as any)} className="h-8 px-2 rounded-full bg-zinc-950 border border-zinc-800 text-[11px] text-zinc-300">
                                <option value="date">Дата</option><option value="bpm">BPM</option><option value="duration">Длина</option><option value="genre">Жанр</option><option value="role">Роль</option>
                              </select>
                              <button onClick={() => setTrackSortDir(d => d === "asc" ? "desc" : "asc")} className="h-8 w-8 rounded-full bg-zinc-950 border border-zinc-800 grid place-items-center text-[11px]">{trackSortDir === "asc" ? "↑" : "↓"}</button>
                              <span className="text-[11px] text-zinc-500 ml-2">{filteredTracks.length} треков • стр {trackPage}/{totalTrackPages}</span>
                            </div>
                          </div>

                          <div className="rounded-2xl overflow-hidden border border-zinc-800 bg-zinc-900">
                            <div className="overflow-x-auto">
                              <table className="w-full text-[13px] min-w-[1200px]">
                                <thead className="sticky top-0 z-10 bg-zinc-900/95 backdrop-blur border-b border-zinc-800">
                                  <tr className="text-[11px] uppercase tracking-wide text-zinc-500 text-left">
                                    <th className="px-3 py-3 w-12">▶</th>
                                    <th className="px-3 py-3 min-w-[240px]">Название</th>
                                    <th className="px-3 py-3 min-w-[160px]">Артисты</th>
                                    <th className="px-3 py-3 min-w-[140px]">Ремиксеры</th>
                                    <th className="px-3 py-3 min-w-[180px]">Релиз</th>
                                    <th className="px-3 py-3 cursor-pointer" onClick={() => { setTrackSortKey("genre"); setTrackSortDir(d => d === "asc" ? "desc" : "asc"); }}>Жанр {trackSortKey === "genre" ? (trackSortDir === "asc" ? "↑" : "↓") : ""}</th>
                                    <th className="px-3 py-3">Под-жанр</th>
                                    <th className="px-3 py-3 cursor-pointer" onClick={() => { setTrackSortKey("bpm"); setTrackSortDir(d => d === "asc" ? "desc" : "asc"); }}>BPM {trackSortKey === "bpm" ? (trackSortDir === "asc" ? "↑" : "↓") : ""}</th>
                                    <th className="px-3 py-3">Ключ</th>
                                    <th className="px-3 py-3 cursor-pointer" onClick={() => { setTrackSortKey("duration"); setTrackSortDir(d => d === "asc" ? "desc" : "asc"); }}>Длина {trackSortKey === "duration" ? (trackSortDir === "asc" ? "↑" : "↓") : ""}</th>
                                    <th className="px-3 py-3 cursor-pointer" onClick={() => { setTrackSortKey("date"); setTrackSortDir(d => d === "asc" ? "desc" : "asc"); }}>Дата {trackSortKey === "date" ? (trackSortDir === "asc" ? "↑" : "↓") : ""}</th>
                                    <th className="px-3 py-3 cursor-pointer" onClick={() => { setTrackSortKey("role"); setTrackSortDir(d => d === "asc" ? "desc" : "asc"); }}>Роль {trackSortKey === "role" ? (trackSortDir === "asc" ? "↑" : "↓") : ""}</th>
                                  </tr>
                                </thead>
                                <tbody className="divide-y divide-zinc-800/60">
                                  {paginatedTracks.map((track) => {
                                    const role = getRoleBadge(track, selectedArtist!.id);
                                    const isPlaying = playingTrack?.id === track.id;
                                    return (
                                      <tr key={track.id} className={`hover:bg-zinc-800/40 transition ${isPlaying ? "bg-[#FF6B00]/10" : ""}`}>
                                        <td className="px-3 py-2"><button onClick={() => { if (playingTrack?.id === track.id) { if (audioRef.current) { if (audioRef.current.paused) audioRef.current.play(); else audioRef.current.pause(); } } else { setPlayingTrack(track); } }} disabled={!track.sample_url} className={`h-8 w-8 rounded-full grid place-items-center border transition ${isPlaying ? "bg-[#FF6B00] border-[#FF6B00] text-black" : track.sample_url ? "bg-zinc-800 border-zinc-700 hover:border-[#FF6B00]/50 hover:text-[#FF6B00] text-zinc-300" : "bg-zinc-900 border-zinc-800 text-zinc-600 cursor-not-allowed"}`}>{isPlaying ? (<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>) : (<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5.14v14l11-7-11-7z" /></svg>)}</button></td>
                                        <td className="px-3 py-2"><div className="font-medium text-zinc-100 line-clamp-2 leading-tight">{track.name}</div>{track.mix_name && (<div className="text-[11px] text-zinc-500">{track.mix_name}</div>)}</td>
                                        <td className="px-3 py-2"><div className="text-zinc-300 truncate max-w-[200px]">{track.artists.map((a) => a.name).join(", ")}</div></td>
                                        <td className="px-3 py-2"><div className="text-zinc-500 truncate max-w-[160px]">{track.remixers.length ? track.remixers.map((a) => a.name).join(", ") : "—"}</div></td>
                                        <td className="px-3 py-2"><a href={track.release ? getBeatportReleaseUrl(track.release as Release) : "#"} target="_blank" onClick={(e) => e.stopPropagation()} className="text-zinc-300 hover:text-[#FF6B00] underline decoration-zinc-700 underline-offset-2 truncate max-w-[200px] inline-block">{track.release?.name || "—"}</a></td>
                                        <td className="px-3 py-2 text-zinc-400">{track.genre?.name || "—"}</td>
                                        <td className="px-3 py-2 text-zinc-500">{track.sub_genre?.name || "—"}</td>
                                        <td className="px-3 py-2 text-zinc-300">{track.bpm || "—"}</td>
                                        <td className="px-3 py-2">{track.key ? (<span className="inline-flex items-center gap-1"><span className="text-zinc-300">{track.key.name}</span>{track.key.camelot_number && (<span className="text-[11px] px-1.5 py-0.5 rounded bg-zinc-800 text-zinc-500">{track.key.camelot_number}{track.key.camelot_letter}</span>)}</span>) : ("—")}</td>
                                        <td className="px-3 py-2 text-zinc-400">{track.length || "—"}</td>
                                        <td className="px-3 py-2 text-zinc-500 text-[12px]">{formatDate(track.publish_date)}</td>
                                        <td className="px-3 py-2"><span className={`inline-flex px-2 py-1 rounded-full text-[10px] font-medium border ${role.className}`}>{role.label}</span></td>
                                      </tr>
                                    );
                                  })}
                                </tbody>
                              </table>
                            </div>
                          </div>

                          <div className="mt-4 flex items-center justify-between">
                            <div className="flex gap-2">
                              <button disabled={trackPage <= 1} onClick={() => setTrackPage(p => Math.max(1, p - 1))} className="h-8 px-4 rounded-full bg-zinc-900 border border-zinc-800 disabled:opacity-50 text-[12px]">← Назад</button>
                              <button disabled={trackPage >= totalTrackPages} onClick={() => setTrackPage(p => Math.min(totalTrackPages, p + 1))} className="h-8 px-4 rounded-full bg-zinc-900 border border-zinc-800 disabled:opacity-50 text-[12px]">Вперед →</button>
                            </div>
                            <div className="text-[11px] text-zinc-500">Страница {trackPage} из {totalTrackPages} • {filteredTracks.length} треков отфильтровано из {discography.tracks.length}</div>
                            <div className="flex gap-2">
                              <span className="text-[11px] text-zinc-500">На странице:</span>
                              <span className="text-[11px] px-2 py-1 rounded-full bg-zinc-800 border border-zinc-700">{paginatedTracks.length}</span>
                            </div>
                          </div>
                        </>
                      )}
                    </>
                  )}
                </div>
              </>
            )}
          </div>
        )}
      </main>

      {playingTrack && (
        <div className="fixed bottom-0 left-0 w-full z-50 border-t border-zinc-800 bg-zinc-950/90 backdrop-blur-xl">
          <div className="mx-auto max-w-[1400px] px-4 sm:px-6 py-3 flex items-center gap-4">
            <div className="h-12 w-12 rounded-xl overflow-hidden bg-zinc-800 flex-shrink-0">{getImageUrl(playingTrack.release?.image as any, 100, 100) ? (<img loading="lazy" src={getImageUrl(playingTrack.release?.image as any, 100, 100)} alt="" className="h-full w-full object-cover" />) : <div className="h-full w-full grid place-items-center text-zinc-600">♪</div>}</div>
            <div className="min-w-0 flex-1"><div className="font-medium text-sm truncate">{playingTrack.name} {playingTrack.mix_name ? `(${playingTrack.mix_name})` : ""}</div><div className="text-xs text-zinc-500 truncate">{playingTrack.artists.map((a) => a.name).join(", ")}</div></div>
            <audio ref={audioRef} src={playingTrack.sample_url} controls className="h-8 w-full max-w-[400px] accent-[#FF6B00]" />
            <button onClick={() => setPlayingTrack(null)} className="h-8 w-8 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 grid place-items-center text-zinc-400 hover:text-zinc-200 transition">✕</button>
          </div>
        </div>
      )}

      {playingTrack && <div className="h-[76px]" />}

      {/* Release Details Modal */}
      {selectedReleaseModal && (
        <div className="fixed inset-0 z-[70] bg-black/80 backdrop-blur-md grid place-items-center p-4 overflow-y-auto" onClick={closeReleaseModal}>
          <div className="w-full max-w-4xl my-8 rounded-[24px] bg-zinc-900 border border-zinc-800 shadow-2xl overflow-hidden max-h-[90vh] flex flex-col" onClick={(e) => e.stopPropagation()}>
            {/* Modal Header */}
            <div className="flex items-start justify-between gap-4 p-6 border-b border-zinc-800 bg-zinc-950/50">
              <div className="flex gap-4 min-w-0">
                <div className="h-20 w-20 sm:h-24 sm:w-24 rounded-2xl overflow-hidden bg-zinc-800 flex-shrink-0 border border-zinc-700">
                  {getImageUrl(selectedReleaseModal.image, 400, 400) ? (
                    // eslint-disable-next-line @next/next/no-img-element
                    <img src={getImageUrl(selectedReleaseModal.image, 400, 400)} alt={selectedReleaseModal.name} className="h-full w-full object-cover" />
                  ) : (
                    <div className="h-full w-full grid place-items-center text-zinc-600">no cover</div>
                  )}
                </div>
                <div className="min-w-0">
                  <h2 className="text-xl sm:text-2xl font-semibold leading-tight truncate">{selectedReleaseModal.name}</h2>
                  <div className="mt-1 text-[13px] text-zinc-400 truncate">{selectedReleaseModal.artists.map(a => a.name).join(", ") || "Various Artists"}</div>
                  {selectedReleaseModal.remixers?.length > 0 && (
                    <div className="mt-1 text-[12px] text-zinc-500">Remixers: {selectedReleaseModal.remixers.map(a => a.name).join(", ")}</div>
                  )}
                  <div className="mt-2 flex flex-wrap gap-1.5">
                    {(() => {
                      if (!selectedArtist) return null;
                      const badge = getReleaseBadge(selectedReleaseModal, selectedArtist.id);
                      const part = getReleaseParticipation(selectedReleaseModal);
                      return (
                        <>
                          <span className={`px-2.5 py-1 rounded-full text-[11px] font-medium border ${badge.className}`}>{badge.label}</span>
                          <span className={`px-2.5 py-1 rounded-full text-[11px] font-medium border ${part.rate >= threshold ? "bg-emerald-500/10 text-emerald-300 border-emerald-500/20" : "bg-amber-500/10 text-amber-300 border-amber-500/20"}`}>{part.count}/{part.total} треков • {Math.round(part.rate * 100)}%</span>
                          <span className="px-2.5 py-1 rounded-full text-[11px] bg-zinc-800 border border-zinc-700 text-zinc-400">{formatDurationMs(releaseDurations.get(selectedReleaseModal.id)?.totalMs)}</span>
                        </>
                      );
                    })()}
                  </div>
                </div>
              </div>
              <button onClick={closeReleaseModal} className="h-9 w-9 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 grid place-items-center text-zinc-400 hover:text-zinc-200 transition flex-shrink-0">
                ✕
              </button>
            </div>

            {/* Modal Content */}
            <div className="flex-1 overflow-y-auto p-6 space-y-6">
              {isReleaseModalLoading ? (
                <div className="py-16 flex flex-col items-center gap-3">
                  <div className="h-10 w-10 rounded-full border-2 border-zinc-700 border-t-[#FF6B00] animate-spin" />
                  <div className="text-sm text-zinc-400">Загрузка полной информации о релизе...</div>
                </div>
              ) : releaseModalError ? (
                <div className="p-4 rounded-xl bg-red-500/10 border border-red-500/20 text-red-300 text-sm">
                  Ошибка: {releaseModalError}
                </div>
              ) : releaseModalDetails ? (
                <>
                  {/* Release Info Grid */}
                  <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">Лейбл</div>
                      <div className="mt-1 font-medium">{releaseModalDetails.release?.label?.name || selectedReleaseModal.label?.name || "—"}</div>
                      <div className="text-[11px] text-zinc-500 mt-1">ID: {releaseModalDetails.release?.label?.id || selectedReleaseModal.label?.id || "—"}</div>
                    </div>
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">Каталожный номер</div>
                      <div className="mt-1 font-mono text-sm">{releaseModalDetails.release?.catalog_number || selectedReleaseModal.catalog_number || "—"}</div>
                    </div>
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">Дата релиза</div>
                      <div className="mt-1 font-medium">{formatDate(releaseModalDetails.release?.publish_date || selectedReleaseModal.publish_date)}</div>
                      <div className="text-[11px] text-zinc-500">{releaseModalDetails.release?.new_release_date ? `New: ${formatDate(releaseModalDetails.release.new_release_date)}` : ""}</div>
                    </div>
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">Треков (official)</div>
                      <div className="mt-1 text-xl font-semibold">{releaseModalDetails.release?.track_count || selectedReleaseModal.track_count || "—"}</div>
                      <div className="text-[11px] text-zinc-500">в API: {releaseModalDetails.tracks?.length || 0} загружено</div>
                    </div>
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">BPM Range</div>
                      <div className="mt-1 font-medium">
                        {releaseModalDetails.release?.bpm_range ? `${releaseModalDetails.release.bpm_range.min || "—"} — ${releaseModalDetails.release.bpm_range.max || "—"}` : "—"}
                      </div>
                      <div className="text-[11px] text-zinc-500">Диапазон темпов</div>
                    </div>
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">Цена / UPC</div>
                      <div className="mt-1 font-medium">{releaseModalDetails.release?.price?.display || selectedReleaseModal.price?.display || "—"}</div>
                      <div className="text-[11px] text-zinc-500 font-mono">{releaseModalDetails.release?.upc || "—"}</div>
                    </div>
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">Exclusive / Explicit / Hype</div>
                      <div className="mt-1 flex flex-wrap gap-1.5 text-[11px]">
                        <span className={`px-2 py-0.5 rounded-full border ${releaseModalDetails.release?.exclusive ? "bg-[#FF6B00]/20 text-[#FF8A33] border-[#FF6B00]/30" : "bg-zinc-800 text-zinc-500 border-zinc-700"}`}>Exclusive: {releaseModalDetails.release?.exclusive ? "Да" : "Нет"}</span>
                        <span className={`px-2 py-0.5 rounded-full border ${releaseModalDetails.release?.is_explicit ? "bg-red-500/20 text-red-300 border-red-500/30" : "bg-zinc-800 text-zinc-500 border-zinc-700"}`}>Explicit: {releaseModalDetails.release?.is_explicit ? "Да" : "Нет"}</span>
                        <span className={`px-2 py-0.5 rounded-full border ${releaseModalDetails.release?.is_hype ? "bg-purple-500/20 text-purple-300 border-purple-500/30" : "bg-zinc-800 text-zinc-500 border-zinc-700"}`}>Hype: {releaseModalDetails.release?.is_hype ? "Да" : "Нет"}</span>
                      </div>
                    </div>
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">Общая длительность (треков артиста)</div>
                      <div className="mt-1 text-lg font-semibold text-[#FF8A33]">{formatDurationMs(releaseDurations.get(selectedReleaseModal.id)?.totalMs)}</div>
                      <div className="text-[11px] text-zinc-500">{releaseDurations.get(selectedReleaseModal.id)?.count || 0} треков артиста в релизе</div>
                    </div>
                    <div className="rounded-xl bg-zinc-950 border border-zinc-800 p-3">
                      <div className="text-[10px] uppercase tracking-wide text-zinc-500">Ссылки</div>
                      <div className="mt-2 flex flex-wrap gap-2">
                        <a href={getBeatportReleaseUrl(selectedReleaseModal)} target="_blank" className="px-3 py-1.5 rounded-full bg-[#FF6B00] hover:bg-[#ff7e26] text-black text-[11px] font-semibold transition">Открыть на Beatport ↗</a>
                        <a href={selectedReleaseModal.url || releaseModalDetails.release?.url || "#"} target="_blank" className="px-3 py-1.5 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-zinc-300 text-[11px] transition">API URL ↗</a>
                      </div>
                    </div>
                  </div>

                  {releaseModalDetails.release?.artists && (
                    <div>
                      <h3 className="text-sm font-semibold text-zinc-300 mb-2">Артисты релиза ({releaseModalDetails.release.artists.length})</h3>
                      <div className="flex flex-wrap gap-2">
                        {releaseModalDetails.release.artists.map((a: Artist) => (
                          <span key={a.id} className={`px-3 py-1 rounded-full text-[12px] border ${a.id === selectedArtist?.id ? "bg-[#FF6B00]/20 text-[#FF8A33] border-[#FF6B00]/30 font-medium" : "bg-zinc-800 text-zinc-400 border-zinc-700"}`}>
                            {a.name} {a.id === selectedArtist?.id ? "• вы" : ""} (ID {a.id})
                          </span>
                        ))}
                      </div>
                    </div>
                  )}

                  {releaseModalDetails.release?.remixers?.length > 0 && (
                    <div>
                      <h3 className="text-sm font-semibold text-zinc-300 mb-2">Ремиксеры релиза</h3>
                      <div className="flex flex-wrap gap-2">
                        {releaseModalDetails.release.remixers.map((a: Artist) => (
                          <span key={a.id} className={`px-3 py-1 rounded-full text-[12px] border ${a.id === selectedArtist?.id ? "bg-purple-500/20 text-purple-300 border-purple-500/30" : "bg-zinc-800 text-zinc-500 border-zinc-700"}`}>
                            {a.name} {a.id === selectedArtist?.id ? "• вы" : ""}
                          </span>
                        ))}
                      </div>
                    </div>
                  )}

                  <div>
                    <h3 className="text-sm font-semibold text-zinc-300 mb-3 flex items-center gap-2">
                      Треклист релиза ({releaseModalDetails.tracks?.length || 0} треков, загружено с Beatport API)
                      {selectedArtist && <span className="text-[11px] font-normal text-zinc-500">• ваши треки подсвечены оранжевым</span>}
                    </h3>
                    {releaseModalDetails.tracks?.length > 0 ? (
                      <div className="rounded-xl overflow-hidden border border-zinc-800 bg-zinc-950">
                        <div className="max-h-[300px] overflow-y-auto">
                          <table className="w-full text-[12px]">
                            <thead className="sticky top-0 bg-zinc-900 border-b border-zinc-800 text-[10px] uppercase tracking-wide text-zinc-500">
                              <tr><th className="px-3 py-2 text-left">#</th><th className="px-3 py-2 text-left">Название</th><th className="px-3 py-2 text-left">Артисты</th><th className="px-3 py-2 text-left">Ремиксеры</th><th className="px-3 py-2">BPM</th><th className="px-3 py-2">Ключ</th><th className="px-3 py-2">Длина</th><th className="px-3 py-2">▶</th></tr>
                            </thead>
                            <tbody className="divide-y divide-zinc-800/50">
                              {releaseModalDetails.tracks.map((t: Track, idx: number) => {
                                const isOwn = t.artists.some((a: Artist) => a.id === selectedArtist?.id) || t.remixers.some((a: Artist) => a.id === selectedArtist?.id);
                                return (
                                  <tr key={t.id} className={`${isOwn ? "bg-[#FF6B00]/10" : ""} hover:bg-zinc-800/50`}>
                                    <td className="px-3 py-2 text-zinc-500">{idx + 1}</td>
                                    <td className="px-3 py-2"><div className="font-medium text-zinc-200">{t.name}</div><div className="text-[11px] text-zinc-500">{t.mix_name}</div></td>
                                    <td className="px-3 py-2 text-zinc-400 truncate max-w-[150px]">{t.artists.map((a: Artist) => a.name).join(", ")}</td>
                                    <td className="px-3 py-2 text-zinc-500 truncate max-w-[120px]">{t.remixers?.map((a: Artist) => a.name).join(", ") || "—"}</td>
                                    <td className="px-3 py-2 text-zinc-400">{t.bpm || "—"}</td>
                                    <td className="px-3 py-2 text-zinc-400">{t.key?.name || "—"}</td>
                                    <td className="px-3 py-2 text-zinc-500">{t.length || ""}</td>
                                    <td className="px-3 py-2">
                                      {t.sample_url ? (
                                        <button onClick={() => { setPlayingTrack(t); }} className="h-6 w-6 rounded-full bg-zinc-800 hover:bg-[#FF6B00] hover:text-black border border-zinc-700 grid place-items-center text-[10px]">▶</button>
                                      ) : "—"}
                                    </td>
                                  </tr>
                                );
                              })}
                            </tbody>
                          </table>
                        </div>
                      </div>
                    ) : (
                      <div className="p-4 rounded-xl bg-zinc-900 border border-zinc-800 text-[12px] text-zinc-500">
                        Треклист не загружен. Возможно, релиз приватный или API вернул 0 треков. Попробуйте открыть на Beatport.
                        <div className="mt-2 text-[11px] text-zinc-600">
                          У нас есть {releaseDurations.get(selectedReleaseModal.id)?.count || 0} треков этого артиста в этом релизе из нашей дискографии (локальные данные).
                        </div>
                      </div>
                    )}
                  </div>

                  <div>
                    <h3 className="text-sm font-semibold text-zinc-300 mb-2">Треки артиста {artistDetails?.name} в этом релизе (из загруженной дискографии)</h3>
                    <div className="grid gap-2">
                      {(discography?.tracks.filter(t => t.release?.id === selectedReleaseModal.id) || []).map(t => (
                        <div key={t.id} className="flex items-center gap-3 p-2 rounded-xl bg-zinc-900 border border-zinc-800">
                          <button onClick={() => setPlayingTrack(t)} className="h-8 w-8 rounded-full bg-zinc-800 border border-zinc-700 grid place-items-center hover:border-[#FF6B00]">▶</button>
                          <div className="min-w-0 flex-1">
                            <div className="text-[13px] font-medium truncate">{t.name} {t.mix_name ? `(${t.mix_name})` : ""}</div>
                            <div className="text-[11px] text-zinc-500 truncate">{t.artists.map(a => a.name).join(", ")} • {t.bpm} BPM • {t.key?.name} • {t.length}</div>
                          </div>
                          <span className={`px-2 py-1 rounded-full text-[10px] border ${getRoleBadge(t, selectedArtist!.id).className}`}>{getRoleBadge(t, selectedArtist!.id).label}</span>
                        </div>
                      ))}
                      {(discography?.tracks.filter(t => t.release?.id === selectedReleaseModal.id).length || 0) === 0 && (
                        <div className="text-[11px] text-zinc-600 p-2">Нет треков этого артиста в этом релизе в загруженной дискографии</div>
                      )}
                    </div>
                  </div>
                </>
              ) : (
                <div className="text-sm text-zinc-500">Нет данных</div>
              )}
            </div>

            <div className="p-4 border-t border-zinc-800 bg-zinc-950/50 flex items-center justify-between gap-3">
              <div className="text-[11px] text-zinc-500">ID {selectedReleaseModal.id} • {selectedReleaseModal.track_count} треков • {formatDurationMs(releaseDurations.get(selectedReleaseModal.id)?.totalMs)} звучания артиста</div>
              <div className="flex gap-2">
                <a href={getBeatportReleaseUrl(selectedReleaseModal)} target="_blank" className="h-9 px-4 rounded-full bg-[#FF6B00] hover:bg-[#ff7e26] text-black font-semibold text-[12px] flex items-center gap-1 transition">Открыть на Beatport ↗</a>
                <button onClick={closeReleaseModal} className="h-9 px-4 rounded-full bg-zinc-800 hover:bg-zinc-700 border border-zinc-700 text-zinc-300 text-[12px]">Закрыть</button>
              </div>
            </div>
          </div>
        </div>
      )}

      <footer className="mt-20 border-t border-zinc-900 py-8">
        <div className="mx-auto max-w-[1400px] px-6 text-center text-[11px] text-zinc-600">
          <div>Beatport Discography Searcher • Universal • Cached • Audited • Threshold {Math.round(threshold*100)}% • CSV Export • Pagination</div>
          <div className="mt-1">Данные: api.beatport.com • Кэш 5/10/15 мин • Shareable URL (?q=&artist=) • Клавиша / для поиска, Esc для закрытия</div>
        </div>
      </footer>
    </div>
  );
}
