import { describe, it, expect } from 'vitest';
import type { LocalFileEntry } from '../api';
import { sortFileEntries, nextSort, formatFileSize, formatFileTimestamp, DEFAULT_FILE_SORT } from './fileView';

function entry(name: string, kind: 'file' | 'directory', size: number, modifiedAt: string): LocalFileEntry {
  return { name, path: name, kind, size, modifiedAt };
}

const ENTRIES: LocalFileEntry[] = [
  entry('banana.txt', 'file', 300, '2026-06-20T00:00:00Z'),
  entry('zeta', 'directory', 0, '2026-06-23T00:00:00Z'),
  entry('apple.txt', 'file', 100, '2026-06-22T00:00:00Z'),
  entry('alpha', 'directory', 0, '2026-06-21T00:00:00Z'),
  entry('cherry.txt', 'file', 200, '2026-06-18T00:00:00Z'),
];

describe('sortFileEntries', () => {
  it('ディレクトリを常に先頭に置く', () => {
    const r = sortFileEntries(ENTRIES, { key: 'size', dir: 'desc' });
    expect(r.slice(0, 2).every(e => e.kind === 'directory')).toBe(true);
    expect(r.slice(2).every(e => e.kind === 'file')).toBe(true);
  });

  it('名前 昇順', () => {
    const r = sortFileEntries(ENTRIES, { key: 'name', dir: 'asc' });
    expect(r.map(e => e.name)).toEqual(['alpha', 'zeta', 'apple.txt', 'banana.txt', 'cherry.txt']);
  });

  it('名前 降順', () => {
    const r = sortFileEntries(ENTRIES, { key: 'name', dir: 'desc' });
    expect(r.map(e => e.name)).toEqual(['zeta', 'alpha', 'cherry.txt', 'banana.txt', 'apple.txt']);
  });

  it('更新日時 新しい順（ファイル部分）', () => {
    const r = sortFileEntries(ENTRIES, { key: 'modified', dir: 'desc' });
    expect(r.filter(e => e.kind === 'file').map(e => e.name)).toEqual(['apple.txt', 'banana.txt', 'cherry.txt']);
  });

  it('サイズ 大きい順（ファイル部分）', () => {
    const r = sortFileEntries(ENTRIES, { key: 'size', dir: 'desc' });
    expect(r.filter(e => e.kind === 'file').map(e => e.name)).toEqual(['banana.txt', 'cherry.txt', 'apple.txt']);
  });

  it('元配列を破壊しない', () => {
    const copy = [...ENTRIES];
    sortFileEntries(ENTRIES, { key: 'size', dir: 'asc' });
    expect(ENTRIES).toEqual(copy);
  });

  it('サイズ同値は名前昇順で安定（dir に依らず副キーは名前昇順）', () => {
    const same: LocalFileEntry[] = [
      entry('c.txt', 'file', 100, '2026-06-01T00:00:00Z'),
      entry('a.txt', 'file', 100, '2026-06-01T00:00:00Z'),
      entry('b.txt', 'file', 100, '2026-06-01T00:00:00Z'),
    ];
    expect(sortFileEntries(same, { key: 'size', dir: 'asc' }).map(e => e.name)).toEqual(['a.txt', 'b.txt', 'c.txt']);
    expect(sortFileEntries(same, { key: 'size', dir: 'desc' }).map(e => e.name)).toEqual(['a.txt', 'b.txt', 'c.txt']);
  });

  it('不正な更新日時は 0 扱いで落ちない', () => {
    const bad: LocalFileEntry[] = [
      entry('good.txt', 'file', 1, '2026-06-01T00:00:00Z'),
      entry('bad.txt', 'file', 1, 'not-a-date'),
    ];
    const r = sortFileEntries(bad, { key: 'modified', dir: 'asc' });
    expect(r.map(e => e.name)).toEqual(['bad.txt', 'good.txt']);
  });
});

describe('nextSort', () => {
  it('同じ列クリックで向きを反転', () => {
    expect(nextSort({ key: 'name', dir: 'asc' }, 'name')).toEqual({ key: 'name', dir: 'desc' });
    expect(nextSort({ key: 'name', dir: 'desc' }, 'name')).toEqual({ key: 'name', dir: 'asc' });
  });

  it('別の列クリックで既定の向き（name=asc / modified=desc / size=desc）', () => {
    expect(nextSort({ key: 'name', dir: 'asc' }, 'modified')).toEqual({ key: 'modified', dir: 'desc' });
    expect(nextSort({ key: 'name', dir: 'asc' }, 'size')).toEqual({ key: 'size', dir: 'desc' });
    expect(nextSort({ key: 'modified', dir: 'desc' }, 'name')).toEqual({ key: 'name', dir: 'asc' });
  });

  it('既定ソートは名前昇順', () => {
    expect(DEFAULT_FILE_SORT).toEqual({ key: 'name', dir: 'asc' });
  });
});

describe('formatFileSize', () => {
  it('単位ごとに整形', () => {
    expect(formatFileSize(0)).toBe('0 B');
    expect(formatFileSize(512)).toBe('512 B');
    expect(formatFileSize(12_000)).toBe('12.0 KB');
    expect(formatFileSize(1_500_000)).toBe('1.5 MB');
    expect(formatFileSize(2_000_000_000)).toBe('2.0 GB');
  });

  it('単位の境界（999/1000 など）', () => {
    expect(formatFileSize(999)).toBe('999 B');
    expect(formatFileSize(1_000)).toBe('1.0 KB');
    expect(formatFileSize(999_999)).toBe('1000.0 KB');
    expect(formatFileSize(1_000_000)).toBe('1.0 MB');
  });

  it('未定義・負数は「—」', () => {
    expect(formatFileSize(undefined)).toBe('—');
    expect(formatFileSize(null)).toBe('—');
    expect(formatFileSize(-5)).toBe('—');
  });
});

describe('formatFileTimestamp', () => {
  it('YYYY/MM/DD HH:mm 形式（ゼロ埋め）を返す', () => {
    // ローカル時刻依存を避けるため形だけ検証する。
    expect(formatFileTimestamp('2026-06-23T07:33:00Z')).toMatch(/^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}$/);
  });
  it('不正・空は空文字', () => {
    expect(formatFileTimestamp('not-a-date')).toBe('');
    expect(formatFileTimestamp('')).toBe('');
    expect(formatFileTimestamp(undefined)).toBe('');
    expect(formatFileTimestamp(null)).toBe('');
  });
});

describe('自然順ソート（numeric）', () => {
  it('file2 が file10 より前', () => {
    const xs: LocalFileEntry[] = [
      entry('file10.txt', 'file', 1, '2026-06-01T00:00:00Z'),
      entry('file2.txt', 'file', 1, '2026-06-01T00:00:00Z'),
      entry('file1.txt', 'file', 1, '2026-06-01T00:00:00Z'),
    ];
    expect(sortFileEntries(xs, { key: 'name', dir: 'asc' }).map(e => e.name))
      .toEqual(['file1.txt', 'file2.txt', 'file10.txt']);
  });
});
