/**
 * @jest-environment node
 */

import {
	createEmptyStep,
	ensureStableIds,
	insertStep,
	updateStepAt,
	removeStepAt,
	reorderSteps,
	getCompleteSteps,
	getVisibleSteps,
} from './steps';
import {
	appendStringItem,
	normalizeStringList,
	removeStringAt,
	updateStringAt,
} from './string-list';
import { isValidUuid } from './uuid';
import { getHowToSetupStatus } from './setup-status';
import {
	buildHowToInnerHtml,
	buildHowToClassList,
	escapeHtml,
	sanitizeStepHtml,
} from './serialize';
import { migrateToV1 } from './migrate';

describe('HowTo step helpers', () => {
	it('creates steps with stable UUID ids', () => {
		const step = createEmptyStep();
		expect(isValidUuid(step.id)).toBe(true);
		expect(step.name).toBe('');
		expect(step.text).toBe('');
		expect(step.imageId).toBe(0);
	});

	it('inserts, edits, removes, and reorders while preserving ids', () => {
		let steps = [createEmptyStep(), createEmptyStep()];
		const firstId = steps[0].id;
		const secondId = steps[1].id;

		steps = insertStep(steps);
		expect(steps).toHaveLength(3);
		expect(steps[0].id).toBe(firstId);
		expect(steps[1].id).toBe(secondId);
		expect(isValidUuid(steps[2].id)).toBe(true);

		steps = updateStepAt(steps, 0, {
			name: 'Prep',
			text: '<p>Wash hands.</p>',
			imageId: 42,
		});
		expect(steps[0].id).toBe(firstId);
		expect(steps[0].name).toBe('Prep');
		expect(steps[0].imageId).toBe(42);

		steps = reorderSteps(steps, 0, 2);
		expect(steps[2].id).toBe(firstId);
		expect(steps[0].id).toBe(secondId);

		steps = removeStepAt(steps, 0);
		expect(steps).toHaveLength(2);
		expect(steps.find((row) => row.id === firstId)).toBeTruthy();
		expect(steps.find((row) => row.id === secondId)).toBeFalsy();
	});

	it('ensureStableIds assigns missing ids and dedupes collisions', () => {
		const shared = '11111111-1111-4111-8111-111111111111';
		const next = ensureStableIds([
			{ id: shared, name: 'A', text: '1' },
			{ id: shared, name: 'B', text: '2' },
			{ name: 'C', text: '3' },
		]);
		expect(next[0].id).toBe(shared);
		expect(next[1].id).not.toBe(shared);
		expect(isValidUuid(next[1].id)).toBe(true);
		expect(isValidUuid(next[2].id)).toBe(true);
		expect(new Set(next.map((row) => row.id)).size).toBe(3);
	});

	it('classifies complete vs visible steps', () => {
		const steps = ensureStableIds([
			{ name: 'Step 1', text: '<p>Do it</p>' },
			{ name: 'Only name', text: '' },
			{ name: '', text: '<p>orphan body</p>' },
			{ name: '', text: '' },
		]);
		expect(getCompleteSteps(steps)).toHaveLength(3);
		expect(getVisibleSteps(steps)).toHaveLength(3);
	});
});

describe('HowTo string lists', () => {
	it('normalizes, appends, updates, and removes items', () => {
		expect(normalizeStringList(['  a ', '', 'b'])).toEqual(['a', 'b']);
		let list = appendStringItem(['flour']);
		expect(list).toEqual(['flour', '']);
		list = updateStringAt(list, 1, 'sugar');
		expect(list).toEqual(['flour', 'sugar']);
		list = removeStringAt(list, 0);
		expect(list).toEqual(['sugar']);
	});
});

describe('HowTo setup status', () => {
	it('does not claim schema is published', () => {
		const empty = getHowToSetupStatus({ name: '', steps: [] });
		expect(empty.status).toBe('empty');
		expect(empty.message.toLowerCase()).toContain('not published');

		const incomplete = getHowToSetupStatus({
			name: 'How to brew',
			steps: [
				{
					id: '11111111-1111-4111-8111-111111111111',
					name: '',
					text: '',
				},
			],
		});
		expect(incomplete.status).toBe('incomplete');

		const ready = getHowToSetupStatus({
			name: 'How to brew',
			steps: [
				{
					id: '11111111-1111-4111-8111-111111111111',
					name: 'Boil water',
					text: '<p>Heat to 100C</p>',
				},
			],
		});
		expect(ready.status).toBe('ready_for_schema');
		expect(ready.message.toLowerCase()).toContain('separately');
		expect(ready.message.toLowerCase()).not.toContain('published to google');
	});
});

describe('HowTo save serialization', () => {
	it('escapes text and strips script handlers', () => {
		expect(escapeHtml('<b>"x"')).toBe('&lt;b&gt;&quot;x&quot;');
		expect(sanitizeStepHtml('<p onclick="alert(1)">Hi</p><script>x</script>')).toBe(
			'<p>Hi</p>'
		);
	});

	it('serializes visible markup with stable step ids', () => {
		const attributes = {
			name: 'How to <test>',
			description: 'Intro & more',
			totalTime: 'PT30M',
			estimatedCost: { currency: 'USD', value: '5' },
			supplies: ['flour'],
			tools: ['whisk'],
			displayMode: 'numbered',
			steps: [
				{
					id: 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee',
					name: 'Step <one>',
					text: '<p>Do it</p>',
				},
				{
					id: 'bbbbbbbb-bbbb-4ccc-8ddd-eeeeeeeeeeee',
					name: '',
					text: '',
				},
			],
		};

		const html = buildHowToInnerHtml(attributes);
		expect(html).toContain(
			'<h2 class="seovault-howto-block__name">How to &lt;test&gt;</h2>'
		);
		expect(html).toContain('Intro &amp; more');
		expect(html).toContain('data-howto-step-id="aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"');
		expect(html).toContain('Step &lt;one&gt;');
		expect(html).toContain('PT30M');
		expect(html).toContain('USD 5');
		expect(html).toContain('<li>flour</li>');
		expect(html).toContain('<li>whisk</li>');
		expect(html).not.toContain('bbbbbbbb-bbbb-4ccc-8ddd-eeeeeeeeeeee');

		expect(buildHowToClassList({ displayMode: 'cards', stylePreset: 'dark' })).toEqual(
			expect.arrayContaining([
				'seovault-howto-block',
				'seovault-howto-block--display-cards',
				'seovault-howto-block--style-dark',
			])
		);
	});
});

describe('HowTo deprecated migration', () => {
	it('migrates title/summary and missing ids to schemaVersion 1', () => {
		const migrated = migrateToV1({
			schemaVersion: 0,
			title: 'Old title',
			summary: 'Hello',
			steps: [{ name: 'One', text: '<p>A</p>' }],
			displayMode: 'cards',
		});

		expect(migrated.schemaVersion).toBe(1);
		expect(migrated.name).toBe('Old title');
		expect(migrated.description).toBe('Hello');
		expect(migrated.displayMode).toBe('cards');
		expect(migrated.steps).toHaveLength(1);
		expect(isValidUuid(migrated.steps[0].id)).toBe(true);
		expect(migrated.steps[0].name).toBe('One');
	});
});
