import { useState, useEffect, type ReactNode } from 'react';
import { Link } from 'react-router';
import { useClient } from 'hooks/useClient';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { Library, Music2, ChevronLeft, Folder, Search, X, RefreshCw, Heart } from 'lucide-react';
import {
  MUSIC_ROOT,
  MUSIC_FAV_CHANNEL,
  MUSIC_RESYNC_CHANNEL,
  coverUrl,
  fuzzyMatch,
  musicParentPath,
  musicPath,
  toRel,
  useMusicCwd,
  type LsResult,
  type Manifest,
  type ManifestAlbum,
} from './shared';

// A row's leading thumbnail: the folder's indexed cover (its folder.jpg/cover.jpg, server-compressed),
// falling back to an icon when it has none or the image fails to load.
const RowThumb = ({ src, fallback }: { src: string | null; fallback: ReactNode }) => {
  const [failed, setFailed] = useState(false);
  useEffect(() => setFailed(false), [src]);
  return (
    <div className="flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded bg-muted">
      {src && !failed ? (
        <img src={src} alt="" className="h-full w-full object-cover" onError={() => setFailed(true)} />
      ) : (
        fallback
      )}
    </div>
  );
};

// Hidden files/folders (dotfiles like .claude, .git) never belong in the library listing.
const visibleDirs = (r: LsResult) =>
  r.entries
    .filter((e) => e.type === 'directory' && !e.name.startsWith('.'))
    .map((e) => e.name)
    .sort();

