// Mirrors internal/api/types.go. Kept minimal — only the shapes the SPA
// actually reads today (no LoginRequest/LoginResponse here; those live in
// client.ts alongside the auth plumbing).

export type Page<T> = {
  items: T[];
  total: number;
  limit: number;
  offset: number;
};

export type ArtistRef = {
  id: string;
  name: string;
  sort_name: string;
  album_count: number;
  cover_url: string;   // "" when no representative album cover
};

export type AlbumRef = {
  id: string;
  title: string;
  sort_title: string;
  artist_id: string;
  artist_name: string;
  year?: number;
  track_count: number;
  duration_sec: number;
  cover_url: string;
  cover_art_source: string | null;
};

export type TrackRef = {
  id: string;
  title: string;
  album_id: string;
  album_title: string;
  artist_id: string;
  artist_name: string;
  track_number?: number;
  disc_number?: number;
  duration_sec: number;
  stream_url: string;
};

export type ArtistDetail = ArtistRef & {
  albums: AlbumRef[];
  // Genres across this artist's tracks (#367). Server guarantees an array.
  genres: string[];
};

export type AlbumDetail = AlbumRef & {
  tracks: TrackRef[];
  // Genres across this album's tracks (#367). Server guarantees an array.
  genres: string[];
};

export type Playlist = {
  id: string;
  user_id: string;
  owner_username: string;
  name: string;
  description: string;
  is_public: boolean;
  kind: 'user' | 'system';
  system_variant: string | null;
  // Server-derived: a singleton system kind addressable by the
  // generic /system/{kind}/{refresh,shuffle} endpoints. Drives the
  // per-tile refresh affordance without the client hardcoding kinds.
  refreshable: boolean;
  seed_artist_id: string | null;
  cover_url: string;        // empty string when no cover; UI renders glyph
  track_count: number;
  duration_sec: number;
  created_at: string;
  updated_at: string;
};

export type PlaylistTrack = {
  position: number;
  track_id: string | null;  // null when upstream track was removed
  album_id: string | null;
  artist_id: string | null;
  title: string;
  artist_name: string;
  album_title: string;
  duration_sec: number;
  stream_url: string | null;
  added_at: string;
};

export type PlaylistDetail = Playlist & {
  tracks: PlaylistTrack[];
};

export type SearchResponse = {
  artists: Page<ArtistRef>;
  albums:  Page<AlbumRef>;
  tracks:  Page<TrackRef>;
};

export type RadioResponse = {
  tracks: TrackRef[];
};

export type LikedIdsResponse = {
  track_ids: string[];
  album_ids: string[];
  artist_ids: string[];
};

export type EventRequest =
  // `source` is the system-playlist variant the play came from
  // (for_you, discover, deep_cuts, …); empty/undefined for library
  // / user-playlist / radio / Subsonic. Server stores it on
  // play_events.source and uses it to advance the rotation. Drift
  // #555: the type was missing `source` so callers had no typed slot
  // for it and a "clean up extra properties" refactor could drop it.
  | { type: 'play_started';  track_id: string; client_id?: string; source?: string }
  | { type: 'play_ended';    play_event_id: string; duration_played_ms: number }
  | { type: 'play_skipped';  play_event_id: string; position_ms: number };

export type PlayStartedResponse = { play_event_id: string; session_id: string };
export type EventOkResponse = { ok: true };

// Lidarr / requests shared enums + shapes.
// Status + Kind are used across lidarr.ts, requests.ts, and admin.ts so they
// live here. Per-module helpers (CreateRequestParams, etc.) stay in their
// respective modules.
export type LidarrRequestStatus =
  | 'pending'
  | 'approved'
  | 'rejected'
  | 'completed'
  | 'failed';

export type LidarrRequestKind = 'artist' | 'album' | 'track';

export type LidarrSearchResult = {
  mbid: string;
  name: string;
  secondary_text: string;
  image_url: string;
  artist_mbid: string;
  album_mbid: string;
  in_library: boolean;
  requested: boolean;
};

// pgtype.UUID and pgtype.Timestamptz JSON-marshal as a native string when
// Valid, and as `null` (or omitted, with omitempty) otherwise. We unify both
// "absent" cases as `string | null` so callers branch on a single check.
export type LidarrRequest = {
  id: string;
  user_id: string;
  status: LidarrRequestStatus;
  kind: LidarrRequestKind;
  lidarr_artist_mbid: string;
  lidarr_album_mbid?: string | null;
  lidarr_track_mbid?: string | null;
  artist_name: string;
  album_title?: string | null;
  track_title?: string | null;
  quality_profile_id?: number | null;
  root_folder_path?: string | null;
  decided_at?: string | null;
  decided_by?: string | null;
  notes?: string | null;
  completed_at?: string | null;
  matched_track_id?: string | null;
  matched_album_id?: string | null;
  matched_artist_id?: string | null;
  requested_at: string;
  updated_at: string;
  // Live ingest counters. Server fills these from the matched entity's
  // children. Useful for in-flight requests so the operator can see
  // "5 albums · 47 tracks" while Lidarr is still working on the rest.
  imported_album_count: number;
  imported_track_count: number;
};

