import { renderHook } from '@testing-library/react';
import { getBottomOffset, useAiraTriggerPosition } from './useAiraTriggerPosition';

describe('useAiraTriggerPosition', () => {
  let resizeObserverCallback;
  let observeMock;
  let disconnectMock;

  beforeEach(() => {
    observeMock = jest.fn();
    disconnectMock = jest.fn();
    resizeObserverCallback = null;

    global.ResizeObserver = jest.fn((cb) => {
      resizeObserverCallback = cb;
      return {
        observe: observeMock,
        disconnect: disconnectMock,
      };
    });
  });

  afterEach(() => {
    jest.clearAllMocks();
    global.ResizeObserver = jest.fn((cb) => {
      resizeObserverCallback = cb;
      return {
        observe: observeMock,
        disconnect: disconnectMock,
      };
    });
  });

  const createRefs = () => {
    const wrapperElement = document.createElement('div');
    const inputElement = document.createElement('textarea');
    wrapperElement.appendChild(inputElement);

    const wrapperRef = { current: wrapperElement };
    const inputRef = { current: inputElement };

    return { wrapperElement, inputElement, wrapperRef, inputRef };
  };

  it('should return default style when there is no error', () => {
    const { wrapperRef, inputRef } = createRefs();

    const { result } = renderHook(() => useAiraTriggerPosition({
      wrapperRef,
      inputRef,
      rightOffset: '2.5rem',
      value: 'hello',
      error: '',
    }));

    expect(result.current).toEqual({
      bottom: '0.83rem',
      right: '2.5rem',
      left: 'auto',
    });
  });

  it('should compute bottom offset when error is present', () => {
    const { wrapperElement, inputElement, wrapperRef, inputRef } = createRefs();

    const errorElement = document.createElement('div');
    errorElement.className = 'webpush-template-message-error';
    wrapperElement.appendChild(errorElement);

    wrapperElement.getBoundingClientRect = jest.fn(() => ({
      top: 0,
      bottom: 160,
      height: 160,
      left: 0,
      right: 100,
      width: 100,
    }));
    inputElement.getBoundingClientRect = jest.fn(() => ({
      top: 20,
      bottom: 100,
      height: 80,
      left: 0,
      right: 100,
      width: 100,
    }));

    const { result } = renderHook(() => useAiraTriggerPosition({
      wrapperRef,
      inputRef,
      rightOffset: '2.5rem',
      value: 'hello',
      error: 'Required',
    }));

    expect(result.current).toEqual({
      bottom: '64px',
      right: '2.5rem',
      left: 'auto',
    });
  });

  it('should observe wrapper and input for layout changes', () => {
    const { wrapperElement, inputElement, wrapperRef, inputRef } = createRefs();

    renderHook(() => useAiraTriggerPosition({
      wrapperRef,
      inputRef,
      rightOffset: '2.5rem',
      value: '',
      error: '',
    }));

    expect(observeMock).toHaveBeenCalledWith(wrapperElement);
    expect(observeMock).toHaveBeenCalledWith(inputElement);
    expect(typeof resizeObserverCallback).toBe('function');
  });

  it('should invoke updatePosition when ResizeObserver fires', () => {
    const { wrapperElement, inputElement, wrapperRef, inputRef } = createRefs();

    renderHook(() => useAiraTriggerPosition({
      wrapperRef,
      inputRef,
      rightOffset: '2.5rem',
      error: '',
    }));

    expect(resizeObserverCallback).toBeTruthy();
    resizeObserverCallback();
    expect(observeMock).toHaveBeenCalledWith(wrapperElement);
    expect(observeMock).toHaveBeenCalledWith(inputElement);
  });

  it('should not observe input when inputRef has no current element', () => {
    const { wrapperElement, wrapperRef } = createRefs();

    renderHook(() => useAiraTriggerPosition({
      wrapperRef,
      inputRef: { current: null },
      rightOffset: '2.5rem',
      error: '',
    }));

    expect(observeMock).toHaveBeenCalledWith(wrapperElement);
    expect(observeMock).toHaveBeenCalledTimes(1);
  });

  it('should use default right offset when omitted', () => {
    const { wrapperRef, inputRef } = createRefs();

    const { result } = renderHook(() => useAiraTriggerPosition({
      wrapperRef,
      inputRef,
      error: '',
    }));

    expect(result.current.right).toBe('2.5rem');
  });

  it('should use default bottom when wrapper ref is not attached', () => {
    const wrapperRef = { current: null };
    const inputRef = { current: null };

    const { result } = renderHook(() => useAiraTriggerPosition({
      wrapperRef,
      inputRef,
      rightOffset: '2.5rem',
      error: '',
    }));

    expect(result.current).toEqual({
      bottom: '0.83rem',
      right: '2.5rem',
      left: 'auto',
    });
  });

  it('should skip disconnect when ResizeObserver is unavailable', () => {
    const saved = global.ResizeObserver;
    delete global.ResizeObserver;

    const { wrapperRef, inputRef } = createRefs();

    const { unmount } = renderHook(() => useAiraTriggerPosition({
      wrapperRef,
      inputRef,
      rightOffset: '2.5rem',
      error: '',
    }));

    expect(disconnectMock).not.toHaveBeenCalled();
    unmount();
    expect(disconnectMock).not.toHaveBeenCalled();

    global.ResizeObserver = saved;
  });
});

describe('getBottomOffset', () => {
  it('returns default when wrapper element is missing', () => {
    const input = document.createElement('textarea');
    expect(getBottomOffset(null, input, true)).toBe('0.83rem');
  });

  it('returns default when input element is missing', () => {
    const wrapper = document.createElement('div');
    expect(getBottomOffset(wrapper, null, true)).toBe('0.83rem');
  });
});

