import { setActivePinia, createPinia } from 'pinia';
import * as workflowsApi from '@/app/api/workflows';
import {
	DUPLICATE_POSTFFIX,
	FORM_NODE_TYPE,
	MANUAL_TRIGGER_NODE_TYPE,
	MAX_WORKFLOW_NAME_LENGTH,
	WAIT_NODE_TYPE,
} from '@/app/constants';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import { useWorkflowsListStore } from '@/app/stores/workflowsList.store';
import {
	useWorkflowDocumentStore,
	createWorkflowDocumentId,
} from '@/app/stores/workflowDocument.store';
import type { INodeUi, IWorkflowDb, IWorkflowSettings } from '@/Interface';
import type { IExecutionResponse } from '@/features/execution/executions/executions.types';

import {
	createEmptyRunExecutionData,
	createRunExecutionData,
	deepCopy,
	NodeConnectionTypes,
	SEND_AND_WAIT_OPERATION,
} from 'n8n-workflow';
import type { IConnection, IConnections, INode, INodeTypeDescription } from 'n8n-workflow';
import { useUIStore } from '@/app/stores/ui.store';
import type { PushPayload } from '@n8n/api-types';
import { flushPromises } from '@vue/test-utils';
import { useNDVStore } from '@/features/ndv/shared/ndv.store';
import { mock } from 'vitest-mock-extended';
import * as apiUtils from '@n8n/rest-api-client';
import {
	createTestNode,
	createTestTaskData,
	createTestWorkflow,
	createTestWorkflowExecutionResponse,
	mockNodeTypeDescription,
} from '@/__tests__/mocks';
import { waitFor } from '@testing-library/vue';
import { useWorkflowState } from '@/app/composables/useWorkflowState';
import { useSourceControlStore } from '@/features/integrations/sourceControl.ee/sourceControl.store';
import type { WorkflowHistory } from '@n8n/rest-api-client';

vi.mock('@/features/ndv/shared/ndv.store', () => ({
	useNDVStore: vi.fn(() => ({
		activeNode: null,
	})),
}));

vi.mock('@/app/api/workflows', () => ({
	getWorkflows: vi.fn(),
	getWorkflow: vi.fn(),
	getNewWorkflow: vi.fn(),
	getLastSuccessfulExecution: vi.fn(),
}));

const getNodeType = vi.fn((_nodeTypeName: string): Partial<INodeTypeDescription> | null => ({
	inputs: [],
	group: [],
	webhooks: [],
	properties: [],
}));
const communityNodeType = vi.fn();
vi.mock('@/app/stores/nodeTypes.store', () => ({
	useNodeTypesStore: vi.fn(() => ({
		getNodeType,
		communityNodeType,
	})),
}));

const track = vi.fn();
vi.mock('@/app/composables/useTelemetry', () => ({
	useTelemetry: () => ({ track }),
}));

vi.mock('@vueuse/core', async (importOriginal) => {
	const actual = await importOriginal<{}>();
	return {
		...actual,
		useLocalStorage: vi.fn().mockReturnValue({ value: undefined }),
	};
});

vi.mock('@/features/integrations/sourceControl.ee/sourceControl.store', () => ({
	useSourceControlStore: vi.fn(() => ({
		preferences: {
			branchReadOnly: false,
		},
	})),
}));

vi.mock('@n8n/permissions', () => ({
	getResourcePermissions: vi.fn((scopes: string[] = []) => ({
		workflow: {
			update: scopes.includes('workflow:update'),
			read: scopes.includes('workflow:read') || scopes.includes('workflow:update'),
		},
	})),
}));

