import { describe, expect, it, vi } from 'vitest';
import { runSequentialGalleryIdentify } from '../../lib/scanner-batch-identify.js';

function makeFile(name) {
  return new File(['x'], name, { type: 'image/jpeg' });
}

describe('runSequentialGalleryIdentify', () => {
  it('processes files one-at-a-time in order on success', async () => {
    const files = [makeFile('a.jpg'), makeFile('b.jpg'), makeFile('c.jpg')];
    const callOrder = [];
    const progressEvents = [];
    const successEvents = [];

    const identifyFn = vi.fn(async (file) => {
      callOrder.push(file.name);
    });

    const { results, cancelled } = await runSequentialGalleryIdentify(files, identifyFn, {
      onProgress: (p) => progressEvents.push({ current: p.current, total: p.total, name: p.file.name }),
      onFileSuccess: ({ file, index }) => successEvents.push({ name: file.name, index }),
    });

    expect(cancelled).toBe(false);
    expect(callOrder).toEqual(['a.jpg', 'b.jpg', 'c.jpg']);
    expect(identifyFn).toHaveBeenCalledTimes(3);
    expect(progressEvents).toEqual([
      { current: 1, total: 3, name: 'a.jpg' },
      { current: 2, total: 3, name: 'b.jpg' },
      { current: 3, total: 3, name: 'c.jpg' },
    ]);
    expect(successEvents).toEqual([
      { name: 'a.jpg', index: 0 },
      { name: 'b.jpg', index: 1 },
      { name: 'c.jpg', index: 2 },
    ]);
    expect(results).toHaveLength(3);
    expect(results.every((r) => r.ok === true)).toBe(true);
    expect(results.map((r) => r.file.name)).toEqual(['a.jpg', 'b.jpg', 'c.jpg']);
  });

  it('stops remaining files when cancelRef is set mid-batch', async () => {
    const files = [makeFile('a.jpg'), makeFile('b.jpg'), makeFile('c.jpg')];
    const cancelRef = { current: false };
    const identifyFn = vi.fn(async (file) => {
      if (file.name === 'a.jpg') {
        cancelRef.current = true;
      }
    });

    const { results, cancelled } = await runSequentialGalleryIdentify(files, identifyFn, {
      cancelRef,
    });

    expect(cancelled).toBe(true);
    expect(identifyFn).toHaveBeenCalledTimes(1);
    expect(results).toEqual([{ file: files[0], ok: true }]);
  });

  it('invokes onFileError and continues after a per-file failure', async () => {
    const files = [makeFile('a.jpg'), makeFile('b.jpg'), makeFile('c.jpg')];
    const err = new Error('identify failed');
    const errorEvents = [];
    const callOrder = [];

    const identifyFn = vi.fn(async (file) => {
      callOrder.push(file.name);
      if (file.name === 'b.jpg') {
        throw err;
      }
    });

    const { results, cancelled } = await runSequentialGalleryIdentify(files, identifyFn, {
      onFileError: ({ file, index, error }) => {
        errorEvents.push({ name: file.name, index, error });
      },
    });

    expect(cancelled).toBe(false);
    expect(callOrder).toEqual(['a.jpg', 'b.jpg', 'c.jpg']);
    expect(identifyFn).toHaveBeenCalledTimes(3);
    expect(errorEvents).toEqual([{ name: 'b.jpg', index: 1, error: err }]);
    expect(results).toHaveLength(3);
    expect(results[0]).toMatchObject({ file: files[0], ok: true });
    expect(results[1]).toMatchObject({ file: files[1], ok: false, error: err });
    expect(results[2]).toMatchObject({ file: files[2], ok: true });
  });

  it('accepts FileList-like iterables via Array.from', async () => {
    const files = [makeFile('solo.jpg')];
    const fileList = {
      length: files.length,
      0: files[0],
      [Symbol.iterator]() {
        let i = 0;
        return {
          next: () => {
            if (i < files.length) {
              return { value: files[i++], done: false };
            }
            return { done: true };
          },
        };
      },
    };

    const identifyFn = vi.fn(async () => {});
    const { results } = await runSequentialGalleryIdentify(fileList, identifyFn);

    expect(identifyFn).toHaveBeenCalledTimes(1);
    expect(results).toEqual([{ file: files[0], ok: true }]);
  });

  it('returns empty results for null/undefined files', async () => {
    const identifyFn = vi.fn(async () => {});
    const { results, cancelled } = await runSequentialGalleryIdentify(null, identifyFn);

    expect(cancelled).toBe(false);
    expect(results).toEqual([]);
    expect(identifyFn).not.toHaveBeenCalled();
  });
});
