import { useState, useEffect, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
  getChannels, getChannelFeed,
  unfollowChannel, indexChannel,
  syncAllChannels, downloadChannel, downloadFollowing,
  setChannelAutoDownload, markChannelsSeen,
  muteChannel, unmuteChannel,
  getChannelGroups, createChannelGroup, deleteChannelGroup, renameChannelGroup,
  addChannelToGroup, removeChannelFromGroup,
  getSettings, bulkChannelAction, followBulk, updateChannelNotes,
} from "../api";
import VideoCard from "../components/VideoCard";
import SortPicker from "../components/SortPicker";

function parseChannelList(text) {
  const lines = text.split(/\r?\n/);
  const handles = new Set();

  // Detect YouTube Takeout CSV (header: Channel Id,Channel Url,Channel Title)
  const isTakeout = lines[0]?.trim().toLowerCase().startsWith("channel id");

  for (let i = isTakeout ? 1 : 0; i < lines.length; i++) {
    const line = lines[i].trim();
    if (!line) continue;

    // CSV row: extract first column (Channel Id = UC...)
    if (isTakeout) {
      const id = line.split(",")[0].trim().replace(/^"(.*)"$/, "$1");
      if (id.startsWith("UC")) handles.add(id);
      continue;
    }

    // UC... ID anywhere on the line
    const ucMatch = line.match(/UC[\w-]{22}/);
    if (ucMatch) { handles.add(ucMatch[0]); continue; }

    // @handle from URL or bare
    const handleMatch = line.match(/@([\w.-]+)/);
    if (handleMatch) { handles.add("@" + handleMatch[1]); continue; }

    // youtube.com/c/name or /user/name
    const pathMatch = line.match(/youtube\.com\/(?:c|user)\/([\w.-]+)/);
    if (pathMatch) { handles.add(pathMatch[1]); continue; }
  }

  return [...handles];
}

const CHANNEL_SORTS = [
  { value: "last_upload",       label: "Last upload" },
  { value: "name",              label: "Name A–Z" },
  { value: "unwatched",         label: "Most unwatched" },
  { value: "downloaded",        label: "Most downloaded" },
  { value: "upload_frequency",  label: "Upload frequency" },
];

const FEED_SORTS = [
  { value: "newest",  label: "Newest" },
  { value: "oldest",  label: "Oldest" },
  { value: "channel", label: "Channel A–Z" },
];

function frequencyLabel(days) {
  if (days == null) return null;
  if (days <= 1.5) return "Daily";
  if (days <= 8) return "Weekly";
  if (days <= 18) return "Biweekly";
  if (days <= 40) return "Monthly";
  if (days <= 100) return "Quarterly";
  return "Rarely";
}

function sortChannels(channels, sort) {
  const arr = [...channels];
  if (sort === "name")             return arr.sort((a, b) => a.name.localeCompare(b.name));
  if (sort === "unwatched")        return arr.sort((a, b) => (b.unwatched_count ?? 0) - (a.unwatched_count ?? 0));
  if (sort === "downloaded")       return arr.sort((a, b) => (b.downloaded_count ?? 0) - (a.downloaded_count ?? 0));
  if (sort === "upload_frequency") return arr.sort((a, b) => {
    const fa = a.upload_frequency_days ?? Infinity;
    const fb = b.upload_frequency_days ?? Infinity;
    return fa - fb;
  });
  return arr.sort((a, b) => new Date(b.last_published_at ?? 0) - new Date(a.last_published_at ?? 0));
}

function sortFeed(items, sort) {
  const arr = [...items];
  if (sort === "oldest")  return arr.sort((a, b) => new Date(a.published_at ?? 0) - new Date(b.published_at ?? 0));
  if (sort === "channel") return arr.sort((a, b) => (a.channel_name ?? "").localeCompare(b.channel_name ?? ""));
  return arr.sort((a, b) => new Date(b.published_at ?? 0) - new Date(a.published_at ?? 0));
}

