// #region ReviewWorkspaceHelpersTest [C:3] [TYPE Module] [SEMANTICS test, dataset-review, helpers, milestones, blockers, seed-prompt]
// @BRIEF Unit tests for shared dataset review workspace helper functions — milestones, blockers, seed prompts, i18n labels, collection merge.
// @RELATION DEPENDS_ON -> [ReviewWorkspaceHelpers]
// @TEST_EDGE: null_session -> buildImportMilestones defaults to pending when session is null
// @TEST_EDGE: empty_blockers -> buildWorkspaceLaunchBlockers returns empty array when session is null
// @TEST_EDGE: null_profile -> buildAssistantSeedPrompt handles missing profile gracefully
// @TEST_EDGE: empty_collection -> mergeCollectionItem returns original array when item has no id

import { describe, it, expect } from 'vitest';

const mockI18n = {
	dataset_review: {
		workspace: {
			import_milestones: {
				recognized: 'Recognized',
				filters: 'Filters',
				variables: 'Variables',
				semantic_sources: 'Sources',
				summary: 'Summary',
			},
			launch_blockers: {
				blocking_findings: 'Blocking findings',
				mapping_approvals: 'Mapping approvals',
				preview_state: 'SQL preview state',
				readiness: 'Readiness',
			},
			state: {
				importing: 'Importing',
				reviewing: 'Reviewing',
			},
			actions: {
				import_from_superset: 'Import',
				generate_sql_preview: 'Generate SQL',
				launch_dataset: 'Launch',
			},
			next_action_label: 'Next',
			resume_action: 'Resume',
			assistant_context: {
				ask_prefix: 'Help review',
				improve_prefix: 'Improve',
				section_label: 'Section',
				context_label: 'Context',
			},
		},
		preview: {
			generate_action: 'Generate',
		},
		launch: {
			launch_action: 'Launch',
		},
	},
	common: {
		not_available: 'N/A',
	},
};

describe('buildImportMilestones', () => {
	it('sets all pending when session and intake are null/empty', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const result = buildImportMilestones(mockI18n, null, null, [], null, null, [], 'idle');
		result.forEach((m) => expect(m.state).toBe('pending'));
	});

	it('sets recognized done when intakeAcknowledgment is true', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const result = buildImportMilestones(mockI18n, true, null, [], null, null, 'idle');
		expect(result[0].state).toBe('done');
	});

	it('sets recognized done when session has dataset_ref', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1' };
		const result = buildImportMilestones(mockI18n, false, session, [], null, null, 'idle');
		expect(result[0].state).toBe('done');
	});

	it('sets filters done when importedFilters has items', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const result = buildImportMilestones(mockI18n, null, null, [{ filter_name: 'Region' }], null, null, 'idle');
		expect(result[1].state).toBe('done');
	});

	it('sets semantics done when profile has business_summary', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const result = buildImportMilestones(mockI18n, null, null, [], null, { dataset_name: 'Sales', business_summary: 'Sales data' }, [], 'idle');
		expect(result[3].state).toBe('done');
		expect(result[4].state).toBe('done');
	});

	it('sets active when currentWorkspaceState is importing', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const result = buildImportMilestones(mockI18n, null, null, [], null, null, [], 'importing');
		result.forEach((m) => expect(m.state).toBe('active'));
	});

	it('sets variables done when latestPreview is truthy', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const result = buildImportMilestones(mockI18n, null, null, [], { preview: 'data' }, null, [], 'idle');
		expect(result[2].state).toBe('done'); // variables milestone
	});

	it('sets semantics done when findings are present', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const result = buildImportMilestones(mockI18n, null, null, [], null, null, [{ id: 'f1' }], 'idle');
		expect(result[3].state).toBe('done'); // semantic_sources milestone
		expect(result[4].state).toBe('done'); // summary milestone
	});

	it('sets semantics done when profile has business_summary and findings exist', async () => {
		const { buildImportMilestones } = await import('../review-workspace-helpers.ts');
		const profile = { dataset_name: 'Sales', business_summary: 'Sales data' };
		const result = buildImportMilestones(mockI18n, null, null, [], null, profile, [{ id: 'f1' }], 'idle');
		expect(result[3].state).toBe('done');
	});
});