// Left panel of the /music workspace — a single-column drill-down LIST navigator (libraries →
// artists → albums as list items; never a grid). Every row is a link to `/music?path=…`; MusicDetail
// (right panel) reads the same param and renders the rich detail (covers/grids/tracklist).
export const MusicBrowser = () => {
  const { get, post, token } = useClient();
  const cwd = useMusicCwd();
  const [favOpen, setFavOpen] = usePanelChannel<boolean>(MUSIC_FAV_CHANNEL, false);
  const [resync, setResync] = usePanelChannel<number>(MUSIC_RESYNC_CHANNEL, 0);
  const [manifest, setManifest] = useState<Record<string, ManifestAlbum>>({});
  const [libraries, setLibraries] = useState<string[]>([]);
  const [folders, setFolders] = useState<string[]>([]);
  const [query, setQuery] = useState('');
  const [reindexing, setReindexing] = useState(false);

  // Refetch manifest + libraries on mount and whenever a reindex bumps the resync nonce.
  useEffect(() => {
    get<Manifest>('/music/manifest')
      .then((m) => setManifest(m.albums))
      .catch(() => setManifest({}));
    get<LsResult>(`/file-browser/ls?path=${encodeURIComponent(MUSIC_ROOT)}`)
      .then((r) => setLibraries(visibleDirs(r)))
      .catch(() => setLibraries([]));
  }, [resync]);

  // The container folder whose children we list = the current folder, or its parent when the current
  // path is an album leaf (so its siblings stay listed while the right shows the tracklist).
  const rel = toRel(cwd);
  const isAlbum = (manifest[rel]?.tracks ?? 0) > 0;
  const navFolder = !cwd ? null : isAlbum ? cwd.split('/').slice(0, -1).join('/') : cwd;
  const selected = cwd ? cwd.split('/').pop() : null;

  useEffect(() => {
    if (!navFolder) {
      setFolders([]);
      return;
    }
    let cancelled = false;
    get<LsResult>(`/file-browser/ls?path=${encodeURIComponent(navFolder)}`)
      .then((r) => {
        if (!cancelled) setFolders(visibleDirs(r));
      })
      .catch(() => {});
    return () => {
      cancelled = true;
    };
  }, [navFolder, resync]);

  // Start each folder unfiltered.
  useEffect(() => setQuery(''), [navFolder]);

  // Trigger a server-side library rebuild, then bump the resync nonce so BOTH panels refetch their
  // manifest / listings / meta (a fresh Date.now() value guarantees the effects re-run).
  const reindex = async () => {
    if (reindexing) return;
    setReindexing(true);
    try {
      await post('/music/reindex');
      setResync(Date.now());
    } finally {
      setReindexing(false);
    }
  };

  const crumbs = navFolder ? navFolder.slice(MUSIC_ROOT.length + 1).split('/') : [];
  const coverFor = (childRel: string) => (manifest[childRel]?.cover ? coverUrl(childRel, token) : null);
  const shownLibraries = libraries.filter((l) => fuzzyMatch(query, l));
  const shownFolders = folders.filter((f) => fuzzyMatch(query, f));

  return (
    <div className="flex h-full flex-col overflow-y-auto p-3">
      <div className="flex items-center gap-1 pb-2">
        {/* Favorites is a view of this panel, not a location, so it stays a channel — but going home
            has to close it explicitly: the route doesn't change when you are already at the root. */}
        <Link
          to="/music"
          onClick={() => setFavOpen(false)}
          className="flex flex-1 cursor-pointer items-center gap-2 px-2 text-left text-foreground"
        >
          <Music2 size={20} className="text-primary" />
          <span className="text-lg font-semibold">Music</span>
        </Link>
        <button
          type="button"
          onClick={() => setFavOpen(!favOpen)}
          title="Favorites"
          className="flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-md hover:bg-muted"
        >
          <Heart
            size={18}
            className={favOpen ? 'fill-red-500 text-red-500' : 'text-muted-foreground hover:text-foreground'}
          />
        </button>
      </div>

      <div className="mb-2 flex items-center gap-1.5 px-1">
        <div className="flex h-8 min-w-0 flex-1 items-center gap-1.5 rounded-md border border-border bg-background px-2">
          <Search size={13} className="shrink-0 text-muted-foreground" />
          <input
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="Filter…"
            className="min-w-0 flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none"
          />
          {query && (
            <button
              type="button"
              onClick={() => setQuery('')}
              className="shrink-0 text-muted-foreground hover:text-foreground"
            >
              <X size={13} />
            </button>
          )}
        </div>
        <button
          type="button"
          onClick={reindex}
          disabled={reindexing}
          title="Reindex library"
          className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50"
        >
          <RefreshCw size={14} className={reindexing ? 'animate-spin' : ''} />
        </button>
      </div>

      {!navFolder ? (
        <>
          <div className="flex items-center gap-2 px-2 pb-1 text-xs font-medium uppercase tracking-wide text-muted-foreground">
            <Library size={13} /> Libraries
          </div>
          <div className="flex flex-col gap-1.5">
            {shownLibraries.map((lib) => (
              <Link
                key={lib}
                to={musicPath(lib)}
                className="flex items-center gap-3 truncate rounded-md px-2 py-2 text-left text-base text-muted-foreground hover:bg-muted/60 hover:text-foreground"
              >
                <RowThumb src={coverFor(lib)} fallback={<Library size={18} className="text-muted-foreground" />} />
                <span className="truncate">{lib}</span>
              </Link>
            ))}
            {!shownLibraries.length && (
              <span className="px-2 text-base text-muted-foreground">{query ? 'No matches' : 'No libraries'}</span>
            )}
          </div>
        </>
      ) : (
        <>
          <Link
            to={musicParentPath(toRel(navFolder))}
            className="mb-1 flex items-center gap-1 truncate px-2 py-1 text-left text-xs text-muted-foreground hover:text-foreground"
          >
            <ChevronLeft size={13} className="shrink-0" />
            <span className="truncate">{crumbs.join(' / ')}</span>
          </Link>
          <div className="flex flex-col gap-1.5">
            {shownFolders.map((f) => (
              <Link
                key={f}
                to={musicPath(toRel(`${navFolder}/${f}`))}
                className={`flex items-center gap-3 truncate rounded-md px-2 py-2 text-left text-base ${
                  selected === f
                    ? 'bg-muted text-foreground'
                    : 'text-muted-foreground hover:bg-muted/60 hover:text-foreground'
                }`}
              >
                <RowThumb
                  src={coverFor(toRel(`${navFolder}/${f}`))}
                  fallback={<Folder size={18} className="text-muted-foreground" />}
                />
                <span className="truncate">{f}</span>
              </Link>
            ))}
            {!shownFolders.length && (
              <span className="px-2 py-2 text-base text-muted-foreground">
                {query ? 'No matches' : 'No subfolders'}
              </span>
            )}
          </div>
        </>
      )}
    </div>
  );
};
