/**
 * SpaceBrowserPanel.tsx — スペース「設定 → ブラウザ」パネル
 *
 * そのスペースのブラウザセッションプロファイル・マクロ・録画をまとめて
 * 管理する。すべて per-space スコープ:
 *   - セッション: GET/POST/DELETE /api/browser-sessions/profiles?spaceId=…
 *   - マクロ/録画: /api/users/me/folder/{list,file}?subdir=…&spaceId=…
 *
 * DEK は per-user のため、自分が作成していないセッション
 * (decryptableByViewer===false) は一覧に出るが復号・利用できない。その行には
 * 「作成者のみ利用可」を明示する。
 *
 * 管理操作（追加・削除）は canEditInSpace をバックエンドが強制する。UI は
 * SpaceMembersPanel と同じ実シグナル（admin / 自分が owner 行）で、判定できる
 * ときだけ管理コントロールを出す。判定できない場合でも 403 はトーストで処理。
 */
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
  listBrowserSessionProfiles, deleteBrowserSessionProfile, testBrowserSessionProfile,
  listFolderFiles, getFolderFile, deleteFolderFile,
  fetchSpaceMembers,
  type BrowserSessionProfile,
} from '../../api';
import { AddBrowserSessionDialog } from '../userfolder/AddBrowserSessionDialog';
import { FilePreview } from '../files/FilePreview';
import { useAuthState } from '../../App';

type ShowToast = (message: string, variant?: 'success' | 'error') => void;

const STATUS_CLASS: Record<BrowserSessionProfile['status'], string> = {
  pending: 'bg-slate-200 text-slate-700',
  active: 'bg-emerald-100 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300',
  expired: 'bg-amber-100 dark:bg-amber-500/15 text-amber-800 dark:text-amber-300',
  revoked: 'bg-slate-200 text-slate-500',
  error: 'bg-rose-100 dark:bg-rose-500/15 text-rose-700 dark:text-rose-300',
};

function StatusPill({ status }: { status: BrowserSessionProfile['status'] }) {
  const { t } = useTranslation('spaces');
  return (
    <span className={`inline-flex items-center rounded px-2 py-0.5 text-2xs font-medium ${STATUS_CLASS[status]}`}>
      {t(`browser.status.${status}`)}
    </span>
  );
}

function errMsg(e: unknown): string {
  return e instanceof Error ? e.message : String(e);
}