describe('buildWorkspaceLaunchBlockers', () => {
	it('returns empty array when session is null', async () => {
		const { buildWorkspaceLaunchBlockers } = await import('../review-workspace-helpers.ts');
		const result = buildWorkspaceLaunchBlockers(mockI18n, null, 1, 0, 'ready');
		expect(result).toEqual([]);
	});

	it('includes blocking findings count', async () => {
		const { buildWorkspaceLaunchBlockers } = await import('../review-workspace-helpers.ts');
		const session = { readiness_state: 'run_ready' };
		const result = buildWorkspaceLaunchBlockers(mockI18n, session, 3, 0, 'ready');
		expect(result[0]).toContain('Blocking findings');
		expect(result[0]).toContain('3');
	});

	it('includes mapping approvals count', async () => {
		const { buildWorkspaceLaunchBlockers } = await import('../review-workspace-helpers.ts');
		const session = { readiness_state: 'run_ready' };
		const result = buildWorkspaceLaunchBlockers(mockI18n, session, 0, 5, 'ready');
		expect(result[0]).toContain('Mapping approvals');
		expect(result[0]).toContain('5');
	});

	it('includes preview state blocker when not ready', async () => {
		const { buildWorkspaceLaunchBlockers } = await import('../review-workspace-helpers.ts');
		const session = { readiness_state: 'run_ready' };
		const result = buildWorkspaceLaunchBlockers(mockI18n, session, 0, 0, 'compiling');
		expect(result.some((b) => b.includes('SQL preview state'))).toBe(true);
	});

	it('includes readiness state blocker when not run_ready', async () => {
		const { buildWorkspaceLaunchBlockers } = await import('../review-workspace-helpers.ts');
		const session = { readiness_state: 'review_ready' };
		const result = buildWorkspaceLaunchBlockers(mockI18n, session, 0, 0, 'ready');
		expect(result.some((b) => b.includes('Readiness'))).toBe(true);
	});
});

describe('buildAssistantSeedPrompt', () => {
	it('returns empty string when session is null', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const result = buildAssistantSeedPrompt(mockI18n, null, null, [], null, []);
		expect(result).toBe('');
	});

	it('includes dataset name from profile', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1' };
		const profile = { dataset_name: 'Revenue', business_summary: 'Monthly revenue data' };
		const result = buildAssistantSeedPrompt(mockI18n, session, profile, [], null, []);
		expect(result).toContain('Revenue');
		expect(result).toContain('Monthly revenue data');
	});

	it('falls back to dataset_ref for name when profile has no dataset_name', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1' };
		const result = buildAssistantSeedPrompt(mockI18n, session, null, [], null, []);
		expect(result).toContain('d1');
	});

	it('appends clarification question text when present', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1' };
		const question = { question_text: 'What granularity?' };
		const result = buildAssistantSeedPrompt(mockI18n, session, null, [], question, []);
		expect(result).toContain('What granularity?');
	});

	it('includes readiness hint for review_ready', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1', readiness_state: 'review_ready' };
		const result = buildAssistantSeedPrompt(mockI18n, session, null, [], null, []);
		expect(result).toContain('готов к проверке');
	});

	it('includes readiness hint for recovery_required', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1', readiness_state: 'recovery_required' };
		const result = buildAssistantSeedPrompt(mockI18n, session, null, [], null, []);
		expect(result).toContain('частично');
	});

	it('includes readiness hint for run_ready', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1', readiness_state: 'run_ready' };
		const result = buildAssistantSeedPrompt(mockI18n, session, null, [], null, []);
		expect(result).toContain('готов к запуску');
	});

	it('includes imported filter names', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1' };
		const filters = [
			{ display_name: 'Region' },
			{ filter_name: 'Year' },
			{ display_name: 'Product' },
		];
		const result = buildAssistantSeedPrompt(mockI18n, session, null, [], null, filters);
		expect(result).toContain('Region');
		expect(result).toContain('Year');
		expect(result).toContain('Product');
	});

	it('includes imported filter names with overflow indicator', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1' };
		const filters = [
			{ display_name: 'A' }, { display_name: 'B' }, { display_name: 'C' }, { display_name: 'D' },
		];
		const result = buildAssistantSeedPrompt(mockI18n, session, null, [], null, filters);
		expect(result).toContain('ещё 1');
	});

	it('skips placeholder business summary', async () => {
		const { buildAssistantSeedPrompt } = await import('../review-workspace-helpers.ts');
		const session = { dataset_ref: 'd1' };
		const profile = {
			dataset_name: 'Sales',
			business_summary: `Review session initialized for ${session.dataset_ref}.`,
		};
		const result = buildAssistantSeedPrompt(mockI18n, session, profile, [], null, []);
		// Should NOT include the placeholder summary
		expect(result).not.toContain('Review session initialized');
		// Should still include dataset name
		expect(result).toContain('Sales');
	});
});

