import type { ReactNode } from 'react';
import { createContext, useContext, useState, useEffect, useRef } from 'react';
import { Link, useNavigate } from 'react-router';
import { useClient } from 'hooks/useClient';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { Play, Pause, ChevronLeft, MicVocal, Volume2 } from 'lucide-react';
import type { LayoutNode, PanelComponents } from 'officerdev';
import { WorkspaceLayout } from 'officerdev';
import { MusicHeart } from 'officerdev';
import { FavoritesView } from './FavoritesView';
import { useMusicPlayer } from 'officerdev';
import type { PlayerTrack } from 'officerdev';
import { LyricsPanel } from './LyricsPanel';
import { MusicMiniBar } from './MusicMiniBar';
import { useLyricsOpen } from 'officerdev';
import {
  MUSIC_ROOT,
  MUSIC_FAV_CHANNEL,
  MUSIC_RESYNC_CHANNEL,
  TYPE_ORDER,
  coverUrl,
  fmtDuration,
  isAudio,
  musicParentPath,
  musicPath,
  sortTracks,
  toRel,
  trackHomePath,
  useMusicCwd,
  type AlbumMeta,
  type Discography,
  type LsResult,
  type Manifest,
  type ManifestAlbum,
  type Track,
} from './shared';

// Turning the lyrics on splits THIS panel in two rather than opening a panel of its own: the workspace
// system is a layout engine as well as a shell, so a nested WorkspaceLayout with a fixed layout and
// components keyed by panel id gets a resizable split with no persistence and no registry entries.
const LYRICS_LAYOUT: LayoutNode = {
  type: 'group',
  id: 'music-detail-split',
  direction: 'horizontal',
  children: [
    { node: { type: 'panel', id: 'music-detail-list', appType: null }, size: 62 },
    { node: { type: 'panel', id: 'music-detail-lyrics', appType: null }, size: 38 },
  ],
};

// The library view is rendered by MusicDetail itself and passed down through context, so toggling the
// split moves the same element rather than mounting a second copy — the fetched album, and every request
// that produced it, survives the toggle.
const LibraryViewContext = createContext<ReactNode>(null);
const LibraryViewPanel = () => <>{useContext(LibraryViewContext)}</>;

const LYRICS_PANELS: PanelComponents = {
  'music-detail-list': LibraryViewPanel,
  'music-detail-lyrics': LyricsPanel,
};

const keepLayout = () => {};

