import type { ReactNode } from 'react';
import { describe, expect, test, beforeEach } from 'bun:test';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook } from '@testing-library/react';
import { usePromptHistory } from './usePromptHistory';

/**
 * Shell-style prompt recall. The interesting state is two refs — where you are in the list, and the draft
 * you were composing when you left it — and every bug this can have looks the same from outside: an arrow
 * key that pastes the wrong thing over what you were typing.
 */

const wrapper = ({ children }: { children: ReactNode }) => (
  <QueryClientProvider client={new QueryClient({ defaultOptions: { queries: { retry: false } } })}>
    {children}
  </QueryClientProvider>
);

const mount = () => renderHook(() => usePromptHistory(), { wrapper }).result;

beforeEach(() => {
  sessionStorage.clear();
});

describe('walking back through what was sent', () => {
  test('Up reaches the newest first, then older, and stops at the oldest', () => {
    const { current } = mount();
    current.record('one');
    current.record('two');

    expect(current.step(-1, '')).toBe('two');
    expect(current.step(-1, '')).toBe('one');
    // Null rather than sticking at 'one': the caller leaves the key alone, so it moves the caret instead
    // of silently doing nothing.
    expect(current.step(-1, '')).toBeNull();
  });

  test('Down comes back, and hands over the draft that was interrupted', () => {
    const { current } = mount();
    current.record('one');
    current.record('two');

    expect(current.step(-1, 'half-typed')).toBe('two');
    expect(current.step(-1, 'half-typed')).toBe('one');
    expect(current.step(1, 'one')).toBe('two');
    // Past the newest is the composer you were in, not an empty box. Losing what you had typed to a key
    // pressed to *get back to it* would be the worst version of this feature.
    expect(current.step(1, 'two')).toBe('half-typed');
  });

  test('Down while composing does nothing — it is only a caret move', () => {
    const { current } = mount();
    current.record('one');

    expect(current.step(1, 'typing')).toBeNull();
  });

  test('an empty history never takes the key', () => {
    const { current } = mount();

    expect(current.step(-1, '')).toBeNull();
    expect(current.step(1, '')).toBeNull();
  });
});

describe('what gets remembered', () => {
  test('re-sending a prompt moves it to the front instead of holding it twice', () => {
    const { current } = mount();
    current.record('one');
    current.record('two');
    current.record('one');

    expect(current.step(-1, '')).toBe('one');
    expect(current.step(-1, '')).toBe('two');
    expect(current.step(-1, '')).toBeNull();
  });

  test('it keeps ten, dropping the oldest', () => {
    const { current } = mount();
    for (let i = 1; i <= 12; i++) current.record(`p${i}`);

    expect(current.step(-1, '')).toBe('p12');
    for (let i = 0; i < 9; i++) current.step(-1, '');
    expect(current.step(-1, '')).toBeNull(); // ten deep, so p2 and p1 are gone
  });

  test('blank sends are not remembered', () => {
    const { current } = mount();
    current.record('   ');

    expect(current.step(-1, '')).toBeNull();
  });

  test('sending resets the position, so the next Up starts from the newest again', () => {
    const { current } = mount();
    current.record('one');
    current.record('two');
    current.step(-1, '');
    current.step(-1, ''); // sitting on 'one'

    current.record('three');

    expect(current.step(-1, '')).toBe('three');
  });
});