export type LidarrConfig = {
  enabled: boolean;
  // api_key is "" when unset, "***" when masked on GET, plain text on PUT.
  base_url: string;
  api_key: string;
  default_quality_profile_id: number;
  default_metadata_profile_id: number;
  default_root_folder_path: string;
};

export type LidarrQualityProfile = {
  id: number;
  name: string;
};

export type LidarrMetadataProfile = {
  id: number;
  name: string;
};

export type LidarrRootFolder = {
  path: string;
  accessible: boolean;
  free_space: number;
};

// testLidarrConnection always returns 200; callers branch on `.ok`.
// On success the response carries the live profile + folder lists so the
// integrations page can pre-fill its dropdowns on first-time setup
// without a separate round-trip.
export type LidarrTestResult =
  | {
      ok: true;
      version: string;
      quality_profiles?: { id: number; name: string }[];
      metadata_profiles?: { id: number; name: string }[];
      root_folders?: { path: string; accessible: boolean; free_space: number }[];
      list_errors?: Record<string, string>;
    }
  | { ok: false; error: string };

// Lidarr quarantine ------------------------------------------------------

export type LidarrQuarantineReason =
  | 'bad_rip'
  | 'wrong_file'
  | 'wrong_tags'
  | 'duplicate'
  | 'other';

export type LidarrQuarantineRow = {
  user_id: string;
  track_id: string;
  reason: LidarrQuarantineReason;
  notes?: string | null;
  created_at: string;
};

// What GET /api/quarantine/mine returns per row
export type LidarrQuarantineMineRow = {
  track_id: string;
  reason: LidarrQuarantineReason;
  notes?: string | null;
  created_at: string;
  track_title: string;
  track_duration_ms: number;
  album_id: string;
  album_title: string;
  album_cover_art_path?: string | null;
  artist_id: string;
  artist_name: string;
};

// What GET /api/admin/quarantine returns per row
export type AdminQuarantineRow = {
  track_id: string;
  track_title: string;
  artist_name: string;
  album_title: string;
  album_id: string;
  lidarr_album_mbid?: string | null;
  report_count: number;
  latest_at: string;
  reason_counts: Partial<Record<LidarrQuarantineReason, number>>;
  reports: AdminQuarantineReport[];
};

export type AdminQuarantineReport = {
  user_id: string;
  username: string;
  reason: LidarrQuarantineReason;
  notes?: string | null;
  created_at: string;
};

// What GET /api/admin/playback-errors returns per row
export type PlaybackErrorKind = 'zero_duration' | 'load_failed' | 'stalled';
export type PlaybackErrorResolution =
  | 'hidden'
  | 'deleted'
  | 'requested'
  | 'fixed'
  | 'ignored';

export type AdminPlaybackError = {
  id: string;
  track_id: string;
  user_id: string;
  username: string;
  client_id: string;
  kind: PlaybackErrorKind;
  detail?: string | null;
  occurred_at: string;
  resolved_at?: string | null;
  resolution?: PlaybackErrorResolution | null;
  track_title: string;
  track_file_path: string;
  artist_name: string;
  album_title: string;
  album_id: string;
};

export type LidarrQuarantineAction = 'resolved' | 'deleted_file' | 'deleted_via_lidarr';

export type LidarrQuarantineActionRow = {
  id: string;
  track_id: string;
  track_title: string;
  artist_name: string;
  album_title?: string | null;
  action: LidarrQuarantineAction;
  admin_id?: string | null;
  lidarr_album_mbid?: string | null;
  affected_users: number;
  created_at: string;
};

export type ActionResult = {
  action_id: string;
  affected_users: number;
  deleted_track_count?: number;
};

export type SeedContribution = {
  artist_id: string;
  name: string;
  contribution: number;
  is_liked: boolean;
  play_count: number;
};

export type ArtistSuggestion = {
  mbid: string;
  name: string;
  score: number;
  attribution: SeedContribution[]; // up to 3 entries, ordered by contribution DESC
  image_url?: string; // resolved on-demand from Lidarr; absent → card placeholder
  // Tags this candidate shares with your taste profile, strongest first (max 3,
  // #2377). Absent for most candidates — tag coverage for artists you don't own
  // is partial by nature (#2376) — so the card falls back to seed attribution.
  matched_tags?: string[];
};

// One parked suggestion — "not right now", not a dislike. The server only
// ever returns rows whose snoozed_until is still in the future, so the client
// never has to compare against the clock to decide what to show.
export type SuggestionSnooze = {
  mbid: string;
  name: string;
  snoozed_until: string; // RFC3339
  created_at: string;
};

// Mirrors internal/api/types.go HomePayload. All slices are non-null
// per the server contract — empty sections render as [].
export type HomePayload = {
  recently_added_albums: AlbumRef[];
  rediscover_albums:     AlbumRef[];
  rediscover_artists:    ArtistRef[];
  most_played_tracks:    TrackRef[];
  last_played_artists:   ArtistRef[];
  you_might_like_albums:  AlbumRef[];
  you_might_like_artists: ArtistRef[];
};