// Right panel of the /music workspace — renders the content of `/music?path=…`: an album (tracklist),
// an artist (album cards grouped by discography type), or a folder grid. Drilling in is a link, so it
// changes the address; playback goes through the app-wide player.
export const MusicDetail = () => {
  const { token, get } = useClient();
  const navigate = useNavigate();
  const player = useMusicPlayer();
  const cwd = useMusicCwd();
  const [favOpen, setFavOpen] = usePanelChannel<boolean>(MUSIC_FAV_CHANNEL, false);
  const [resync] = usePanelChannel<number>(MUSIC_RESYNC_CHANNEL, 0);
  const [lyricsOpen, toggleLyrics] = useLyricsOpen();

  const [manifest, setManifest] = useState<Record<string, ManifestAlbum>>({});
  const [libraries, setLibraries] = useState<string[]>([]);
  const [folders, setFolders] = useState<string[]>([]);
  const [album, setAlbum] = useState<Track[] | null>(null);
  const [disco, setDisco] = useState<Discography | null>(null);
  const [loading, setLoading] = useState(false);

  // On first mount with no location yet, open the currently-playing album — so a reload/return lands on
  // the track you were listening to (the player itself restores via the saved now-playing snapshot).
  // Once only, so it never yanks you back after you navigate away (e.g. up to the library root).
  const autoNavRef = useRef(false);
  useEffect(() => {
    if (autoNavRef.current) return;
    if (cwd) {
      autoNavRef.current = true;
      return;
    }
    if (player.current) {
      autoNavRef.current = true;
      // `replace`: landing on /music and being moved to the playing album is one arrival, not two, so
      // Back should leave the screen rather than undo a jump the user never asked for.
      navigate(musicPath(player.current.albumRel), { replace: true });
    }
  }, [player.current, cwd, navigate]);

  // Navigating anywhere (left panel or from within Favorites) closes the Favorites view.
  useEffect(() => {
    setFavOpen(false);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [cwd]);

  // Refetch manifest + libraries on mount and whenever a reindex bumps the resync nonce. A fresh manifest
  // object identity also re-runs the [cwd, manifest] listing effect below, refreshing the folder grid /
  // album meta for whatever is currently open — so the right panel updates in place, no nav required.
  useEffect(() => {
    get<Manifest>('/music/manifest')
      .then((m) => setManifest(m.albums))
      .catch(() => setManifest({}));
    get<LsResult>(`/file-browser/ls?path=${encodeURIComponent(MUSIC_ROOT)}`)
      .then((r) =>
        setLibraries(
          r.entries
            .filter((e) => e.type === 'directory' && !e.name.startsWith('.'))
            .map((e) => e.name)
            .sort(),
        ),
      )
      .catch(() => setLibraries([]));
  }, [resync]);

  const rel = toRel(cwd);
  const childRel = (name: string) => (rel ? `${rel}/${name}` : name);

  useEffect(() => {
    if (!cwd) {
      setFolders([]);
      setAlbum(null);
      setDisco(null);
      return;
    }
    let cancelled = false;
    setLoading(true);
    setAlbum(null);
    setDisco(null);
    setFolders([]);
    get<LsResult>(`/file-browser/ls?path=${encodeURIComponent(cwd)}`)
      .then(async (r) => {
        if (cancelled) return;
        setFolders(
          r.entries
            .filter((e) => e.type === 'directory' && !e.name.startsWith('.'))
            .map((e) => e.name)
            .sort(),
        );
        const audio = r.entries.filter((e) => e.type === 'file' && isAudio(e.name)).map((e) => e.name);
        if (audio.length) {
          try {
            const meta = await get<AlbumMeta>(`/music/meta?path=${encodeURIComponent(rel)}`);
            if (!cancelled) setAlbum(sortTracks(meta.tracks));
          } catch {
            if (!cancelled) setAlbum(audio.sort().map((f) => ({ file: f })));
          }
        } else if (manifest[rel]?.disco) {
          try {
            const d = await get<Discography>(`/music/discography?path=${encodeURIComponent(rel)}`);
            if (!cancelled) setDisco(d);
          } catch {
            /* plain grid */
          }
        }
      })
      .catch(() => {})
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [cwd, manifest]);

  const playAlbum = async (albumRel: string, startIndex = 0) => {
    try {
      const meta = await get<AlbumMeta>(`/music/meta?path=${encodeURIComponent(albumRel)}`);
      const queue: PlayerTrack[] = sortTracks(meta.tracks).map((t) => ({
        albumRel,
        file: t.file,
        title: t.title,
        artist: t.artist,
      }));
      player.playQueue(queue, startIndex);
    } catch {
      /* ignore */
    }
  };
  const playCurrent = (i: number) => {
    if (!album) return;
    const queue: PlayerTrack[] = album.map((t) => ({ albumRel: rel, file: t.file, title: t.title, artist: t.artist }));
    player.playQueue(queue, i);
  };
  const isCurrent = (albumRel: string, file: string) =>
    player.current?.albumRel === albumRel && player.current?.file === file;

  // The album's play button is the dock's play button when the dock is already on this album: same
  // useGlobal state, so it shows pause while it plays and resumes where it stopped. It only starts the
  // album from the top when something else (or nothing) is loaded.
  const albumLoaded = !!album && player.current?.albumRel === rel;
  const albumPlaying = albumLoaded && player.playing;
  const toggleAlbum = () => (albumLoaded ? player.toggle() : playCurrent(0));

  const crumbs = rel ? rel.split('/') : [];

  // `r` is already the child's rel, so it is both the cover key and the link target — a library root and
  // a nested album need no different treatment. Play and heart are siblings of the anchor, never inside it.
  const Card = ({ r, name, playable }: { r: string; name: string; playable: boolean }) => (
    <div className="group relative">
      <Link
        to={musicPath(r)}
        className="flex w-full flex-col gap-2 rounded-lg bg-card/60 p-3 text-left transition-colors hover:bg-card"
      >
        <div className="aspect-square w-full overflow-hidden rounded-md bg-muted">
          <img
            src={coverUrl(r, token)}
            alt=""
            loading="lazy"
            className="h-full w-full object-cover"
            onError={(e) => {
              (e.currentTarget as HTMLImageElement).style.visibility = 'hidden';
            }}
          />
        </div>
        <span className="truncate text-sm font-medium text-foreground">{name}</span>
      </Link>
      {playable && (
        <button
          type="button"
          onClick={() => playAlbum(r, 0)}
          className="absolute bottom-14 right-4 flex h-10 w-10 translate-y-2 items-center justify-center rounded-full bg-primary text-primary-foreground opacity-0 shadow-lg transition-all group-hover:translate-y-0 group-hover:opacity-100 hover:scale-105"
        >
          <Play size={18} className="ml-0.5" />
        </button>
      )}
      {playable && (
        <MusicHeart
          kind="album"
          favKey={r}
          size={18}
          hoverReveal
          className="absolute right-2 top-2 rounded-full bg-black/40 p-1.5 text-white"
        />
      )}
    </div>
  );

  const content = favOpen ? (
    <div className="min-h-0 flex-1">
      <FavoritesView />
    </div>
  ) : (
    <div className="min-h-0 flex-1 overflow-y-auto p-4 md:p-6">
      {cwd && (
        <Link
          to={musicParentPath(rel)}
          className="mb-4 flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
        >
          <ChevronLeft size={16} />
          <span className="truncate">{crumbs.length ? crumbs.join(' / ') : cwd.split('/')[1]}</span>
        </Link>
      )}

      {loading && <p className="text-sm text-muted-foreground">Loading…</p>}

      {/* Home — the library roots carry their own folder art (the manifest indexes them like any other
          folder), so they get the same cards as everything else rather than a wall of flat tiles. */}
      {!cwd && (
        <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
          {libraries.map((lib) => (
            <Card key={lib} r={lib} name={lib} playable={(manifest[lib]?.tracks ?? 0) > 0} />
          ))}
        </div>
      )}

      {/* Album */}
      {album && (
        <div className="flex flex-col gap-5">
          <div className="flex items-end gap-5">
            <div className="h-40 w-40 shrink-0 overflow-hidden rounded-lg bg-muted shadow-lg">
              <img
                src={coverUrl(rel, token)}
                alt=""
                className="h-full w-full object-cover"
                onError={(e) => {
                  (e.currentTarget as HTMLImageElement).style.visibility = 'hidden';
                }}
              />
            </div>
            <div className="min-w-0">
              <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Album</p>
              <h1 className="truncate text-3xl font-bold text-foreground">{crumbs[crumbs.length - 1]}</h1>
              <p className="mt-1 truncate text-sm text-muted-foreground">
                {crumbs[crumbs.length - 2] ?? ''} · {album.length} songs
              </p>
              <div className="mt-3 flex items-center gap-3">
                <button
                  type="button"
                  onClick={toggleAlbum}
                  title={albumPlaying ? 'Pause' : 'Play'}
                  className="flex h-10 w-10 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground hover:scale-105"
                >
                  {albumPlaying ? <Pause size={18} /> : <Play size={18} className="ml-0.5" />}
                </button>
                <MusicHeart kind="album" favKey={rel} size={24} className="p-1" />
                <button
                  type="button"
                  onClick={toggleLyrics}
                  title={lyricsOpen ? 'Hide lyrics' : 'Show lyrics'}
                  aria-pressed={lyricsOpen}
                  className={`cursor-pointer p-1 hover:text-foreground ${
                    lyricsOpen ? 'text-primary' : 'text-muted-foreground'
                  }`}
                >
                  <MicVocal size={22} />
                </button>
              </div>
            </div>
          </div>
          <div className="flex flex-col">
            {album.map((t, i) => {
              const cur = isCurrent(rel, t.file);
              const artist = t.artist ?? t.albumArtist ?? '';
              const dur = fmtDuration(t.durationSec);
              return (
                <div
                  key={t.file}
                  className={`group flex items-center gap-3 rounded px-3 py-2 ${
                    cur ? 'bg-primary/10 text-primary' : 'text-foreground hover:bg-muted'
                  }`}
                >
                  <button
                    type="button"
                    onClick={() => playCurrent(i)}
                    className="flex min-w-0 flex-1 items-center gap-3 text-left"
                  >
                    <span className="flex w-5 shrink-0 justify-end">
                      {cur ? (
                        <Volume2 size={15} className="text-primary" />
                      ) : (
                        <span className="text-sm tabular-nums text-muted-foreground">{i + 1}</span>
                      )}
                    </span>
                    <span className="min-w-0 flex-1">
                      <span className={`block truncate text-sm ${cur ? 'font-medium' : ''}`}>{t.title ?? t.file}</span>
                      {artist && <span className="block truncate text-xs text-muted-foreground">{artist}</span>}
                    </span>
                    {dur && <span className="shrink-0 text-xs tabular-nums text-muted-foreground">{dur}</span>}
                  </button>
                  <MusicHeart
                    kind="track"
                    favKey={trackHomePath(rel, t.file)}
                    size={16}
                    hoverReveal
                    className="shrink-0"
                  />
                </div>
              );
            })}
          </div>
        </div>
      )}

      {/* Artist — discography sections */}
      {disco && !album && (
        <div className="flex flex-col gap-6">
          <div className="flex items-center gap-3">
            <h1 className="text-3xl font-bold text-foreground">{disco.artist}</h1>
            <MusicHeart kind="artist" favKey={rel} size={22} className="p-1" />
          </div>
          {TYPE_ORDER.filter((type) => folders.some((f) => (disco.albums[f] ?? 'Other') === type)).map((type) => (
            <section key={type}>
              <h2 className="mb-2 text-lg font-semibold text-foreground">
                {type === 'Studio' ? 'Studio Albums' : type}
              </h2>
              <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
                {folders
                  .filter((f) => (disco.albums[f] ?? 'Other') === type)
                  .map((f) => (
                    <Card key={f} r={childRel(f)} name={f} playable={(manifest[childRel(f)]?.tracks ?? 0) > 0} />
                  ))}
              </div>
            </section>
          ))}
        </div>
      )}

      {/* Grid — a library root, or an artist folder (crumbs>=2) whose albums aren't grouped by a
          discography. Show an artist header + heart on the latter. */}
      {!album && !disco && cwd && !loading && (
        <div className="flex flex-col gap-6">
          {crumbs.length >= 2 && (
            <div className="flex items-center gap-3">
              <h1 className="text-3xl font-bold text-foreground">{crumbs[crumbs.length - 1]}</h1>
              <MusicHeart kind="artist" favKey={rel} size={22} className="p-1" />
            </div>
          )}
          <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
            {folders.map((f) => (
              <Card key={f} r={childRel(f)} name={f} playable={(manifest[childRel(f)]?.tracks ?? 0) > 0} />
            ))}
            {!folders.length && <p className="text-sm text-muted-foreground">Empty</p>}
          </div>
        </div>
      )}
    </div>
  );

  // The scrubber sits at the foot of this panel instead of the app-wide dock, which hides itself on
  // /music: the album view already carries the transport, so all the dock added here was a second row.
  const libraryView = (
    <div className="flex h-full flex-col">
      {content}
      <MusicMiniBar />
    </div>
  );

  if (!lyricsOpen) return libraryView;

  return (
    <LibraryViewContext.Provider value={libraryView}>
      <WorkspaceLayout layout={LYRICS_LAYOUT} onLayoutChange={keepLayout} components={LYRICS_PANELS} noHeader />
    </LibraryViewContext.Provider>
  );
};