function formatSubs(n) {
  if (!n) return null;
  if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}M`;
  if (n >= 1_000) return `${Math.round(n / 1_000)}K`;
  return String(n);
}

function avatarColor(name) {
  if (!name) return "#52525b";
  let h = 0;
  for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) % 360;
  return `hsl(${h}, 55%, 42%)`;
}

function timeAgo(dateStr) {
  if (!dateStr) return null;
  const diff = Date.now() - new Date(dateStr).getTime();
  const days = Math.floor(diff / 86400000);
  if (days === 0) return "today";
  if (days === 1) return "yesterday";
  if (days < 7) return `${days}d ago`;
  if (days < 30) return `${Math.floor(days / 7)}w ago`;
  if (days < 365) return `${Math.floor(days / 30)}mo ago`;
  return `${Math.floor(days / 365)}y ago`;
}

function muteLabel(muteUntil) {
  if (!muteUntil) return null;
  const until = new Date(muteUntil);
  if (until <= new Date()) return null;
  return `muted until ${until.toLocaleDateString("en-US", { month: "short", day: "numeric" })}`;
}

function AutoDownloadToggle({ channelId, value }) {
  const qc = useQueryClient();
  const mut = useMutation({
    mutationFn: (next) => setChannelAutoDownload(channelId, next),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["channels"] }),
  });

  const states = [
    { value: null,  label: "Auto",  title: "Use global setting" },
    { value: true,  label: "On",    title: "Always auto-download" },
    { value: false, label: "Off",   title: "Never auto-download" },
  ];
  const current = states.find((s) => s.value === value) ?? states[0];
  const next = states[(states.indexOf(current) + 1) % states.length];

  return (
    <button
      onClick={() => mut.mutate(next.value)}
      disabled={mut.isPending}
      title={`Auto-download: ${current.title}. Click to set to "${next.label}"`}
      className={[
        "text-xs px-2.5 py-1 rounded-lg font-medium transition-colors",
        value === true  && "bg-accent/20 text-accent",
        value === false && "bg-zinc-700 text-zinc-500",
        value === null  && "bg-zinc-800 text-zinc-400",
      ].filter(Boolean).join(" ")}
    >
      DL: {current.label}
    </button>
  );
}

function ChannelNotes({ channel }) {
  const qc = useQueryClient();
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(channel.notes || "");

  const mut = useMutation({
    mutationFn: (notes) => updateChannelNotes(channel.id, notes),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["channels"] }),
  });

  const save = () => {
    mut.mutate(draft);
    setEditing(false);
  };

  if (editing) {
    return (
      <input
        autoFocus
        value={draft}
        onChange={e => setDraft(e.target.value)}
        onBlur={save}
        onKeyDown={e => { if (e.key === "Enter") save(); if (e.key === "Escape") setEditing(false); }}
        placeholder="Add a note…"
        className="mt-1 w-full text-xs bg-zinc-800 text-zinc-200 rounded px-2 py-1 focus:outline-none border border-zinc-600"
      />
    );
  }

  return (
    <p
      onClick={e => { e.stopPropagation(); setDraft(channel.notes || ""); setEditing(true); }}
      className="mt-1 text-xs text-zinc-600 italic cursor-pointer hover:text-zinc-400 transition-colors truncate"
    >
      {channel.notes || "Add a note…"}
    </p>
  );
}

function ChannelRow({ channel, groups, onGroupToggle, hideSubCount = false, selectable = false, selected = false, onSelect }) {
  const navigate = useNavigate();
  const qc = useQueryClient();
  const [dlResult, setDlResult] = useState(null);
  const [showGroups, setShowGroups] = useState(false);

  const isMuted = channel.muted_until && new Date(channel.muted_until) > new Date();
  const muteInfo = muteLabel(channel.muted_until);

  const unfollowMut = useMutation({
    mutationFn: () => unfollowChannel(channel.id),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["channels"] });
      qc.invalidateQueries({ queryKey: ["channel-feed"] });
    },
  });

  const indexMut = useMutation({
    mutationFn: () => indexChannel(channel.id),
    onSuccess: () => {
      setTimeout(() => {
        qc.invalidateQueries({ queryKey: ["channels"] });
        qc.invalidateQueries({ queryKey: ["channel-feed"] });
      }, 3000);
    },
  });

  const dlMut = useMutation({
    mutationFn: () => downloadChannel(channel.id),
    onSuccess: (res) => {
      setDlResult(res.data.queued);
      qc.invalidateQueries({ queryKey: ["downloads"] });
    },
  });

  const muteMut = useMutation({
    mutationFn: () => isMuted ? unmuteChannel(channel.id) : muteChannel(channel.id),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["channels"] }),
  });

  const letter = channel.name?.[0]?.toUpperCase() ?? "?";
  const bg = avatarColor(channel.name);
  const unwatched = channel.unwatched_count ?? 0;
  const downloaded = channel.downloaded_count ?? 0;
  const total = channel.video_count ?? 0;
  const watched = channel.watched_count ?? 0;
  const isNew = (channel.new_count ?? 0) > 0;
  const subs = formatSubs(channel.subscriber_count);

  const freqLabel = frequencyLabel(channel.upload_frequency_days);
  const daysSinceUpload = channel.last_published_at
    ? (Date.now() - new Date(channel.last_published_at)) / (1000 * 60 * 60 * 24)
    : null;
  const isDormant = daysSinceUpload !== null && daysSinceUpload > 180;

  return (
    <div className={`flex items-start gap-3 py-2.5 px-3 rounded-lg transition-colors group ${isMuted ? "opacity-50" : "hover:bg-zinc-900/60"} ${selected ? "bg-zinc-800/50" : ""}`}>
      {/* Checkbox (selectable mode) */}
      {selectable && (
        <input
          type="checkbox"
          checked={selected}
          onChange={() => onSelect?.(channel.id)}
          onClick={(e) => e.stopPropagation()}
          className="mt-2 shrink-0 accent-accent w-3.5 h-3.5"
        />
      )}
      {/* Avatar */}
      <div className="shrink-0 cursor-pointer relative mt-0.5" onClick={() => !selectable && navigate(`/channels/${channel.id}`)}>
        {channel.thumbnail_url ? (
          <img src={channel.thumbnail_url} alt={channel.name} className="w-8 h-8 rounded-full object-cover" />
        ) : (
          <div
            className="w-8 h-8 rounded-full flex items-center justify-center font-bold text-white text-sm"
            style={{ backgroundColor: bg }}
          >
            {letter}
          </div>
        )}
        {isNew && !isMuted && (
          <span className="absolute -top-0.5 -right-0.5 w-2.5 h-2.5 bg-accent rounded-full border-2 border-zinc-950" />
        )}
      </div>

      {/* Name + stats */}
      <div className="flex-1 min-w-0 cursor-pointer" onClick={() => selectable ? onSelect?.(channel.id) : navigate(`/channels/${channel.id}`)}>
        <div className="flex items-center gap-2 min-w-0">
          <p className="text-sm font-medium text-zinc-100 truncate">{channel.name}</p>
          {isNew && !isMuted && <span className="text-xs text-accent shrink-0">{channel.new_count} new</span>}
          {isMuted && <span className="text-xs text-zinc-600 shrink-0">{muteInfo}</span>}
          {isDormant && !isMuted && <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-zinc-800 text-zinc-500 shrink-0">dormant</span>}
        </div>
        <p className="text-xs text-zinc-500 mt-0.5 truncate">
          {[
            subs && !hideSubCount && `${subs} subscribers`,
            unwatched > 0 && `${unwatched} unwatched`,
            downloaded > 0 && `${downloaded} downloaded`,
            channel.last_published_at && timeAgo(channel.last_published_at),
            freqLabel && freqLabel,
          ].filter(Boolean).join(" · ")}
        </p>
        {channel.latest_video_id && !isMuted && (
          <div className="flex items-center gap-2 mt-1.5">
            <img
              src={`https://i.ytimg.com/vi/${channel.latest_video_id}/mqdefault.jpg`}
              alt=""
              className="w-20 h-[45px] object-cover rounded shrink-0"
              loading="lazy"
            />
            <p className="text-xs text-zinc-400 line-clamp-2 leading-snug">{channel.latest_video_title}</p>
          </div>
        )}
        {total > 0 && watched > 0 && !isMuted && (
          <div className="h-0.5 mt-1.5 bg-zinc-800 rounded-full overflow-hidden w-full">
            <div className="h-full bg-accent/50 rounded-full" style={{ width: `${Math.round((watched / total) * 100)}%` }} />
          </div>
        )}
        {/* Group tags */}
        {groups.filter(g => g.channel_ids.includes(channel.id)).length > 0 && (
          <div className="flex flex-wrap gap-1 mt-1">
            {groups.filter(g => g.channel_ids.includes(channel.id)).map(g => (
              <span key={g.id} className="text-[10px] px-1.5 py-0.5 rounded-full bg-zinc-800 text-zinc-500">{g.name}</span>
            ))}
          </div>
        )}
        {!selectable && <ChannelNotes channel={channel} />}
      </div>

      {/* Actions */}
      <div className="flex items-center gap-1 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
        {dlResult != null && (
          <span className="text-xs text-accent font-mono mr-1">
            {dlResult === 0 ? "✓" : `+${dlResult}`}
          </span>
        )}

        <AutoDownloadToggle channelId={channel.id} value={channel.auto_download ?? null} />

        {/* Group assign button */}
        {groups.length > 0 && (
          <div className="relative">
            <button
              onClick={() => setShowGroups(v => !v)}
              title="Assign to group"
              className="p-1.5 rounded-md bg-zinc-800 text-zinc-400 hover:text-zinc-100 hover:bg-zinc-700 transition-colors"
            >
              <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A2 2 0 013 12V7a2 2 0 012-2z"/>
              </svg>
            </button>
            {showGroups && (
              <div className="absolute right-0 top-full mt-1 z-20 bg-zinc-800 border border-zinc-700 rounded-xl shadow-xl py-1 min-w-[120px]">
                {groups.map(g => {
                  const inGroup = g.channel_ids.includes(channel.id);
                  return (
                    <button
                      key={g.id}
                      onClick={() => { onGroupToggle(g.id, channel.id, inGroup); setShowGroups(false); }}
                      className="w-full flex items-center gap-2 px-3 py-2 text-xs hover:bg-zinc-700 transition-colors text-left"
                    >
                      <span className={`w-3 h-3 rounded-full border ${inGroup ? "bg-accent border-accent" : "border-zinc-600"}`} />
                      {g.name}
                    </button>
                  );
                })}
              </div>
            )}
          </div>
        )}

        {/* Mute / unmute */}
        <button
          onClick={() => muteMut.mutate()}
          disabled={muteMut.isPending}
          title={isMuted ? "Unmute channel" : "Mute for 30 days"}
          className={`p-1.5 rounded-md transition-colors disabled:opacity-50 ${
            isMuted
              ? "bg-zinc-700 text-zinc-400 hover:text-zinc-100 hover:bg-zinc-600"
              : "bg-zinc-800 text-zinc-400 hover:text-amber-400 hover:bg-amber-900/30"
          }`}
        >
          {isMuted ? (
            <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.536 8.464a5 5 0 010 7.072M12 6v12m0 0l-3-3m3 3l3-3M5.636 5.636a9 9 0 000 12.728"/>
            </svg>
          ) : (
            <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"/>
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"/>
            </svg>
          )}
        </button>

        <button
          onClick={() => dlMut.mutate()}
          disabled={dlMut.isPending}
          title="Download new videos"
          className="p-1.5 rounded-md bg-zinc-800 text-zinc-400 hover:text-zinc-100 hover:bg-zinc-700 transition-colors disabled:opacity-50"
        >
          {dlMut.isPending ? (
            <svg className="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24">
              <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/>
              <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
            </svg>
          ) : (
            <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
            </svg>
          )}
        </button>

        <button
          onClick={() => indexMut.mutate()}
          disabled={indexMut.isPending || indexMut.isSuccess}
          title={indexMut.isSuccess ? "Done" : "Re-index channel"}
          className="p-1.5 rounded-md bg-zinc-800 text-zinc-400 hover:text-zinc-100 hover:bg-zinc-700 transition-colors disabled:opacity-50"
        >
          {indexMut.isPending ? (
            <svg className="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24">
              <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/>
              <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
            </svg>
          ) : indexMut.isSuccess ? (
            <svg className="w-3.5 h-3.5 text-accent" fill="currentColor" viewBox="0 0 20 20">
              <path fillRule="evenodd" d="M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z" clipRule="evenodd"/>
            </svg>
          ) : (
            <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
            </svg>
          )}
        </button>

        <button
          onClick={() => unfollowMut.mutate()}
          disabled={unfollowMut.isPending}
          title="Unfollow"
          className="p-1.5 rounded-md bg-zinc-800 text-zinc-400 hover:text-red-400 hover:bg-red-900/30 transition-colors disabled:opacity-50"
        >
          <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12"/>
          </svg>
        </button>
      </div>
    </div>
  );
}

