import { pickModel } from './pick-model';

describe('pickModel', () => {
  // ── Null / undefined / invalid source ──────────────────────────
  describe('when source is nullish or invalid', () => {
    it('should return defaults when source is null', () => {
      expect(pickModel(null, { id: '', name: '' })).toEqual({ id: '', name: '' });
    });

    it('should return defaults when source is undefined', () => {
      expect(pickModel(undefined, { id: '', name: '' })).toEqual({ id: '', name: '' });
    });

    it('should return defaults when source is a primitive', () => {
      expect(pickModel(42, { id: '', name: '' })).toEqual({ id: '', name: '' });
    });

    it('should return a clone, not the same defaults reference', () => {
      const defaults = { id: '', name: '' };
      const result = pickModel(null, defaults);
      expect(result).not.toBe(defaults);
    });
  });

  // ── Flat primitives ────────────────────────────────────────────
  describe('flat primitives', () => {
    it('should pick matching keys from source', () => {
      const source = { id: '1', name: 'Tag A', createdAt: '2024-01-01' };
      const result = pickModel(source, { id: '', name: '' });
      expect(result).toEqual({ id: '1', name: 'Tag A' });
    });

    it('should use default for keys missing in source', () => {
      const source = { id: '1' };
      const result = pickModel(source, { id: '', name: 'untitled' });
      expect(result).toEqual({ id: '1', name: 'untitled' });
    });

    it('should use default when source value is null', () => {
      const source = { id: '1', name: null };
      const result = pickModel(source, { id: '', name: 'untitled' });
      expect(result).toEqual({ id: '1', name: 'untitled' });
    });

    it('should preserve falsy values that are not null/undefined', () => {
      const source = { id: '1', count: 0, active: false, label: '' };
      const result = pickModel(source, { id: '', count: 99, active: true, label: 'default' });
      expect(result).toEqual({ id: '1', count: 0, active: false, label: '' });
    });

    it('should never leak extra properties from source', () => {
      const source = { id: '1', name: 'Tag', secret: 'leaked', internal: true };
      const result = pickModel(source, { id: '', name: '' });
      expect(result).toEqual({ id: '1', name: 'Tag' });
      expect('secret' in result).toBe(false);
      expect('internal' in result).toBe(false);
    });
  });

  // ── Nested objects ─────────────────────────────────────────────
  describe('nested objects', () => {
    it('should deep-pick nested object keys', () => {
      const source = {
        id: '1',
        address: { street: '123 Main', city: 'NYC', zipCode: '10001' },
      };
      const result = pickModel(source, {
        id: '',
        address: { street: '', city: '' },
      });
      expect(result).toEqual({
        id: '1',
        address: { street: '123 Main', city: 'NYC' },
      });
    });

    it('should not leak extra keys from nested objects', () => {
      const source = {
        id: '1',
        address: { street: '123 Main', city: 'NYC', zipCode: '10001' },
      };
      const result = pickModel(source, { id: '', address: { street: '', city: '' } });
      expect('zipCode' in result.address).toBe(false);
    });

    it('should use nested defaults when nested source is missing', () => {
      const source = { id: '1' };
      const result = pickModel(source, {
        id: '',
        address: { street: '', city: '' },
      });
      expect(result).toEqual({
        id: '1',
        address: { street: '', city: '' },
      });
    });

    it('should use nested defaults when nested source is null', () => {
      const source = { id: '1', address: null };
      const result = pickModel(source, {
        id: '',
        address: { street: '', city: '' },
      });
      expect(result).toEqual({
        id: '1',
        address: { street: '', city: '' },
      });
    });
  });

  // ── Deep nesting (3+ levels) ──────────────────────────────────
  describe('deep nesting', () => {
    it('should recurse through multiple levels', () => {
      const source = {
        id: '1',
        customer: {
          name: 'Acme',
          contact: { email: 'a@b.com', phone: '555', fax: '666' },
          internalCode: 'X99',
        },
      };
      const result = pickModel(source, {
        id: '',
        customer: { name: '', contact: { email: '', phone: '' } },
      });
      expect(result).toEqual({
        id: '1',
        customer: { name: 'Acme', contact: { email: 'a@b.com', phone: '555' } },
      });
      expect('fax' in result.customer.contact).toBe(false);
      expect('internalCode' in result.customer).toBe(false);
    });
  });

  // ── Arrays of primitives ──────────────────────────────────────
  describe('arrays of primitives', () => {
    it('should copy array of strings from source', () => {
      const source = { id: '1', tags: ['angular', 'signals'] };
      const result = pickModel(source, { id: '', tags: [''] });
      expect(result).toEqual({ id: '1', tags: ['angular', 'signals'] });
    });

    it('should copy array of numbers from source', () => {
      const source = { id: '1', scores: [10, 20, 30] };
      const result = pickModel(source, { id: '', scores: [0] });
      expect(result).toEqual({ id: '1', scores: [10, 20, 30] });
    });

    it('should use default when source array is missing', () => {
      const source = { id: '1' };
      const result = pickModel(source, { id: '', tags: ['default'] });
      expect(result).toEqual({ id: '1', tags: ['default'] });
    });

    it('should not share reference with source array', () => {
      const sourceTags = ['angular', 'signals'];
      const source = { tags: sourceTags };
      const result = pickModel(source, { tags: [''] });
      expect(result.tags).not.toBe(sourceTags);
    });
  });

  // ── Arrays of objects ─────────────────────────────────────────
  describe('arrays of objects', () => {
    it('should deep-pick each item using defaults[0] as template', () => {
      const source = {
        id: '1',
        lines: [
          { product: 'Widget', qty: 5, internalSku: 'W-001' },
          { product: 'Gadget', qty: 3, internalSku: 'G-002' },
        ],
      };
      const result = pickModel(source, {
        id: '',
        lines: [{ product: '', qty: 0 }],
      });
      expect(result).toEqual({
        id: '1',
        lines: [
          { product: 'Widget', qty: 5 },
          { product: 'Gadget', qty: 3 },
        ],
      });
    });

    it('should not leak extra keys from array items', () => {
      const source = {
        lines: [{ product: 'Widget', qty: 5, internalSku: 'W-001' }],
      };
      const result = pickModel(source, { lines: [{ product: '', qty: 0 }] });
      expect('internalSku' in result.lines[0]).toBe(false);
    });

    it('should handle empty source array', () => {
      const source = { id: '1', lines: [] };
      const result = pickModel(source, { id: '', lines: [{ product: '', qty: 0 }] });
      expect(result).toEqual({ id: '1', lines: [] });
    });

    it('should use default when source array is missing', () => {
      const source = { id: '1' };
      const result = pickModel(source, {
        id: '',
        lines: [{ product: '', qty: 0 }],
      });
      expect(result).toEqual({
        id: '1',
        lines: [{ product: '', qty: 0 }],
      });
    });
  });

  // ── Combined / complex structures ─────────────────────────────
  describe('combined structures', () => {
    it('should handle nested objects inside array items', () => {
      const source = {
        id: '1',
        lines: [
          {
            product: 'Widget',
            supplier: { name: 'Acme', code: 'A1', region: 'EU' },
          },
        ],
      };
      const result = pickModel(source, {
        id: '',
        lines: [{ product: '', supplier: { name: '', code: '' } }],
      });
      expect(result).toEqual({
        id: '1',
        lines: [{ product: 'Widget', supplier: { name: 'Acme', code: 'A1' } }],
      });
    });

    it('should handle arrays of primitives inside array items', () => {
      const source = {
        lines: [{ product: 'Widget', tags: ['sale', 'new'], secret: 'x' }],
      };
      const result = pickModel(source, {
        lines: [{ product: '', tags: [''] }],
      });
      expect(result).toEqual({
        lines: [{ product: 'Widget', tags: ['sale', 'new'] }],
      });
      expect('secret' in result.lines[0]).toBe(false);
    });

    it('should handle a full complex form structure', () => {
      const source = {
        id: '1',
        name: 'Order #42',
        customer: {
          name: 'Acme Corp',
          contact: { email: 'a@b.com', phone: '555', fax: '666' },
          taxId: 'XX-123',
        },
        lines: [
          { product: 'Widget', qty: 5, tags: ['promo'], notes: 'rush' },
          { product: 'Gadget', qty: 2, tags: ['sale', 'clearance'], notes: 'fragile' },
        ],
        metadata: { createdBy: 'admin', ip: '1.2.3.4' },
      };

      const result = pickModel(source, {
        id: '',
        name: '',
        customer: {
          name: '',
          contact: { email: '', phone: '' },
        },
        lines: [{ product: '', qty: 0, tags: [''] }],
      });

      expect(result).toEqual({
        id: '1',
        name: 'Order #42',
        customer: {
          name: 'Acme Corp',
          contact: { email: 'a@b.com', phone: '555' },
        },
        lines: [
          { product: 'Widget', qty: 5, tags: ['promo'] },
          { product: 'Gadget', qty: 2, tags: ['sale', 'clearance'] },
        ],
      });
      expect('metadata' in result).toBe(false);
      expect('taxId' in result.customer).toBe(false);
      expect('fax' in result.customer.contact).toBe(false);
      expect('notes' in result.lines[0]).toBe(false);
    });
  });

  // ── Immutability ──────────────────────────────────────────────
  describe('immutability', () => {
    it('should not mutate the source object', () => {
      const source = { id: '1', name: 'Tag', extra: true };
      const copy = { ...source };
      pickModel(source, { id: '', name: '' });
      expect(source).toEqual(copy);
    });

    it('should not mutate the defaults object', () => {
      const defaults = { id: '', address: { street: '', city: '' } };
      const copy = structuredClone(defaults);
      pickModel({ id: '1' }, defaults);
      expect(defaults).toEqual(copy);
    });

    it('should not share nested references with source', () => {
      const sourceAddress = { street: '123 Main', city: 'NYC' };
      const source = { id: '1', address: sourceAddress };
      const result = pickModel(source, { id: '', address: { street: '', city: '' } });
      expect(result.address).not.toBe(sourceAddress);
    });
  });
});
