import { describe, test, expect } from 'vitest';
import { kData, kPatch } from '../src/utils/symbols.js';
import type { APIData } from './mixinClasses.js';
import { Base, Mixed, MixedWithExtended } from './mixinClasses.js';

describe('Mixin function', () => {
	const data: APIData = {
		id: '1',
		property1: 23,
	};

	test('Mixed class has all getters', () => {
		const instance = new Mixed(data);
		expect(instance.id).toBe(data.id);
		expect(instance.property1).toBe(data.property1);
		expect(instance.property2).toBe(data.property2);
	});

	test('Mixed class has all methods', () => {
		const instance = new Mixed(data);
		expect(instance.getId()).toBe(data.id);
		expect(instance.getProperty1()).toBe(data.property1);
		expect(instance.getProperty2()).toBe(data.property2);
		expect(instance.getProperties()).toEqual({
			property1: data.property1,
			property2: data.property2,
		});
	});

	test('Mixed with extended class has all getters', () => {
		const instance = new MixedWithExtended(data);
		expect(instance.id).toBe(data.id);
		expect(instance.property1).toBe(data.property1);
		expect(instance.property2).toBe(data.property2);
		expect(instance.isExtended).toBe(true);
	});

	test('Mixed with extended class has all methods', () => {
		const instance = new MixedWithExtended(data);
		expect(instance.getId()).toBe(data.id);
		expect(instance.getProperty1()).toBe(data.property1);
		expect(instance.getProperty2()).toBe(data.property2);
		expect(instance.getProperties()).toEqual({
			property1: data.property1,
			property2: data.property2,
		});
	});

	test('Mixed class calls construct methods on construct', () => {
		const instance1 = new Mixed(data);
		const instance2 = new MixedWithExtended(data);
		expect(instance1.constructCalled).toBe(true);
		expect(instance2.constructCalled).toBe(true);
	});

	test('Mixed class respects mixin data optimizations', () => {
		expect(typeof Object.getOwnPropertyDescriptor(Mixed.DataTemplate, 'mixinOptimize')?.set).toBe('function');
		const missingOptimizedInstance = new Mixed(data);
		const alreadyOptimizedInstance = new Mixed({ ...data, mixinOptimize: 'true', baseOptimize: 'true' });
		const baseOptimizedInstance = new Base({ ...data, mixinOptimize: 'true', baseOptimize: 'true' });

		expect(missingOptimizedInstance.baseOptimize).toBe(null);
		expect(missingOptimizedInstance.mixinOptimize).toBe(null);
		// Setters pass this
		expect('baseOptimize' in missingOptimizedInstance[kData]).toBe(true);
		expect('mixinOptimize' in missingOptimizedInstance[kData]).toBe(true);
		expect(missingOptimizedInstance[kData].baseOptimize).toBeUndefined();
		expect(missingOptimizedInstance[kData].mixinOptimize).toBeUndefined();

		expect(alreadyOptimizedInstance.baseOptimize).toBe(true);
		expect(alreadyOptimizedInstance.mixinOptimize).toBe(true);
		// Setters pass this
		expect('baseOptimize' in alreadyOptimizedInstance[kData]).toBe(true);
		expect('mixinOptimize' in alreadyOptimizedInstance[kData]).toBe(true);
		expect(alreadyOptimizedInstance[kData].baseOptimize).toBeUndefined();
		expect(alreadyOptimizedInstance[kData].mixinOptimize).toBeUndefined();
		expect(alreadyOptimizedInstance.toJSON()).toEqual({ ...data, mixinOptimize: 'true', baseOptimize: 'true' });

		alreadyOptimizedInstance[kPatch]({ mixinOptimize: '', baseOptimize: '' });

		expect(alreadyOptimizedInstance.baseOptimize).toBe(false);
		expect(alreadyOptimizedInstance.mixinOptimize).toBe(false);
		// Setters pass this
		expect('baseOptimize' in alreadyOptimizedInstance[kData]).toBe(true);
		expect('mixinOptimize' in alreadyOptimizedInstance[kData]).toBe(true);
		expect(alreadyOptimizedInstance[kData].baseOptimize).toBeUndefined();
		expect(alreadyOptimizedInstance[kData].mixinOptimize).toBeUndefined();

		// Ensure mixin optimizations don't happen on base (ie overwritten DataTemplate)
		expect(baseOptimizedInstance.baseOptimize).toBe(true);
		expect('mixinOptimize' in baseOptimizedInstance).toBe(false);
		// Setters pass this
		expect('baseOptimize' in baseOptimizedInstance[kData]).toBe(true);
		expect('mixinOptimize' in baseOptimizedInstance[kData]).toBe(true);
		expect(baseOptimizedInstance[kData].baseOptimize).toBeUndefined();
		expect(baseOptimizedInstance[kData].mixinOptimize).toBe('true');
	});
});
