import { useNDVStore } from '@/features/ndv/shared/ndv.store';
import { injectWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
import {
	isExpression as isExpressionUtil,
	stringifyExpressionResult,
} from '@/app/utils/expressions';

import debounce from 'lodash/debounce';
import { createResultError, createResultOk, type IDataObject, type Result } from 'n8n-workflow';
import {
	computed,
	onMounted,
	ref,
	toRef,
	toValue,
	inject,
	type MaybeRefOrGetter,
	watch,
} from 'vue';
import { useWorkflowHelpers, type ResolveParameterOptions } from './useWorkflowHelpers';
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
import { ExpressionLocalResolveContextSymbol } from '@/app/constants';
import type { ExpressionLocalResolveContext } from '@/app/types/expressions';

export function useResolvedExpression({
	expression,
	additionalData,
	isForCredential,
	stringifyObject,
	contextNodeName,
}: {
	expression: MaybeRefOrGetter<unknown>;
	additionalData?: MaybeRefOrGetter<IDataObject>;
	isForCredential?: MaybeRefOrGetter<boolean>;
	stringifyObject?: MaybeRefOrGetter<boolean>;
	contextNodeName?: MaybeRefOrGetter<string>;
}) {
	const workflowExecutionStateStore = injectWorkflowExecutionStateStore();
	const workflowDocumentStore = injectWorkflowDocumentStore();
	const ndvStore = computed(() => useNDVStore(workflowDocumentStore.value.documentId));

	const { resolveExpression } = useWorkflowHelpers();

	const expressionLocalResolveCtx = inject(
		ExpressionLocalResolveContextSymbol,
		computed(() => undefined),
	);

	const resolvedExpression = ref<unknown>(null);
	const resolvedExpressionString = ref('');

	const targetItem = computed(() => ndvStore.value.expressionTargetItem ?? undefined);
	const activeNode = computed(() => ndvStore.value.activeNode);
	const hasRunData = computed(() =>
		Boolean(
			workflowExecutionStateStore.value.activeExecutionRunData?.[activeNode.value?.name ?? ''],
		),
	);
	const isExpression = computed(() => isExpressionUtil(toValue(expression)));

	async function resolve(ctx?: ExpressionLocalResolveContext): Promise<Result<unknown, Error>> {
		const expressionString = toValue(expression);

		if (!isExpression.value || typeof expressionString !== 'string') {
			return { ok: true, result: '' };
		}

		const options: ResolveParameterOptions | ExpressionLocalResolveContext = ctx ?? {
			isForCredential: toValue(isForCredential),
			additionalKeys: toValue(additionalData),
			contextNodeName: toValue(contextNodeName),
			...(contextNodeName === undefined && ndvStore.value.isInputParentOfActiveNode
				? {
						targetItem: targetItem.value ?? undefined,
						inputNodeName: ndvStore.value.ndvInputNodeName,
						inputRunIndex: ndvStore.value.ndvInputRunIndex,
						inputBranchIndex: ndvStore.value.ndvInputBranchIndex,
					}
				: {}),
		};

		try {
			const resolvedValue = (await resolveExpression(
				expressionString,
				undefined,
				options,
				toValue(stringifyObject) ?? true,
			)) as unknown;

			return createResultOk(resolvedValue);
		} catch (error) {
			return createResultError(error);
		}
	}

	const debouncedUpdateExpression = debounce(updateExpression, 200);

	let updateExpressionInvocation = 0;

	async function updateExpression() {
		const currentInvocation = ++updateExpressionInvocation;

		if (isExpression.value) {
			const resolved = await resolve(expressionLocalResolveCtx.value);

			// Discard stale results if a newer invocation has started
			if (currentInvocation !== updateExpressionInvocation) return;

			resolvedExpression.value = resolved.ok ? resolved.result : null;
			resolvedExpressionString.value = stringifyExpressionResult(
				resolved,
				workflowDocumentStore.value.getPinDataSnapshot(),
				hasRunData.value,
			);
		} else {
			resolvedExpression.value = null;
			resolvedExpressionString.value = '';
		}
	}

	watch(
		[
			expressionLocalResolveCtx,
			toRef(expression),
			toRef(additionalData),
			() => workflowExecutionStateStore.value.activeExecution,
			() => workflowExecutionStateStore.value.activeExecutionRunData,
			() => workflowDocumentStore.value.name,
			targetItem,
		],
		debouncedUpdateExpression,
	);

	onMounted(updateExpression);

	return { resolvedExpression, resolvedExpressionString, isExpression };
}