describe('buildAssistantContextPrompt', () => {
	it('uses label from payload', async () => {
		const { buildAssistantContextPrompt } = await import('../review-workspace-helpers.ts');
		const result = buildAssistantContextPrompt(mockI18n, { label: 'mapping', section: 'workspace', prompt: 'Check' });
		expect(result).toContain('Help review mapping');
	});

	it('uses improve prefix for mode=improve', async () => {
		const { buildAssistantContextPrompt } = await import('../review-workspace-helpers.ts');
		const result = buildAssistantContextPrompt(mockI18n, { label: 'filter', section: 'summary', prompt: 'Fix it', mode: 'improve' });
		expect(result).toContain('Improve filter');
	});

	it('uses improve prefix for mode=improve without context', async () => {
		const { buildAssistantContextPrompt } = await import('../review-workspace-helpers.ts');
		const result = buildAssistantContextPrompt(mockI18n, { label: 'item', section: 'workspace', mode: 'improve' });
		expect(result).toContain('Improve item');
		expect(result).not.toContain('Context');
	});

	it('falls back to target when label is missing', async () => {
		const { buildAssistantContextPrompt } = await import('../review-workspace-helpers.ts');
		const result = buildAssistantContextPrompt(mockI18n, { target: 'sql-preview', section: 'sql-preview' });
		expect(result).toContain('Help review sql-preview');
	});

	it('handles payload without context or prompt', async () => {
		const { buildAssistantContextPrompt } = await import('../review-workspace-helpers.ts');
		const result = buildAssistantContextPrompt(mockI18n, { label: 'mapping', section: 'launch' });
		expect(result).toContain('Help review mapping');
		expect(result).toContain('Section: launch');
		expect(result).not.toContain('Context:');
	});
});

describe('getWorkspaceStateLabel', () => {
	it('returns i18n label when found', async () => {
		const { getWorkspaceStateLabel } = await import('../review-workspace-helpers.ts');
		expect(getWorkspaceStateLabel(mockI18n, 'importing')).toBe('Importing');
	});

	it('returns state key when i18n missing', async () => {
		const { getWorkspaceStateLabel } = await import('../review-workspace-helpers.ts');
		expect(getWorkspaceStateLabel(mockI18n, 'unknown_state')).toBe('unknown_state');
	});
});

describe('getRecommendedActionLabel', () => {
	it('returns i18n label when found', async () => {
		const { getRecommendedActionLabel } = await import('../review-workspace-helpers.ts');
		expect(getRecommendedActionLabel(mockI18n, 'launch_dataset')).toBe('Launch');
	});

	it('returns normalized action as fallback', async () => {
		const { getRecommendedActionLabel } = await import('../review-workspace-helpers.ts');
		expect(getRecommendedActionLabel(mockI18n, 'unknown_action')).toBe('unknown_action');
	});
});

describe('getPrimaryActionCtaLabel', () => {
	it('returns next_action_label when session is null', async () => {
		const { getPrimaryActionCtaLabel } = await import('../review-workspace-helpers.ts');
		expect(getPrimaryActionCtaLabel(mockI18n, null)).toBe('Next');
	});

	it('returns resume_action for resume_session recommended action', async () => {
		const { getPrimaryActionCtaLabel } = await import('../review-workspace-helpers.ts');
		const session = { recommended_action: 'resume_session' };
		expect(getPrimaryActionCtaLabel(mockI18n, session)).toBe('Resume');
	});

	it('returns generate_action for generate_sql_preview', async () => {
		const { getPrimaryActionCtaLabel } = await import('../review-workspace-helpers.ts');
		const session = { recommended_action: 'generate_sql_preview' };
		expect(getPrimaryActionCtaLabel(mockI18n, session)).toBe('Generate');
	});

	it('returns launch_action for launch_dataset', async () => {
		const { getPrimaryActionCtaLabel } = await import('../review-workspace-helpers.ts');
		const session = { recommended_action: 'launch_dataset' };
		expect(getPrimaryActionCtaLabel(mockI18n, session)).toBe('Launch');
	});

	it('returns default next_action_label for unknown action', async () => {
		const { getPrimaryActionCtaLabel } = await import('../review-workspace-helpers.ts');
		const session = { recommended_action: 'unknown' };
		expect(getPrimaryActionCtaLabel(mockI18n, session)).toBe('Next');
	});
});