function GroupsPanel({ groups, channels }) {
  const qc = useQueryClient();
  const [newName, setNewName] = useState("");
  const [editingId, setEditingId] = useState(null);
  const [editName, setEditName] = useState("");

  const createMut = useMutation({
    mutationFn: () => createChannelGroup(newName.trim()),
    onSuccess: () => { setNewName(""); qc.invalidateQueries({ queryKey: ["channel-groups"] }); },
  });

  const deleteMut = useMutation({
    mutationFn: (id) => deleteChannelGroup(id),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["channel-groups"] }),
  });

  const renameMut = useMutation({
    mutationFn: ({ id, name }) => renameChannelGroup(id, name),
    onSuccess: () => { setEditingId(null); qc.invalidateQueries({ queryKey: ["channel-groups"] }); },
  });

  return (
    <div className="flex flex-col gap-4">
      {groups.map(g => {
        const members = channels.filter(c => g.channel_ids.includes(c.id));
        return (
          <div key={g.id} className="bg-zinc-900 rounded-xl p-4 flex flex-col gap-3">
            <div className="flex items-center justify-between gap-2">
              {editingId === g.id ? (
                <input
                  autoFocus
                  value={editName}
                  onChange={e => setEditName(e.target.value)}
                  onKeyDown={e => {
                    if (e.key === "Enter") renameMut.mutate({ id: g.id, name: editName });
                    if (e.key === "Escape") setEditingId(null);
                  }}
                  className="flex-1 bg-zinc-800 text-zinc-100 text-sm rounded-lg px-3 py-1.5 focus:outline-none border border-zinc-600"
                />
              ) : (
                <h3 className="font-medium text-zinc-100 text-sm">{g.name}</h3>
              )}
              <div className="flex items-center gap-1">
                {editingId === g.id ? (
                  <button
                    onClick={() => renameMut.mutate({ id: g.id, name: editName })}
                    className="text-xs px-2 py-1 bg-accent text-black rounded-lg font-medium"
                  >Save</button>
                ) : (
                  <button
                    onClick={() => { setEditingId(g.id); setEditName(g.name); }}
                    className="p-1.5 rounded-md text-zinc-600 hover:text-zinc-300 hover:bg-zinc-800 transition-colors"
                    title="Rename group"
                  >
                    <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
                    </svg>
                  </button>
                )}
                <button
                  onClick={() => deleteMut.mutate(g.id)}
                  disabled={deleteMut.isPending}
                  className="p-1.5 rounded-md text-zinc-600 hover:text-red-400 hover:bg-red-900/20 transition-colors"
                  title="Delete group"
                >
                  <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
                  </svg>
                </button>
              </div>
            </div>
            {members.length > 0 ? (
              <div className="flex flex-wrap gap-2">
                {members.map(c => (
                  <span key={c.id} className="flex items-center gap-1.5 px-2 py-1 bg-zinc-800 rounded-full text-xs text-zinc-300">
                    {c.thumbnail_url
                      ? <img src={c.thumbnail_url} alt="" className="w-4 h-4 rounded-full object-cover" />
                      : <div className="w-4 h-4 rounded-full" style={{ backgroundColor: avatarColor(c.name) }} />
                    }
                    {c.name}
                  </span>
                ))}
              </div>
            ) : (
              <p className="text-xs text-zinc-600">No channels yet — assign from the channel list.</p>
            )}
          </div>
        );
      })}

      <form
        onSubmit={e => { e.preventDefault(); if (newName.trim()) createMut.mutate(); }}
        className="flex gap-2"
      >
        <input
          value={newName}
          onChange={e => setNewName(e.target.value)}
          placeholder="New group name…"
          className="flex-1 bg-zinc-900 border border-zinc-800 rounded-xl px-3 py-2 text-sm text-zinc-200 placeholder-zinc-600 focus:outline-none focus:border-zinc-600"
        />
        <button
          type="submit"
          disabled={!newName.trim() || createMut.isPending}
          className="px-4 py-2 rounded-xl bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 transition-colors disabled:opacity-40"
        >
          Create
        </button>
      </form>
    </div>
  );
}

