/**
 * fileView.ts — ファイル窓の「表示モード（アイコン/詳細）」と「並べ替え」の純ロジック。
 *
 * 状態は一切持たない。React 状態と localStorage 記憶は useFileView フックが担う。
 * ここはソート・列クリック遷移・サイズ整形だけを純関数で提供し、単体テストしやすくする。
 */
import type { LocalFileEntry } from '../api';

export type FileViewMode = 'icon' | 'detail';
export type FileSortKey = 'name' | 'modified' | 'size';
export type FileSortDir = 'asc' | 'desc';
export interface FileSortState {
  key: FileSortKey;
  dir: FileSortDir;
}

export const DEFAULT_FILE_SORT: FileSortState = { key: 'name', dir: 'asc' };

/** 列見出しを最初にクリックしたときの既定の向き（エクスプローラー準拠）。 */
const DEFAULT_DIR: Record<FileSortKey, FileSortDir> = {
  name: 'asc',
  modified: 'desc',
  size: 'desc',
};

/**
 * 列見出しクリック時の次のソート状態を返す。
 * - 同じ列をクリック → 向きを反転
 * - 別の列をクリック → その列の既定の向き
 */
export function nextSort(current: FileSortState, key: FileSortKey): FileSortState {
  if (current.key === key) {
    return { key, dir: current.dir === 'asc' ? 'desc' : 'asc' };
  }
  return { key, dir: DEFAULT_DIR[key] };
}

/**
 * エントリを並べ替える。フォルダは常に先頭、その後ファイル。各グループ内で key/dir に従う。
 * 元配列は変更しない。
 */
export function sortFileEntries(entries: LocalFileEntry[], sort: FileSortState): LocalFileEntry[] {
  const dirs = entries.filter((e) => e.kind === 'directory');
  const files = entries.filter((e) => e.kind !== 'directory');
  const mul = sort.dir === 'asc' ? 1 : -1;

  const cmp = (a: LocalFileEntry, b: LocalFileEntry): number => {
    let primary: number;
    if (sort.key === 'modified') {
      primary = toTime(a.modifiedAt) - toTime(b.modifiedAt);
    } else if (sort.key === 'size') {
      primary = (a.size ?? 0) - (b.size ?? 0);
    } else {
      primary = nameCmp(a.name, b.name);
    }
    if (primary !== 0) return primary * mul;
    // 主キーが同値のときは、向き(dir)に関係なく名前の昇順で安定させる。
    return nameCmp(a.name, b.name);
  };

  return [...dirs.sort(cmp), ...files.sort(cmp)];
}

// 名前比較は自然順（file2 < file10）でエクスプローラーに合わせる。
function nameCmp(a: string, b: string): number {
  return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });
}

function toTime(iso: string | undefined): number {
  if (!iso) return 0;
  const t = new Date(iso).getTime();
  return Number.isNaN(t) ? 0 : t;
}

/**
 * 詳細表示の「更新日時」列用の絶対時刻フォーマッタ（YYYY/MM/DD HH:mm、ローカル時刻）。
 * 一覧の相対表記と違い、列ソートと目視順が一致する固定書式にする。
 * 不正な日付は空文字。
 */
export function formatFileTimestamp(iso: string | undefined | null): string {
  if (!iso) return '';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return '';
  const p = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}/${p(d.getMonth() + 1)}/${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
}

/**
 * バイト数を人間可読に整形する。ディレクトリ（size 不定）は「—」を渡す側で扱う想定だが、
 * 負数や未定義は「—」を返す。
 */
export function formatFileSize(bytes: number | undefined | null): string {
  if (bytes == null || bytes < 0) return '—';
  if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(1)} GB`;
  if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB`;
  if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(1)} KB`;
  return `${bytes} B`;
}
