/**
 * FileDetailList — ファイル/ディレクトリの詳細（テーブル）表示。Windows エクスプローラーの
 * 「詳細表示」に相当し、名前・更新日時・サイズを一覧で見せる。
 *
 * FileTileGrid と同じ操作プロップ（open/select/delete/download）を受ける純表示部品。
 * 並べ替えは列見出しクリックで行い、状態は呼出側（useFileView）が持つ。
 *
 * testid は idPrefix から組み立てる（'task' → task-files-list / task-file-row …）。
 */
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LocalFileEntry } from '../../api';
import { FileTypeIcon } from './FileTypeIcon';
import { formatFileSize, formatFileTimestamp, type FileSortKey, type FileSortState } from '../../lib/fileView';
import { workspaceDirRole } from '../../lib/workspaceDirs';
import { DND_FILES_MIME, dragSources, readDragSources } from '../../lib/fileDnd';

interface FileDetailListProps {
  entries: LocalFileEntry[];
  idPrefix: string;
  canManage: boolean;
  selected: Set<string>;
  onToggleSelect: (path: string) => void;
  onOpenDir: (path: string) => void;
  onOpenFile: (path: string, name: string) => void;
  onDeleteOne: (path: string) => void;
  isDeleting?: boolean;
  fileHref?: (entry: LocalFileEntry) => string;
  emptyHint?: React.ReactNode;
  /** 現在の並べ替え状態（見出しの ▲▼ 表示に使う）。 */
  sort: FileSortState;
  /** 列見出しクリック。 */
  onSort: (key: FileSortKey) => void;
  /**
   * 行末の操作列に差し込む任意アクション（例: スペースの「アプリとして実行」）。
   * アイコン表示の renderTileOverlay と対になる詳細表示版。
   */
  renderRowAction?: (entry: LocalFileEntry) => React.ReactNode;
  /**
   * Phase 2: ドラッグ移動。指定すると（かつ canManage が true なら）行をドラッグして
   * フォルダ行へドロップで移動できる。sourcePaths は選択を考慮した移動元集合。
   */
  onMoveDrop?: (sourcePaths: string[], destDir: string) => void;
  /** フォルダの zip ダウンロード。指定するとフォルダ行に zip DL ボタンを出す（構造フォルダ含む全フォルダ）。 */
  onDownloadDir?: (path: string) => void;
}

const HEADERS: Array<{ key: FileSortKey; labelKey: string; align: 'left' | 'right'; className: string }> = [
  { key: 'name', labelKey: 'detail.header.name', align: 'left', className: '' },
  { key: 'modified', labelKey: 'detail.header.modified', align: 'left', className: 'w-36 whitespace-nowrap' },
  { key: 'size', labelKey: 'detail.header.size', align: 'right', className: 'w-24 whitespace-nowrap' },
];

function SortArrow({ active, dir }: { active: boolean; dir: 'asc' | 'desc' }) {
  if (!active) return null;
  return <span className="ml-1 text-[9px] text-slate-400" aria-hidden>{dir === 'asc' ? '▲' : '▼'}</span>;
}