describe('useWorkflowsStore', () => {
	let workflowsStore: ReturnType<typeof useWorkflowsStore>;
	let workflowsListStore: ReturnType<typeof useWorkflowsListStore>;
	let uiStore: ReturnType<typeof useUIStore>;

	beforeEach(() => {
		setActivePinia(createPinia());
		workflowsStore = useWorkflowsStore();
		workflowsListStore = useWorkflowsListStore();
		uiStore = useUIStore();
		track.mockReset();
	});

	it('should initialize with default state', () => {
		expect(workflowsStore.workflow.name).toBe('');
		expect(workflowsStore.workflow.id).toBe('');
	});

	describe('isWaitingExecution', () => {
		it('should return false if no activeNode and no waiting nodes in workflow', () => {
			workflowsStore.setNodes([
				{ type: 'type1' },
				{ type: 'type2' },
			] as unknown as IWorkflowDb['nodes']);

			const isWaiting = workflowsStore.isWaitingExecution;
			expect(isWaiting).toEqual(false);
		});

		it('should return false if no activeNode and waiting node in workflow and waiting node is disabled', () => {
			workflowsStore.setNodes([
				{ type: FORM_NODE_TYPE, disabled: true },
				{ type: 'type2' },
			] as unknown as IWorkflowDb['nodes']);

			const isWaiting = workflowsStore.isWaitingExecution;
			expect(isWaiting).toEqual(false);
		});

		it('should return true if no activeNode and wait node in workflow', () => {
			workflowsStore.setNodes([
				{ type: WAIT_NODE_TYPE },
				{ type: 'type2' },
			] as unknown as IWorkflowDb['nodes']);

			const isWaiting = workflowsStore.isWaitingExecution;
			expect(isWaiting).toEqual(true);
		});

		it('should return true if no activeNode and form node in workflow', () => {
			workflowsStore.setNodes([
				{ type: FORM_NODE_TYPE },
				{ type: 'type2' },
			] as unknown as IWorkflowDb['nodes']);

			const isWaiting = workflowsStore.isWaitingExecution;
			expect(isWaiting).toEqual(true);
		});

		it('should return true if no activeNode and sendAndWait node in workflow', () => {
			workflowsStore.setNodes([
				{ type: 'type1', parameters: { operation: SEND_AND_WAIT_OPERATION } },
				{ type: 'type2' },
			] as unknown as IWorkflowDb['nodes']);

			const isWaiting = workflowsStore.isWaitingExecution;
			expect(isWaiting).toEqual(true);
		});

		it('should return true if activeNode is waiting node', () => {
			vi.mocked(useNDVStore).mockReturnValue({
				activeNode: { type: WAIT_NODE_TYPE } as unknown as INodeUi,
			} as unknown as ReturnType<typeof useNDVStore>);

			const isWaiting = workflowsStore.isWaitingExecution;
			expect(isWaiting).toEqual(true);
		});
	});

	describe('workflowValidationIssues', () => {
		it('collects issues only from connected, enabled nodes', () => {
			const connections: IConnections = {
				Start: {
					main: [
						[
							{
								node: 'Fetch',
								type: 'main',
								index: 0,
							},
						],
					],
				},
			};

			workflowsStore.workflow.nodes = [
				{
					id: 'start',
					name: 'Start',
					type: 'n8n-nodes-base.manualTrigger',
					typeVersion: 1,
					parameters: {},
					position: [0, 0],
				},
				{
					id: 'fetch',
					name: 'Fetch',
					type: 'n8n-nodes-base.httpRequest',
					typeVersion: 1,
					parameters: {},
					issues: {
						parameters: {
							url: ['Missing URL', 'Invalid URL.'],
						},
						credentials: {
							httpBasicAuth: ['Credentials not set'],
						},
					},
					position: [300, 0],
				},
				{
					id: 'orphan',
					name: 'Disconnected',
					type: 'n8n-nodes-base.set',
					typeVersion: 1,
					parameters: {},
					issues: {
						parameters: { field: ['Should be ignored'] },
					},
					position: [0, 400],
				},
				{
					id: 'disabled',
					name: 'Disabled Node',
					type: 'n8n-nodes-base.set',
					typeVersion: 1,
					disabled: true,
					parameters: {},
					issues: {
						parameters: { field: ['Disabled issue'] },
					},
					position: [0, 600],
				},
			];
			workflowsStore.workflow.connections = connections;

			const issues = workflowsStore.workflowValidationIssues;
			expect(issues).toEqual([
				{ node: 'Fetch', type: 'parameters', value: ['Missing URL', 'Invalid URL.'] },
				{ node: 'Fetch', type: 'credentials', value: ['Credentials not set'] },
			]);
		});
	});

	describe('formatIssueMessage', () => {
		it('joins array entries and trims trailing period', () => {
			const message = workflowsStore.formatIssueMessage(['Missing URL', 'Invalid value.']);
			expect(message).toBe('Missing URL, Invalid value');
		});

		it('returns string representation for non-array values', () => {
			expect(workflowsStore.formatIssueMessage('Simple issue.')).toBe('Simple issue.');
		});
	});

	describe('allWorkflows', () => {
		it('should return sorted workflows by name', () => {
			const workflowsListStore = useWorkflowsListStore();
			workflowsListStore.setWorkflows([
				{ id: '3', name: 'Zeta' },
				{ id: '1', name: 'Alpha' },
				{ id: '2', name: 'Beta' },
			] as IWorkflowDb[]);

			const allWorkflows = workflowsListStore.allWorkflows;
			expect(allWorkflows[0].name).toBe('Alpha');
			expect(allWorkflows[1].name).toBe('Beta');
			expect(allWorkflows[2].name).toBe('Zeta');
		});

		it('should return empty array when no workflows are set', () => {
			const workflowsListStore = useWorkflowsListStore();
			workflowsListStore.setWorkflows([]);

			const allWorkflows = workflowsListStore.allWorkflows;
			expect(allWorkflows).toEqual([]);
		});
	});

	describe('isWorkflowSaved', () => {
		it('should return undefined for a new workflow', () => {
			expect(workflowsStore.isWorkflowSaved[workflowsStore.workflowId]).toBeUndefined();
		});

		it('should return true for an existing workflow', () => {
			useWorkflowState().setWorkflowId('123');
			// Add the workflow to workflowsById to simulate it being loaded from backend
			workflowsListStore.addWorkflow(
				createTestWorkflow({
					id: '123',
					name: 'Test Workflow',
					active: false,
					versionId: '1',
				}),
			);
			expect(workflowsStore.isWorkflowSaved['123']).toBe(true);
		});
	});

	describe('workflowTriggerNodes', () => {
		it('should return only nodes that are triggers', () => {
			getNodeType.mockImplementation(
				(nodeTypeName: string) =>
					({
						group: nodeTypeName === 'triggerNode' ? ['trigger'] : [],
						inputs: [],
						webhooks: [],
						properties: [],
					}) as Partial<INodeTypeDescription> | null,
			);

			workflowsStore.setNodes([
				{ type: 'triggerNode', typeVersion: '1' },
				{ type: 'nonTriggerNode', typeVersion: '1' },
			] as unknown as IWorkflowDb['nodes']);

			expect(workflowsStore.workflowTriggerNodes).toHaveLength(1);
			expect(workflowsStore.workflowTriggerNodes[0].type).toBe('triggerNode');
		});

		it('should return empty array when no nodes are triggers', () => {
			workflowsStore.setNodes([
				{ type: 'nonTriggerNode1', typeVersion: '1' },
				{ type: 'nonTriggerNode2', typeVersion: '1' },
			] as unknown as IWorkflowDb['nodes']);

			expect(workflowsStore.workflowTriggerNodes).toHaveLength(0);
		});
	});

	describe('currentWorkflowHasWebhookNode', () => {
		it('should return true when a node has a webhookId', () => {
			workflowsStore.setNodes([
				{ name: 'Node1', webhookId: 'webhook1' },
				{ name: 'Node2' },
			] as unknown as IWorkflowDb['nodes']);

			const hasWebhookNode = workflowsStore.currentWorkflowHasWebhookNode;
			expect(hasWebhookNode).toBe(true);
		});

		it('should return false when no nodes have a webhookId', () => {
			workflowsStore.setNodes([
				{ name: 'Node1' },
				{ name: 'Node2' },
			] as unknown as IWorkflowDb['nodes']);

			const hasWebhookNode = workflowsStore.currentWorkflowHasWebhookNode;
			expect(hasWebhookNode).toBe(false);
		});

		it('should return false when there are no nodes', () => {
			workflowsStore.workflow.nodes = [];

			const hasWebhookNode = workflowsStore.currentWorkflowHasWebhookNode;
			expect(hasWebhookNode).toBe(false);
		});
	});

	describe('getWorkflowRunData', () => {
		it('should return null when no execution data is present', () => {
			workflowsStore.workflowExecutionData = null;

			const runData = workflowsStore.getWorkflowRunData;
			expect(runData).toBeNull();
		});

		it('should return null when execution data does not contain resultData', () => {
			workflowsStore.workflowExecutionData = { data: {} } as IExecutionResponse;

			const runData = workflowsStore.getWorkflowRunData;
			expect(runData).toBeNull();
		});

		it('should return runData when execution data contains resultData', () => {
			const expectedRunData = { node1: [{}, {}], node2: [{}] };
			workflowsStore.workflowExecutionData = {
				data: { resultData: { runData: expectedRunData } },
			} as unknown as IExecutionResponse;

			const runData = workflowsStore.getWorkflowRunData;
			expect(runData).toEqual(expectedRunData);
		});
	});

	describe('nodesIssuesExist', () => {
		it('should return true when a node has issues and connected', () => {
			workflowsStore.setNodes([
				{ name: 'Node1', issues: { error: ['Error message'] } },
				{ name: 'Node2' },
			] as unknown as IWorkflowDb['nodes']);

			workflowsStore.setConnections({
				Node1: { main: [[{ node: 'Node2' } as IConnection]] },
			});

			const hasIssues = workflowsStore.nodesIssuesExist;
			expect(hasIssues).toBe(true);
		});

		it('should return false when node has issues but it is not connected', () => {
			workflowsStore.setNodes([
				{ name: 'Node1', issues: { error: ['Error message'] } },
				{ name: 'Node2' },
			] as unknown as IWorkflowDb['nodes']);

			const hasIssues = workflowsStore.nodesIssuesExist;
			expect(hasIssues).toBe(false);
		});

		it('should return false when no nodes have issues', () => {
			workflowsStore.setNodes([
				{ name: 'Node1' },
				{ name: 'Node2' },
			] as unknown as IWorkflowDb['nodes']);

			workflowsStore.setConnections({
				Node1: { main: [[{ node: 'Node2' } as IConnection]] },
			});

			const hasIssues = workflowsStore.nodesIssuesExist;
			expect(hasIssues).toBe(false);
		});

		it('should return false when there are no nodes', () => {
			workflowsStore.workflow.nodes = [];

			const hasIssues = workflowsStore.nodesIssuesExist;
			expect(hasIssues).toBe(false);
		});
	});

	describe('getWorkflowResultDataByNodeName()', () => {
		it('should return null when no workflow run data is present', () => {
			workflowsStore.workflowExecutionData = null;

			const resultData = workflowsStore.getWorkflowResultDataByNodeName('Node1');
			expect(resultData).toBeNull();
		});

		it('should return null when node name is not present in workflow run data', () => {
			workflowsStore.workflowExecutionData = {
				data: { resultData: { runData: {} } },
			} as unknown as IExecutionResponse;

			const resultData = workflowsStore.getWorkflowResultDataByNodeName('Node1');
			expect(resultData).toBeNull();
		});

		it('should return result data when node name is present in workflow run data', () => {
			const expectedData = [{}, {}];
			workflowsStore.workflowExecutionData = {
				data: { resultData: { runData: { Node1: expectedData } } },
			} as unknown as IExecutionResponse;

			const resultData = workflowsStore.getWorkflowResultDataByNodeName('Node1');
			expect(resultData).toEqual(expectedData);
		});
	});

	describe('isNodeInOutgoingNodeConnections()', () => {
		it('should return false when no outgoing connections from root node', () => {
			workflowsStore.setConnections({});

			const result = workflowsStore.isNodeInOutgoingNodeConnections('RootNode', 'SearchNode');
			expect(result).toBe(false);
		});

		it('should return true when search node is directly connected to root node', () => {
			workflowsStore.setConnections({
				RootNode: { main: [[{ node: 'SearchNode' } as IConnection]] },
			});

			const result = workflowsStore.isNodeInOutgoingNodeConnections('RootNode', 'SearchNode');
			expect(result).toBe(true);
		});

		it('should return true when search node is indirectly connected to root node', () => {
			workflowsStore.setConnections({
				RootNode: { main: [[{ node: 'IntermediateNode' } as IConnection]] },
				IntermediateNode: { main: [[{ node: 'SearchNode' } as IConnection]] },
			});

			const result = workflowsStore.isNodeInOutgoingNodeConnections('RootNode', 'SearchNode');
			expect(result).toBe(true);
		});

		it('should return false when search node is not connected to root node', () => {
			workflowsStore.setConnections({
				RootNode: { main: [[{ node: 'IntermediateNode' } as IConnection]] },
				IntermediateNode: { main: [[{ node: 'AnotherNode' } as IConnection]] },
			});

			const result = workflowsStore.isNodeInOutgoingNodeConnections('RootNode', 'SearchNode');
			expect(result).toBe(false);
		});

		it('should return true if connection is indirect within `depth`', () => {
			workflowsStore.setConnections({
				RootNode: { main: [[{ node: 'IntermediateNode' } as IConnection]] },
				IntermediateNode: { main: [[{ node: 'SearchNode' } as IConnection]] },
			});

			const result = workflowsStore.isNodeInOutgoingNodeConnections('RootNode', 'SearchNode', 2);
			expect(result).toBe(true);
		});

		it('should return false if connection is indirect beyond `depth`', () => {
			workflowsStore.setConnections({
				RootNode: { main: [[{ node: 'IntermediateNode' } as IConnection]] },
				IntermediateNode: { main: [[{ node: 'SearchNode' } as IConnection]] },
			});

			const result = workflowsStore.isNodeInOutgoingNodeConnections('RootNode', 'SearchNode', 1);
			expect(result).toBe(false);
		});

		it('should return false if depth is 0', () => {
			workflowsStore.setConnections({
				RootNode: { main: [[{ node: 'SearchNode' } as IConnection]] },
			});

			const result = workflowsStore.isNodeInOutgoingNodeConnections('RootNode', 'SearchNode', 0);
			expect(result).toBe(false);
		});
	});

	describe('findRootWithMainConnection()', () => {
		it('returns children connected via ai tool when they also have a main parent', () => {
			const toolNode = createTestNode({ name: 'ToolNode' });
			const upstreamParentNode = createTestNode({ name: 'UpstreamNode' });
			const rootNode = createTestNode({ name: 'RootNode' });

			workflowsStore.setNodes([toolNode, upstreamParentNode, rootNode]);

			workflowsStore.setConnections({
				[toolNode.name]: {
					[NodeConnectionTypes.AiTool]: [
						[
							{
								node: rootNode.name,
								type: NodeConnectionTypes.AiTool,
								index: 0,
							},
						],
					],
				},
				[upstreamParentNode.name]: {
					main: [
						[
							{
								node: rootNode.name,
								type: NodeConnectionTypes.Main,
								index: 0,
							},
						],
					],
				},
			});

			const result = workflowsStore.findRootWithMainConnection(toolNode.name);

			expect(result).toBe(rootNode.name);
		});

		it('finds the root for a deeply nested vector tool chain', () => {
			const embeddingsNode = createTestNode({ name: 'EmbeddingsNode' });
			const vectorStoreNode = createTestNode({ name: 'VectorStoreNode' });
			const vectorToolNode = createTestNode({ name: 'VectorToolNode' });
			const agentNode = createTestNode({ name: 'AI Agent' });
			const setNode = createTestNode({ name: 'SetNode' });

			workflowsStore.setNodes([
				embeddingsNode,
				vectorStoreNode,
				vectorToolNode,
				agentNode,
				setNode,
			]);

			workflowsStore.setConnections({
				[embeddingsNode.name]: {
					[NodeConnectionTypes.AiEmbedding]: [
						[
							{
								node: vectorStoreNode.name,
								type: NodeConnectionTypes.AiEmbedding,
								index: 0,
							},
						],
					],
				},
				[vectorStoreNode.name]: {
					[NodeConnectionTypes.AiVectorStore]: [
						[
							{
								node: vectorToolNode.name,
								type: NodeConnectionTypes.AiVectorStore,
								index: 0,
							},
						],
					],
				},
				[vectorToolNode.name]: {
					[NodeConnectionTypes.AiTool]: [
						[
							{
								node: agentNode.name,
								type: NodeConnectionTypes.AiTool,
								index: 0,
							},
						],
					],
				},
				[setNode.name]: {
					main: [
						[
							{
								node: agentNode.name,
								type: NodeConnectionTypes.Main,
								index: 0,
							},
						],
					],
				},
			});

			expect(workflowsStore.findRootWithMainConnection(embeddingsNode.name)).toBe(agentNode.name);
		});

		it('returns null when no child has a main input connection', () => {
			const parent = createTestNode({ name: 'ParentNode' });
			const aiChild = createTestNode({ name: 'AiChild' });

			workflowsStore.setNodes([parent, aiChild]);

			workflowsStore.setConnections({
				[parent.name]: {
					[NodeConnectionTypes.AiTool]: [
						[
							{
								node: aiChild.name,
								type: NodeConnectionTypes.AiTool,
								index: 0,
							},
						],
					],
				},
			});

			const result = workflowsStore.findRootWithMainConnection(parent.name);

			expect(result).toBeNull();
		});
	});

	describe('fetchAllWorkflows()', () => {
		it('should fetch workflows successfully', async () => {
			const workflowsListStore = useWorkflowsListStore();
			const mockWorkflows = [{ id: '1', name: 'Test Workflow' }] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: mockWorkflows.length,
				data: mockWorkflows,
			});

			await workflowsListStore.fetchAllWorkflows();

			expect(workflowsApi.getWorkflows).toHaveBeenCalled();
			expect(Object.values(workflowsListStore.workflowsById)).toEqual(mockWorkflows);
		});
	});

	describe('searchWorkflows()', () => {
		beforeEach(() => {
			vi.mocked(workflowsApi).getWorkflows.mockClear();
		});

		it('should search workflows with no filters', async () => {
			const mockWorkflows = [
				{ id: '1', name: 'Workflow 1', isArchived: false },
				{ id: '2', name: 'Workflow 2', isArchived: false },
			] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: mockWorkflows.length,
				data: mockWorkflows,
			});

			const result = await workflowsListStore.searchWorkflows({});

			expect(workflowsApi.getWorkflows).toHaveBeenCalledWith(
				expect.any(Object),
				undefined,
				undefined,
				undefined,
			);
			expect(result).toEqual(mockWorkflows);
		});

		it('should search workflows with query filter', async () => {
			const mockWorkflows = [{ id: '1', name: 'Test Workflow' }] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: mockWorkflows.length,
				data: mockWorkflows,
			});

			const result = await workflowsListStore.searchWorkflows({ query: 'test' });

			expect(workflowsApi.getWorkflows).toHaveBeenCalledWith(
				expect.any(Object),
				{ query: 'test' },
				undefined,
				undefined,
			);
			expect(result).toEqual(mockWorkflows);
		});

		it('should search workflows with isArchived filter set to false', async () => {
			const mockWorkflows = [
				{ id: '1', name: 'Active Workflow 1', isArchived: false },
				{ id: '2', name: 'Active Workflow 2', isArchived: false },
			] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: mockWorkflows.length,
				data: mockWorkflows,
			});

			const result = await workflowsListStore.searchWorkflows({ isArchived: false });

			expect(workflowsApi.getWorkflows).toHaveBeenCalledWith(
				expect.any(Object),
				{ isArchived: false },
				undefined,
				undefined,
			);
			expect(result).toEqual(mockWorkflows);
		});

		it('should search workflows with isArchived filter set to true', async () => {
			const mockWorkflows = [
				{ id: '3', name: 'Archived Workflow 1', isArchived: true },
				{ id: '4', name: 'Archived Workflow 2', isArchived: true },
			] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: mockWorkflows.length,
				data: mockWorkflows,
			});

			const result = await workflowsListStore.searchWorkflows({ isArchived: true });

			expect(workflowsApi.getWorkflows).toHaveBeenCalledWith(
				expect.any(Object),
				{ isArchived: true },
				undefined,
				undefined,
			);
			expect(result).toEqual(mockWorkflows);
		});

		it('should search workflows with multiple filters including isArchived', async () => {
			const mockWorkflows = [
				{ id: '1', name: 'Test Workflow', isArchived: false },
			] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: mockWorkflows.length,
				data: mockWorkflows,
			});

			const result = await workflowsListStore.searchWorkflows({
				query: 'test',
				isArchived: false,
				projectId: 'project-123',
				tags: ['tag1', 'tag2'],
				nodeTypes: ['n8n-nodes-base.httpRequest'],
			});

			expect(workflowsApi.getWorkflows).toHaveBeenCalledWith(
				expect.any(Object),
				{
					query: 'test',
					isArchived: false,
					projectId: 'project-123',
					tags: ['tag1', 'tag2'],
					nodeTypes: ['n8n-nodes-base.httpRequest'],
				},
				undefined,
				undefined,
			);
			expect(result).toEqual(mockWorkflows);
		});

		it('should search workflows with select fields', async () => {
			const mockWorkflows = [
				{ id: '1', name: 'Workflow 1' },
				{ id: '2', name: 'Workflow 2' },
			] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: mockWorkflows.length,
				data: mockWorkflows,
			});

			const result = await workflowsListStore.searchWorkflows({
				select: ['id', 'name'],
			});

			expect(workflowsApi.getWorkflows).toHaveBeenCalledWith(
				expect.any(Object),
				undefined,
				undefined,
				['id', 'name'],
			);
			expect(result).toEqual(mockWorkflows);
		});

		it('should handle empty filter object correctly', async () => {
			const mockWorkflows = [] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: 0,
				data: mockWorkflows,
			});

			const result = await workflowsListStore.searchWorkflows({
				projectId: undefined,
				query: undefined,
				nodeTypes: undefined,
				tags: undefined,
				isArchived: undefined,
			});

			expect(workflowsApi.getWorkflows).toHaveBeenCalledWith(
				expect.any(Object),
				undefined,
				undefined,
				undefined,
			);
			expect(result).toEqual(mockWorkflows);
		});

		it('should pass isArchived as undefined when not specified', async () => {
			const mockWorkflows = [
				{ id: '1', name: 'Workflow 1', isArchived: false },
				{ id: '2', name: 'Workflow 2', isArchived: true },
			] as IWorkflowDb[];
			vi.mocked(workflowsApi).getWorkflows.mockResolvedValue({
				count: mockWorkflows.length,
				data: mockWorkflows,
			});

			const result = await workflowsListStore.searchWorkflows({ query: 'workflow' });

			expect(workflowsApi.getWorkflows).toHaveBeenCalledWith(
				expect.any(Object),
				{ query: 'workflow', isArchived: undefined },
				undefined,
				undefined,
			);
			expect(result).toEqual(mockWorkflows);
		});
	});

	describe('setWorkflowActive()', () => {
		it('should set workflow as active in list cache and clear dirty state', async () => {
			const workflowsListStore = useWorkflowsListStore();
			uiStore.markStateDirty();
			workflowsListStore.workflowsById = { '1': { active: false } as IWorkflowDb };
			workflowsStore.workflow.id = '1';

			const mockActiveVersion: WorkflowHistory = {
				versionId: 'test-version-id',
				name: 'Test Version',
				authors: 'Test Author',
				description: 'A test workflow version',
				createdAt: new Date().toISOString(),
				updatedAt: new Date().toISOString(),
				workflowPublishHistory: [],
			};

			workflowsStore.setWorkflowActive('1', mockActiveVersion, true);

			expect(workflowsListStore.activeWorkflows).toContain('1');
			expect(workflowsListStore.workflowsById['1'].active).toBe(true);
			expect(uiStore.stateIsDirty).toBe(false);
		});

		it('should not modify active workflows when workflow is already active', () => {
			const workflowsListStore = useWorkflowsListStore();
			workflowsListStore.activeWorkflows = ['1'];
			workflowsListStore.workflowsById = { '1': { active: true } as IWorkflowDb };
			workflowsStore.workflow.id = '1';

			const mockActiveVersion: WorkflowHistory = {
				versionId: 'test-version-id',
				name: 'Test Version',
				authors: 'Test Author',
				description: 'A test workflow version',
				createdAt: new Date().toISOString(),
				updatedAt: new Date().toISOString(),
				workflowPublishHistory: [],
			};

			workflowsStore.setWorkflowActive('1', mockActiveVersion, true);

			expect(workflowsListStore.activeWorkflows).toEqual(['1']);
			expect(workflowsListStore.workflowsById['1'].active).toBe(true);
		});

		it('should not clear dirty state when targeting a different workflow', () => {
			const workflowsListStore = useWorkflowsListStore();
			uiStore.markStateDirty();
			workflowsStore.workflow.id = '1';
			workflowsListStore.workflowsById = { '1': { active: false } as IWorkflowDb };

			const mockActiveVersion: WorkflowHistory = {
				versionId: 'test-version-id',
				name: 'Test Version',
				authors: 'Test Author',
				description: 'A test workflow version',
				createdAt: new Date().toISOString(),
				updatedAt: new Date().toISOString(),
				workflowPublishHistory: [],
			};

			workflowsStore.setWorkflowActive('2', mockActiveVersion, true);
			expect(workflowsListStore.workflowsById['1'].active).toBe(false);
			expect(uiStore.stateIsDirty).toBe(true);
		});
	});

	describe('setWorkflowInactive()', () => {
		it('should set workflow as inactive when it exists', () => {
			const workflowsListStore = useWorkflowsListStore();
			workflowsListStore.activeWorkflows = ['1', '2'];
			workflowsListStore.workflowsById = { '1': { active: true } as IWorkflowDb };
			workflowsStore.setWorkflowInactive('1');
			expect(workflowsListStore.workflowsById['1'].active).toBe(false);
			expect(workflowsListStore.activeWorkflows).toEqual(['2']);
		});

		it('should not modify active workflows when workflow is not active', () => {
			const workflowsListStore = useWorkflowsListStore();
			workflowsListStore.workflowsById = { '2': { active: true } as IWorkflowDb };
			workflowsListStore.activeWorkflows = ['2'];
			workflowsStore.setWorkflowInactive('1');
			expect(workflowsListStore.activeWorkflows).toEqual(['2']);
			expect(workflowsListStore.workflowsById['2'].active).toBe(true);
		});

		it('should update list cache when targeting any workflow', () => {
			const workflowsListStore = useWorkflowsListStore();
			workflowsListStore.activeWorkflows = ['1'];
			workflowsListStore.workflowsById = { '1': { active: true } as IWorkflowDb };
			workflowsStore.workflow.id = '1';
			workflowsStore.setWorkflowInactive('1');
			expect(workflowsListStore.workflowsById['1'].active).toBe(false);
			expect(workflowsListStore.activeWorkflows).toEqual([]);
		});
	});

	describe('getDuplicateCurrentWorkflowName()', () => {
		it('should return the same name if appending postfix exceeds max length', async () => {
			const longName = 'a'.repeat(MAX_WORKFLOW_NAME_LENGTH - DUPLICATE_POSTFFIX.length + 1);
			const newName = await workflowsStore.getDuplicateCurrentWorkflowName(longName);
			expect(newName).toBe(longName);
		});

		it('should append postfix to the name if it does not exceed max length', async () => {
			const name = 'TestWorkflow';
			const expectedName = `${name}${DUPLICATE_POSTFFIX}`;
			vi.mocked(workflowsApi).getNewWorkflow.mockResolvedValue({
				name: expectedName,
				settings: {} as IWorkflowSettings,
			});
			const newName = await workflowsStore.getDuplicateCurrentWorkflowName(name);
			expect(newName).toBe(expectedName);
		});

		it('should handle API failure gracefully', async () => {
			const name = 'TestWorkflow';
			const expectedName = `${name}${DUPLICATE_POSTFFIX}`;
			vi.mocked(workflowsApi).getNewWorkflow.mockRejectedValue(new Error('API Error'));
			const newName = await workflowsStore.getDuplicateCurrentWorkflowName(name);
			expect(newName).toBe(expectedName);
		});
	});

	describe('updateNodeExecutionRunData', () => {
		beforeEach(() => {
			workflowsStore.workflowExecutionData = createTestWorkflowExecutionResponse({
				id: 'test-execution',
				data: createRunExecutionData({
					resultData: {
						runData: {
							n0: [
								createTestTaskData({
									executionIndex: 0,
									executionStatus: 'success',
									executionTime: 33,
								}),
								createTestTaskData({
									executionIndex: 1,
									executionStatus: 'success',
									executionTime: 44,
								}),
								createTestTaskData({
									executionIndex: 2,
									executionStatus: 'running',
									executionTime: undefined,
								}),
							],
						},
					},
				}),
			});
		});

		it('should replace run data at the matched index in the execution data', () => {
			workflowsStore.updateNodeExecutionRunData({
				executionId: 'test-execution',
				nodeName: 'n0',
				data: createTestTaskData({
					executionIndex: 2,
					executionStatus: 'success',
					executionTime: 100,
				}),
				itemCountByConnectionType: { main: [1] },
			});

			const runData = workflowsStore.workflowExecutionData?.data?.resultData?.runData.n0;

			expect(runData).toHaveLength(3);
			expect(runData?.[0].executionIndex).toBe(0);
			expect(runData?.[0].executionStatus).toBe('success');
			expect(runData?.[0].executionTime).toBe(33);
			expect(runData?.[1].executionIndex).toBe(1);
			expect(runData?.[1].executionStatus).toBe('success');
			expect(runData?.[1].executionTime).toBe(44);
			expect(runData?.[2].executionIndex).toBe(2);
			expect(runData?.[2].executionStatus).toBe('success');
			expect(runData?.[2].executionTime).toBe(100);
		});

		it('should not modify execution data if there is no matched index in execution data', () => {
			workflowsStore.updateNodeExecutionRunData({
				executionId: 'test-execution',
				nodeName: 'n0',
				data: createTestTaskData({
					executionIndex: 3,
					executionStatus: 'success',
					executionTime: 100,
				}),
				itemCountByConnectionType: { main: [1] },
			});

			const runData = workflowsStore.workflowExecutionData?.data?.resultData?.runData.n0;

			expect(runData).toHaveLength(3);
			expect(runData?.[0].executionIndex).toBe(0);
			expect(runData?.[0].executionStatus).toBe('success');
			expect(runData?.[0].executionTime).toBe(33);
			expect(runData?.[1].executionIndex).toBe(1);
			expect(runData?.[1].executionStatus).toBe('success');
			expect(runData?.[1].executionTime).toBe(44);
			expect(runData?.[2].executionIndex).toBe(2);
			expect(runData?.[2].executionStatus).toBe('running');
			expect(runData?.[2].executionTime).toBe(undefined);
		});
	});

	describe('updateNodeExecutionStatus', () => {
		let successEvent: ReturnType<typeof generateMockExecutionEvents>['successEvent'];
		let errorEvent: ReturnType<typeof generateMockExecutionEvents>['errorEvent'];
		let executionResponse: ReturnType<typeof generateMockExecutionEvents>['executionResponse'];

		beforeEach(() => {
			const events = generateMockExecutionEvents();
			successEvent = events.successEvent;
			errorEvent = events.errorEvent;
			executionResponse = events.executionResponse;
		});

		it('should throw error if not initialized', () => {
			expect(() => workflowsStore.updateNodeExecutionStatus(successEvent)).toThrowError();
		});

		it('should add node success run data', () => {
			useWorkflowState().setWorkflowExecutionData(executionResponse);

			workflowsStore.nodesByName[successEvent.nodeName] = mock<INodeUi>({
				type: 'n8n-nodes-base.manualTrigger',
			});

			// ACT
			workflowsStore.updateNodeExecutionStatus(successEvent);

			expect(workflowsStore.workflowExecutionData).toEqual({
				...executionResponse,
				data: {
					version: 1,
					resultData: {
						lastNodeExecuted: 'When clicking ‘Execute workflow’',
						runData: {
							[successEvent.nodeName]: [successEvent.data],
						},
					},
				},
			});
		});

		it('should add node error event and track errored executions', async () => {
			workflowsStore.workflow.pinData = {};
			useWorkflowState().setWorkflowExecutionData(executionResponse);
			workflowsStore.addNode({
				parameters: {},
				id: '554c7ff4-7ee2-407c-8931-e34234c5056a',
				name: 'Edit Fields',
				type: 'n8n-nodes-base.set',
				position: [680, 180],
				typeVersion: 3.4,
			});

			getNodeType.mockReturnValue(getMockEditFieldsNode());

			// ACT
			workflowsStore.updateNodeExecutionStatus(errorEvent);
			await flushPromises();

			expect(workflowsStore.workflowExecutionData).toEqual({
				...executionResponse,
				data: {
					version: 1,
					resultData: {
						lastNodeExecuted: 'Edit Fields',
						runData: {
							[errorEvent.nodeName]: [errorEvent.data],
						},
					},
				},
			});
			expect(track).toHaveBeenCalledWith('Manual exec errored', {
				error_title: 'invalid syntax',
				node_type: 'n8n-nodes-base.set',
				node_type_version: 3.4,
				node_id: '554c7ff4-7ee2-407c-8931-e34234c5056a',
				node_graph_string:
					'{"node_types":["n8n-nodes-base.set"],"node_connections":[],"nodes":{"0":{"id":"554c7ff4-7ee2-407c-8931-e34234c5056a","type":"n8n-nodes-base.set","version":3.4,"position":[680,180]}},"notes":{},"is_pinned":false}',
			});
		});

		it('should replace placeholder task data in waiting nodes correctly', () => {
			const runWithExistingRunData = deepCopy(executionResponse);
			runWithExistingRunData.data = createRunExecutionData({
				resultData: {
					runData: {
						[successEvent.nodeName]: [
							{
								hints: [],
								startTime: 1727867966633,
								executionIndex: 2,
								executionTime: 1,
								source: [],
								executionStatus: 'waiting',
								data: {
									main: [
										[
											{
												json: {},
												pairedItem: {
													item: 0,
												},
											},
										],
									],
								},
							},
						],
					},
				},
			});
			useWorkflowState().setWorkflowExecutionData(runWithExistingRunData);

			workflowsStore.nodesByName[successEvent.nodeName] = mock<INodeUi>({
				type: 'n8n-nodes-base.manualTrigger',
			});

			// ACT
			workflowsStore.updateNodeExecutionStatus(successEvent);

			expect(workflowsStore.workflowExecutionData).toEqual({
				...runWithExistingRunData,
				data: createRunExecutionData({
					resultData: {
						lastNodeExecuted: 'When clicking ‘Execute workflow’',
						runData: {
							[successEvent.nodeName]: [successEvent.data],
						},
					},
				}),
			});
		});

		it('should replace existing placeholder task data and lastNodeExecuted', () => {
			const successEventWithExecutionIndex = deepCopy(successEvent);
			successEventWithExecutionIndex.data.executionIndex = 1;

			const runWithExistingRunData = executionResponse;
			runWithExistingRunData.data = createRunExecutionData({
				resultData: {
					runData: {
						[successEventWithExecutionIndex.nodeName]: [
							{
								hints: [],
								startTime: 1727867966633,
								executionIndex: successEventWithExecutionIndex.data.executionIndex,
								executionTime: 1,
								source: [],
								executionStatus: 'running',
								data: {
									main: [
										[
											{
												json: {},
												pairedItem: {
													item: 0,
												},
											},
										],
									],
								},
							},
						],
					},
				},
			});
			useWorkflowState().setWorkflowExecutionData(runWithExistingRunData);

			workflowsStore.nodesByName[successEvent.nodeName] = mock<INodeUi>({
				type: 'n8n-nodes-base.manualTrigger',
			});

			// ACT
			workflowsStore.updateNodeExecutionStatus(successEventWithExecutionIndex);

			expect(workflowsStore.workflowExecutionData).toEqual({
				...executionResponse,
				data: createRunExecutionData({
					resultData: {
						lastNodeExecuted: 'When clicking ‘Execute workflow’',
						runData: {
							[successEvent.nodeName]: [successEventWithExecutionIndex.data],
						},
					},
				}),
			});
		});
	});

	describe('setNodes()', () => {
		it('should transform credential-only nodes', () => {
			const setNodeId = '1';
			const credentialOnlyNodeId = '2';
			workflowsStore.setNodes([
				mock<INode>({
					id: setNodeId,
					name: 'Edit Fields',
					type: 'n8n-nodes-base.set',
				}),
				mock<INode>({
					id: credentialOnlyNodeId,
					name: 'AlienVault Request',
					type: 'n8n-nodes-base.httpRequest',
					extendsCredential: 'alienVaultApi',
				}),
			]);

			expect(workflowsStore.workflow.nodes[0].id).toEqual(setNodeId);
			expect(workflowsStore.workflow.nodes[1].id).toEqual(credentialOnlyNodeId);
			expect(workflowsStore.workflow.nodes[1].type).toEqual('n8n-creds-base.alienVaultApi');
			expect(workflowsStore.nodeMetadata).toEqual({
				'AlienVault Request': { pristine: true },
				'Edit Fields': { pristine: true },
			});
		});
	});

	describe('findNodeByPartialId', () => {
		test.each([
			[[], 'D', undefined],
			[['A', 'B', 'C'], 'D', undefined],
			[['A', 'B', 'C'], 'B', 1],
			[['AA', 'BB', 'CC'], 'B', 1],
			[['AA', 'BB', 'BC'], 'B', 1],
			[['AA', 'BB', 'BC'], 'BC', 2],
		] as Array<[string[], string, number | undefined]>)(
			'with input %s , %s returns node with index %s',
			(ids, id, expectedIndex) => {
				workflowsStore.workflow.nodes = ids.map((x) => ({ id: x }) as never);

				expect(workflowsStore.findNodeByPartialId(id)).toBe(
					workflowsStore.workflow.nodes[expectedIndex ?? -1],
				);
			},
		);
	});

	describe('getPartialIdForNode', () => {
		test.each([
			[[], 'Alphabet', 'Alphabet'],
			[['Alphabet'], 'Alphabet', 'Alphab'],
			[['Alphabet', 'Alphabeta'], 'Alphabeta', 'Alphabeta'],
			[['Alphabet', 'Alphabeta', 'Alphabetagamma'], 'Alphabet', 'Alphabet'],
			[['Alphabet', 'Alphabeta', 'Alphabetagamma'], 'Alphabeta', 'Alphabeta'],
			[['Alphabet', 'Alphabeta', 'Alphabetagamma'], 'Alphabetagamma', 'Alphabetag'],
		] as Array<[string[], string, string]>)(
			'with input %s , %s returns %s',
			(ids, id, expected) => {
				workflowsStore.workflow.nodes = ids.map((x) => ({ id: x }) as never);

				expect(workflowsStore.getPartialIdForNode(id)).toBe(expected);
			},
		);
	});

	describe('archiveWorkflow', () => {
		it('should call the API to archive the workflow without checksum', async () => {
			const workflowsListStore = useWorkflowsListStore();
			const workflowId = '1';
			const versionId = '00000000-0000-0000-0000-000000000000';
			const updatedVersionId = '11111111-1111-1111-1111-111111111111';

			workflowsListStore.workflowsById = {
				'1': { active: true, isArchived: false, versionId } as IWorkflowDb,
			};
			workflowsStore.workflow.active = true;
			workflowsStore.workflow.id = workflowId;
			workflowsStore.workflow.versionId = versionId;

			const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId(workflowId));
			workflowDocumentStore.setIsArchived(false);

			const makeRestApiRequestSpy = vi
				.spyOn(apiUtils, 'makeRestApiRequest')
				.mockImplementation(async () => ({
					versionId: updatedVersionId,
					checksum: 'checksum',
				}));

			await workflowsStore.archiveWorkflow(workflowId);

			expect(workflowsListStore.workflowsById['1'].active).toBe(false);
			expect(workflowsListStore.workflowsById['1'].isArchived).toBe(true);
			expect(workflowsListStore.workflowsById['1'].versionId).toBe(updatedVersionId);
			expect(workflowDocumentStore.isArchived).toBe(true);
			expect(workflowsStore.workflow.versionId).toBe(updatedVersionId);
			expect(makeRestApiRequestSpy).toHaveBeenCalledWith(
				expect.objectContaining({
					baseUrl: '/rest',
					pushRef: expect.any(String),
				}),
				'POST',
				`/workflows/${workflowId}/archive`,
				{ expectedChecksum: undefined },
			);
		});

		it('should pass expectedChecksum to the API when provided', async () => {
			const workflowsListStore = useWorkflowsListStore();
			const workflowId = '1';
			const versionId = '00000000-0000-0000-0000-000000000000';
			const updatedVersionId = '11111111-1111-1111-1111-111111111111';
			const expectedChecksum = 'test-checksum-123';

			workflowsListStore.workflowsById = {
				'1': { active: true, isArchived: false, versionId } as IWorkflowDb,
			};
			workflowsStore.workflow.id = workflowId;
			workflowsStore.workflow.versionId = versionId;

			const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId(workflowId));
			workflowDocumentStore.setIsArchived(false);

			const makeRestApiRequestSpy = vi
				.spyOn(apiUtils, 'makeRestApiRequest')
				.mockImplementation(async () => ({
					versionId: updatedVersionId,
					checksum: 'checksum',
				}));

			await workflowsStore.archiveWorkflow(workflowId, expectedChecksum);

			expect(makeRestApiRequestSpy).toHaveBeenCalledWith(
				expect.objectContaining({
					baseUrl: '/rest',
					pushRef: expect.any(String),
				}),
				'POST',
				`/workflows/${workflowId}/archive`,
				{ expectedChecksum },
			);
		});
	});

	describe('unarchiveWorkflow', () => {
		it('should call the API to unarchive the workflow', async () => {
			const workflowsListStore = useWorkflowsListStore();
			const workflowId = '1';
			const versionId = '00000000-0000-0000-0000-000000000000';
			const updatedVersionId = '11111111-1111-1111-1111-111111111111';

			workflowsListStore.workflowsById = {
				'1': { active: false, isArchived: true, versionId } as IWorkflowDb,
			};
			workflowsStore.workflow.active = false;
			workflowsStore.workflow.id = workflowId;
			workflowsStore.workflow.versionId = versionId;

			const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId(workflowId));
			workflowDocumentStore.setIsArchived(true);

			const makeRestApiRequestSpy = vi
				.spyOn(apiUtils, 'makeRestApiRequest')
				.mockImplementation(async () => ({
					versionId: updatedVersionId,
					checksum: 'checksum',
				}));

			await workflowsStore.unarchiveWorkflow(workflowId);

			expect(workflowsListStore.workflowsById['1'].active).toBe(false);
			expect(workflowsListStore.workflowsById['1'].isArchived).toBe(false);
			expect(workflowsListStore.workflowsById['1'].versionId).toBe(updatedVersionId);
			expect(workflowDocumentStore.isArchived).toBe(false);
			expect(workflowsStore.workflow.versionId).toBe(updatedVersionId);
			expect(makeRestApiRequestSpy).toHaveBeenCalledWith(
				expect.objectContaining({
					baseUrl: '/rest',
					pushRef: expect.any(String),
				}),
				'POST',
				`/workflows/${workflowId}/unarchive`,
			);
		});
	});

	describe('updateWorkflowSetting', () => {
		beforeEach(() => {
			vi.clearAllMocks();
		});

		it('updates current workflow setting and store state', async () => {
			workflowsStore.workflow.id = 'w1';
			workflowsStore.workflow.versionId = 'v1';
			workflowsStore.workflow.settings = {
				executionOrder: 'v1',
				timezone: 'UTC',
			};

			// Also populate the document store since updateWorkflowSetting reads from it
			const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId('w1'));
			workflowDocumentStore.setSettings({ executionOrder: 'v1', timezone: 'UTC' });

			const makeRestApiRequestSpy = vi.spyOn(apiUtils, 'makeRestApiRequest').mockResolvedValue(
				createTestWorkflow({
					id: 'w1',
					versionId: 'v1',
					settings: {
						executionOrder: 'v1',
						timezone: 'UTC',
						executionTimeout: 10,
					},
					nodes: [],
					connections: {},
				}),
			);

			// Act
			const result = await workflowsStore.updateWorkflowSetting('w1', 'executionTimeout', 10);

			// Assert request payload
			expect(makeRestApiRequestSpy).toHaveBeenCalledWith(
				{ baseUrl: '/rest', pushRef: expect.any(String) },
				'PATCH',
				'/workflows/w1',
				expect.objectContaining({
					versionId: 'v1',
					settings: expect.objectContaining({ executionTimeout: 10, timezone: 'UTC' }),
				}),
			);

			// Assert returned value and store updates
			expect(result.versionId).toBe('v1');
			expect(workflowsStore.workflow.versionId).toBe('v1');
			expect(workflowDocumentStore.settings).toEqual({
				executionOrder: 'v1',
				binaryMode: 'separate',
				timezone: 'UTC',
				executionTimeout: 10,
			});
		});

		it('updates cached workflow without fetching when present in store', async () => {
			const workflowsListStore = useWorkflowsListStore();
			workflowsListStore.workflowsById = {
				w2: createTestWorkflow({
					id: 'w2',
					name: 'Cached',
					versionId: 'v2',
					nodes: [],
					connections: {},
					active: false,
					isArchived: false,
					settings: { executionOrder: 'v1' },
				}),
			};

			const getWorkflowSpy = vi.spyOn(workflowsApi, 'getWorkflow');

			vi.spyOn(apiUtils, 'makeRestApiRequest').mockResolvedValue(
				createTestWorkflow({
					id: 'w2',
					versionId: 'v3',
					nodes: [],
					connections: {},
					active: false,
					isArchived: false,
					settings: {
						executionOrder: 'v1',
						timezone: 'Europe/Berlin',
					},
				}),
			);

			await workflowsStore.updateWorkflowSetting('w2', 'timezone', 'Europe/Berlin');

			// Should not fetch since cached with versionId exists
			expect(getWorkflowSpy).not.toHaveBeenCalled();
			expect(workflowsListStore.workflowsById['w2'].versionId).toBe('v3');
			expect(workflowsListStore.workflowsById['w2'].settings).toEqual({
				executionOrder: 'v1',
				timezone: 'Europe/Berlin',
			});
		});

		it('fetches workflow when not cached and updates store', async () => {
			const workflowsListStore = useWorkflowsListStore();
			workflowsListStore.workflowsById = {} as Record<string, IWorkflowDb>;

			vi.mocked(workflowsApi).getWorkflow.mockResolvedValue(
				createTestWorkflow({
					id: 'w3',
					name: 'Fetched',
					versionId: 'v100',
					nodes: [],
					connections: {},
					active: false,
					isArchived: false,
					settings: { executionOrder: 'v1' },
				}),
			);

			vi.spyOn(apiUtils, 'makeRestApiRequest').mockResolvedValue(
				createTestWorkflow({
					id: 'w3',
					versionId: 'v101',
					nodes: [],
					connections: {},
					active: false,
					isArchived: false,
					settings: {
						executionOrder: 'v1',
						timezone: 'Asia/Tokyo',
					},
				}),
			);

			await workflowsStore.updateWorkflowSetting('w3', 'timezone', 'Asia/Tokyo');

			expect(workflowsApi.getWorkflow).toHaveBeenCalledWith(
				{ baseUrl: '/rest', pushRef: expect.any(String) },
				'w3',
			);
			expect(workflowsListStore.workflowsById['w3'].versionId).toBe('v101');
			expect(workflowsListStore.workflowsById['w3'].settings).toEqual({
				executionOrder: 'v1',
				timezone: 'Asia/Tokyo',
			});
		});
	});

	describe('renameNodeSelectedAndExecution', () => {
		it('should rename node and update execution data', () => {
			const nodeName = 'Rename me';
			const newName = 'Renamed';

			workflowsStore.workflowExecutionData = {
				data: {
					resultData: {
						runData: {
							"When clicking 'Test workflow'": [
								{
									startTime: 1747389900668,
									executionIndex: 0,
									source: [],
									hints: [],
									executionTime: 1,
									executionStatus: 'success',
									data: {},
								},
							],
							[nodeName]: [
								{
									startTime: 1747389900670,
									executionIndex: 2,
									source: [
										{
											previousNode: "When clicking 'Test workflow'",
										},
									],
									hints: [],
									executionTime: 1,
									executionStatus: 'success',
									data: {},
								},
							],
							'Edit Fields': [
								{
									startTime: 1747389900671,
									executionIndex: 3,
									source: [
										{
											previousNode: nodeName,
										},
									],
									hints: [],
									executionTime: 3,
									executionStatus: 'success',
									data: {},
								},
							],
						},
						pinData: {
							[nodeName]: [
								{
									json: {
										foo: 'bar',
									},
									pairedItem: [
										{
											item: 0,
											sourceOverwrite: {
												previousNode: "When clicking 'Test workflow'",
											},
										},
									],
								},
							],
							'Edit Fields': [
								{
									json: {
										bar: 'foo',
									},
									pairedItem: {
										item: 1,
										sourceOverwrite: {
											previousNode: nodeName,
										},
									},
								},
							],
						},
						lastNodeExecuted: 'Edit Fields',
					},
				},
			} as unknown as IExecutionResponse;

			workflowsStore.workflow.id = 'test-workflow-id';

			workflowsStore.addNode({
				parameters: {},
				id: '554c7ff4-7ee2-407c-8931-e34234c5056a',
				name: nodeName,
				type: 'n8n-nodes-base.set',
				position: [680, 180],
				typeVersion: 3.4,
			});

			const workflowDocumentStore = useWorkflowDocumentStore(
				createWorkflowDocumentId(workflowsStore.workflow.id),
			);
			workflowDocumentStore.setPinData({
				[nodeName]: [
					{
						json: {
							foo: 'bar',
						},
						pairedItem: {
							item: 2,
							sourceOverwrite: {
								previousNode: "When clicking 'Test workflow'",
							},
						},
					},
				],
				'Edit Fields': [
					{
						json: {
							bar: 'foo',
						},
						pairedItem: [
							{
								item: 3,
								sourceOverwrite: {
									previousNode: nodeName,
								},
							},
						],
					},
				],
			});

			workflowsStore.renameNodeSelectedAndExecution({ old: nodeName, new: newName });

			expect(workflowsStore.nodeMetadata[nodeName]).not.toBeDefined();
			expect(workflowsStore.nodeMetadata[newName]).toEqual({});
			expect(
				workflowsStore.workflowExecutionData?.data?.resultData.runData[nodeName],
			).not.toBeDefined();
			expect(workflowsStore.workflowExecutionData?.data?.resultData.runData[newName]).toBeDefined();
			expect(
				workflowsStore.workflowExecutionData?.data?.resultData.runData['Edit Fields'][0].source,
			).toEqual([
				{
					previousNode: newName,
				},
			]);
			expect(
				workflowsStore.workflowExecutionData?.data?.resultData.pinData?.[nodeName],
			).not.toBeDefined();
			expect(
				workflowsStore.workflowExecutionData?.data?.resultData.pinData?.[newName],
			).toBeDefined();
			expect(
				workflowsStore.workflowExecutionData?.data?.resultData.pinData?.['Edit Fields'][0]
					.pairedItem,
			).toEqual({
				item: 1,
				sourceOverwrite: {
					previousNode: newName,
				},
			});

			expect(workflowDocumentStore.pinData?.[nodeName]).not.toBeDefined();
			expect(workflowDocumentStore.pinData?.[newName]).toBeDefined();
			expect(workflowDocumentStore.pinData?.['Edit Fields'][0].pairedItem).toEqual([
				{
					item: 3,
					sourceOverwrite: {
						previousNode: newName,
					},
				},
			]);
		});
	});

	describe('selectedTriggerNode', () => {
		const n0 = createTestNode({ type: MANUAL_TRIGGER_NODE_TYPE, name: 'n0' });
		const n1 = createTestNode({ type: MANUAL_TRIGGER_NODE_TYPE, name: 'n1' });
		const n2 = createTestNode({ type: MANUAL_TRIGGER_NODE_TYPE, name: 'n2' });

		beforeEach(() => {
			workflowsStore.setNodes([n0, n1]);
			getNodeType.mockImplementation(() => mockNodeTypeDescription({ group: ['trigger'] }));
		});

		it('should select newly added trigger node automatically', async () => {
			await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe('n0'));
			workflowsStore.addNode(n2);
			await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe('n2'));
		});

		it('should re-select a trigger when selected trigger gets disabled or removed', async () => {
			await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe('n0'));
			workflowsStore.removeNode(n0);
			await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe('n1'));
			useWorkflowState().setNodeValue({ name: 'n1', key: 'disabled', value: true });
			await waitFor(() => expect(workflowsStore.selectedTriggerNodeName).toBe(undefined));
		});
	});

	describe('assignCredentialToMatchingNodes', () => {
		beforeEach(() => {
			// Reset mock
			getNodeType.mockReset();
		});

		it("should assign credential to nodes that support it but don't have it set", () => {
			const credential = { id: 'cred-1', name: 'Test Credential' };
			const credentialType = 'slackApi';

			// Set up nodes with different scenarios
			workflowsStore.setNodes([
				createTestNode({
					name: 'Current Node',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
				createTestNode({
					name: 'Slack Node 1',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
				createTestNode({
					name: 'Slack Node 2',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
			]);

			// Mock getNodeType to return node type with credential support
			getNodeType.mockReturnValue({
				credentials: [{ name: 'slackApi', required: true }],
				inputs: [],
				group: [],
				webhooks: [],
				properties: [],
			});

			// Simulate setting credential on the first node
			const result = workflowsStore.assignCredentialToMatchingNodes({
				credentials: credential,
				type: credentialType,
				currentNodeName: 'Current Node',
			});

			expect(result).toBe(2); // Should update 2 nodes (excluding current node)
			expect(workflowsStore.workflow.nodes[1].credentials).toEqual({
				slackApi: credential,
			});
			expect(workflowsStore.workflow.nodes[2].credentials).toEqual({
				slackApi: credential,
			});
		});

		it('should not overwrite existing credentials of the same type', () => {
			const newCredential = { id: 'cred-new', name: 'New Credential' };
			const existingCredential = { id: 'cred-old', name: 'Existing Credential' };
			const credentialType = 'slackApi';

			workflowsStore.setNodes([
				createTestNode({
					name: 'Current Node',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
				createTestNode({
					name: 'Node With Existing Cred',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
					credentials: {
						slackApi: existingCredential,
					},
				}),
				createTestNode({
					name: 'Node Without Cred',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
			]);

			getNodeType.mockReturnValue({
				credentials: [{ name: 'slackApi', required: true }],
				inputs: [],
				group: [],
				webhooks: [],
				properties: [],
			});

			// Simulate assigning new credential to the first node
			const result = workflowsStore.assignCredentialToMatchingNodes({
				credentials: newCredential,
				type: credentialType,
				currentNodeName: 'Current Node',
			});

			expect(result).toBe(1); // Only 1 node updated (the one without credentials)
			expect(workflowsStore.workflow.nodes[1].credentials?.slackApi).toEqual(existingCredential); // Unchanged
			expect(workflowsStore.workflow.nodes[2].credentials?.slackApi).toEqual(newCredential); // Updated
		});

		it("should not affect nodes that don't support the credential type", () => {
			const credential = { id: 'cred-1', name: 'Test Credential' };
			const credentialType = 'slackApi';

			workflowsStore.setNodes([
				createTestNode({
					name: 'Current Node',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
				createTestNode({
					name: 'Slack Node',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
				createTestNode({
					name: 'HTTP Node',
					type: 'n8n-nodes-base.httpRequest',
					typeVersion: 1,
				}),
			]);

			// Mock getNodeType to return different credentials for different node types
			getNodeType.mockImplementation((nodeType: string) => {
				if (nodeType === 'n8n-nodes-base.slack') {
					return {
						credentials: [{ name: 'slackApi', required: true }],
						inputs: [],
						group: [],
						webhooks: [],
						properties: [],
					};
				}
				return {
					credentials: [{ name: 'httpBasicAuth', required: false }],
					inputs: [],
					group: [],
					webhooks: [],
					properties: [],
				};
			});

			// Simulate assigning credential to the first node
			const result = workflowsStore.assignCredentialToMatchingNodes({
				credentials: credential,
				type: credentialType,
				currentNodeName: 'Current Node',
			});

			expect(result).toBe(1); // Only the Slack node updated
			expect(workflowsStore.workflow.nodes[1].credentials?.slackApi).toEqual(credential);
			expect(workflowsStore.workflow.nodes[2].credentials).toBeUndefined(); // HTTP node unchanged
		});

		it('should handle nodes with no credential support', () => {
			const credential = { id: 'cred-1', name: 'Test Credential' };
			const credentialType = 'slackApi';

			workflowsStore.setNodes([
				createTestNode({
					name: 'Current Node',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
				createTestNode({
					name: 'Node Without Creds Support',
					type: 'n8n-nodes-base.noOp',
					typeVersion: 1,
				}),
			]);

			getNodeType.mockImplementation((nodeType: string) => {
				if (nodeType === 'n8n-nodes-base.slack') {
					return {
						credentials: [{ name: 'slackApi', required: true }],
						inputs: [],
						group: [],
						webhooks: [],
						properties: [],
					};
				}
				// Return node type without credentials field
				return {
					inputs: [],
					group: [],
					webhooks: [],
					properties: [],
				};
			});

			// Simulate assigning credential to the first node
			const result = workflowsStore.assignCredentialToMatchingNodes({
				credentials: credential,
				type: credentialType,
				currentNodeName: 'Current Node',
			});

			expect(result).toBe(0); // No nodes updated
			expect(workflowsStore.workflow.nodes[1].credentials).toBeUndefined();
		});

		it('should not assign credential to nodes where displayOptions do not match current parameters', () => {
			const credential = { id: 'cred-1', name: 'Header Auth Credential' };
			const credentialType = 'httpHeaderAuth';

			workflowsStore.setNodes([
				createTestNode({
					name: 'HTTP Request',
					type: 'n8n-nodes-base.httpRequest',
					typeVersion: 1,
				}),
				createTestNode({
					name: 'Webhook',
					type: 'n8n-nodes-base.webhook',
					typeVersion: 1,
					parameters: { authentication: 'none' },
				}),
			]);

			getNodeType.mockImplementation((nodeType: string) => {
				if (nodeType === 'n8n-nodes-base.httpRequest') {
					return {
						credentials: [{ name: 'httpHeaderAuth', required: false }],
						inputs: [],
						group: [],
						webhooks: [],
						properties: [],
					};
				}
				// Webhook supports httpHeaderAuth but only when authentication = 'headerAuth'
				return {
					credentials: [
						{
							name: 'httpHeaderAuth',
							required: false,
							displayOptions: { show: { authentication: ['headerAuth'] } },
						},
					],
					inputs: [],
					group: [],
					webhooks: [],
					properties: [
						{
							displayName: 'Authentication',
							name: 'authentication',
							type: 'options',
							default: 'none',
							options: [
								{ name: 'None', value: 'none' },
								{ name: 'Header Auth', value: 'headerAuth' },
							],
						},
					],
				};
			});

			const result = workflowsStore.assignCredentialToMatchingNodes({
				credentials: credential,
				type: credentialType,
				currentNodeName: 'HTTP Request',
			});

			expect(result).toBe(0); // Webhook should NOT get the credential (authentication is 'none')
			expect(workflowsStore.workflow.nodes[1].credentials).toBeUndefined();
		});

		it('should return 0 when there are no matching nodes', () => {
			const credential = { id: 'cred-1', name: 'Test Credential' };
			const credentialType = 'slackApi';

			workflowsStore.setNodes([
				createTestNode({
					name: 'Current Node',
					type: 'n8n-nodes-base.slack',
					typeVersion: 1,
				}),
			]);

			getNodeType.mockReturnValue({
				credentials: [{ name: 'slackApi', required: true }],
				inputs: [],
				group: [],
				webhooks: [],
				properties: [],
			});

			// Simulate assigning credential to the first node
			const result = workflowsStore.assignCredentialToMatchingNodes({
				credentials: credential,
				type: credentialType,
				currentNodeName: 'Current Node',
			});

			expect(result).toBe(0); // No nodes to update (only current node exists)
		});
	});

	describe('getWebhookUrl', () => {
		it('should return undefined when node does not exist', async () => {
			workflowsStore.setNodes([]);

			const result = await workflowsStore.getWebhookUrl('non-existent-node', 'test');

			expect(result).toBeUndefined();
		});

		it('should return undefined when node type does not exist', async () => {
			const testNode = createTestNode({ id: 'node-1', name: 'Webhook Node' });
			workflowsStore.setNodes([testNode]);
			getNodeType.mockReturnValue(null);

			const result = await workflowsStore.getWebhookUrl('node-1', 'test');

			expect(result).toBeUndefined();
		});

		it('should return undefined when node type has no webhooks', async () => {
			const testNode = createTestNode({ id: 'node-1', name: 'Webhook Node' });
			workflowsStore.setNodes([testNode]);
			getNodeType.mockReturnValue({
				inputs: [],
				group: [],
				webhooks: [],
				properties: [],
			});

			const result = await workflowsStore.getWebhookUrl('node-1', 'test');

			expect(result).toBeUndefined();
		});

		it('should return webhook URL for test type', async () => {
			const testNode = createTestNode({
				id: 'node-1',
				name: 'Webhook Node',
				type: 'n8n-nodes-base.webhook',
			});
			workflowsStore.setNodes([testNode]);
			getNodeType.mockReturnValue({
				inputs: [],
				group: [],
				webhooks: [{ name: 'default', httpMethod: 'GET', path: 'webhook' }],
				properties: [],
			});

			const result = await workflowsStore.getWebhookUrl('node-1', 'test');

			expect(result).toBeDefined();
			expect(typeof result).toBe('string');
			expect(result).toContain('webhook');
		});

		it('should return webhook URL for production type', async () => {
			const testNode = createTestNode({
				id: 'node-1',
				name: 'Webhook Node',
				type: 'n8n-nodes-base.webhook',
			});
			workflowsStore.setNodes([testNode]);
			getNodeType.mockReturnValue({
				inputs: [],
				group: [],
				webhooks: [{ name: 'default', httpMethod: 'POST', path: 'webhook' }],
				properties: [],
			});

			const result = await workflowsStore.getWebhookUrl('node-1', 'production');

			expect(result).toBeDefined();
			expect(typeof result).toBe('string');
			expect(result).toContain('webhook');
		});

		it('should use the first webhook when node has multiple webhooks', async () => {
			const testNode = createTestNode({
				id: 'node-1',
				name: 'Webhook Node',
				type: 'n8n-nodes-base.webhook',
			});
			workflowsStore.setNodes([testNode]);
			getNodeType.mockReturnValue({
				inputs: [],
				group: [],
				webhooks: [
					{ name: 'default', httpMethod: 'GET', path: 'webhook1' },
					{ name: 'default', httpMethod: 'POST', path: 'webhook2' },
				],
				properties: [],
			});

			const result = await workflowsStore.getWebhookUrl('node-1', 'test');

			expect(result).toBeDefined();
			expect(typeof result).toBe('string');
			expect(result).toContain('webhook1');
		});
	});

	describe('fetchLastSuccessfulExecution', () => {
		beforeEach(() => {
			// Ensure currentView is set to a non-readonly view (VIEWS.WORKFLOW = 'NodeViewExisting')
			uiStore.currentView = 'NodeViewExisting';

			// Reset sourceControlStore mock to default
			vi.mocked(useSourceControlStore).mockReturnValue({
				preferences: {
					branchReadOnly: false,
				},
			} as ReturnType<typeof useSourceControlStore>);

			// Reset the API mock
			vi.mocked(workflowsApi).getLastSuccessfulExecution.mockReset();
		});

		it('should fetch and store the last successful execution', async () => {
			const mockExecution = createTestWorkflowExecutionResponse({
				id: 'execution-456',
				status: 'success',
			});

			const workflowId = 'workflow-123';
			const testWorkflow = createTestWorkflow({
				id: workflowId,
				scopes: ['workflow:update'],
			});

			workflowsStore.workflow = testWorkflow;
			// Add workflow to workflowsById to simulate it being loaded from backend
			workflowsListStore.addWorkflow(testWorkflow);

			const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId(workflowId));
			workflowDocumentStore.setScopes(testWorkflow.scopes ?? []);

			// Verify the mock is set up correctly
			expect(workflowsStore.workflow.scopes).toContain('workflow:update');
			expect(workflowsStore.workflow.id).toBe('workflow-123');
			expect(workflowDocumentStore.isArchived).toBe(false);

			vi.mocked(workflowsApi).getLastSuccessfulExecution.mockResolvedValue(mockExecution);

			await workflowsStore.fetchLastSuccessfulExecution();

			expect(workflowsApi.getLastSuccessfulExecution).toHaveBeenCalledWith(
				expect.objectContaining({
					baseUrl: '/rest',
					pushRef: expect.any(String),
				}),
				'workflow-123',
			);
			expect(workflowsStore.lastSuccessfulExecution).toEqual(mockExecution);
		});

		it('should handle null response from API', async () => {
			const testWorkflow = createTestWorkflow({
				id: 'workflow-123',
				scopes: ['workflow:update'],
			});

			workflowsStore.workflow = testWorkflow;
			// Add workflow to workflowsById to simulate it being loaded from backend
			workflowsListStore.addWorkflow(testWorkflow);

			const workflowDocumentStore = useWorkflowDocumentStore(
				createWorkflowDocumentId(testWorkflow.id),
			);
			workflowDocumentStore.setScopes(testWorkflow.scopes ?? []);

			vi.mocked(workflowsApi).getLastSuccessfulExecution.mockResolvedValue(null);

			await workflowsStore.fetchLastSuccessfulExecution();

			expect(workflowsApi.getLastSuccessfulExecution).toHaveBeenCalledWith(
				expect.objectContaining({
					baseUrl: '/rest',
					pushRef: expect.any(String),
				}),
				'workflow-123',
			);
			expect(workflowsStore.lastSuccessfulExecution).toBeNull();
		});

		it('should not throw error when API call fails', async () => {
			const testWorkflow = createTestWorkflow({
				id: 'workflow-123',
				scopes: ['workflow:update'],
			});

			workflowsStore.workflow = testWorkflow;
			// Add workflow to workflowsById to simulate it being loaded from backend
			workflowsListStore.addWorkflow(testWorkflow);

			const workflowDocumentStore = useWorkflowDocumentStore(
				createWorkflowDocumentId(testWorkflow.id),
			);
			workflowDocumentStore.setScopes(testWorkflow.scopes ?? []);

			const error = new Error('API Error');
			vi.mocked(workflowsApi).getLastSuccessfulExecution.mockRejectedValue(error);

			// Should not throw
			await expect(workflowsStore.fetchLastSuccessfulExecution()).resolves.toBeUndefined();

			expect(workflowsApi.getLastSuccessfulExecution).toHaveBeenCalledWith(
				expect.objectContaining({
					baseUrl: '/rest',
					pushRef: expect.any(String),
				}),
				'workflow-123',
			);
		});

		it('should not fetch when workflow is placeholder empty workflow', async () => {
			workflowsStore.workflow = createTestWorkflow({
				id: '',
				scopes: ['workflow:update'],
			});

			await workflowsStore.fetchLastSuccessfulExecution();

			expect(workflowsApi.getLastSuccessfulExecution).not.toHaveBeenCalled();
		});

		it('should not fetch when workflow is read-only', async () => {
			workflowsStore.workflow = createTestWorkflow({
				id: 'workflow-123',
				scopes: ['workflow:update'],
			});
			// Set currentView to a read-only view (not WORKFLOW, NEW_WORKFLOW, or EXECUTION_DEBUG)
			uiStore.currentView = 'execution';

			await workflowsStore.fetchLastSuccessfulExecution();

			expect(workflowsApi.getLastSuccessfulExecution).not.toHaveBeenCalled();
		});

		it('should not fetch when workflow is archived', async () => {
			const workflowId = 'workflow-123';
			workflowsStore.workflow = createTestWorkflow({
				id: workflowId,
				scopes: ['workflow:update'],
			});
			workflowsListStore.addWorkflow(workflowsStore.workflow);

			const workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId(workflowId));
			workflowDocumentStore.setIsArchived(true);

			await workflowsStore.fetchLastSuccessfulExecution();

			expect(workflowsApi.getLastSuccessfulExecution).not.toHaveBeenCalled();
		});

		it('should not fetch when user does not have update permissions', async () => {
			workflowsStore.workflow = createTestWorkflow({
				id: 'workflow-123',
				scopes: ['workflow:read'],
			});

			await workflowsStore.fetchLastSuccessfulExecution();

			expect(workflowsApi.getLastSuccessfulExecution).not.toHaveBeenCalled();
		});

		it('should not fetch when branch is read-only in source control', async () => {
			// Mock the source control store BEFORE reinitializing workflows store
			vi.mocked(useSourceControlStore).mockReturnValue({
				preferences: {
					branchReadOnly: true,
				},
			} as ReturnType<typeof useSourceControlStore>);

			// Create a fresh Pinia instance and reinitialize the workflows store to pick up the new mock
			setActivePinia(createPinia());
			workflowsStore = useWorkflowsStore();

			workflowsStore.workflow = createTestWorkflow({
				id: 'workflow-123',
				scopes: ['workflow:update'],
			});

			await workflowsStore.fetchLastSuccessfulExecution();

			expect(workflowsApi.getLastSuccessfulExecution).not.toHaveBeenCalled();
		});
	});

	describe('getNodeTypes() - getByNameAndVersion', () => {
		beforeEach(() => {
			setActivePinia(createPinia());
			workflowsStore = useWorkflowsStore();
		});

		it('should return node type for core nodes', () => {
			const mockNodeType = mockNodeTypeDescription({
				name: 'n8n-nodes-base.httpRequest',
				displayName: 'HTTP Request',
			});

			getNodeType.mockReturnValue(mockNodeType);

			const nodeTypes = workflowsStore.getNodeTypes();
			const result = nodeTypes.getByNameAndVersion('n8n-nodes-base.httpRequest', 1);

			expect(result).toBeDefined();
			expect(result?.description.name).toBe('n8n-nodes-base.httpRequest');
		});

		it('should fallback to community node type when core node not found', () => {
			const mockCommunityNodeDescription = mockNodeTypeDescription({
				name: 'n8n-nodes-test.test',
				displayName: 'Test Node',
			});

			getNodeType.mockReturnValue(null);

			communityNodeType.mockReturnValue({
				name: 'n8n-nodes-test.test',
				packageName: 'n8n-nodes-test',
				checksum: 'test-checksum',
				npmVersion: '1.0.0',
				createdAt: '2024-01-01',
				updatedAt: '2024-01-01',
				numberOfStars: 0,
				numberOfDownloads: 0,
				authorGithubUrl: '',
				authorName: '',
				description: '',
				displayName: 'Test Node',
				isOfficialNode: false,
				nodeDescription: mockCommunityNodeDescription,
				isInstalled: false,
			});

			const nodeTypes = workflowsStore.getNodeTypes();
			const result = nodeTypes.getByNameAndVersion('n8n-nodes-test.test');

			expect(result).toBeDefined();
			expect(result?.description.name).toBe('n8n-nodes-test.test');
		});

		it('should return undefined when node type is not found', () => {
			getNodeType.mockReturnValue(null);

			communityNodeType.mockReturnValue(undefined);

			const nodeTypes = workflowsStore.getNodeTypes();
			const result = nodeTypes.getByNameAndVersion('non-existent-node');

			expect(result).toBeUndefined();
		});

		it('should use community node description when available and core node is null', () => {
			const mockCommunityNodeDescription = mockNodeTypeDescription({
				name: 'n8n-nodes-community.customNode',
				displayName: 'Custom Community Node',
				inputs: ['main'],
				outputs: ['main'],
			});

			getNodeType.mockReturnValue(null);

			communityNodeType.mockReturnValue({
				name: 'n8n-nodes-community.customNode',
				packageName: 'n8n-nodes-community',
				checksum: 'test-checksum',
				npmVersion: '1.0.0',
				createdAt: '2024-01-01',
				updatedAt: '2024-01-01',
				numberOfStars: 10,
				numberOfDownloads: 100,
				authorGithubUrl: '',
				authorName: '',
				description: '',
				displayName: 'Custom Community Node',
				isOfficialNode: false,
				nodeDescription: mockCommunityNodeDescription,
				isInstalled: true,
			});

			const nodeTypes = workflowsStore.getNodeTypes();
			const result = nodeTypes.getByNameAndVersion('n8n-nodes-community.customNode');

			expect(result).toBeDefined();
			expect(result?.description.name).toBe('n8n-nodes-community.customNode');
			expect(result?.description.displayName).toBe('Custom Community Node');
		});
	});
});

function getMockEditFieldsNode(): Partial<INodeTypeDescription> {
	return {
		displayName: 'Edit Fields (Set)',
		name: 'n8n-nodes-base.set',
		icon: 'fa:pen',
		group: ['input'],
		description: 'Modify, add, or remove item fields',
		defaultVersion: 3.4,
		iconColor: 'blue',
		version: [3, 3.1, 3.2, 3.3, 3.4],
		subtitle: '={{$parameter["mode"]}}',
		defaults: {
			name: 'Edit Fields',
		},
		inputs: ['main'],
		outputs: ['main'],
		properties: [],
	};
}

function generateMockExecutionEvents() {
	const executionResponse: IExecutionResponse = {
		id: '1',
		workflowData: {
			id: '1',
			name: '',
			createdAt: '1',
			updatedAt: '1',
			nodes: [],
			connections: {},
			active: false,
			activeVersionId: null,
			isArchived: false,
			versionId: '1',
		},
		finished: false,
		mode: 'cli',
		startedAt: new Date(),
		createdAt: new Date(),
		status: 'new',
		data: createEmptyRunExecutionData(),
	};
	const successEvent: PushPayload<'nodeExecuteAfter'> = {
		executionId: '59',
		nodeName: 'When clicking ‘Execute workflow’',
		itemCountByConnectionType: { main: [1] },
		data: {
			hints: [],
			startTime: 1727867966633,
			executionIndex: 0,
			executionTime: 1,
			source: [],
			executionStatus: 'success',
		},
	};

	const errorEvent: PushPayload<'nodeExecuteAfter'> = {
		executionId: '61',
		nodeName: 'Edit Fields',
		data: {
			hints: [],
			startTime: 1727869043441,
			executionIndex: 0,
			executionTime: 2,
			source: [
				{
					previousNode: 'When clicking ‘Execute workflow’',
				},
			],
			executionStatus: 'error',
			// @ts-expect-error simpler data type, not BE class with methods
			error: {
				level: 'error',
				tags: {
					packageName: 'workflow',
				},
				context: {
					itemIndex: 0,
				},
				functionality: 'regular',
				name: 'NodeOperationError',
				timestamp: 1727869043442,
				node: {
					parameters: {
						mode: 'manual',
						duplicateItem: false,
						assignments: {
							assignments: [
								{
									id: '87afdb19-4056-4551-93ef-d0126a34eb83',
									name: "={{ $('Wh }}",
									value: '',
									type: 'string',
								},
							],
						},
						includeOtherFields: false,
						options: {},
					},
					id: '9fb34d2d-7191-48de-8f18-91a6a28d0230',
					name: 'Edit Fields',
					type: 'n8n-nodes-base.set',
					typeVersion: 3.4,
					position: [1120, 180],
				},
				messages: [],
				message: 'invalid syntax',
				stack: 'NodeOperationError: invalid syntax',
			},
		},
	};

	return { executionResponse, errorEvent, successEvent };
}
