/**
 * Tests for KTRepeater component
 */

import { describe, it, expect, afterEach } from 'vitest';
import { KTRepeater } from '../repeater';

function createFixture(options?: {
	withInitialChild?: boolean;
	limit?: number;
	hiddenTemplate?: boolean;
	withDeleteInTarget?: boolean;
}) {
	document.body.innerHTML = '';
	const container = document.createElement('div');
	container.id = 'repeater-test-container';

	const wrapper = document.createElement('div');
	wrapper.id = 'repeater-wrapper';
	wrapper.className = 'space-y-3';
	container.appendChild(wrapper);

	const target = document.createElement('div');
	target.id = 'repeater-target';
	const input = document.createElement('input');
	input.type = 'text';
	input.placeholder = 'Enter Name';
	target.appendChild(input);
	if (options?.withDeleteInTarget) {
		const removeBtn = document.createElement('span');
		removeBtn.setAttribute('data-kt-repeater-delete', '');
		removeBtn.className = 'remove-btn';
		target.appendChild(removeBtn);
	}

	if (options?.hiddenTemplate) {
		const hidden = document.createElement('div');
		hidden.id = 'repeater-template-container';
		hidden.className = 'hidden';
		hidden.style.display = 'none';
		hidden.appendChild(target);
		container.appendChild(hidden);
	} else if (options?.withInitialChild !== false) {
		wrapper.appendChild(target);
	}

	const trigger = document.createElement('button');
	trigger.type = 'button';
	trigger.setAttribute('data-kt-repeater', '');
	trigger.setAttribute('data-kt-repeater-target', '#repeater-target');
	trigger.setAttribute('data-kt-repeater-wrapper', '#repeater-wrapper');
	if (options?.limit != null) {
		trigger.setAttribute('data-kt-repeater-limit', String(options.limit));
	}
	trigger.textContent = 'Add';
	container.appendChild(trigger);

	document.body.appendChild(container);
	return { container, wrapper, target, trigger };
}