describe('mergeCollectionItem', () => {
	it('returns original collection when item is null', async () => {
		const { mergeCollectionItem } = await import('../review-workspace-helpers.ts');
		const col = [{ id: 'a' }];
		expect(mergeCollectionItem(col, null)).toBe(col);
	});

	it('returns original collection when item has no id', async () => {
		const { mergeCollectionItem } = await import('../review-workspace-helpers.ts');
		const col = [{ id: 'a' }];
		expect(mergeCollectionItem(col, { name: 'no-id' })).toBe(col);
	});

	it('adds new item to collection when id not present', async () => {
		const { mergeCollectionItem } = await import('../review-workspace-helpers.ts');
		const col = [{ id: 'a' }];
		const result = mergeCollectionItem(col, { id: 'b', value: 2 });
		expect(result).toHaveLength(2);
		expect(result[1]).toEqual({ id: 'b', value: 2 });
	});

	it('replaces existing item when id matches', async () => {
		const { mergeCollectionItem } = await import('../review-workspace-helpers.ts');
		const col = [{ id: 'a', value: 1 }, { id: 'b', value: 2 }];
		const result = mergeCollectionItem(col, { id: 'a', value: 99, extra: true });
		expect(result).toHaveLength(2);
		expect(result[0]).toEqual({ id: 'a', value: 99, extra: true });
	});

	it('uses custom idKey', async () => {
		const { mergeCollectionItem } = await import('../review-workspace-helpers.ts');
		const col = [{ key: 'x', val: 1 }];
		const result = mergeCollectionItem(col, { key: 'x', val: 2 }, 'key');
		expect(result).toHaveLength(1);
		expect(result[0]).toEqual({ key: 'x', val: 2 });
	});

	it('returns empty array when collection is undefined and item has id', async () => {
		const { mergeCollectionItem } = await import('../review-workspace-helpers.ts');
		const result = mergeCollectionItem(undefined, { id: 'a' });
		expect(result).toEqual([{ id: 'a' }]);
	});
});

describe('stringifyValue', () => {
	it('returns N/A for null', async () => {
		const { stringifyValue } = await import('../review-workspace-helpers.ts');
		expect(stringifyValue(mockI18n, null)).toBe('N/A');
	});

	it('returns N/A for undefined', async () => {
		const { stringifyValue } = await import('../review-workspace-helpers.ts');
		expect(stringifyValue(mockI18n, undefined)).toBe('N/A');
	});

	it('returns N/A for empty string', async () => {
		const { stringifyValue } = await import('../review-workspace-helpers.ts');
		expect(stringifyValue(mockI18n, '')).toBe('N/A');
	});

	it('returns string value as-is', async () => {
		const { stringifyValue } = await import('../review-workspace-helpers.ts');
		expect(stringifyValue(mockI18n, 'hello')).toBe('hello');
	});

	it('JSON stringifies objects', async () => {
		const { stringifyValue } = await import('../review-workspace-helpers.ts');
		expect(stringifyValue(mockI18n, { key: 'val' })).toBe('{"key":"val"}');
	});

	it('returns string representation for numbers', async () => {
		const { stringifyValue } = await import('../review-workspace-helpers.ts');
		expect(stringifyValue(mockI18n, 42)).toBe('42');
	});

	it('returns string representation for boolean true', async () => {
		const { stringifyValue } = await import('../review-workspace-helpers.ts');
		expect(stringifyValue(mockI18n, true)).toBe('true');
	});

	it('handles circular JSON gracefully via catch', async () => {
		const { stringifyValue } = await import('../review-workspace-helpers.ts');
		const circular: Record<string, unknown> = { name: 'test' };
		circular.self = circular;
		// Should not throw - catches JSON.stringify error and falls back to String()
		const result = stringifyValue(mockI18n, circular);
		expect(typeof result).toBe('string');
	});
});
// #endregion ReviewWorkspaceHelpersTest
