import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/svelte';
import { pageUrlModule } from '$test-utils/mocks/appState';

const state = vi.hoisted(() => ({
  pageUrl: new URL('http://localhost/')
}));

vi.mock('$app/state', () => pageUrlModule(state));

vi.mock('$app/navigation', () => ({
  goto: vi.fn()
}));

import SearchInput from './SearchInput.svelte';
import { goto } from '$app/navigation';

beforeEach(() => {
  vi.useFakeTimers();
  state.pageUrl = new URL('http://localhost/');
});

afterEach(() => {
  vi.clearAllMocks();
  vi.useRealTimers();
});

describe('SearchInput', () => {
  test('mount pre-fills value from page.url.searchParams', () => {
    state.pageUrl = new URL('http://localhost/search?q=hello');
    render(SearchInput);
    const input = screen.getByRole('searchbox') as HTMLInputElement;
    expect(input.value).toBe('hello');
  });

  test('typing fires goto once after 250ms debounce', async () => {
    render(SearchInput);
    const input = screen.getByRole('searchbox') as HTMLInputElement;
    await fireEvent.input(input, { target: { value: 'h' } });
    expect(goto).not.toHaveBeenCalled();
    vi.advanceTimersByTime(250);
    expect(goto).toHaveBeenCalledTimes(1);
    expect(goto).toHaveBeenCalledWith('/search?q=h', { replaceState: false, keepFocus: true });
  });

  test('rapid keystrokes debounce to a single goto', async () => {
    render(SearchInput);
    const input = screen.getByRole('searchbox') as HTMLInputElement;
    await fireEvent.input(input, { target: { value: 'h' } });
    vi.advanceTimersByTime(100);
    await fireEvent.input(input, { target: { value: 'he' } });
    vi.advanceTimersByTime(100);
    await fireEvent.input(input, { target: { value: 'hel' } });
    vi.advanceTimersByTime(250);
    expect(goto).toHaveBeenCalledTimes(1);
    expect(goto).toHaveBeenCalledWith('/search?q=hel', { replaceState: false, keepFocus: true });
  });

  test('typing while on /search uses replaceState=true', async () => {
    state.pageUrl = new URL('http://localhost/search?q=h');
    render(SearchInput);
    const input = screen.getByRole('searchbox') as HTMLInputElement;
    await fireEvent.input(input, { target: { value: 'he' } });
    vi.advanceTimersByTime(250);
    expect(goto).toHaveBeenCalledWith('/search?q=he', { replaceState: true, keepFocus: true });
  });

  test('clearing input on /search calls goto("/search") (drops ?q=)', async () => {
    state.pageUrl = new URL('http://localhost/search?q=h');
    render(SearchInput);
    const input = screen.getByRole('searchbox') as HTMLInputElement;
    await fireEvent.input(input, { target: { value: '' } });
    vi.advanceTimersByTime(250);
    expect(goto).toHaveBeenCalledWith('/search', { replaceState: true, keepFocus: true });
  });

  test('Escape clears the input value', async () => {
    render(SearchInput);
    const input = screen.getByRole('searchbox') as HTMLInputElement;
    await fireEvent.input(input, { target: { value: 'hello' } });
    expect(input.value).toBe('hello');
    await fireEvent.keyDown(input, { key: 'Escape' });
    expect(input.value).toBe('');
  });

  test('encodeURIComponent applied to special chars', async () => {
    render(SearchInput);
    const input = screen.getByRole('searchbox') as HTMLInputElement;
    await fireEvent.input(input, { target: { value: 'a b&c' } });
    vi.advanceTimersByTime(250);
    expect(goto).toHaveBeenCalledWith('/search?q=a%20b%26c', { replaceState: false, keepFocus: true });
  });
});