describe('KTRepeater', () => {
	afterEach(() => {
		document.body.innerHTML = '';
	});

	describe('initialization', () => {
		it('initializes on trigger element with data-kt-repeater and valid target/wrapper', () => {
			const { trigger } = createFixture();
			const instance = new KTRepeater(trigger);
			expect(instance).toBeInstanceOf(KTRepeater);
			expect(instance.getElement()).toBe(trigger);
			instance.dispose();
		});

		it('reads target, wrapper, limit from data attributes', () => {
			const { trigger } = createFixture({ limit: 5 });
			const instance = new KTRepeater(trigger);
			expect(instance.getOption('target')).toBe('#repeater-target');
			expect(instance.getOption('wrapper')).toBe('#repeater-wrapper');
			expect(Number(instance.getOption('limit'))).toBe(5);
			instance.dispose();
		});

		it('does not double-initialize when constructor called twice on same trigger', () => {
			const { trigger, wrapper } = createFixture();
			new KTRepeater(trigger);
			new KTRepeater(trigger);
			expect(KTRepeater.getInstance(trigger)).not.toBeNull();
			KTRepeater.getInstance(trigger)!.add();
			expect(wrapper.children.length).toBe(2);
			KTRepeater.getInstance(trigger)!.dispose();
		});

		it('accepts config object when wrapper has no id (config overrides)', () => {
			const { trigger, wrapper } = createFixture();
			wrapper.id = 'custom-wrapper';
			trigger.removeAttribute('data-kt-repeater-wrapper');
			const instance = new KTRepeater(trigger, {
				target: '#repeater-target',
				wrapper: '#custom-wrapper',
				limit: 0,
			});
			expect(instance).toBeInstanceOf(KTRepeater);
			instance.add();
			expect(wrapper.children.length).toBe(2);
			instance.dispose();
		});
	});

	describe('add behavior', () => {
		it('clones target into wrapper when trigger is clicked', () => {
			const { wrapper, trigger } = createFixture();
			const instance = new KTRepeater(trigger);
			expect(wrapper.children.length).toBe(1);
			trigger.click();
			expect(wrapper.children.length).toBe(2);
			trigger.click();
			expect(wrapper.children.length).toBe(3);
			instance.dispose();
		});

		it('add() method has the same effect as clicking the trigger', () => {
			const { wrapper, trigger } = createFixture();
			const instance = new KTRepeater(trigger);
			expect(wrapper.children.length).toBe(1);
			instance.add();
			expect(wrapper.children.length).toBe(2);
			instance.add();
			expect(wrapper.children.length).toBe(3);
			instance.dispose();
		});

		it('fires add event with cloned element in payload', () => {
			const { trigger } = createFixture();
			const instance = new KTRepeater(trigger);
			const addedElements: HTMLElement[] = [];
			instance.on('add', (payload: { element: HTMLElement }) => {
				addedElements.push(payload.element);
			});
			instance.add();
			instance.add();
			expect(addedElements.length).toBe(2);
			expect(addedElements[0]).toBeInstanceOf(HTMLElement);
			expect(addedElements[0].querySelector('input')).not.toBeNull();
			instance.dispose();
		});

		it('dispatches DOM add event with detail.payload.element', () => {
			const { trigger } = createFixture();
			const instance = new KTRepeater(trigger);
			const events: CustomEvent[] = [];
			trigger.addEventListener('add', ((e: Event) =>
				events.push(e as CustomEvent)) as EventListener);
			instance.add();
			expect(events.length).toBe(1);
			expect(events[0].detail?.payload?.element).toBeInstanceOf(HTMLElement);
			instance.dispose();
		});
	});

	describe('limit', () => {
		it('does not add more clones when at limit', () => {
			const { trigger, wrapper } = createFixture({ limit: 2 });
			const instance = new KTRepeater(trigger);
			expect(wrapper.children.length).toBe(1);
			instance.add();
			expect(wrapper.children.length).toBe(2);
			instance.add();
			expect(wrapper.children.length).toBe(2);
			trigger.click();
			expect(wrapper.children.length).toBe(2);
			instance.dispose();
		});

		it('disables trigger when at limit', () => {
			const { trigger } = createFixture({ limit: 2 });
			const instance = new KTRepeater(trigger);
			expect(trigger.hasAttribute('disabled')).toBe(false);
			instance.add();
			expect(trigger.hasAttribute('disabled')).toBe(true);
			instance.dispose();
		});

		it('re-enables trigger when a clone is removed (delete) and count drops below limit', () => {
			const { wrapper, trigger } = createFixture({
				limit: 2,
				withDeleteInTarget: true,
			});
			const instance = new KTRepeater(trigger);
			instance.add();
			expect(wrapper.children.length).toBe(2);
			expect(trigger.hasAttribute('disabled')).toBe(true);
			const secondClone = wrapper.children[1];
			const deleteBtn = secondClone.querySelector('[data-kt-repeater-delete]');
			expect(deleteBtn).not.toBeNull();
			(deleteBtn as HTMLElement).click();
			expect(wrapper.children.length).toBe(1);
			expect(trigger.hasAttribute('disabled')).toBe(false);
			instance.dispose();
		});

		it('allows unlimited clones when limit is 0 or omitted', () => {
			const { trigger, wrapper } = createFixture();
			trigger.removeAttribute('data-kt-repeater-limit');
			const instance = new KTRepeater(trigger);
			for (let i = 0; i < 5; i++) instance.add();
			expect(wrapper.children.length).toBe(6);
			instance.dispose();
		});
	});

	describe('delete', () => {
		it('removes clone when clicking element with data-kt-repeater-delete', () => {
			const { wrapper, trigger } = createFixture({ withDeleteInTarget: true });
			const instance = new KTRepeater(trigger);
			instance.add();
			expect(wrapper.children.length).toBe(2);
			const firstClone = wrapper.children[1];
			const deleteBtn = firstClone.querySelector(
				'[data-kt-repeater-delete]',
			) as HTMLElement;
			deleteBtn.click();
			expect(wrapper.children.length).toBe(1);
			instance.dispose();
		});

		it('does not remove the last row (keeps at least one for template)', () => {
			const { wrapper, trigger } = createFixture({ withDeleteInTarget: true });
			const instance = new KTRepeater(trigger);
			expect(wrapper.children.length).toBe(1);
			const onlyRow = wrapper.children[0];
			const deleteBtn = onlyRow.querySelector(
				'[data-kt-repeater-delete]',
			) as HTMLElement;
			deleteBtn.click();
			expect(wrapper.children.length).toBe(1);
			instance.dispose();
		});
	});

	describe('predefined (hidden) template', () => {
		it('clones from hidden template and appends to wrapper', () => {
			const { wrapper, trigger } = createFixture({
				hiddenTemplate: true,
				withInitialChild: false,
			});
			expect(wrapper.children.length).toBe(0);
			const instance = new KTRepeater(trigger);
			instance.add();
			expect(wrapper.children.length).toBe(1);
			expect(
				wrapper.querySelector('input[placeholder="Enter Name"]'),
			).not.toBeNull();
			instance.dispose();
		});
	});

	describe('getInstance and static methods', () => {
		it('getInstance returns null for element without data-kt-repeater', () => {
			const { trigger } = createFixture();
			trigger.removeAttribute('data-kt-repeater');
			expect(KTRepeater.getInstance(trigger)).toBeNull();
		});

		it('getInstance returns instance for initialized trigger', () => {
			const { trigger } = createFixture();
			const instance = new KTRepeater(trigger);
			expect(KTRepeater.getInstance(trigger)).toBe(instance);
			instance.dispose();
		});

		it('getOrCreateInstance returns existing instance or creates new one', () => {
			const { trigger } = createFixture();
			const a = KTRepeater.getOrCreateInstance(trigger);
			const b = KTRepeater.getOrCreateInstance(trigger);
			expect(a).toBe(b);
			a.dispose();
		});

		it('createInstances initializes all data-kt-repeater elements', () => {
			const { container, trigger } = createFixture();
			const trigger2 = document.createElement('button');
			trigger2.setAttribute('data-kt-repeater', '');
			trigger2.setAttribute('data-kt-repeater-target', '#repeater-target');
			trigger2.setAttribute('data-kt-repeater-wrapper', '#repeater-wrapper');
			container.appendChild(trigger2);
			KTRepeater.createInstances();
			expect(KTRepeater.getInstance(trigger)).not.toBeNull();
			expect(KTRepeater.getInstance(trigger2)).not.toBeNull();
			KTRepeater.getInstance(trigger)!.dispose();
			KTRepeater.getInstance(trigger2)!.dispose();
		});

		it('init calls createInstances', () => {
			const { trigger } = createFixture();
			KTRepeater.init();
			expect(KTRepeater.getInstance(trigger)).not.toBeNull();
			KTRepeater.getInstance(trigger)!.dispose();
		});
	});

	describe('dispose', () => {
		it('removes listeners and allows re-initialization', () => {
			const { wrapper, trigger } = createFixture();
			const instance = new KTRepeater(trigger);
			instance.dispose();
			const instance2 = new KTRepeater(trigger);
			instance2.add();
			expect(wrapper.children.length).toBe(2);
			instance2.dispose();
		});

		it('dispose is idempotent', () => {
			const { trigger } = createFixture();
			const instance = new KTRepeater(trigger);
			instance.dispose();
			expect(() => instance.dispose()).not.toThrow();
		});
	});
});