export function SpaceBrowserPanel({ spaceId, showToast }: { spaceId: string; showToast?: ShowToast }) {
  const { t } = useTranslation('spaces');
  const auth = useAuthState();
  const qc = useQueryClient();

  // 管理可否は SpaceMembersPanel と同じ実シグナルで判定する。判定できない
  // ときも管理コントロールは出すが、403 は mutation onError でトースト処理。
  const { data: members } = useQuery({
    queryKey: ['space-members', spaceId],
    queryFn: () => fetchSpaceMembers(spaceId),
    staleTime: 30_000,
  });
  const ownerRow = (members ?? []).find(m => m.isOwner);
  const canManage =
    auth.mode === 'disabled' ||
    (auth.mode === 'authenticated' &&
      (auth.user.role === 'admin' || (!!ownerRow && ownerRow.userId === auth.user.id)));

  // ── セッション ──────────────────────────────────────────────
  const { data: profiles = [], isLoading: sessLoading } = useQuery({
    queryKey: ['space-browser-sessions', spaceId],
    queryFn: () => listBrowserSessionProfiles(spaceId),
  });
  const delSess = useMutation({
    mutationFn: (id: number) => deleteBrowserSessionProfile(id, spaceId),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['space-browser-sessions', spaceId] }),
    onError: (e) => showToast?.(t('browser.toast.sessionDeleteFailed', { msg: errMsg(e) }), 'error'),
  });
  const testSess = useMutation({
    mutationFn: (id: number) => testBrowserSessionProfile(id),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['space-browser-sessions', spaceId] }),
    onError: (e) => showToast?.(t('browser.toast.sessionTestFailed', { msg: errMsg(e) }), 'error'),
  });
  const [adding, setAdding] = useState(false);

  // ── マクロ / 録画 ───────────────────────────────────────────
  const macros = useQuery({
    queryKey: ['space-browser-macros', spaceId],
    queryFn: () => listFolderFiles('browser-macros', spaceId),
  });
  const recordings = useQuery({
    queryKey: ['space-browser-recordings', spaceId],
    queryFn: () => listFolderFiles('recordings', spaceId),
  });
  const delMacro = useMutation({
    mutationFn: (name: string) => deleteFolderFile('browser-macros', name, spaceId),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['space-browser-macros', spaceId] }),
    onError: (e) => showToast?.(t('browser.toast.deleteFailed', { msg: errMsg(e) }), 'error'),
  });
  const delRecording = useMutation({
    mutationFn: (name: string) => deleteFolderFile('recordings', name, spaceId),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['space-browser-recordings', spaceId] }),
    onError: (e) => showToast?.(t('browser.toast.deleteFailed', { msg: errMsg(e) }), 'error'),
  });

  // ファイル内容プレビュー（マクロ・録画共通）。
  const [preview, setPreview] = useState<{ name: string; content: string } | null>(null);
  async function openPreview(subdir: 'browser-macros' | 'recordings', name: string) {
    try {
      const content = await getFolderFile(subdir, name, spaceId);
      setPreview({ name, content });
    } catch (e) {
      showToast?.(t('browser.toast.contentFetchFailed', { msg: errMsg(e) }), 'error');
    }
  }

  return (
    <div className="h-full overflow-y-auto p-6" data-testid="space-browser-panel">
      <div className="max-w-2xl space-y-8">
        {/* ── セッション ── */}
        <section data-testid="space-browser-sessions">
          <div className="mb-2 flex items-center justify-between">
            <h2 className="text-base font-semibold text-slate-800">{t('browser.sessions.heading')}</h2>
            {canManage && (
              <button
                type="button"
                data-testid="space-browser-add-session"
                onClick={() => setAdding(true)}
                className="rounded-md bg-accent px-3 py-1.5 text-xs font-medium text-accent-fg hover:bg-accent-deep"
              >
                {t('browser.sessions.add')}
              </button>
            )}
          </div>
          <p className="mb-3 text-xs text-slate-500">
            {t('browser.sessions.intro')}
          </p>

          {sessLoading && <div className="text-xs text-slate-500">{t('common:loading')}</div>}

          <div className="divide-y divide-hairline rounded-md border border-hairline">
            {profiles.length === 0 && !sessLoading && (
              <div className="px-3 py-6 text-center text-xs text-slate-400">
                <div>{t('browser.sessions.empty')}</div>
                {canManage && <div className="mt-1">{t('browser.sessions.emptyHint')}</div>}
              </div>
            )}
            {profiles.map(p => {
              const usable = p.decryptableByViewer !== false;
              return (
                <div key={p.id} data-testid={`space-browser-session-${p.id}`} className="flex items-center justify-between px-3 py-2">
                  <div className="min-w-0">
                    <div className="flex items-center gap-2">
                      <span className="truncate text-[13px] font-medium text-slate-800">{p.label}</span>
                      <StatusPill status={p.status} />
                      {!usable && (
                        <span className="inline-flex items-center rounded bg-slate-200 px-2 py-0.5 text-2xs font-medium text-slate-600">
                          {t('browser.sessions.creatorOnly')}
                        </span>
                      )}
                    </div>
                    <div className="truncate text-2xs text-slate-500">{p.startUrl}</div>
                    {p.lastError && <div className="truncate text-2xs text-rose-600">{p.lastError}</div>}
                    {!usable && (
                      <div className="text-2xs text-slate-400">
                        {t('browser.sessions.creatorOnlyHint')}
                      </div>
                    )}
                  </div>
                  <div className="flex shrink-0 items-center gap-2">
                    {usable && (
                      <button
                        type="button"
                        onClick={() => testSess.mutate(p.id)}
                        disabled={testSess.isPending}
                        className="rounded px-2 py-1 text-xs text-slate-700 hover:bg-surface hover:text-slate-900 disabled:opacity-50"
                      >
                        {t('browser.sessions.test')}
                      </button>
                    )}
                    {canManage && (
                      <button
                        type="button"
                        onClick={() => { if (confirm(t('browser.deleteConfirm', { name: p.label }))) delSess.mutate(p.id); }}
                        className="rounded px-2 py-1 text-xs text-rose-600 hover:bg-rose-50 hover:text-rose-800 dark:hover:bg-rose-500/15 dark:hover:text-rose-300"
                      >
                        {t('common:delete')}
                      </button>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        </section>

        {/* ── マクロ ── */}
        <FolderSection
          title={t('browser.macros.title')}
          testid="space-browser-macros"
          subdir="browser-macros"
          query={macros}
          emptyText={t('browser.macros.empty')}
          hint={t('browser.macros.hint')}
          canManage={canManage}
          onView={(name) => openPreview('browser-macros', name)}
          onDelete={(name) => { if (confirm(t('browser.deleteConfirm', { name }))) delMacro.mutate(name); }}
        />

        {/* ── 録画 ── */}
        <FolderSection
          title={t('browser.recordings.title')}
          testid="space-browser-recordings"
          subdir="recordings"
          query={recordings}
          emptyText={t('browser.recordings.empty')}
          hint={t('browser.recordings.hint')}
          canManage={canManage}
          onView={(name) => openPreview('recordings', name)}
          onDelete={(name) => { if (confirm(t('browser.deleteConfirm', { name }))) delRecording.mutate(name); }}
        />
      </div>

      {adding && (
        <AddBrowserSessionDialog spaceId={spaceId} onClose={() => setAdding(false)} />
      )}

      {preview && (
        <FilePreview name={preview.name} content={preview.content} imageSrc="" onClose={() => setPreview(null)} />
      )}
    </div>
  );
}

interface FolderSectionProps {
  title: string;
  testid: string;
  subdir: 'browser-macros' | 'recordings';
  query: { data?: { name: string; size: number; mtime: string }[]; isLoading: boolean };
  emptyText: string;
  hint: string;
  canManage: boolean;
  onView: (name: string) => void;
  onDelete: (name: string) => void;
}

function FolderSection({ title, testid, query, emptyText, hint, canManage, onView, onDelete }: FolderSectionProps) {
  const { t } = useTranslation('spaces');
  const files = query.data ?? [];
  return (
    <section data-testid={testid}>
      <h2 className="mb-2 text-base font-semibold text-slate-800">{title}</h2>
      <p className="mb-3 text-xs text-slate-500">{hint}</p>
      {query.isLoading && <div className="text-xs text-slate-500">{t('common:loading')}</div>}
      <div className="divide-y divide-hairline rounded-md border border-hairline">
        {files.length === 0 && !query.isLoading && (
          <div className="px-3 py-6 text-center text-xs text-slate-400">{emptyText}</div>
        )}
        {files.map(f => (
          <div key={f.name} className="flex items-center justify-between px-3 py-2">
            <button
              type="button"
              onClick={() => onView(f.name)}
              className="min-w-0 flex-1 text-left"
            >
              <div className="truncate text-[13px] font-medium text-slate-800">{f.name}</div>
              <div className="text-2xs text-slate-400">{(f.size / 1024).toFixed(1)} KB · {new Date(f.mtime).toLocaleString()}</div>
            </button>
            {canManage && (
              <button
                type="button"
                onClick={() => onDelete(f.name)}
                className="ml-2 shrink-0 rounded px-2 py-1 text-xs text-rose-600 hover:bg-rose-50 hover:text-rose-800 dark:hover:bg-rose-500/15 dark:hover:text-rose-300"
              >
                {t('common:delete')}
              </button>
            )}
          </div>
        ))}
      </div>
    </section>
  );
}