export function FileDetailList({
  entries,
  idPrefix,
  canManage,
  selected,
  onToggleSelect,
  onOpenDir,
  onOpenFile,
  onDeleteOne,
  isDeleting,
  fileHref,
  emptyHint,
  sort,
  onSort,
  renderRowAction,
  onMoveDrop,
  onDownloadDir,
}: FileDetailListProps) {
  const { t } = useTranslation('files');
  const [dragOverPath, setDragOverPath] = useState<string | null>(null);
  const dndEnabled = canManage && !!onMoveDrop;
  return (
    <div className="overflow-x-auto">
      <table data-testid={`${idPrefix}-files-list`} className="w-full min-w-[420px] border-collapse text-xs">
        <thead>
          <tr className="border-b border-hairline text-slate-500">
            {canManage && <th className="w-7 px-1 py-1.5" />}
            {HEADERS.map((h) => {
              const label = t(h.labelKey);
              return (
              <th
                key={h.key}
                aria-sort={sort.key === h.key ? (sort.dir === 'asc' ? 'ascending' : 'descending') : 'none'}
                className={`px-2 py-1.5 font-medium ${h.align === 'right' ? 'text-right' : 'text-left'} ${h.className}`}
              >
                <button
                  type="button"
                  data-testid={`${idPrefix}-files-sort-${h.key}`}
                  onClick={() => onSort(h.key)}
                  className={`inline-flex items-center hover:text-slate-800 ${h.align === 'right' ? 'flex-row-reverse' : ''}`}
                  title={t('detail.sortBy', { label })}
                >
                  {label}
                  <SortArrow active={sort.key === h.key} dir={sort.dir} />
                </button>
              </th>
              );
            })}
            {/* 行操作（ダウンロード/削除）用の余白列 */}
            <th className="w-16 px-1 py-1.5" />
          </tr>
        </thead>
        <tbody>
          {entries.map((entry) => {
            const isFile = entry.kind !== 'directory';
            // 構造フォルダ（input/output/logs/apps/readonly 等 = role 有）は足場なので
            // 選択・削除・移動の対象外。ダウンロードは全フォルダ可（下の onDownloadDir）。
            const dirRole = workspaceDirRole(entry.path, entry.name, entry.kind);
            const canSelectDelete = canManage && (isFile || !dirRole);
            const isChecked = canSelectDelete && selected.has(entry.path);
            const isDropTarget = dndEnabled && entry.kind === 'directory';
            const isDragOver = isDropTarget && dragOverPath === entry.path;
            const canDrag = dndEnabled && !dirRole;
            return (
              <tr
                key={`${entry.kind}:${entry.path}`}
                data-testid={`${idPrefix}-file-row`}
                data-kind={entry.kind}
                data-name={entry.name}
                draggable={canDrag || undefined}
                onDragStart={canDrag ? e => {
                  e.dataTransfer.setData(DND_FILES_MIME, JSON.stringify(dragSources(entry, selected)));
                  e.dataTransfer.effectAllowed = 'move';
                } : undefined}
                onDragOver={isDropTarget ? e => {
                  if (!e.dataTransfer.types.includes(DND_FILES_MIME)) return;
                  e.preventDefault();
                  e.dataTransfer.dropEffect = 'move';
                  if (dragOverPath !== entry.path) setDragOverPath(entry.path);
                } : undefined}
                onDragLeave={isDropTarget ? () => setDragOverPath(p => (p === entry.path ? null : p)) : undefined}
                onDrop={isDropTarget ? e => {
                  e.preventDefault();
                  setDragOverPath(null);
                  const sources = readDragSources(e.dataTransfer);
                  if (sources.length) onMoveDrop!(sources, entry.path);
                } : undefined}
                data-drop-target={isDropTarget ? 'true' : undefined}
                className={`group border-b border-hairline/60 hover:bg-surface ${
                  isDragOver ? 'ring-2 ring-inset ring-[var(--brand-primary)] bg-surface' : isChecked ? 'bg-surface' : ''
                }`}
              >
                {canManage && (
                  <td className="px-1 py-1 align-middle">
                    {canSelectDelete && (
                      <input
                        type="checkbox"
                        data-testid={`${idPrefix}-file-select-${entry.name}`}
                        checked={isChecked}
                        onChange={() => onToggleSelect(entry.path)}
                        aria-label={t('detail.selectAria', { name: entry.name })}
                        className="h-3.5 w-3.5 rounded border-hairline accent-[var(--brand-primary)]"
                      />
                    )}
                  </td>
                )}
                <td className="px-2 py-1 align-middle">
                  <button
                    type="button"
                    data-testid={`${idPrefix}-file-tile`}
                    data-kind={entry.kind}
                    data-name={entry.name}
                    onClick={() => (entry.kind === 'directory' ? onOpenDir(entry.path) : onOpenFile(entry.path, entry.name))}
                    title={entry.name}
                    className="flex min-w-0 items-center gap-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 rounded"
                  >
                    <span className="flex h-5 w-5 shrink-0 items-center justify-center text-slate-400" aria-hidden>
                      {entry.kind === 'directory' ? (
                        <svg className="h-5 w-5 text-amber-400" viewBox="0 0 16 16" fill="currentColor" stroke="none">
                          <path d="M2 4.5A1.5 1.5 0 013.5 3h3l1.5 2h4.5A1.5 1.5 0 0114 6.5v5A1.5 1.5 0 0112.5 13h-9A1.5 1.5 0 012 11.5v-7z" />
                        </svg>
                      ) : (
                        <FileTypeIcon name={entry.name} className="h-5 w-5" />
                      )}
                    </span>
                    <span className="truncate text-slate-700">{entry.name}</span>
                    {(() => {
                      const role = workspaceDirRole(entry.path, entry.name, entry.kind);
                      if (!role) return null;
                      return (
                        <span
                          data-testid={`${idPrefix}-dir-badge-${entry.name}`}
                          data-writable={role.writable ? 'true' : 'false'}
                          title={role.title}
                          className={`shrink-0 inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 text-[10px] font-medium ${role.className}`}
                        >
                          {!role.writable && (
                            <svg className="h-2.5 w-2.5" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden>
                              <rect x="3.5" y="7" width="9" height="6" rx="1" />
                              <path d="M5.5 7V5a2.5 2.5 0 015 0v2" />
                            </svg>
                          )}
                          {role.label}
                        </span>
                      );
                    })()}
                  </button>
                </td>
                <td className="px-2 py-1 align-middle whitespace-nowrap text-slate-500" title={entry.modifiedAt ?? ''}>
                  {formatFileTimestamp(entry.modifiedAt)}
                </td>
                <td className="px-2 py-1 align-middle whitespace-nowrap text-right tabular-nums text-slate-500">
                  {isFile ? formatFileSize(entry.size) : '—'}
                </td>
                <td className="px-1 py-1 align-middle">
                  {(() => {
                    // 行アクション（リネーム等）はファイル・フォルダ両方。ファイルは <a> 直 DL、
                    // フォルダは zip DL（onDownloadDir）。削除はファイル＋ユーザー作成フォルダ。
                    const rowAction = renderRowAction?.(entry);
                    const showDirDownload = entry.kind === 'directory' && !!onDownloadDir;
                    const showOps = (isFile && fileHref) || showDirDownload || canSelectDelete;
                    if (!rowAction && !showOps) return null;
                    return (
                    <div className="flex items-center justify-end gap-0.5">
                      {rowAction}
                      {isFile && fileHref && (
                        <a
                          href={fileHref(entry)}
                          download={entry.name}
                          onClick={(e) => e.stopPropagation()}
                          data-testid={`${idPrefix}-file-download-${entry.name}`}
                          title={t('detail.download')}
                          aria-label={t('detail.downloadAria', { name: entry.name })}
                          className="inline-flex h-5 w-5 items-center justify-center rounded text-slate-400 opacity-0 transition-opacity hover:bg-surface-2 hover:text-slate-700 focus-visible:opacity-100 group-hover:opacity-100 reveal-hover"
                        >
                          <svg className="h-3 w-3" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                            <path d="M8 2v8M4.5 6.5L8 10l3.5-3.5M2.5 13h11" />
                          </svg>
                        </a>
                      )}
                      {showDirDownload && (
                        <button
                          type="button"
                          data-testid={`${idPrefix}-dir-download-${entry.name}`}
                          onClick={(e) => { e.stopPropagation(); onDownloadDir!(entry.path); }}
                          title={t('detail.dirDownload')}
                          aria-label={t('detail.dirDownloadAria', { name: entry.name })}
                          className="inline-flex h-5 w-5 items-center justify-center rounded text-slate-400 opacity-0 transition-opacity hover:bg-surface-2 hover:text-slate-700 focus-visible:opacity-100 group-hover:opacity-100 reveal-hover"
                        >
                          <svg className="h-3 w-3" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                            <path d="M8 2v8M4.5 6.5L8 10l3.5-3.5M2.5 13h11" />
                          </svg>
                        </button>
                      )}
                      {canSelectDelete && (
                        <button
                          type="button"
                          data-testid={`${idPrefix}-file-delete-${entry.name}`}
                          onClick={() => onDeleteOne(entry.path)}
                          disabled={isDeleting}
                          title={entry.kind === 'directory' ? t('detail.deleteDir') : t('detail.delete')}
                          aria-label={t('detail.deleteAria', { name: entry.name })}
                          className="inline-flex h-5 w-5 items-center justify-center rounded text-slate-400 opacity-0 transition-opacity hover:bg-red-50 hover:text-red-700 focus-visible:opacity-100 group-hover:opacity-100 reveal-hover disabled:opacity-50 dark:hover:bg-red-500/15 dark:hover:text-red-300"
                        >
                          <svg className="h-3 w-3" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                            <path d="M3 4h10M6.5 4V2.5h3V4M5 4l.5 9h5l.5-9" />
                          </svg>
                        </button>
                      )}
                    </div>
                    );
                  })()}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
      {entries.length === 0 && emptyHint && (
        <div className="px-1 py-6 text-center text-xs text-slate-500">{emptyHint}</div>
      )}
    </div>
  );
}
