/**
 * Tests for mergeWithDefaults utilities
 */

import {
  safeValue,
  isNumber,
  isString,
  isBoolean,
  isArray,
  isObject,
  deepMergeWithDefaults,
  createFormInitializer,
} from '@/lib/forms/utils/mergeWithDefaults';

describe('Type Guards', () => {
  describe('isNumber', () => {
    it('returns true for valid numbers', () => {
      expect(isNumber(0)).toBe(true);
      expect(isNumber(42)).toBe(true);
      expect(isNumber(-10)).toBe(true);
      expect(isNumber(3.14)).toBe(true);
    });

    it('returns false for NaN', () => {
      expect(isNumber(NaN)).toBe(false);
    });

    it('returns false for non-numbers', () => {
      expect(isNumber('42')).toBe(false);
      expect(isNumber(null)).toBe(false);
      expect(isNumber(undefined)).toBe(false);
      expect(isNumber({})).toBe(false);
    });
  });

  describe('isString', () => {
    it('returns true for strings', () => {
      expect(isString('')).toBe(true);
      expect(isString('hello')).toBe(true);
    });

    it('returns false for non-strings', () => {
      expect(isString(42)).toBe(false);
      expect(isString(null)).toBe(false);
      expect(isString(undefined)).toBe(false);
    });
  });

  describe('isBoolean', () => {
    it('returns true for booleans', () => {
      expect(isBoolean(true)).toBe(true);
      expect(isBoolean(false)).toBe(true);
    });

    it('returns false for non-booleans', () => {
      expect(isBoolean(0)).toBe(false);
      expect(isBoolean(1)).toBe(false);
      expect(isBoolean('true')).toBe(false);
    });
  });

  describe('isArray', () => {
    it('returns true for arrays', () => {
      expect(isArray([])).toBe(true);
      expect(isArray([1, 2, 3])).toBe(true);
    });

    it('returns false for non-arrays', () => {
      expect(isArray({})).toBe(false);
      expect(isArray('array')).toBe(false);
      expect(isArray(null)).toBe(false);
    });

    it('validates item types when itemCheck provided', () => {
      expect(isArray([1, 2, 3], isNumber)).toBe(true);
      expect(isArray(['a', 'b'], isString)).toBe(true);
      expect(isArray([1, 'two', 3], isNumber)).toBe(false);
    });
  });

  describe('isObject', () => {
    it('returns true for plain objects', () => {
      expect(isObject({})).toBe(true);
      expect(isObject({ key: 'value' })).toBe(true);
    });

    it('returns false for null', () => {
      expect(isObject(null)).toBe(false);
    });

    it('returns false for arrays', () => {
      expect(isObject([])).toBe(false);
    });

    it('returns false for primitives', () => {
      expect(isObject('string')).toBe(false);
      expect(isObject(42)).toBe(false);
    });
  });
});

describe('safeValue', () => {
  it('returns value when type check passes', () => {
    expect(safeValue(42, 0, isNumber)).toBe(42);
    expect(safeValue('hello', '', isString)).toBe('hello');
  });

  it('returns default when type check fails', () => {
    expect(safeValue('not a number', 0, isNumber)).toBe(0);
    expect(safeValue(42, '', isString)).toBe('');
  });

  it('returns default for null/undefined', () => {
    expect(safeValue(null, 0, isNumber)).toBe(0);
    expect(safeValue(undefined, 'default', isString)).toBe('default');
  });
});

