import { LitElement, html, css, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { designTokens } from '../../styles/tokens.css'; import { LookupReleaseGroup, BrowseReleases, GetThumbnail, } from '@go/explore/Service'; import { GetAlbumTracks, GetFilePathsByAlbums, GetFilePathsByRecordingMBIDs, } from '@go/library/Library'; import { library } from '@go/models'; import type { download, explore } from '@go/models'; type MBReleaseGroup = explore.MBReleaseGroup; type MBRelease = explore.MBRelease; type MBTrack = explore.MBTrack; import { exploreCache } from '../../store/explore-cache'; import { libraryStore } from '../../store/library-store'; import { artistLink, exploreLinkStyles } from '../../utils/explore-link'; import { describeError } from '../../utils/describe-error'; import { EventsOn } from '@runtime/runtime'; import { Events } from '../../events'; import '@awesome.me/webawesome/dist/components/icon/icon.js'; import '../library-status-indicator/library-status-indicator.js'; import { libraryStatusFor } from '@utils/library-status'; import type { LibraryStatus } from '../library-status-indicator/library-status-indicator'; import '../catalog-scope-notice/catalog-scope-notice.js'; import type { CatalogScope } from '../catalog-scope-notice/catalog-scope-notice.js'; import '@awesome.me/webawesome/dist/components/button/button.js'; import '../download-picker/download-picker'; import { downloadStore } from '../../store/download-store'; import { queueStore } from '../../store/queue-store'; import { notificationStore } from '../../store/notification-store'; import '../notifications/inline-notice'; /** * The region the album header's own failures are rendered in. * * "Inline" says *not global*, not *where* — so the region is named * once, here, rather than spelled at each call site. */ export const ExploreAlbumRegion = 'explore-album'; /* ── Utility functions (duplicated per Knowledge Pattern #9 — no cross-component imports) ── */ function extractYear(dateStr: string): string { if (!dateStr) return ''; return dateStr.substring(0, 4); } /** * Convert a duration in milliseconds to a human-readable "m:ss" string. * Returns "0:00" for zero/negative/NaN values. */ function formatDuration(ms: number): string { if (!ms || ms <= 0) return '0:00'; const totalSeconds = Math.round(ms / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}:${String(seconds).padStart(2, '0')}`; } /* ── Types ── */ interface ReleaseCluster { representative: MBRelease; allReleases: MBRelease[]; fingerprint: string; /** Standard-version score, computed by buildClusters. */ score: number; } /** Synthetic kinds — virtual entries pinned at the top of the dropdown. */ type SyntheticKind = 'standard' | 'comprehensive' | 'library'; /** Unified version-entry shape covering both synthetic and real clusters. */ interface VersionEntry { /** Stable identifier for the dropdown