/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { renderHook } from '../../test-utils/render.js';
import { act } from 'react';
import {
  describe,
  it,
  expect,
  vi,
  beforeEach,
  afterEach,
  type Mock,
} from 'vitest';
import { useInlineEditBuffer } from './useInlineEditBuffer.js';

describe('useEditBuffer', () => {
  let mockOnCommit: Mock;

  beforeEach(() => {
    vi.useFakeTimers();
    vi.clearAllMocks();
    mockOnCommit = vi.fn();
  });

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

  it('should initialize with empty state', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );
    expect(result.current.editState.editingKey).toBeNull();
    expect(result.current.editState.buffer).toBe('');
    expect(result.current.editState.cursorPos).toBe(0);
  });

  it('should start editing correctly', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );
    act(() => result.current.startEditing('my-key', 'initial'));

    expect(result.current.editState.editingKey).toBe('my-key');
    expect(result.current.editState.buffer).toBe('initial');
    expect(result.current.editState.cursorPos).toBe(7); // End of string
  });

  it('should commit edit and reset state', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );

    act(() => result.current.startEditing('my-key', 'text'));
    act(() => result.current.commitEdit());

    expect(mockOnCommit).toHaveBeenCalledWith('my-key', 'text');
    expect(result.current.editState.editingKey).toBeNull();
    expect(result.current.editState.buffer).toBe('');
  });

  it('should move cursor left and right', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );
    act(() => result.current.startEditing('key', 'ab')); // cursor at 2

    act(() => result.current.editDispatch({ type: 'MOVE_LEFT' }));
    expect(result.current.editState.cursorPos).toBe(1);

    act(() => result.current.editDispatch({ type: 'MOVE_LEFT' }));
    expect(result.current.editState.cursorPos).toBe(0);

    // Shouldn't go below 0
    act(() => result.current.editDispatch({ type: 'MOVE_LEFT' }));
    expect(result.current.editState.cursorPos).toBe(0);

    act(() => result.current.editDispatch({ type: 'MOVE_RIGHT' }));
    expect(result.current.editState.cursorPos).toBe(1);
  });

  it('should handle home and end', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );
    act(() => result.current.startEditing('key', 'testing')); // cursor at 7

    act(() => result.current.editDispatch({ type: 'HOME' }));
    expect(result.current.editState.cursorPos).toBe(0);

    act(() => result.current.editDispatch({ type: 'END' }));
    expect(result.current.editState.cursorPos).toBe(7);
  });

  it('should delete characters to the left (backspace)', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );
    act(() => result.current.startEditing('key', 'abc')); // cursor at 3

    act(() => result.current.editDispatch({ type: 'DELETE_LEFT' }));
    expect(result.current.editState.buffer).toBe('ab');
    expect(result.current.editState.cursorPos).toBe(2);

    // Move to start, shouldn't delete
    act(() => result.current.editDispatch({ type: 'HOME' }));
    act(() => result.current.editDispatch({ type: 'DELETE_LEFT' }));
    expect(result.current.editState.buffer).toBe('ab');
  });

  it('should delete characters to the right (delete tab)', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );
    act(() => result.current.startEditing('key', 'abc'));
    act(() => result.current.editDispatch({ type: 'HOME' })); // cursor at 0

    act(() => result.current.editDispatch({ type: 'DELETE_RIGHT' }));
    expect(result.current.editState.buffer).toBe('bc');
    expect(result.current.editState.cursorPos).toBe(0);
  });

  it('should insert valid characters into string', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );
    act(() => result.current.startEditing('key', 'ab'));
    act(() => result.current.editDispatch({ type: 'MOVE_LEFT' })); // cursor at 1

    act(() =>
      result.current.editDispatch({
        type: 'INSERT_CHAR',
        char: 'x',
        isNumberType: false,
      }),
    );
    expect(result.current.editState.buffer).toBe('axb');
    expect(result.current.editState.cursorPos).toBe(2);
  });

  it('should validate number character insertions', async () => {
    const { result } = await renderHook(() =>
      useInlineEditBuffer({ onCommit: mockOnCommit }),
    );
    act(() => result.current.startEditing('key', '12'));

    // Valid number char
    act(() =>
      result.current.editDispatch({
        type: 'INSERT_CHAR',
        char: '.',
        isNumberType: true,
      }),
    );
    expect(result.current.editState.buffer).toBe('12.');

    // Invalid number char
    act(() =>
      result.current.editDispatch({
        type: 'INSERT_CHAR',
        char: 'a',
        isNumberType: true,
      }),
    );
    expect(result.current.editState.buffer).toBe('12.'); // Unchanged
  });
});
