// Shared types/helpers for the /music workspace panels (MusicBrowser + MusicDetail), which coordinate
// via the `?path=` search param and play through the app-wide useMusicPlayer.

import { useSearchParams } from 'react-router';

export const MUSIC_ROOT = 'Music';
export const MUSIC_FAV_CHANNEL = 'music:favorites';
// Bumped (to a fresh nonce) when a library reindex finishes, so BOTH panels re-run their manifest /
// listing / meta fetches — otherwise only the panel that triggered the reindex refreshes.
export const MUSIC_RESYNC_CHANNEL = 'music:resync';

// Album folders are named "[year] Album Name" → display as "Album Name" + year.
const ALBUM_NAME_RE = /^\[(\d{4})\]\s*(.+)$/;
export const parseAlbumName = (name: string): { title: string; year?: string } => {
  const m = ALBUM_NAME_RE.exec(name.trim());
  return m ? { title: m[2]!.trim(), year: m[1] } : { title: name };
};

export type DirEntry = { name: string; type: 'directory' | 'file'; size: number; modifiedAt: number };
export type LsResult = { entries: DirEntry[] };
export type ManifestAlbum = { v: string; cover: boolean; tracks: number; disco?: boolean };
export type Manifest = { albums: Record<string, ManifestAlbum> };
export type Track = {
  file: string;
  title?: string;
  artist?: string;
  albumArtist?: string;
  track?: string;
  durationSec?: number;
};
export type AlbumMeta = { path: string; cover?: string; tracks: Track[] };

/** Seconds → "m:ss" (or "h:mm:ss" once past an hour); '' when unknown. */
export const fmtDuration = (sec?: number): string => {
  if (!sec || sec <= 0) return '';
  const s = Math.round(sec);
  const h = Math.floor(s / 3600);
  const m = Math.floor((s % 3600) / 60);
  const ss = String(s % 60).padStart(2, '0');
  return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${ss}` : `${m}:${ss}`;
};

/** Seconds → "m:ss" for a running clock: unknown reads as 0:00, never blank, so it doesn't jitter. */
export const fmtClock = (sec: number): string =>
  Number.isFinite(sec) && sec >= 0
    ? `${Math.floor(sec / 60)}:${String(Math.floor(sec % 60)).padStart(2, '0')}`
    : '0:00';

/** Case-insensitive subsequence fuzzy match: every char of `query` appears in order within `text`. */
export const fuzzyMatch = (query: string, text: string): boolean => {
  const q = query.trim().toLowerCase();
  if (!q) return true;
  const t = text.toLowerCase();
  let qi = 0;
  for (let ti = 0; ti < t.length && qi < q.length; ti++) if (t[ti] === q[qi]!) qi++;
  return qi === q.length;
};

/** Parse a track-number tag ("7", "07", "7/14") to a number, or null when absent/unparseable. */
const trackNo = (t: Track): number | null => {
  const raw = t.track?.split('/')[0]?.trim();
  if (!raw) return null;
  const n = parseInt(raw, 10);
  return Number.isFinite(n) ? n : null;
};

/**
 * Canonical album track order: by the `track` NUMBER, falling back to the tag title only for tracks
 * that have no number (numbered tracks always precede unnumbered ones; filename breaks a final tie).
 * meta.json is in ffprobe/readdir order (arbitrary), so every consumer must sort with this.
 */
export const sortTracks = <T extends Track>(tracks: T[]): T[] => {
  const key = (t: Track) => (t.title || t.file).toLowerCase();
  return [...tracks].sort((a, b) => {
    const na = trackNo(a);
    const nb = trackNo(b);
    if (na !== null && nb !== null) return na - nb || key(a).localeCompare(key(b));
    if (na !== null) return -1;
    if (nb !== null) return 1;
    return key(a).localeCompare(key(b));
  });
};
export type Discography = { artist: string; albums: Record<string, string> };

export type FavoriteKind = 'track' | 'album' | 'artist';
export type GroupedFavorites = { tracks: string[]; albums: string[]; artists: string[] };

/** Per-user "currently playing" snapshot (GET/PUT /api/music/now-playing). */
export type NowPlaying = {
  homePath: string;
  dir: string;
  title: string;
  artist: string;
  album: string;
  durationSec: number;
  positionSec: number;
  updatedAt: string;
};

/** homePath ("Music/<rel>/<file>") for a track — its favorite key + /stream path. */
export const trackHomePath = (rel: string, file: string) => `${MUSIC_ROOT}/${rel}/${file}`;

const AUDIO_EXT = new Set(['mp3', 'flac', 'm4a', 'aac', 'ogg', 'opus', 'wav', 'wma', 'mp4']);
export const isAudio = (n: string) => {
  const d = n.lastIndexOf('.');
  return d >= 0 && AUDIO_EXT.has(n.slice(d + 1).toLowerCase());
};

// Section order for an artist's discography.
export const TYPE_ORDER = [
  'Studio',
  'Live',
  'Compilation',
  'EP',
  'Single',
  'Soundtrack',
  'Remix',
  'DJ-Mix',
  'Demo',
  'Mixtape',
  'Bootleg',
  'Other',
];

export const coverUrl = (rel: string, token: string | null) =>
  `/api/music/cover?path=${encodeURIComponent(rel)}${token ? `&token=${encodeURIComponent(token)}` : ''}`;

/** Path (home-relative) → rel (relative to the Music root). */
export const toRel = (cwd: string | null) => (cwd ? cwd.slice(MUSIC_ROOT.length + 1) : '');

// Where you are in the library is `/music?path=<rel>`, not a `music:cwd` channel. A query param rather
// than `/music/*` because the location is one of several things this screen holds (the lyrics split and
// the favorites view are the others), and because a splat would have to be the last segment of the
// route — the same reason /chat spells its group that way. `rel === ''` is the library root, which is
// the bare /music and a real state, so there is no redirect guard.
export const MUSIC_PATH_PARAM = 'path';

/** Link target for a library location. `rel` is relative to the Music root; '' is the root itself. */
export const musicPath = (rel: string) => (rel ? `/music?${MUSIC_PATH_PARAM}=${encodeURIComponent(rel)}` : '/music');

/** Link target for the parent of `rel` — '' (the root) is its own parent, which is where "up" stops. */
export const musicParentPath = (rel: string) => musicPath(rel.split('/').slice(0, -1).join('/'));

/**
 * The open library folder as a home-relative path ("Music/…"), or null at the root — the vocabulary the
 * panels already speak, so reading the URL costs them nothing. Each panel calls this itself; they never
 * tell each other where they are.
 */
export const useMusicCwd = (): string | null => {
  const rel = useSearchParams()[0].get(MUSIC_PATH_PARAM)?.trim() ?? '';
  return rel ? `${MUSIC_ROOT}/${rel}` : null;
};