describe('deepMergeWithDefaults', () => {
  it('returns defaults when source is null', () => {
    const defaults = { name: 'default', value: 10 };
    expect(deepMergeWithDefaults(defaults, null)).toEqual(defaults);
  });

  it('returns defaults when source is undefined', () => {
    const defaults = { name: 'default', value: 10 };
    expect(deepMergeWithDefaults(defaults, undefined)).toEqual(defaults);
  });

  it('merges source values over defaults', () => {
    const defaults = { name: 'default', value: 10 };
    const source = { name: 'custom' };
    expect(deepMergeWithDefaults(defaults, source)).toEqual({
      name: 'custom',
      value: 10,
    });
  });

  it('preserves default for missing source keys', () => {
    const defaults = { a: 1, b: 2, c: 3 };
    const source = { a: 10 };
    expect(deepMergeWithDefaults(defaults, source)).toEqual({
      a: 10,
      b: 2,
      c: 3,
    });
  });

  it('recursively merges nested objects', () => {
    const defaults = {
      config: { temperature: 0.7, max_tokens: 8192 },
    };
    // Source has partial nested config - deepMerge fills in missing fields
    const source = {
      config: { temperature: 0.5 },
    } as unknown as Partial<typeof defaults>;
    expect(deepMergeWithDefaults(defaults, source)).toEqual({
      config: { temperature: 0.5, max_tokens: 8192 },
    });
  });

  it('only uses source values if types match', () => {
    const defaults = { value: 10, name: 'default' };
    const source = { value: 'not a number' as unknown as number };
    expect(deepMergeWithDefaults(defaults, source)).toEqual({
      value: 10,
      name: 'default',
    });
  });

  it('handles arrays - uses source array if types match', () => {
    const defaults = { items: ['a', 'b'] };
    const source = { items: ['c', 'd', 'e'] };
    expect(deepMergeWithDefaults(defaults, source)).toEqual({
      items: ['c', 'd', 'e'],
    });
  });

  it('skips undefined source values', () => {
    const defaults = { name: 'default' };
    const source = { name: undefined };
    expect(deepMergeWithDefaults(defaults, source)).toEqual({
      name: 'default',
    });
  });

  it('handles null values when defaults are null', () => {
    const defaults = { value: null };
    const source = { value: null };
    expect(deepMergeWithDefaults(defaults, source)).toEqual({ value: null });
  });

  it('uses source value when default is null but source has value', () => {
    const defaults = { description: null as string | null, name: '' };
    const source = { description: 'A real description', name: 'Test' };
    expect(deepMergeWithDefaults(defaults, source)).toEqual({
      description: 'A real description',
      name: 'Test',
    });
  });
});

describe('createFormInitializer', () => {
  it('returns defaults when called with null', () => {
    const defaults = { name: '', age: 0 };
    const initializer = createFormInitializer(defaults);
    expect(initializer(null)).toEqual(defaults);
  });

  it('returns defaults when called with undefined', () => {
    const defaults = { name: '', age: 0 };
    const initializer = createFormInitializer(defaults);
    expect(initializer(undefined)).toEqual(defaults);
  });

  it('merges API data with defaults', () => {
    const defaults = { name: '', age: 0, active: false };
    const initializer = createFormInitializer(defaults);
    const result = initializer({ name: 'John', age: 25 });
    expect(result).toEqual({ name: 'John', age: 25, active: false });
  });

  it('uses custom transform function when provided', () => {
    interface Form {
      fullName: string;
      isActive: boolean;
    }
    interface Api {
      first_name: string;
      last_name: string;
      active: boolean;
    }

    const defaults: Form = { fullName: '', isActive: false };
    const initializer = createFormInitializer<Form, Api>(defaults, (apiData, defs) => ({
      fullName: apiData ? `${apiData.first_name} ${apiData.last_name}` : defs.fullName,
      isActive: apiData?.active ?? defs.isActive,
    }));

    const result = initializer({
      first_name: 'John',
      last_name: 'Doe',
      active: true,
    });
    expect(result).toEqual({ fullName: 'John Doe', isActive: true });
  });

  it('transform receives defaults when apiData is null', () => {
    const defaults = { name: 'default' };
    const initializer = createFormInitializer(defaults, (_, defs) => ({
      name: defs.name.toUpperCase(),
    }));
    expect(initializer(null)).toEqual({ name: 'DEFAULT' });
  });
});
