import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { MarkdownText } from '../../lib/markdown-text';
import { useBackdropClose } from '../../lib/useBackdropClose';

interface Subscription {
  consumer_user_id: string;
  publisher_user_id: string;
  folder: string;
  mode: 'search' | 'inject';
  enabled: number;
}

interface DiscoverRow {
  owner_id: string;
  folder: string;
  file_name: string;
  title: string | null;
  visibility: string;
  mode_hint: string | null;
  updated_at: number;
}

interface InjectItem {
  owner_id: string;
  folder: string;
  file_name: string;
  size_kb: number;
}

interface InjectPreview {
  items: InjectItem[];
  total_kb: number;
  budget_kb: number;
  per_note_max_kb: number;
}

function NotesListExpanded({
  ownerId,
  folder,
  onSelectNote,
}: {
  ownerId: string;
  folder: string;
  onSelectNote: (fileName: string) => void;
}) {
  const list = useQuery<{ rows: DiscoverRow[] }>({
    queryKey: ['notes-folder-list', ownerId, folder],
    queryFn: async () => {
      const r = await fetch(
        `/api/notes/discover?owner_id=${encodeURIComponent(ownerId)}&folder=${encodeURIComponent(folder)}&limit=200`,
        { credentials: 'include' },
      );
      if (!r.ok) throw new Error(`${r.status}`);
      return r.json();
    },
    staleTime: 30_000,
  });

  if (list.isLoading) return <p className="text-2xs text-slate-400 pl-3 py-1">Loading…</p>;
  if (list.isError) return <p className="text-2xs text-red-500 pl-3 py-1">Failed to load notes.</p>;
  const rows = list.data?.rows ?? [];
  if (rows.length === 0) return <p className="text-2xs text-slate-400 pl-3 py-1">(空)</p>;

  return (
    <ul className="pl-3 pt-1 pb-1 space-y-0.5">
      {rows.map((n) => (
        <li key={n.file_name}>
          <button
            type="button"
            onClick={() => onSelectNote(n.file_name)}
            className="w-full text-left flex items-center gap-2 px-2 py-1 rounded text-2xs hover:bg-surface-2/60 transition-colors"
            title={`${n.owner_id}/${n.folder}/${n.file_name}`}
          >
            <span className="text-slate-400 flex-shrink-0">📄</span>
            <span className="flex-1 min-w-0 truncate text-slate-700">
              {n.title || <span className="font-mono text-slate-500">{n.file_name}</span>}
            </span>
            {n.mode_hint && (
              <span className="text-slate-400 text-[10px] font-mono">{n.mode_hint}</span>
            )}
          </button>
        </li>
      ))}
    </ul>
  );
}

function NoteContentModal({
  ownerId,
  folder,
  fileName,
  onClose,
}: {
  ownerId: string;
  folder: string;
  fileName: string;
  onClose: () => void;
}) {
  const note = useQuery<{ fm: Record<string, unknown>; body: string; content: string }>({
    queryKey: ['notes-cross-user-file', ownerId, folder, fileName],
    queryFn: async () => {
      const r = await fetch(
        `/api/notes/file?owner_id=${encodeURIComponent(ownerId)}&folder=${encodeURIComponent(folder)}&file_name=${encodeURIComponent(fileName)}`,
        { credentials: 'include' },
      );
      if (!r.ok) throw new Error(`${r.status}`);
      return r.json();
    },
    staleTime: 30_000,
  });

  const title = (note.data?.fm.title as string | undefined) || fileName;
  const backdrop = useBackdropClose(onClose);

  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 p-4"
      {...backdrop}
    >
      <div
        className="bg-surface rounded-md shadow-lg w-full max-w-3xl max-h-[85vh] flex flex-col overflow-hidden"
        onClick={(e) => e.stopPropagation()}
      >
        <header className="flex items-center justify-between border-b border-hairline px-4 py-3 flex-shrink-0">
          <div className="min-w-0">
            <h3 className="text-[13px] font-semibold text-slate-900 truncate">{title}</h3>
            <p className="text-2xs text-slate-500 font-mono truncate">
              {ownerId}/{folder}/{fileName}
            </p>
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="閉じる"
            className="px-2 py-1 text-slate-500 hover:text-slate-800 rounded hover:bg-surface-2"
          >
            ×
          </button>
        </header>
        <div className="flex-1 min-h-0 overflow-y-auto px-5 py-4">
          {note.isLoading && <p className="text-[13px] text-slate-400">Loading…</p>}
          {note.isError && <p className="text-[13px] text-red-500">読み込みに失敗しました。</p>}
          {note.data && note.data.body
            ? <MarkdownText text={note.data.body} />
            : note.data && <p className="text-[13px] text-slate-400 italic">(本文なし)</p>}
        </div>
      </div>
    </div>
  );
}

