import {
	hasRevertVersionId,
	type ChatRequest,
	type TextMessageWithRevertVersionId,
} from '@/features/ai/assistant/assistant.types';
import { useAIAssistantHelpers } from '@/features/ai/assistant/composables/useAIAssistantHelpers';
import { useFocusedNodesStore } from '@/features/ai/assistant/focusedNodes.store';
import { usePostHog } from '@/app/stores/posthog.store';
import {
	AI_BUILDER_TEMPLATE_EXAMPLES_EXPERIMENT,
	CODE_WORKFLOW_BUILDER_EXPERIMENT,
	MERGE_ASK_BUILD_EXPERIMENT,
} from '@/app/constants/experiments';
import type { IRunExecutionData } from 'n8n-workflow';
import type { IWorkflowDb } from '@/Interface';
import { getWorkflowVersionsByIds } from '@n8n/rest-api-client/api/workflowHistory';
import type { IRestApiContext } from '@n8n/rest-api-client';

export function generateShortId() {
	return Math.random().toString(36).substring(2, 11);
}

export function generateMessageId(): string {
	return `${Date.now()}-${generateShortId()}`;
}

export async function createBuilderPayload(
	text: string,
	id: string,
	options: {
		quickReplyType?: string;
		executionData?: IRunExecutionData['resultData'];
		workflow?: IWorkflowDb;
		nodesForSchema?: string[];
		mode?: 'build' | 'plan';
		isPlanModeEnabled?: boolean;
		allowSendingParameterValues?: boolean;
	} = {},
): Promise<ChatRequest.UserChatMessage> {
	const assistantHelpers = useAIAssistantHelpers();
	const posthogStore = usePostHog();
	const focusedNodesStore = useFocusedNodesStore();
	const workflowContext: ChatRequest.WorkflowContext = {};

	// When privacy is OFF (allowSendingParameterValues=false), exclude parameter values from workflow
	const shouldExcludeParameterValues = options.allowSendingParameterValues === false;

	if (options.workflow) {
		workflowContext.currentWorkflow = {
			...(await assistantHelpers.simplifyWorkflowForAssistant(options.workflow, {
				excludeParameterValues: shouldExcludeParameterValues,
			})),
			id: options.workflow.id,
		};
	}

	if (options.executionData) {
		workflowContext.executionData = assistantHelpers.simplifyResultData(options.executionData, {
			compact: true,
			removeParameterValues: shouldExcludeParameterValues,
		});

		if (options.workflow && !shouldExcludeParameterValues) {
			// Extract and include expression values with their resolved values
			// Pass execution data to only extract from nodes that have executed
			workflowContext.expressionValues = await assistantHelpers.extractExpressionsFromWorkflow(
				options.workflow,
				options.executionData,
			);
		}
	}

	const selectedNodes = focusedNodesStore.buildContextPayload();
	if (selectedNodes.length > 0) {
		workflowContext.selectedNodes = selectedNodes;
	}

	// Get feature flags from Posthog
	const codeBuilderVariant = posthogStore.getVariant(CODE_WORKFLOW_BUILDER_EXPERIMENT.name);
	const isCodeBuilderEnabled =
		codeBuilderVariant === CODE_WORKFLOW_BUILDER_EXPERIMENT.codeNoPinData ||
		codeBuilderVariant === CODE_WORKFLOW_BUILDER_EXPERIMENT.codePinData;
	const isPinDataEnabled = codeBuilderVariant === CODE_WORKFLOW_BUILDER_EXPERIMENT.codePinData;

	const featureFlags: ChatRequest.BuilderFeatureFlags = {
		templateExamples:
			posthogStore.getVariant(AI_BUILDER_TEMPLATE_EXAMPLES_EXPERIMENT.name) ===
			AI_BUILDER_TEMPLATE_EXAMPLES_EXPERIMENT.variant,
		codeBuilder: isCodeBuilderEnabled,
		pinData: isPinDataEnabled,
		planMode: options.isPlanModeEnabled ?? false,
		mergeAskBuild: posthogStore.isFeatureEnabled(MERGE_ASK_BUILD_EXPERIMENT.name),
	};

	if (options.nodesForSchema?.length) {
		const { schemas, pinnedNodeNames } = assistantHelpers.getNodesSchemas(
			options.nodesForSchema,
			shouldExcludeParameterValues,
		);
		workflowContext.executionSchema = schemas;
		workflowContext.valuesExcluded = shouldExcludeParameterValues;
		if (pinnedNodeNames.length > 0) {
			workflowContext.pinnedNodes = pinnedNodeNames;
		}
	}

	return {
		role: 'user',
		type: 'message',
		id,
		text,
		quickReplyType: options.quickReplyType,
		workflowContext,
		featureFlags,
		mode: options.mode,
	};
}

export function shouldShowChat(routeName: string): boolean {
	const ENABLED_VIEWS = ['workflow', 'workflowExecution'];
	return ENABLED_VIEWS.includes(routeName);
}

/**
 * Extracts all revertVersionId values from an array of messages.
 * Only extracts from messages that have a string revertVersionId property.
 */
export function extractRevertVersionIds(messages: ChatRequest.MessageResponse[]): string[] {
	return messages.filter(hasRevertVersionId).map((msg) => msg.revertVersionId);
}

/**
 * Fetches which version IDs still exist in workflow history.
 * Returns a Map of versionId -> createdAt for existing versions.
 * Returns an empty map if the fetch fails (all versions will be treated as non-existent).
 */
export async function fetchExistingVersionIds(
	restApiContext: IRestApiContext,
	workflowId: string,
	versionIds: string[],
): Promise<Map<string, string>> {
	if (versionIds.length === 0) {
		return new Map();
	}

	try {
		const versionsResponse = await getWorkflowVersionsByIds(restApiContext, workflowId, versionIds);
		return new Map(versionsResponse.versions.map((v) => [v.versionId, v.createdAt]));
	} catch {
		// Continue without enriching - all revertVersionIds will be removed
		return new Map();
	}
}

/**
 * Removes revertVersionId from a TextMessage, returning a valid TextMessage.
 * Since revertVersionId is optional on TextMessage, the result is still a valid TextMessage.
 */
function removeRevertVersionId(msg: TextMessageWithRevertVersionId): ChatRequest.TextMessage {
	const { revertVersionId: _, ...rest } = msg;
	return rest;
}

/**
 * Enriches messages with revertVersion object containing both id and createdAt.
 * If version doesn't exist in the map, removes revertVersionId from the message.
 */
export function enrichMessagesWithRevertVersion(
	messages: ChatRequest.MessageResponse[],
	versionMap: Map<string, string>,
): ChatRequest.MessageResponse[] {
	return messages.map((msg) => {
		if (!hasRevertVersionId(msg)) {
			return msg;
		}

		const createdAt = versionMap.get(msg.revertVersionId);
		if (createdAt) {
			// Transform revertVersionId into revertVersion object
			return {
				...msg,
				revertVersion: { id: msg.revertVersionId, createdAt },
			};
		}

		// Version doesn't exist, remove revertVersionId
		return removeRevertVersionId(msg);
	});
}