const PAGE_SIZE = 15;
const FEED_PAGE = 24;

export default function Following() {
  const qc = useQueryClient();
  const [tab, setTab] = useState("channels");
  const [syncDone, setSyncDone] = useState(false);
  const [dlResult, setDlResult] = useState(null);
  const [search, setSearch] = useState("");
  const [page, setPage] = useState(0);
  const [channelSort, setChannelSort] = useState("last_upload");
  const [feedSort, setFeedSort] = useState("newest");
  const [feedOffset, setFeedOffset] = useState(0);
  const [feedItems, setFeedItems] = useState([]);
  const [activeGroup, setActiveGroup] = useState(null); // null = all
  const [selectMode, setSelectMode] = useState(false);
  const [selectedIds, setSelectedIds] = useState(new Set());
  const [showImport, setShowImport] = useState(false);
  const [importResult, setImportResult] = useState(null);

  const { data: channels = [], isLoading: loadingChannels } = useQuery({
    queryKey: ["channels"],
    queryFn: () => getChannels().then((r) => r.data),
  });

  const { data: appSettings } = useQuery({
    queryKey: ["settings"],
    queryFn: () => getSettings().then(r => r.data),
    staleTime: 5 * 60_000,
  });
  const hideSubCount = appSettings?.hide_subscriber_counts ?? false;

  const { data: groups = [] } = useQuery({
    queryKey: ["channel-groups"],
    queryFn: () => getChannelGroups().then((r) => r.data),
  });

  const { data: feedPage = [], isLoading: loadingFeed, isFetching: fetchingFeed } = useQuery({
    queryKey: ["channel-feed", feedOffset],
    queryFn: () => getChannelFeed(feedOffset).then((r) => r.data),
    enabled: channels.length > 0,
  });

  useEffect(() => {
    if (feedOffset === 0) setFeedItems(feedPage);
    else setFeedItems((prev) => [...prev, ...feedPage]);
  }, [feedPage]); // eslint-disable-line react-hooks/exhaustive-deps

  const hasMoreFeed = feedPage.length === FEED_PAGE;
  const sortedFeed = useMemo(() => sortFeed(feedItems, feedSort), [feedItems, feedSort]);

  useEffect(() => {
    if (channels.length > 0) {
      markChannelsSeen().then(() => {
        qc.invalidateQueries({ queryKey: ["channels"] });
      });
    }
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const syncMut = useMutation({
    mutationFn: syncAllChannels,
    onSuccess: () => {
      setSyncDone(true);
      setTimeout(() => {
        qc.invalidateQueries({ queryKey: ["channels"] });
        qc.invalidateQueries({ queryKey: ["channel-feed"] });
        setSyncDone(false);
      }, 4000);
    },
  });

  const dlAllMut = useMutation({
    mutationFn: downloadFollowing,
    onSuccess: (res) => {
      setDlResult(res.data.queued);
      qc.invalidateQueries({ queryKey: ["downloads"] });
    },
  });

  const bulkMut = useMutation({
    mutationFn: ({ action }) => bulkChannelAction([...selectedIds], action),
    onSuccess: () => {
      setSelectedIds(new Set());
      setSelectMode(false);
      qc.invalidateQueries({ queryKey: ["channels"] });
    },
  });

  const importMut = useMutation({
    mutationFn: (handles) => followBulk(handles),
    onSuccess: (res) => {
      setImportResult(res.data);
      qc.invalidateQueries({ queryKey: ["channels"] });
    },
  });

  const handleImportFile = (file) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      const text = e.target.result;
      const handles = parseChannelList(text);
      if (handles.length === 0) return;
      importMut.mutate(handles);
    };
    reader.readAsText(file);
  };

  const handleSelectToggle = (id) => {
    setSelectedIds(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };

  const handleSelectAll = () => {
    if (selectedIds.size === pageChannels.length) {
      setSelectedIds(new Set());
    } else {
      setSelectedIds(new Set(pageChannels.map(c => c.id)));
    }
  };

  const handleGroupToggle = (groupId, channelId, inGroup) => {
    const fn = inGroup
      ? removeChannelFromGroup(groupId, channelId)
      : addChannelToGroup(groupId, channelId);
    fn.then(() => qc.invalidateQueries({ queryKey: ["channel-groups"] }));
  };

  if (loadingChannels) {
    return (
      <div className="flex items-center justify-center py-24">
        <div className="w-8 h-8 border-2 border-accent border-t-transparent rounded-full animate-spin" />
      </div>
    );
  }

  if (!channels.length) {
    return (
      <div className="flex flex-col items-center gap-4 py-20 text-center">
        <div className="w-16 h-16 rounded-full bg-zinc-800 flex items-center justify-center">
          <svg className="w-8 h-8 text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
              d="M15 10l4.553-2.069A1 1 0 0121 8.82v6.36a1 1 0 01-1.447.893L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
          </svg>
        </div>
        <div>
          <p className="text-zinc-300 font-medium">Not following anyone yet</p>
          <p className="text-zinc-500 text-sm mt-1">
            Hit Follow on a channel while watching a video or searching.
          </p>
        </div>
      </div>
    );
  }

  const totalVideos = channels.reduce((n, c) => n + (c.video_count ?? 0), 0);
  const totalUnwatched = channels.reduce((n, c) => n + (c.unwatched_count ?? 0), 0);

  const filtered = useMemo(() => {
    const q = search.trim().toLowerCase();
    let list = q ? channels.filter((c) => c.name.toLowerCase().includes(q)) : channels;
    if (activeGroup !== null) {
      const g = groups.find(g => g.id === activeGroup);
      if (g) list = list.filter(c => g.channel_ids.includes(c.id));
    }
    return sortChannels(list, channelSort);
  }, [channels, groups, search, channelSort, activeGroup]);

  const totalPages = Math.ceil(filtered.length / PAGE_SIZE);
  const safePage = Math.min(page, Math.max(0, totalPages - 1));
  const pageChannels = filtered.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);

  const Spinner = () => (
    <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
      <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/>
      <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
    </svg>
  );

  return (
    <div className="flex flex-col gap-6 max-w-5xl mx-auto">

      {/* Header */}
      <div className="flex items-start justify-between gap-4 flex-wrap">
        <div>
          <h1 className="font-display font-bold text-2xl text-white">Following</h1>
          <p className="text-sm text-zinc-500 mt-1">
            {channels.length} channel{channels.length !== 1 ? "s" : ""}
            {" · "}{totalVideos} indexed
            {totalUnwatched > 0 && <> · <span className="text-zinc-400">{totalUnwatched} unwatched</span></>}
          </p>
        </div>

        <div className="flex items-center gap-2 flex-wrap">
          {dlResult != null && (
            <span className="text-sm text-accent font-mono">
              {dlResult === 0 ? "Up to date" : `${dlResult} queued`}
            </span>
          )}
          <button
            onClick={() => dlAllMut.mutate()}
            disabled={dlAllMut.isPending}
            className="flex items-center gap-2 px-4 py-2 rounded-xl bg-accent text-black text-sm font-semibold hover:bg-yellow-300 transition-colors disabled:opacity-60"
          >
            {dlAllMut.isPending ? <><Spinner /> Queuing…</> : "Download all new"}
          </button>
          <button
            onClick={() => syncMut.mutate()}
            disabled={syncMut.isPending || syncDone}
            className="flex items-center gap-2 px-4 py-2 rounded-xl bg-zinc-800 text-zinc-200 text-sm font-medium hover:bg-zinc-700 transition-colors disabled:opacity-60"
          >
            {syncMut.isPending ? <><Spinner /> Syncing…</> : syncDone ? "Syncing ✓" : "Sync all"}
          </button>
        </div>
      </div>

      {/* Tabs */}
      <div className="flex items-center gap-1 border-b border-zinc-800">
        {[["channels", "Channels"], ["feed", "Latest uploads"], ["groups", "Groups"]].map(([key, label]) => (
          <button
            key={key}
            onClick={() => setTab(key)}
            className={[
              "px-4 py-2 text-sm font-medium border-b-2 transition-colors -mb-px",
              tab === key
                ? "border-accent text-zinc-100"
                : "border-transparent text-zinc-500 hover:text-zinc-300",
            ].join(" ")}
          >
            {label}
            {key === "groups" && groups.length > 0 && (
              <span className="ml-1.5 text-xs text-zinc-600">{groups.length}</span>
            )}
          </button>
        ))}
      </div>

      {/* ── Channels tab ── */}
      {tab === "channels" && (
        <div className="flex flex-col gap-4">
          {/* Toolbar: import + select */}
          <div className="flex items-center justify-between gap-3">
            <button
              onClick={() => { setShowImport(v => !v); setImportResult(null); }}
              className="flex items-center gap-1.5 text-xs text-zinc-500 hover:text-zinc-300 transition-colors"
            >
              <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
              </svg>
              Import
            </button>
            <button
              onClick={() => { setSelectMode(v => !v); setSelectedIds(new Set()); }}
              className={[
                "text-xs px-3 py-1.5 rounded-lg font-medium transition-colors",
                selectMode ? "bg-accent text-black" : "bg-zinc-800 text-zinc-400 hover:text-zinc-200",
              ].join(" ")}
            >
              {selectMode ? "Cancel" : "Select"}
            </button>
          </div>

          {/* Import panel */}
          {showImport && (
            <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4 flex flex-col gap-3">
              <div>
                <p className="text-sm font-medium text-zinc-200">Import subscriptions</p>
                <p className="text-xs text-zinc-500 mt-0.5">
                  Upload a <span className="text-zinc-400">YouTube Takeout CSV</span> (subscriptions.csv) or any plain-text file with one channel URL, handle, or UC… ID per line.
                </p>
              </div>
              <label className={[
                "flex flex-col items-center justify-center gap-2 border-2 border-dashed rounded-xl py-6 cursor-pointer transition-colors",
                importMut.isPending ? "border-zinc-700 opacity-50 cursor-not-allowed" : "border-zinc-700 hover:border-zinc-500",
              ].join(" ")}>
                <svg className="w-6 h-6 text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
                </svg>
                <span className="text-sm text-zinc-400">
                  {importMut.isPending ? "Importing…" : "Choose file or drop here"}
                </span>
                <input
                  type="file"
                  accept=".csv,.txt,text/plain,text/csv"
                  className="sr-only"
                  disabled={importMut.isPending}
                  onChange={(e) => { if (e.target.files[0]) handleImportFile(e.target.files[0]); }}
                />
              </label>
              {importResult && (
                <p className="text-xs text-zinc-400">
                  Done — followed <span className="text-accent font-semibold">{importResult.followed}</span> new
                  {importResult.already_following > 0 && `, ${importResult.already_following} already following`}
                  {importResult.new_channels > 0 && `, ${importResult.new_channels} new channel stubs created`}.
                  Run <span className="text-zinc-300">Sync all</span> to fetch their videos.
                </p>
              )}
              {importMut.isError && (
                <p className="text-xs text-red-400">Import failed — check the file format.</p>
              )}
            </div>
          )}

          {/* Group filter pills */}
          {groups.length > 0 && (
            <div className="flex flex-wrap gap-2">
              <button
                onClick={() => setActiveGroup(null)}
                className={`px-3 py-1 rounded-full text-xs font-medium transition-colors ${
                  activeGroup === null ? "bg-zinc-700 text-zinc-100" : "bg-zinc-900 text-zinc-500 hover:text-zinc-300"
                }`}
              >
                All
              </button>
              {groups.map(g => (
                <button
                  key={g.id}
                  onClick={() => setActiveGroup(activeGroup === g.id ? null : g.id)}
                  className={`px-3 py-1 rounded-full text-xs font-medium transition-colors ${
                    activeGroup === g.id ? "bg-accent text-black" : "bg-zinc-900 text-zinc-500 hover:text-zinc-300"
                  }`}
                >
                  {g.name}
                  <span className="ml-1 opacity-60">{g.channel_ids.length}</span>
                </button>
              ))}
            </div>
          )}

          {/* Search + sort row */}
          <div className="flex items-center gap-3">
            {channels.length > 4 && (
              <div className="relative flex-1">
                <svg className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
                </svg>
                <input
                  type="text"
                  value={search}
                  onChange={(e) => { setSearch(e.target.value); setPage(0); }}
                  placeholder="Filter channels…"
                  className="w-full bg-zinc-900 border border-zinc-800 rounded-xl pl-9 pr-8 py-2 text-sm text-zinc-200 placeholder-zinc-600 focus:outline-none focus:border-zinc-600"
                />
                {search && (
                  <button onClick={() => { setSearch(""); setPage(0); }} className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300">×</button>
                )}
              </div>
            )}
            <SortPicker value={channelSort} onChange={(v) => { setChannelSort(v); setPage(0); }} options={CHANNEL_SORTS} />
          </div>

          {filtered.length === 0 ? (
            <p className="text-zinc-500 text-sm px-2">
              {activeGroup !== null ? "No channels in this group." : `No channels match "${search}"`}
            </p>
          ) : (
            <>
              {selectMode && pageChannels.length > 0 && (
                <div className="flex items-center gap-3 px-3 py-1.5 bg-zinc-900 rounded-lg">
                  <input
                    type="checkbox"
                    checked={selectedIds.size === pageChannels.length && pageChannels.length > 0}
                    onChange={handleSelectAll}
                    className="accent-accent w-3.5 h-3.5"
                  />
                  <span className="text-xs text-zinc-400">
                    {selectedIds.size === pageChannels.length ? "Deselect all" : "Select all"}
                  </span>
                  {selectedIds.size > 0 && (
                    <span className="text-xs text-zinc-500 ml-auto">{selectedIds.size} selected</span>
                  )}
                </div>
              )}
              <div className="divide-y divide-zinc-800/50">
                {pageChannels.map((ch) => (
                  <ChannelRow
                    key={ch.id}
                    channel={ch}
                    groups={groups}
                    onGroupToggle={handleGroupToggle}
                    hideSubCount={hideSubCount}
                    selectable={selectMode}
                    selected={selectedIds.has(ch.id)}
                    onSelect={handleSelectToggle}
                  />
                ))}
              </div>
              {totalPages > 1 && (
                <div className="flex items-center justify-between px-1">
                  <button onClick={() => setPage((p) => Math.max(0, p - 1))} disabled={safePage === 0}
                    className="flex items-center gap-1 text-sm text-zinc-400 hover:text-zinc-100 disabled:opacity-30 disabled:cursor-not-allowed transition-colors">
                    <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7"/></svg>
                    Prev
                  </button>
                  <span className="text-xs text-zinc-600 tabular-nums">{safePage + 1} / {totalPages}</span>
                  <button onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))} disabled={safePage >= totalPages - 1}
                    className="flex items-center gap-1 text-sm text-zinc-400 hover:text-zinc-100 disabled:opacity-30 disabled:cursor-not-allowed transition-colors">
                    Next
                    <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7"/></svg>
                  </button>
                </div>
              )}
            </>
          )}

          {/* Sticky bulk action bar */}
          {selectMode && selectedIds.size > 0 && (
            <div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 flex items-center gap-3 px-5 py-3 bg-zinc-800 border border-zinc-700 rounded-2xl shadow-2xl">
              <span className="text-sm text-zinc-300 font-medium mr-1">{selectedIds.size} selected</span>
              <button
                onClick={() => bulkMut.mutate({ action: "mute" })}
                disabled={bulkMut.isPending}
                className="px-3 py-1.5 rounded-lg bg-amber-900/40 text-amber-400 text-sm font-medium hover:bg-amber-900/60 transition-colors disabled:opacity-50"
              >
                Mute
              </button>
              <button
                onClick={() => bulkMut.mutate({ action: "unfollow" })}
                disabled={bulkMut.isPending}
                className="px-3 py-1.5 rounded-lg bg-red-900/30 text-red-400 text-sm font-medium hover:bg-red-900/50 transition-colors disabled:opacity-50"
              >
                Unfollow
              </button>
            </div>
          )}
        </div>
      )}

      {/* ── Feed tab ── */}
      {tab === "feed" && (
        <div className="flex flex-col gap-4">
          <div className="flex justify-end">
            <SortPicker value={feedSort} onChange={setFeedSort} options={FEED_SORTS} />
          </div>

          {loadingFeed && feedOffset === 0 ? (
            <div className="flex items-center justify-center py-8">
              <div className="w-6 h-6 border-2 border-accent border-t-transparent rounded-full animate-spin" />
            </div>
          ) : !sortedFeed.length ? (
            <p className="text-zinc-500 text-sm">No videos indexed yet — hit Sync all to pull the latest from YouTube.</p>
          ) : (
            <>
              <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
                {sortedFeed.map((v) => <VideoCard key={v.youtube_video_id} video={v} />)}
              </div>
              {hasMoreFeed && (
                <div className="flex justify-center mt-2">
                  <button onClick={() => setFeedOffset((o) => o + FEED_PAGE)} disabled={fetchingFeed}
                    className="flex items-center gap-2 px-5 py-2 rounded-xl bg-zinc-800 text-zinc-300 text-sm font-medium hover:bg-zinc-700 transition-colors disabled:opacity-50">
                    {fetchingFeed ? <><Spinner /> Loading…</> : "Load more"}
                  </button>
                </div>
              )}
            </>
          )}
        </div>
      )}

      {/* ── Groups tab ── */}
      {tab === "groups" && (
        <GroupsPanel groups={groups} channels={channels} />
      )}

    </div>
  );
}