function ModeSelect({
  mode,
  onChange,
}: {
  mode: 'search' | 'inject';
  onChange: (m: 'search' | 'inject') => void;
}) {
  return (
    <select
      className="border border-hairline rounded text-2xs px-1 py-0.5 bg-canvas focus:outline-none focus:ring-1 focus:ring-accent"
      value={mode}
      onChange={(e) => onChange(e.target.value as 'search' | 'inject')}
    >
      <option value="search">search</option>
      <option value="inject">inject</option>
    </select>
  );
}

export function SubscriptionsPanel({ currentUserId }: { currentUserId: string }) {
  const qc = useQueryClient();
  const [q, setQ] = useState('');
  // Track which (owner, folder) rows are expanded to show the notes list inline.
  const [expanded, setExpanded] = useState<Set<string>>(new Set());
  const toggleExpanded = (key: string) =>
    setExpanded((prev) => {
      const next = new Set(prev);
      if (next.has(key)) next.delete(key);
      else next.add(key);
      return next;
    });
  // Open-modal target for note content preview.
  const [openNote, setOpenNote] = useState<{ ownerId: string; folder: string; fileName: string } | null>(null);

  const subs = useQuery<{ rows: Subscription[] }>({
    queryKey: ['notes-subscriptions'],
    queryFn: async () => {
      const r = await fetch('/api/notes/subscriptions', { credentials: 'include' });
      if (!r.ok) throw new Error(`${r.status}`);
      return r.json();
    },
    staleTime: 30_000,
  });

  const discover = useQuery<{ rows: DiscoverRow[] }>({
    queryKey: ['notes-discover', q],
    queryFn: async () => {
      const r = await fetch(`/api/notes/discover?q=${encodeURIComponent(q)}`, {
        credentials: 'include',
      });
      if (!r.ok) throw new Error(`${r.status}`);
      return r.json();
    },
    staleTime: 15_000,
  });

  const preview = useQuery<InjectPreview>({
    queryKey: ['notes-inject-preview'],
    queryFn: async () => {
      const r = await fetch('/api/notes/inject-preview', { credentials: 'include' });
      if (!r.ok) throw new Error(`${r.status}`);
      return r.json();
    },
    staleTime: 30_000,
  });

  const subscribe = useMutation({
    mutationFn: async ({
      publisher,
      folder,
      mode,
    }: {
      publisher: string;
      folder: string;
      mode: 'search' | 'inject';
    }) => {
      const r = await fetch('/api/notes/subscriptions', {
        method: 'PUT',
        credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ publisher_user_id: publisher, folder, mode, enabled: true }),
      });
      if (!r.ok) {
        const j = await r.json().catch(() => ({ error: 'failed' }));
        throw new Error((j as { error?: string }).error ?? 'failed');
      }
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['notes-subscriptions'] });
      qc.invalidateQueries({ queryKey: ['notes-inject-preview'] });
      qc.invalidateQueries({ queryKey: ['notes-discover'] });
    },
  });

  const unsubscribe = useMutation({
    mutationFn: async ({ publisher, folder }: { publisher: string; folder: string }) => {
      const r = await fetch(
        `/api/notes/subscriptions?publisher_user_id=${encodeURIComponent(publisher)}&folder=${encodeURIComponent(folder)}`,
        { method: 'DELETE', credentials: 'include' },
      );
      if (!r.ok) throw new Error(`${r.status}`);
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['notes-subscriptions'] });
      qc.invalidateQueries({ queryKey: ['notes-inject-preview'] });
    },
  });

  // Group discover rows by (owner_id, folder)
  const folderGroups = new Map<
    string,
    { owner_id: string; folder: string; count: number; visibility: string }
  >();
  for (const row of discover.data?.rows ?? []) {
    const key = `${row.owner_id}/${row.folder}`;
    const existing = folderGroups.get(key);
    if (existing) {
      existing.count++;
    } else {
      folderGroups.set(key, {
        owner_id: row.owner_id,
        folder: row.folder,
        count: 1,
        visibility: row.visibility,
      });
    }
  }

  const myFolders = (subs.data?.rows ?? []).filter(
    (s) => s.publisher_user_id === currentUserId,
  );
  const otherSubs = (subs.data?.rows ?? []).filter(
    (s) => s.publisher_user_id !== currentUserId,
  );

  return (
    <div className="h-full overflow-y-auto">
      <div className="max-w-2xl mx-auto px-6 py-6 space-y-8">

        {/* My Folders */}
        <section>
          <h3 className="text-[13px] font-semibold text-slate-800 mb-2">
            My Folders{' '}
            <span className="text-slate-400 font-normal">({myFolders.length})</span>
          </h3>
          {subs.isLoading && (
            <p className="text-[13px] text-slate-400">Loading…</p>
          )}
          {subs.isError && (
            <p className="text-[13px] text-red-500">Failed to load subscriptions.</p>
          )}
          {!subs.isLoading && !subs.isError && myFolders.length === 0 && (
            <p className="text-[13px] text-slate-400">まだ folder がありません</p>
          )}
          <ul className="space-y-1">
            {myFolders.map((s) => {
              const key = `${s.publisher_user_id}/${s.folder}`;
              const isOpen = expanded.has(key);
              return (
                <li
                  key={key}
                  className="rounded-md bg-surface-2/40 border border-hairline overflow-hidden"
                >
                  <div className="flex items-center gap-2 px-3 py-2">
                    <button
                      type="button"
                      onClick={() => toggleExpanded(key)}
                      aria-label={isOpen ? '折りたたむ' : '展開'}
                      className="text-slate-500 hover:text-slate-800 text-2xs w-4"
                    >
                      {isOpen ? '▼' : '▶'}
                    </button>
                    <span className="flex-1 text-[13px] font-mono text-slate-700">{s.folder}</span>
                    <ModeSelect
                      mode={s.mode}
                      onChange={(m) =>
                        subscribe.mutate({ publisher: s.publisher_user_id, folder: s.folder, mode: m })
                      }
                    />
                  </div>
                  {isOpen && (
                    <NotesListExpanded
                      ownerId={s.publisher_user_id}
                      folder={s.folder}
                      onSelectNote={(fileName) =>
                        setOpenNote({ ownerId: s.publisher_user_id, folder: s.folder, fileName })
                      }
                    />
                  )}
                </li>
              );
            })}
          </ul>
        </section>

        {/* My Subscriptions */}
        <section>
          <h3 className="text-[13px] font-semibold text-slate-800 mb-2">
            My Subscriptions{' '}
            <span className="text-slate-400 font-normal">({otherSubs.length})</span>
          </h3>
          {!subs.isLoading && !subs.isError && otherSubs.length === 0 && (
            <p className="text-[13px] text-slate-400">購読中の他ユーザー folder はありません</p>
          )}
          <ul className="space-y-1">
            {otherSubs.map((s) => {
              const key = `${s.publisher_user_id}/${s.folder}`;
              const isOpen = expanded.has(key);
              return (
                <li
                  key={key}
                  className="rounded-md bg-surface-2/40 border border-hairline overflow-hidden"
                >
                  <div className="flex items-center gap-2 px-3 py-2">
                    <button
                      type="button"
                      onClick={() => toggleExpanded(key)}
                      aria-label={isOpen ? '折りたたむ' : '展開'}
                      className="text-slate-500 hover:text-slate-800 text-2xs w-4"
                    >
                      {isOpen ? '▼' : '▶'}
                    </button>
                    <span className="flex-1 text-[13px] font-mono text-slate-700">
                      {s.publisher_user_id}/{s.folder}
                    </span>
                    <ModeSelect
                      mode={s.mode}
                      onChange={(m) =>
                        subscribe.mutate({ publisher: s.publisher_user_id, folder: s.folder, mode: m })
                      }
                    />
                    <button
                      type="button"
                      className="text-2xs text-red-600 hover:text-red-800 dark:hover:text-red-300 font-medium px-2 py-0.5 rounded hover:bg-red-50 dark:hover:bg-red-500/15 transition-colors"
                      onClick={() =>
                        unsubscribe.mutate({ publisher: s.publisher_user_id, folder: s.folder })
                      }
                      disabled={unsubscribe.isPending}
                    >
                      Unsubscribe
                    </button>
                  </div>
                  {isOpen && (
                    <NotesListExpanded
                      ownerId={s.publisher_user_id}
                      folder={s.folder}
                      onSelectNote={(fileName) =>
                        setOpenNote({ ownerId: s.publisher_user_id, folder: s.folder, fileName })
                      }
                    />
                  )}
                </li>
              );
            })}
          </ul>
          {unsubscribe.isError && (
            <p className="mt-1 text-2xs text-red-600">{(unsubscribe.error as Error).message}</p>
          )}
        </section>

        {/* Discover */}
        <section>
          <h3 className="text-[13px] font-semibold text-slate-800 mb-2">Discover</h3>
          <input
            className="border border-hairline rounded px-2 py-1.5 mb-3 w-full text-[13px] bg-canvas focus:outline-none focus:ring-1 focus:ring-accent placeholder:text-slate-400"
            placeholder="Search by title, tag, body…"
            value={q}
            onChange={(e) => setQ(e.target.value)}
          />
          {discover.isLoading && (
            <p className="text-[13px] text-slate-400">Searching…</p>
          )}
          {discover.isError && (
            <p className="text-[13px] text-red-500">Search failed.</p>
          )}
          {!discover.isLoading && !discover.isError && folderGroups.size === 0 && (
            <p className="text-[13px] text-slate-400">
              {q ? '結果なし' : '検索してフォルダーを探してください'}
            </p>
          )}
          <ul className="space-y-2">
            {Array.from(folderGroups.values()).map((g) => {
              const alreadySubbed = (subs.data?.rows ?? []).some(
                (s) => s.publisher_user_id === g.owner_id && s.folder === g.folder,
              );
              return (
                <li
                  key={`${g.owner_id}/${g.folder}`}
                  className="border border-hairline rounded-md px-3 py-2 bg-surface-2/30"
                >
                  <div className="flex items-center gap-2 mb-1.5">
                    <span className="flex-1 text-[13px] font-mono text-slate-700">
                      {g.owner_id}/{g.folder}
                    </span>
                    <span className="text-2xs text-slate-400">
                      {g.visibility} · {g.count} notes
                    </span>
                  </div>
                  {alreadySubbed ? (
                    <span className="text-2xs text-slate-400 italic">購読中</span>
                  ) : (
                    <div className="flex gap-2">
                      <button
                        type="button"
                        className="text-2xs bg-surface-2 border border-hairline px-2 py-0.5 rounded hover:bg-slate-100 transition-colors disabled:opacity-50"
                        disabled={subscribe.isPending}
                        onClick={() =>
                          subscribe.mutate({ publisher: g.owner_id, folder: g.folder, mode: 'search' })
                        }
                      >
                        Subscribe (search)
                      </button>
                      <button
                        type="button"
                        className="text-2xs bg-surface-2 border border-hairline px-2 py-0.5 rounded hover:bg-slate-100 transition-colors disabled:opacity-50"
                        disabled={subscribe.isPending}
                        onClick={() =>
                          subscribe.mutate({ publisher: g.owner_id, folder: g.folder, mode: 'inject' })
                        }
                      >
                        Subscribe (inject)
                      </button>
                    </div>
                  )}
                </li>
              );
            })}
          </ul>
          {subscribe.isError && (
            <p className="mt-1 text-2xs text-red-600">{(subscribe.error as Error).message}</p>
          )}
        </section>

        {/* Inject Preview */}
        <section>
          <h3 className="text-[13px] font-semibold text-slate-800 mb-1">Inject Preview</h3>
          <p className="text-2xs text-slate-500 mb-2">
            現在 inject モードで購読中のノートが LLM コンテキストに挿入されます。
          </p>
          {preview.isLoading && (
            <p className="text-[13px] text-slate-400">Loading…</p>
          )}
          {preview.isError && (
            <p className="text-[13px] text-red-500">Failed to load inject preview.</p>
          )}
          {!preview.isLoading && !preview.isError && (
            <>
              {(preview.data?.items ?? []).length === 0 ? (
                <p className="text-[13px] text-slate-400">inject 対象のノートはありません</p>
              ) : (
                <ul className="space-y-0.5 mb-2">
                  {(preview.data?.items ?? []).map((it) => (
                    <li
                      key={`${it.owner_id}/${it.folder}/${it.file_name}`}
                      className="flex items-center gap-2 text-[13px] font-mono text-slate-700"
                    >
                      <span className="flex-1">{it.owner_id}/{it.folder}/{it.file_name}</span>
                      <span className="text-slate-400 text-2xs">{it.size_kb} KB</span>
                    </li>
                  ))}
                </ul>
              )}
              <div className="text-2xs text-slate-500 pt-1 border-t border-hairline">
                Total:{' '}
                <span className="font-semibold text-slate-700">{preview.data?.total_kb ?? 0} KB</span>
                {' '}/{' '}
                <span className="font-semibold text-slate-700">{preview.data?.budget_kb ?? 0} KB</span>
                {' '}budget
              </div>
            </>
          )}
        </section>

      </div>
      {openNote && (
        <NoteContentModal
          ownerId={openNote.ownerId}
          folder={openNote.folder}
          fileName={openNote.fileName}
          onClose={() => setOpenNote(null)}
        />
      )}
    </div>
  );
}
