/**
 * FileTileGrid — ファイル/ディレクトリのアイコングリッド（auto-fill タイル）。
 * スペースのファイルタブとタスクのファイルタブで共有する純プレゼンテーショナル部品。
 * 状態は一切持たず、表示と入力イベントの中継だけを行う。
 *
 * testid は `idPrefix` から組み立てる（'space' → space-files-grid / space-file-tile …）。
 * 既存 e2e の testid を保つため、idPrefix を変えるだけで両者を再現できるようにしている。
 */
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LocalFileEntry } from '../../api';
import { FileTypeIcon } from './FileTypeIcon';
import { workspaceDirRole } from '../../lib/workspaceDirs';
import { DND_FILES_MIME, dragSources, readDragSources } from '../../lib/fileDnd';

interface FileTileGridProps {
  /** 表示するエントリ（呼出側でソート済み）。 */
  entries: LocalFileEntry[];
  /** 'space' | 'task' などの testid 接頭辞。 */
  idPrefix: string;
  /** ファイルに選択チェック + 個別削除ボタンを出すか（owner/admin かつ書込可のとき true）。 */
  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;
  /**
   * ファイルの生 URL を返す。指定すると各ファイルタイルに個別ダウンロード
   * （`<a download>`）を出す。ダウンロードは閲覧操作なので canManage に関係なく出す。
   */
  fileHref?: (entry: LocalFileEntry) => string;
  /** タイル下部に差し込む任意のオーバーレイ（例: スペースの「アプリとして実行」）。 */
  renderTileOverlay?: (entry: LocalFileEntry) => React.ReactNode;
  /**
   * Phase 2: 各タイル右上に差し込む任意アクション（リネーム等）。ファイル・フォルダ両方に
   * 出る。詳細表示の renderRowAction と対をなす。
   */
  renderEntryAction?: (entry: LocalFileEntry) => React.ReactNode;
  /**
   * Phase 2: ドラッグ移動。指定すると（かつ canManage が true なら）タイルをドラッグして
   * フォルダタイルへドロップで移動できる。sourcePaths は選択を考慮した移動元集合。
   */
  onMoveDrop?: (sourcePaths: string[], destDir: string) => void;
  /** フォルダの zip ダウンロード。指定するとフォルダタイルに zip DL ボタンを出す（構造フォルダ含む全フォルダ）。 */
  onDownloadDir?: (path: string) => void;
  /** エントリ 0 件のとき表示するヒント。 */
  emptyHint?: React.ReactNode;
}

export function FileTileGrid({
  entries,
  idPrefix,
  canManage,
  selected,
  onToggleSelect,
  onOpenDir,
  onOpenFile,
  onDeleteOne,
  isDeleting,
  fileHref,
  renderTileOverlay,
  renderEntryAction,
  onMoveDrop,
  onDownloadDir,
  emptyHint,
}: FileTileGridProps) {
  const { t } = useTranslation('files');
  const [dragOverPath, setDragOverPath] = useState<string | null>(null);
  const dndEnabled = canManage && !!onMoveDrop;
  return (
    <div
      data-testid={`${idPrefix}-files-grid`}
      className="grid grid-cols-[repeat(auto-fill,minmax(80px,1fr))] gap-1 sm:grid-cols-[repeat(auto-fill,minmax(92px,1fr))]"
    >
      {entries.map(entry => {
        const isFile = entry.kind !== 'directory';
        // 構造フォルダ（input/output/... = 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 entryAction = renderEntryAction?.(entry);
        const canDrag = dndEnabled && !dirRole;
        return (
          <div
            key={`${entry.kind}:${entry.path}`}
            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 relative rounded-lg border transition-colors ${
              isDragOver
                ? 'border-[var(--brand-primary)] ring-2 ring-[var(--brand-primary)] bg-surface'
                : isChecked
                  ? 'border-[var(--brand-primary)] bg-surface'
                  : 'border-transparent hover:border-hairline hover:bg-surface'
            }`}
          >
            <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 w-full flex-col items-center gap-1.5 rounded-lg px-1.5 py-2.5 text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400"
            >
              <span className="flex h-9 w-9 items-center justify-center text-slate-400" aria-hidden>
                {entry.kind === 'directory' ? (
                  <svg className="h-9 w-9 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-9 w-9" />
                )}
              </span>
              <span
                className="line-clamp-2 w-full break-words text-[11px] leading-tight text-slate-700"
                style={{ wordBreak: 'break-word' }}
              >
                {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={`inline-flex items-center gap-0.5 rounded px-1.5 py-0.5 text-[9px] 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>
            {renderTileOverlay?.(entry)}
            {canSelectDelete && (
              <input
                type="checkbox"
                data-testid={`${idPrefix}-file-select-${entry.name}`}
                checked={isChecked}
                onChange={() => onToggleSelect(entry.path)}
                title={t('tile.select')}
                aria-label={t('tile.selectAria', { name: entry.name })}
                className={`absolute left-1.5 top-1.5 h-3.5 w-3.5 rounded border-hairline accent-[var(--brand-primary)] transition-opacity reveal-hover ${
                  isChecked ? 'opacity-100' : 'opacity-0 group-hover:opacity-100 focus-visible:opacity-100'
                }`}
              />
            )}
            {/* 右上クラスタ: リネーム等（ファイル/フォルダ）+ ダウンロード（ファイル=直/フォルダ=zip）+ 削除。 */}
            {(entryAction || (isFile && fileHref) || (entry.kind === 'directory' && onDownloadDir) || canSelectDelete) && (
              <div className="absolute right-1 top-1 flex items-center gap-0.5">
                {entryAction}
                {isFile && fileHref && (
                  <a
                    href={fileHref(entry)}
                    download={entry.name}
                    onClick={e => e.stopPropagation()}
                    data-testid={`${idPrefix}-file-download-${entry.name}`}
                    title={t('tile.download')}
                    aria-label={t('tile.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>
                )}
                {entry.kind === 'directory' && onDownloadDir && (
                  <button
                    type="button"
                    data-testid={`${idPrefix}-dir-download-${entry.name}`}
                    onClick={e => { e.stopPropagation(); onDownloadDir(entry.path); }}
                    title={t('tile.dirDownload')}
                    aria-label={t('tile.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('tile.deleteDir') : t('tile.delete')}
                    aria-label={t('tile.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>
            )}
          </div>
        );
      })}
      {entries.length === 0 && emptyHint && (
        <div className="col-span-full px-1 py-6 text-center text-xs text-slate-500">
          {emptyHint}
        </div>
      )}
    </div>
  );
}
