<script setup lang="ts">
import { useStorage } from '@/app/composables/useStorage';
import { saveAs } from 'file-saver';
import NodeSettingsHint from '@/features/ndv/settings/components/NodeSettingsHint.vue';
import type {
	IBinaryData,
	IConnectedNode,
	IDataObject,
	INodeExecutionData,
	INodeOutputConfiguration,
	IRunExecutionData,
	ITaskMetadata,
	NodeError,
	NodeHint,
	NodeConnectionType,
} from 'n8n-workflow';
import {
	isTerminalExecutionStatus,
	parseErrorMetadata,
	NodeConnectionTypes,
	NodeHelpers,
} from 'n8n-workflow';
import { computed, defineAsyncComponent, onBeforeUnmount, onMounted, ref, toRef, watch } from 'vue';

import type { INodeUi, IRunDataDisplayMode, ITab } from '@/Interface';
import type { IExecutionResponse } from '@/features/execution/executions/executions.types';
import type { NodePanelType } from '@/features/ndv/shared/ndv.types';
import type { WorkflowObjectAccessors } from '@/app/types/workflow';

import {
	CORE_NODES_CATEGORY,
	DATA_EDITING_DOCS_URL,
	DATA_PINNING_DOCS_URL,
	HTML_NODE_TYPE,
	LOCAL_STORAGE_PIN_DATA_DISCOVERY_CANVAS_FLAG,
	LOCAL_STORAGE_PIN_DATA_DISCOVERY_NDV_FLAG,
	MAX_DISPLAY_DATA_SIZE,
	MAX_DISPLAY_DATA_SIZE_SCHEMA_VIEW,
	NODE_TYPES_EXCLUDED_FROM_OUTPUT_NAME_APPEND,
	RUN_DATA_DEFAULT_PAGE_SIZE,
} from '@/app/constants';
import { DUMMY_PIN_DATA } from '@/app/constants/samples';

import BinaryDataDisplay from './BinaryDataDisplay.vue';
import NodeErrorView from './error/NodeErrorView.vue';
import JsonEditor from '@/features/shared/editors/components/JsonEditor/JsonEditor.vue';

import { useInjectWorkflowId } from '@/app/composables/useInjectWorkflowId';
import { useRunWorkflow } from '@/app/composables/useRunWorkflow';
import RunDataPinButton from './RunDataPinButton.vue';
import { useExternalHooks } from '@/app/composables/useExternalHooks';
import { useI18n } from '@n8n/i18n';
import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
import { useNodeType } from '@/app/composables/useNodeType';
import type { PinDataSource, UnpinDataSource } from '@/app/composables/usePinnedData';
import { usePinnedData } from '@/app/composables/usePinnedData';
import { useTelemetry } from '@/app/composables/useTelemetry';
import { useToast } from '@/app/composables/useToast';
import { dataPinningEventBus } from '@/app/event-bus';
import { ndvEventBus } from '@/features/ndv/shared/ndv.eventBus';
import { injectNDVStore } from '@/features/ndv/shared/ndv.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useRootStore } from '@n8n/stores/useRootStore';
import { useSourceControlStore } from '@/features/integrations/sourceControl.ee/sourceControl.store';
import { useCollaborationStore } from '@/features/collaboration/collaboration/collaboration.store';
import { injectWorkflowExecutionStateStore } from '@/app/stores/workflowExecutionState.store';
import { executionDataToJson } from '@/app/utils/nodeTypesUtils';
import { getGenericHints } from '@/app/utils/nodeViewUtils';
import { searchInObject } from '@/app/utils/objectUtils';
import { clearJsonKey, isEmpty, isPresent } from '@/app/utils/typesUtils';
import isEqual from 'lodash/isEqual';
import isObject from 'lodash/isObject';
import { useRoute, useRouter } from 'vue-router';
import { useSchemaPreviewStore } from '@/features/ndv/runData/schemaPreview.store';
import { asyncComputed } from '@vueuse/core';
import ViewSubExecution from '@/features/execution/executions/components/ViewSubExecution.vue';
import RunDataItemCount from './RunDataItemCount.vue';
import RunDataDisplayModeSelect from './RunDataDisplayModeSelect.vue';
import RunDataPaginationBar from './RunDataPaginationBar.vue';
import { parseAiContent } from '@/app/utils/aiUtils';
import { I18nT } from 'vue-i18n';
import RunDataBinary from './RunDataBinary.vue';
import { hasTrimmedRunData } from '@/features/execution/executions/executions.utils';
import NDVEmptyState from '@/features/ndv/panel/components/NDVEmptyState.vue';
import { type SearchShortcut } from '@/features/workflows/canvas/canvas.types';

import {
	N8nBlockUi,
	N8nButton,
	N8nCallout,
	N8nIconButton,
	N8nInfoTip,
	N8nLink,
	N8nOption,
	N8nSelect,
	N8nSpinner,
	N8nTabs,
	N8nText,
	N8nTooltip,
} from '@n8n/design-system';
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';

const LazyRunDataTable = defineAsyncComponent(async () => await import('./RunDataTable.vue'));
const LazyRunDataJson = defineAsyncComponent(async () => await import('./RunDataJson.vue'));

const LazyRunDataSchema = defineAsyncComponent(async () => await import('./VirtualSchema.vue'));
const LazyRunDataHtml = defineAsyncComponent(async () => await import('./RunDataHtml.vue'));
const LazyRunDataAi = defineAsyncComponent(
	async () => await import('./RunDataParsedAiContent.vue'),
);
const LazyRunDataSearch = defineAsyncComponent(async () => await import('./RunDataSearch.vue'));

export type EnterEditModeArgs = {
	origin: 'editIconButton' | 'insertTestDataLink';
};

type Props = {
	workflowObject: WorkflowObjectAccessors;
	workflowExecution?: IRunExecutionData;
	runIndex: number;
	executingMessage: string;
	pushRef?: string;
	paneType: NodePanelType;
	displayMode: IRunDataDisplayMode;
	noDataInBranchMessage: string;
	node?: INodeUi | null;
	nodes?: IConnectedNode[];
	linkedRuns?: boolean;
	canLinkRuns?: boolean;
	isExecuting?: boolean;
	overrideOutputs?: number[];
	mappingEnabled?: boolean;
	distanceFromActive?: number;
	blockUI?: boolean;
	isProductionExecutionPreview?: boolean;
	searchShortcut?: SearchShortcut;
	hidePagination?: boolean;
	calloutMessage?: string;
	disableRunIndexSelection?: boolean;
	disableDisplayModeSelection?: boolean;
	disableEdit?: boolean;
	disablePin?: boolean;
	compact?: boolean;
	showActionsOnHover?: boolean;
	tableHeaderBgColor?: 'base' | 'light';
	disableHoverHighlight?: boolean;
	disableSettingsHint?: boolean;
	disableAiContent?: boolean;
	collapsingTableColumnName: string | null;
	truncateLimit?: number;
};

const props = withDefaults(defineProps<Props>(), {
	node: null,
	nodes: () => [],
	overrideOutputs: undefined,
	distanceFromActive: 0,
	blockUI: false,
	searchShortcut: undefined,
	isProductionExecutionPreview: false,
	mappingEnabled: false,
	isExecuting: false,
	hidePagination: false,
	calloutMessage: undefined,
	disableRunIndexSelection: false,
	disableDisplayModeSelection: false,
	disableEdit: false,
	disablePin: false,
	disableHoverHighlight: false,
	disableSettingsHint: false,
	compact: false,
	showActionsOnHover: false,
	tableHeaderBgColor: 'base',
	workflowExecution: undefined,
	disableAiContent: false,
});

defineSlots<{
	content: {};
	'callout-message': {};
	header: {};
	'header-end': (props: InstanceType<typeof RunDataItemCount>['$props']) => unknown;
	'input-select': {};
	'before-data': {};
	'run-info': {};
	'node-waiting': {};
	'node-not-run': {};
	'no-output-data': {};
	'recovered-artificial-output-data': {};
	'data-redacted': {};
	'redacted-error': {};
}>();

const emit = defineEmits<{
	search: [search: string];
	runChange: [runIndex: number];
	itemHover: [
		item: {
			outputIndex: number;
			itemIndex: number;
		} | null,
	];
	linkRun: [];
	unlinkRun: [];
	activatePane: [];
	tableMounted: [
		{
			avgRowHeight: number;
		},
	];
	displayModeChange: [IRunDataDisplayMode];
	collapsingTableColumnChanged: [columnName: string | null];
	captureWheelDataContainer: [WheelEvent];
}>();

const connectionType = ref<NodeConnectionType>(NodeConnectionTypes.Main);
const dataSize = ref(0);
const showData = ref(false);
const userEnabledShowData = ref(false);
const outputIndex = ref(0);
const binaryDataDisplayData = ref<IBinaryData | null>(null);
const currentPage = ref(1);
const pageSize = ref(10);
const previousExecutionDataUsedInEditMode = ref<boolean>(false);

const pinDataDiscoveryTooltipVisible = ref(false);
const isControlledPinDataTooltip = ref(false);
const search = ref('');

const dataContainerRef = ref<HTMLDivElement>();

const workflowId = useInjectWorkflowId();
const nodeTypesStore = useNodeTypesStore();
const ndvStore = injectNDVStore();
const workflowExecutionStateStore = injectWorkflowExecutionStateStore();
const currentExecution = computed(() => workflowExecutionStateStore.value.activeExecution);
const lastSuccessfulExecution = computed(
	() => workflowExecutionStateStore.value.lastSuccessfulExecution,
);
const workflowDocumentStore = injectWorkflowDocumentStore();
const sourceControlStore = useSourceControlStore();
const collaborationStore = useCollaborationStore();
const rootStore = useRootStore();
const schemaPreviewStore = useSchemaPreviewStore();

const toast = useToast();
const route = useRoute();
const nodeHelpers = useNodeHelpers();
const externalHooks = useExternalHooks();
const telemetry = useTelemetry();
const i18n = useI18n();
const router = useRouter();

const { runWorkflow } = useRunWorkflow({ router });

const node = toRef(props, 'node');

const pinnedData = usePinnedData(node, {
	runIndex: props.runIndex,
	displayMode: props.displayMode,
});
const { isSubNodeType } = useNodeType({
	node,
});

const isArchivedWorkflow = computed(() => workflowDocumentStore?.value?.isArchived ?? false);
const isReadOnlyRoute = computed(() => route.meta.readOnlyCanvas === true);
const isWaitNodeWaiting = computed(() => {
	return (
		node.value?.name &&
		workflowExecution.value?.resultData?.runData?.[node.value?.name]?.[props.runIndex]
			?.executionStatus === 'waiting'
	);
});

const activeNode = computed(() => ndvStore.value.activeNode);
const nodeType = computed(() => {
	if (!node.value) return null;

	return nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion);
});

const isPaneTypeInput = computed(() => props.paneType === 'input');
const isPaneTypeOutput = computed(() => props.paneType === 'output');

const isSchemaView = computed(() => props.displayMode === 'schema');
const isSearchInSchemaView = computed(() => isSchemaView.value && !!search.value);
const hasMultipleInputNodes = computed(() => isPaneTypeInput.value && props.nodes.length > 0);
const displaysMultipleNodes = computed(() => isSchemaView.value && hasMultipleInputNodes.value);
const hasAnyUpstreamExecuted = computed(() => {
	return (
		hasMultipleInputNodes.value &&
		props.nodes.some((inputNode) => nodeHelpers.hasNodeExecuted(inputNode.name))
	);
});

const hasAnyDataAvailable = computed(() => {
	return (
		node.value?.disabled ||
		hasPreviewSchema.value ||
		hasAnyUpstreamExecuted.value ||
		!!lastSuccessfulExecution.value
	);
});
const isSingleNodeView = computed(() => !displaysMultipleNodes.value);
const hasBinaryData = computed(() => binaryData.value?.length > 0);
const hasNoData = computed(() => !rawInputData.value.length && !pinnedData.hasData.value);
const isReadOnly = computed(
	() =>
		isReadOnlyRoute.value ||
		readOnlyEnv.value ||
		isArchivedWorkflow.value ||
		collaborationStore.shouldBeReadOnly,
);

const shouldShowSchemaView = computed(() => {
	if (!isSchemaView.value) return false;
	return (
		hasNodeRun.value ||
		hasPreviewSchema.value ||
		(!hasNodeRun.value && (hasAnyUpstreamExecuted.value || lastSuccessfulExecution.value))
	);
});

// Helper: Get run data for current node (returns null if not available)
const currentNodeRunData = computed(() => {
	if (!node.value || !workflowRunData.value) return null;
	const nodeName = node.value.name;
	return workflowRunData.value.hasOwnProperty(nodeName) ? workflowRunData.value[nodeName] : null;
});

const shouldShowNodeNotRunState = computed(() => {
	return !hasNodeRun.value && !(displaysMultipleNodes.value && hasAnyDataAvailable.value);
});

const shouldShowDisabledNodeHint = computed(() => {
	return isPaneTypeInput.value && isSingleNodeView.value && node.value?.disabled;
});

const shouldShowNoDataInBranch = computed(() => {
	return (
		hasNodeRun.value &&
		(!unfilteredDataCount.value || (search.value && !dataCount.value)) &&
		isSingleNodeView.value &&
		branches.value.length > 1
	);
});

const shouldShowNoOutputData = computed(() => {
	return hasNodeRun.value && !inputData.value.length && isSingleNodeView.value && !search.value;
});

const shouldShowBinaryOnlyHint = computed(() => {
	return (
		hasNodeRun.value &&
		props.displayMode === 'table' &&
		binaryData.value.length > 0 &&
		inputData.value.length === 1 &&
		Object.keys(jsonData.value[0] || {}).length === 0
	);
});

const isTriggerNode = computed(() => node.value && nodeTypesStore.isTriggerNode(node.value.type));

const canPinData = computed(
	() =>
		node.value &&
		pinnedData.canPinNode(false, currentOutputIndex.value) &&
		!isPaneTypeInput.value &&
		pinnedData.isValidNodeType.value &&
		!hasBinaryData.value,
);

const hasNodeRun = computed(() =>
	Boolean(
		!props.isExecuting &&
			node.value &&
			((workflowRunData.value && workflowRunData.value.hasOwnProperty(node.value.name)) ||
				pinnedData.hasData.value),
	),
);

const isArtificialRecoveredEventItem = computed(
	() => rawInputData.value?.[0]?.json?.isArtificialRecoveredEventItem,
);

const parentNodeError = computed(() => {
	const parentNode = props.workflowObject.getChildNodes(node.value?.name ?? '', 'ALL_NON_MAIN')[0];
	return workflowRunData.value?.[parentNode]?.[props.runIndex]?.error as NodeError;
});

const workflowRunErrorAsNodeError = computed(() => {
	if (!node.value) return null;

	const selfTaskData = workflowRunData.value?.[node.value.name]?.[props.runIndex];

	if (!selfTaskData && isSubNodeType.value && isPaneTypeInput.value) {
		return parentNodeError.value;
	}
	return selfTaskData?.error as NodeError;
});

const hasRedactedError = computed(() => {
	if (!node.value) return false;
	const selfTaskData = workflowRunData.value?.[node.value.name]?.[props.runIndex];
	return !!selfTaskData?.redactedError;
});

const hasRunError = computed(
	() =>
		node.value &&
		!isPaneTypeInput.value &&
		(!!workflowRunErrorAsNodeError.value || hasRedactedError.value),
);

const executionHints = computed(() => {
	if (hasNodeRun.value) {
		const hints = node.value && workflowRunData.value?.[node.value.name]?.[props.runIndex]?.hints;

		if (hints) return hints;
	}

	return [];
});

const workflowExecution = computed(
	() => props.workflowExecution ?? currentExecution.value?.data ?? undefined,
);
const workflowRunData = computed(() => {
	if (workflowExecution.value === undefined) {
		return null;
	}
	const executionData: IRunExecutionData | undefined = workflowExecution.value;
	if (executionData?.resultData) {
		return executionData.resultData.runData;
	}
	return null;
});
const dataCount = computed(() =>
	getDataCount(props.runIndex, currentOutputIndex.value, connectionType.value),
);

const isTrimmedManualExecutionDataItem = computed(() =>
	workflowRunData.value ? hasTrimmedRunData(workflowRunData.value) : false,
);

const isExecutionInTerminalState = computed(() =>
	isTerminalExecutionStatus(currentExecution.value?.status ?? undefined),
);

const isExecutionRedacted = computed(
	() =>
		hasNodeRun.value &&
		workflowExecution.value?.redactionInfo?.isRedacted === true &&
		!pinnedData.hasData.value &&
		!hasRunError.value,
);

const unfilteredDataCount = computed(() =>
	pinnedData.data.value ? pinnedData.data.value.length : rawInputData.value.length,
);
const dataSizeInMB = computed(() => (dataSize.value / (1024 * 1024)).toFixed(1));
const maxOutputIndex = computed(() => {
	if (!node.value || props.runIndex === undefined) return 0;
	const nodeRunData = currentNodeRunData.value;
	if (!nodeRunData || nodeRunData.length <= props.runIndex) return 0;

	const taskData = nodeRunData[props.runIndex]?.data;
	return taskData?.main ? taskData.main.length - 1 : 0;
});
const currentPageOffset = computed(() => pageSize.value * (currentPage.value - 1));
const showBranchSwitch = computed(
	() => maxOutputIndex.value > 0 && branches.value.length > 1 && isSingleNodeView.value,
);

const maxRunIndex = computed(() => {
	const nodeRunData = currentNodeRunData.value;
	return nodeRunData?.length ? nodeRunData.length - 1 : 0;
});

const runSelectorOptionsCount = computed(() => {
	const nodeRunData = currentNodeRunData.value;
	if (!nodeRunData) return 0;

	// If there is branch selector – we show all runs in the run selector
	if (showBranchSwitch.value) {
		return maxRunIndex.value + 1;
	}

	// If there is only one branch - we show only the runs containing the data in the connected branch
	return nodeRunData.filter((nodeRun) => {
		const nodeOutput = nodeRun?.data?.[connectionType.value]?.[currentOutputIndex.value];
		return nodeOutput && nodeOutput.length > 0;
	}).length;
});

const rawInputData = computed(() =>
	getRawInputData(props.runIndex, currentOutputIndex.value, connectionType.value),
);

const unfilteredInputData = computed(() => getPinDataOrLiveData(rawInputData.value));
const inputData = computed(() => getFilteredData(unfilteredInputData.value));
const inputDataPage = computed(() => {
	const offset = pageSize.value * (currentPage.value - 1);
	return inputData.value.slice(offset, offset + pageSize.value);
});
const jsonData = computed(() => executionDataToJson(inputData.value));
const binaryData = computed(() => {
	if (!node.value) {
		return [];
	}

	return nodeHelpers
		.getBinaryData(workflowRunData.value, node.value.name, props.runIndex, currentOutputIndex.value)
		.filter((data) => Boolean(data && Object.keys(data).length));
});
const inputHtml = computed(() => String(inputData.value[0]?.json?.html ?? ''));
const currentOutputIndex = computed(() => {
	if (props.overrideOutputs?.length && !props.overrideOutputs.includes(outputIndex.value)) {
		return props.overrideOutputs[0];
	}

	// In some cases nodes may switch their outputCount while the user still
	// has a higher outputIndex selected. We could adjust outputIndex directly,
	// but that loses data as we can keep the user selection if the branch reappears.
	return Math.min(outputIndex.value, maxOutputIndex.value);
});
const branches = computed(() => {
	const capitalize = (name: string) => name.charAt(0).toLocaleUpperCase() + name.slice(1);

	const result: Array<ITab<number>> = [];

	for (let i = 0; i <= maxOutputIndex.value; i++) {
		if (props.overrideOutputs && !props.overrideOutputs.includes(i)) {
			continue;
		}
		const totalItemsCount = getRawInputData(props.runIndex, i).length;
		const itemsCount = getDataCount(props.runIndex, i);
		const items = search.value
			? i18n.baseText('ndv.search.items', {
					adjustToNumber: totalItemsCount,
					interpolate: { matched: itemsCount, total: totalItemsCount },
				})
			: i18n.baseText('ndv.output.items', {
					adjustToNumber: itemsCount,
					interpolate: { count: itemsCount },
				});
		let outputName = getOutputName(i);

		if (`${outputName}` === `${i}`) {
			outputName = `${i18n.baseText('ndv.output')} ${outputName}`;
		} else {
			const appendBranchWord = NODE_TYPES_EXCLUDED_FROM_OUTPUT_NAME_APPEND.includes(
				node.value?.type ?? '',
			)
				? ''
				: ` ${i18n.baseText('ndv.output.branch')}`;
			outputName = capitalize(`${getOutputName(i)}${appendBranchWord}`);
		}
		result.push({
			label:
				(search.value && itemsCount) || totalItemsCount ? `${outputName} (${items})` : outputName,
			value: i,
		});
	}
	return result;
});

const editMode = computed(() => {
	return isPaneTypeInput.value ? { enabled: false, value: '' } : ndvStore.value.outputPanelEditMode;
});

const readOnlyEnv = computed(() => sourceControlStore.preferences.branchReadOnly);
const showIOSearch = computed(
	() =>
		hasNodeRun.value &&
		!hasRunError.value &&
		(unfilteredInputData.value.length > 0 || displaysMultipleNodes.value),
);
const inputSelectLocation = computed(() => {
	if (isSchemaView.value) return 'none';
	if (!hasNodeRun.value) return 'header';
	if (maxRunIndex.value > 0) return 'runs';
	if (maxOutputIndex.value > 0 && branches.value.length > 1) {
		return 'outputs';
	}

	return 'items';
});

const showIoSearchNoMatchContent = computed(
	() => hasNodeRun.value && !inputData.value.length && !!search.value && isSingleNodeView.value,
);

const shouldShowDisplayModeSelect = computed(() => {
	if (editMode.value.enabled) return false;
	return (
		hasAnyDataAvailable.value ||
		(hasNodeRun.value &&
			(inputData.value.length ||
				binaryData.value.length ||
				search.value ||
				hasMultipleInputNodes.value))
	);
});

const parentNodeOutputData = computed(() => {
	const parentNode = props.workflowObject.getParentNodesByDepth(node.value?.name ?? '')[0];
	let parentNodeData: INodeExecutionData[] = [];

	if (parentNode?.name) {
		parentNodeData = nodeHelpers.getNodeInputData(
			props.workflowObject.getNode(parentNode?.name),
			props.runIndex,
			outputIndex.value,
			'input',
			connectionType.value,
		);
	}

	return parentNodeData;
});

const parentNodePinnedData = computed(() => {
	const parentNode = props.workflowObject.getParentNodesByDepth(node.value?.name ?? '')[0];
	return workflowDocumentStore?.value?.pinnedDataByNodeName?.[parentNode?.name || ''] ?? [];
});

const showPinButton = computed(
	() =>
		!props.disablePin &&
		!hasNoData.value &&
		!editMode.value.enabled &&
		!isExecutionRedacted.value &&
		(hasBinaryData.value ? isPaneTypeOutput.value : canPinData.value),
);

const pinButtonDisabled = computed(
	() => hasNoData.value || hasBinaryData.value || isReadOnly.value,
);

const activeTaskMetadata = computed((): ITaskMetadata | null => {
	if (!node.value) return null;
	const errorMetadata = parseErrorMetadata(workflowRunErrorAsNodeError.value);
	if (errorMetadata !== undefined) {
		return errorMetadata;
	}

	// This is needed for the WorkflowRetriever to display the associated execution
	if (parentNodeError.value) {
		const subNodeMetadata = parseErrorMetadata(parentNodeError.value);
		if (subNodeMetadata !== undefined) {
			return subNodeMetadata;
		}
	}

	return workflowRunData.value?.[node.value.name]?.[props.runIndex]?.metadata ?? null;
});

const hasInputOverwrite = computed((): boolean => {
	if (!node.value) return false;
	const taskData = nodeHelpers.getNodeTaskData(node.value.name, props.runIndex);
	return Boolean(taskData?.inputOverride);
});

const isSchemaPreviewEnabled = computed(
	() =>
		props.paneType === 'input' &&
		!(nodeType.value?.codex?.categories ?? []).some((category) => category === CORE_NODES_CATEGORY),
);

const isNDVV2 = computed(() => true);

const hasPreviewSchema = asyncComputed(async () => {
	if (!isSchemaPreviewEnabled.value || props.nodes.length === 0) return false;
	const nodes = props.nodes
		.filter((n) => n.depth === 1)
		.map((n) => workflowDocumentStore?.value?.getNodeByName(n.name) ?? null)
		.filter(isPresent);

	for (const connectedNode of nodes) {
		const { type, typeVersion, parameters } = connectedNode;
		const hasPreview = await schemaPreviewStore.getSchemaPreview({
			nodeType: type,
			version: typeVersion,
			resource: parameters.resource as string,
			operation: parameters.operation as string,
		});

		if (hasPreview.ok) return true;
	}
	return false;
}, false);

const itemsCountProps = computed<InstanceType<typeof RunDataItemCount>['$props']>(() => ({
	search: search.value,
	dataCount: dataCount.value,
	unfilteredDataCount: unfilteredDataCount.value,
	subExecutionsCount: activeTaskMetadata.value?.subExecutionsCount,
}));

const parsedAiContent = computed(() =>
	props.disableAiContent ? [] : parseAiContent(rawInputData.value, connectionType.value),
);

const hasParsedAiContent = computed(() =>
	parsedAiContent.value.some((prr) => prr.parsedContent?.parsed),
);

const binaryDataDisplayVisible = computed(
	() => binaryDataDisplayData.value !== null && props.displayMode === 'binary',
);

function setInputBranchIndex(value: number) {
	if (props.paneType === 'input') {
		outputIndex.value = value;
	}
}

watch(node, (newNode, prevNode) => {
	if (newNode?.id === prevNode?.id) return;
	init();
});

watch([hasNodeRun, isTrimmedManualExecutionDataItem], () => {
	if (props.paneType === 'output') {
		setDisplayMode();
	} else {
		// InputPanel relies on the outputIndex to check if we have data
		outputIndex.value = determineInitialOutputIndex();
	}
});

watch(
	inputDataPage,
	(data: INodeExecutionData[]) => {
		if (props.paneType && data) {
			ndvStore.value.setNDVPanelDataIsEmpty({
				panel: props.paneType,
				isEmpty: data.every((item) => isEmpty(item.json)),
			});
		}
	},
	{ immediate: true, deep: true },
);

watch(jsonData, (data: IDataObject[], prevData: IDataObject[]) => {
	if (isEqual(data, prevData)) return;
	refreshDataSize();
	if (dataCount.value) {
		resetCurrentPageIfTooFar();
	}
	showPinDataDiscoveryTooltip(data);
});

watch(binaryData, (newData, prevData) => {
	if (newData.length && !prevData.length && props.displayMode !== 'binary') {
		switchToBinary();
	} else if (!newData.length && props.displayMode === 'binary') {
		onDisplayModeChange('table');
	}
});

watch(currentOutputIndex, (branchIndex: number) => {
	ndvStore.value.setNDVBranchIndex({
		pane: props.paneType,
		branchIndex,
	});
});

watch(search, (newSearch) => {
	emit('search', newSearch);
});

// Switch to AI display mode if it's most suitable
watch(
	hasParsedAiContent,
	(hasAiContent) => {
		if (hasAiContent && props.displayMode !== 'ai') {
			emit('displayModeChange', 'ai');
		}
	},
	{ immediate: true },
);

onMounted(() => {
	init();

	ndvEventBus.on('setInputBranchIndex', setInputBranchIndex);

	if (!isPaneTypeInput.value) {
		showPinDataDiscoveryTooltip(jsonData.value);
	}
	ndvStore.value.setNDVBranchIndex({
		pane: props.paneType,
		branchIndex: currentOutputIndex.value,
	});

	if (props.paneType === 'output') {
		activatePane();
	}

	if (hasRunError.value && node.value) {
		const error = workflowRunData.value?.[node.value.name]?.[props.runIndex]?.error;
		const errorsToTrack = ['unknown error'];

		if (error && errorsToTrack.some((e) => error.message?.toLowerCase().includes(e))) {
			telemetry.track('User encountered an error', {
				node: node.value.type,
				errorMessage: error.message,
				nodeVersion: node.value.typeVersion,
				n8nVersion: rootStore.versionCli,
			});
		}
	}
});

onBeforeUnmount(() => {
	hidePinDataDiscoveryTooltip();
	ndvEventBus.off('setInputBranchIndex', setInputBranchIndex);
});

function getResolvedNodeOutputs() {
	if (node.value && nodeType.value) {
		const workflowNode = props.workflowObject.getNode(node.value.name);

		if (workflowNode) {
			const outputs = NodeHelpers.getNodeOutputs(
				props.workflowObject,
				workflowNode,
				nodeType.value,
			);
			return outputs;
		}
	}
	return [];
}

function shouldHintBeDisplayed(hint: NodeHint): boolean {
	const { location, whenToDisplay } = hint;

	if (location) {
		if (location === 'ndv' && !['input', 'output'].includes(props.paneType)) {
			return false;
		}
		if (location === 'inputPane' && props.paneType !== 'input') {
			return false;
		}

		if (location === 'outputPane' && props.paneType !== 'output') {
			return false;
		}
	}

	if (whenToDisplay === 'afterExecution' && !hasNodeRun.value) {
		return false;
	}

	if (whenToDisplay === 'beforeExecution' && hasNodeRun.value) {
		return false;
	}

	return true;
}

const nodeHints = computed<NodeHint[]>(() => {
	try {
		if (node.value && nodeType.value) {
			const workflowNode = props.workflowObject.getNode(node.value.name);

			if (workflowNode) {
				const hints = nodeHelpers.getNodeHints(props.workflowObject, workflowNode, nodeType.value, {
					runExecutionData: workflowExecution.value ?? null,
					runIndex: props.runIndex,
					connectionInputData: parentNodeOutputData.value,
				});

				const hasMultipleInputItems =
					parentNodeOutputData.value.length > 1 || parentNodePinnedData.value.length > 1;

				const nodeOutputData =
					workflowRunData.value?.[node.value.name]?.[props.runIndex]?.data?.main?.[0] ?? [];

				const genericHints = getGenericHints({
					workflowNode,
					node: node.value,
					nodeType: nodeType.value,
					nodeOutputData,
					getNodeByName: (name) => props.workflowObject.getNode(name),
					connections: props.workflowObject.connectionsBySourceNode,
					hasNodeRun: hasNodeRun.value,
					hasMultipleInputItems,
				});

				return executionHints.value.concat(hints, genericHints).filter(shouldHintBeDisplayed);
			}
		}
	} catch (error) {
		console.error('Error while getting node hints', error);
	}

	return [];
});

function onItemHover(itemIndex: number | null) {
	if (itemIndex === null) {
		emit('itemHover', null);

		return;
	}
	emit('itemHover', {
		outputIndex: currentOutputIndex.value,
		itemIndex,
	});
}

function onClickDataPinningDocsLink() {
	telemetry.track('User clicked ndv link', {
		workflow_id: workflowId.value,
		push_ref: props.pushRef,
		node_type: activeNode.value?.type,
		pane: 'output',
		type: 'data-pinning-docs',
	});
}

function showPinDataDiscoveryTooltip(value: IDataObject[]) {
	if (!isTriggerNode.value) {
		return;
	}

	const pinDataDiscoveryFlag = useStorage(LOCAL_STORAGE_PIN_DATA_DISCOVERY_NDV_FLAG).value;

	if (
		value &&
		value.length > 0 &&
		!isReadOnlyRoute.value &&
		!isArchivedWorkflow.value &&
		!pinDataDiscoveryFlag
	) {
		pinDataDiscoveryComplete();

		setTimeout(() => {
			isControlledPinDataTooltip.value = true;
			pinDataDiscoveryTooltipVisible.value = true;
			dataPinningEventBus.emit('data-pinning-discovery', { isTooltipVisible: true });
		}, 500); // Wait for NDV to open
	}
}

function hidePinDataDiscoveryTooltip() {
	if (pinDataDiscoveryTooltipVisible.value) {
		isControlledPinDataTooltip.value = false;
		pinDataDiscoveryTooltipVisible.value = false;
		dataPinningEventBus.emit('data-pinning-discovery', { isTooltipVisible: false });
	}
}

function pinDataDiscoveryComplete() {
	useStorage(LOCAL_STORAGE_PIN_DATA_DISCOVERY_NDV_FLAG).value = 'true';
	useStorage(LOCAL_STORAGE_PIN_DATA_DISCOVERY_CANVAS_FLAG).value = 'true';
}

function enterEditMode({ origin }: EnterEditModeArgs) {
	const inputData = pinnedData.data.value
		? clearJsonKey(pinnedData.data.value)
		: executionDataToJson(rawInputData.value);

	const inputDataLength = Array.isArray(inputData)
		? inputData.length
		: Object.keys(inputData ?? {}).length;

	const lastSuccessfulExecutionItems = getOutputtedNodeItems(
		lastSuccessfulExecution.value,
		node.value,
	);
	previousExecutionDataUsedInEditMode.value =
		inputDataLength === 0 && Boolean(lastSuccessfulExecutionItems.length);
	const mockData = lastSuccessfulExecutionItems.length
		? executionDataToJson(lastSuccessfulExecutionItems)
		: DUMMY_PIN_DATA;
	const data = inputDataLength > 0 ? inputData : mockData;

	ndvStore.value.setOutputPanelEditModeEnabled(true);
	ndvStore.value.setOutputPanelEditModeValue(JSON.stringify(data, null, 2));

	telemetry.track('User opened ndv edit state', {
		node_type: activeNode.value?.type,
		click_type: origin === 'editIconButton' ? 'button' : 'link',
		push_ref: props.pushRef,
		run_index: props.runIndex,
		is_output_present: hasNodeRun.value || pinnedData.hasData.value,
		view: !hasNodeRun.value && !pinnedData.hasData.value ? 'undefined' : props.displayMode,
		is_data_pinned: pinnedData.hasData.value,
		workflow_id: workflowId.value,
		node_id: activeNode.value?.id,
	});
}

function getOutputtedNodeItems(
	execution?: IExecutionResponse | null,
	node?: INodeUi | null,
	runIndex = 0,
	outputIndex = 0,
	connectionType = 'main',
): INodeExecutionData[] {
	if (!node) {
		return [];
	}

	return (
		execution?.data?.resultData.runData?.[node?.name]?.[runIndex]?.data?.[connectionType]?.[
			outputIndex
		] ?? []
	);
}

function onClickCancelEdit() {
	ndvStore.value.setOutputPanelEditModeEnabled(false);
	ndvStore.value.setOutputPanelEditModeValue('');
	onExitEditMode({ type: 'cancel' });
}

function onClickSaveEdit() {
	if (!node.value) {
		return;
	}

	const { value } = editMode.value;

	toast.clearAllStickyNotifications();

	try {
		const clearedValue = clearJsonKey(value) as INodeExecutionData[];
		try {
			pinnedData.setData(clearedValue, 'save-edit');
		} catch (error) {
			// setData function already shows toasts on error, so just return here
			return;
		}
	} catch (error) {
		toast.showError(error, i18n.baseText('ndv.pinData.error.syntaxError.title'));
		return;
	}

	ndvStore.value.setOutputPanelEditModeEnabled(false);

	onExitEditMode({ type: 'save' });
}

function onExitEditMode({ type }: { type: 'save' | 'cancel' }) {
	telemetry.track('User closed ndv edit state', {
		node_type: activeNode.value?.type,
		push_ref: props.pushRef,
		run_index: props.runIndex,
		view: props.displayMode,
		type,
	});
}

async function onTogglePinData({ source }: { source: PinDataSource | UnpinDataSource }) {
	if (!node.value) {
		return;
	}

	if (source === 'pin-icon-click') {
		const telemetryPayload = {
			node_type: activeNode.value?.type,
			push_ref: props.pushRef,
			run_index: props.runIndex,
			view: !hasNodeRun.value && !pinnedData.hasData.value ? 'none' : props.displayMode,
			workflow_id: workflowId.value,
			node_id: activeNode.value?.id,
		};

		void externalHooks.run('runData.onTogglePinData', telemetryPayload);
		telemetry.track('User clicked pin data icon', telemetryPayload);
	}

	nodeHelpers.updateNodeParameterIssues(node.value);

	if (pinnedData.hasData.value) {
		pinnedData.unsetData(source);
		return;
	}

	try {
		pinnedData.setData(rawInputData.value, 'pin-icon-click');
	} catch (error) {
		console.error(error);
		return;
	}

	if (maxRunIndex.value > 0) {
		toast.showToast({
			title: i18n.baseText('ndv.pinData.pin.multipleRuns.title', {
				interpolate: {
					index: `${props.runIndex}`,
				},
			}),
			message: i18n.baseText('ndv.pinData.pin.multipleRuns.description'),
			type: 'success',
			duration: 2000,
		});
	}

	hidePinDataDiscoveryTooltip();
	pinDataDiscoveryComplete();
}

function switchToBinary() {
	onDisplayModeChange('binary');
}

function onBranchChange(value: number) {
	outputIndex.value = value;

	telemetry.track('User changed ndv branch', {
		push_ref: props.pushRef,
		branch_index: value,
		node_type: activeNode.value?.type,
		node_type_input_selection: nodeType.value ? nodeType.value.name : '',
		pane: props.paneType,
	});
}

function showTooMuchData() {
	showData.value = true;
	userEnabledShowData.value = true;
	telemetry.track('User clicked ndv button', {
		node_type: activeNode.value?.type,
		workflow_id: workflowId.value,
		push_ref: props.pushRef,
		pane: props.paneType,
		type: 'showTooMuchData',
	});
}

function toggleLinkRuns() {
	if (props.linkedRuns) {
		unlinkRun();
	} else {
		linkRun();
	}
}

function linkRun() {
	emit('linkRun');
}

function unlinkRun() {
	emit('unlinkRun');
}

function onCurrentPageChange(value: number) {
	currentPage.value = value;
	telemetry.track('User changed ndv page', {
		node_type: activeNode.value?.type,
		workflow_id: workflowId.value,
		push_ref: props.pushRef,
		pane: props.paneType,
		page_selected: currentPage.value,
		page_size: pageSize.value,
		items_total: dataCount.value,
	});
}

function resetCurrentPageIfTooFar() {
	const maxPage = Math.ceil(dataCount.value / pageSize.value);
	if (maxPage < currentPage.value) {
		currentPage.value = maxPage;
	}
}

function onPageSizeChange(newPageSize: number) {
	pageSize.value = newPageSize;

	resetCurrentPageIfTooFar();

	telemetry.track('User changed ndv page size', {
		node_type: activeNode.value?.type,
		workflow_id: workflowId.value,
		push_ref: props.pushRef,
		pane: props.paneType,
		page_selected: currentPage.value,
		page_size: pageSize.value,
		items_total: dataCount.value,
	});
}

function onDisplayModeChange(newDisplayMode: IRunDataDisplayMode) {
	const previous = props.displayMode;
	emit('displayModeChange', newDisplayMode);

	if (!userEnabledShowData.value) updateShowData();

	if (dataContainerRef.value) {
		const dataDisplay = dataContainerRef.value.children[0];

		if (dataDisplay) {
			dataDisplay.scrollTo(0, 0);
		}
	}

	closeBinaryDataDisplay();
	void externalHooks.run('runData.displayModeChanged', {
		newValue: newDisplayMode,
		oldValue: previous,
	});
	if (activeNode.value) {
		telemetry.track('User changed ndv item view', {
			previous_view: previous,
			new_view: newDisplayMode,
			node_type: activeNode.value.type,
			workflow_id: workflowId.value,
			push_ref: props.pushRef,
			pane: props.paneType,
		});
	}
}

function getRunLabel(option: number) {
	if (!node.value) {
		return;
	}

	let itemsCount = 0;
	for (let i = 0; i <= maxOutputIndex.value; i++) {
		itemsCount += getPinDataOrLiveData(getRawInputData(option - 1, i)).length;
	}
	const items = i18n.baseText('ndv.output.items', {
		adjustToNumber: itemsCount,
		interpolate: { count: itemsCount },
	});

	const metadata = workflowRunData.value?.[node.value.name]?.[option - 1]?.metadata ?? null;
	const subexecutions = metadata?.subExecutionsCount
		? i18n.baseText('ndv.output.andSubExecutions', {
				adjustToNumber: metadata.subExecutionsCount,
				interpolate: {
					count: metadata.subExecutionsCount,
				},
			})
		: '';

	const itemsLabel = itemsCount > 0 ? ` (${items}${subexecutions})` : '';
	return option + i18n.baseText('ndv.output.of') + runSelectorOptionsCount.value + itemsLabel;
}

function getRawInputData(
	runIndex: number,
	outputIndex: number,
	connectionType: NodeConnectionType = NodeConnectionTypes.Main,
): INodeExecutionData[] {
	let inputData: INodeExecutionData[] = [];

	if (node.value) {
		inputData = nodeHelpers.getNodeInputData(
			node.value,
			runIndex,
			outputIndex,
			props.paneType,
			connectionType,
			workflowExecution.value,
		);
	}

	if (inputData.length === 0 || !Array.isArray(inputData)) {
		return [];
	}

	return inputData;
}

function getPinDataOrLiveData(data: INodeExecutionData[]): INodeExecutionData[] {
	if (pinnedData.data.value && !props.isProductionExecutionPreview) {
		return Array.isArray(pinnedData.data.value)
			? pinnedData.data.value.map((value) => ({
					json: value,
				}))
			: [
					{
						json: pinnedData.data.value,
					},
				];
	}
	return data;
}

function getFilteredData(data: INodeExecutionData[]): INodeExecutionData[] {
	if (!search.value || isSchemaView.value) {
		return data;
	}

	currentPage.value = 1;
	return data.filter(({ json }) => searchInObject(json, search.value));
}

function getDataCount(
	runIndex: number,
	outputIndex: number,
	connectionType: NodeConnectionType = NodeConnectionTypes.Main,
) {
	if (!node.value) {
		return 0;
	}

	if (workflowRunData.value?.[node.value.name]?.[runIndex]?.hasOwnProperty('error')) {
		return 1;
	}

	const rawInputData = getRawInputData(runIndex, outputIndex, connectionType);
	const pinOrLiveData = getPinDataOrLiveData(rawInputData);
	return getFilteredData(pinOrLiveData).length;
}

function determineInitialOutputIndex() {
	for (let i = 0; i <= maxOutputIndex.value; i++) {
		if (getRawInputData(props.runIndex, i).length) {
			return i;
		}
	}

	return 0;
}

function init() {
	// Reset the selected output index every time another node gets selected
	outputIndex.value = determineInitialOutputIndex();
	refreshDataSize();
	closeBinaryDataDisplay();

	let outputTypes: NodeConnectionType[] = [];
	if (node.value && nodeType.value) {
		const outputs = getResolvedNodeOutputs();
		outputTypes = NodeHelpers.getConnectionTypes(outputs);
	}
	connectionType.value = outputTypes.length === 0 ? NodeConnectionTypes.Main : outputTypes[0];
	if (binaryData.value.length > 0) {
		emit('displayModeChange', 'binary');
	} else if (props.displayMode === 'binary') {
		emit('displayModeChange', 'schema');
	}

	if (isNDVV2.value) {
		pageSize.value = RUN_DATA_DEFAULT_PAGE_SIZE;
	}

	if (props.paneType === 'output') {
		setDisplayMode();
	}
}

function closeBinaryDataDisplay() {
	binaryDataDisplayData.value = null;
}

function downloadJsonData() {
	const fileName = (node.value?.name ?? '').replace(/[^\w\d]/g, '_');
	const blob = new Blob([JSON.stringify(rawInputData.value, null, 2)], {
		type: 'application/json',
	});

	saveAs(blob, `${fileName}.json`);
}

function displayBinaryData(index: number, key: string | number) {
	const { data, mimeType } = binaryData.value[index][key];

	binaryDataDisplayData.value = {
		node: node.value?.name,
		runIndex: props.runIndex,
		outputIndex: currentOutputIndex.value,
		index,
		key,
		data,
		mimeType,
	};
}

function getOutputName(outputIndex: number) {
	if (node.value === null) {
		return outputIndex + 1;
	}

	const outputs = getResolvedNodeOutputs();
	const outputConfiguration = outputs?.[outputIndex] as INodeOutputConfiguration;

	if (outputConfiguration && isObject(outputConfiguration)) {
		return outputConfiguration?.displayName;
	}
	if (!nodeType.value?.outputNames || nodeType.value.outputNames.length <= outputIndex) {
		return outputIndex + 1;
	}

	return nodeType.value.outputNames[outputIndex];
}

function refreshDataSize() {
	// Hide by default the data from being displayed
	showData.value = false;
	const jsonItems = inputDataPage.value.map((item) => item.json);
	const byteSize = new Blob([JSON.stringify(jsonItems)]).size;
	dataSize.value = byteSize;
	updateShowData();
}

function updateShowData() {
	// Display data if it is reasonably small (< 1MB)
	showData.value =
		(isSchemaView.value && dataSize.value < MAX_DISPLAY_DATA_SIZE_SCHEMA_VIEW) ||
		dataSize.value < MAX_DISPLAY_DATA_SIZE;
}

function onRunIndexChange(run: number) {
	emit('runChange', run);
}

function enableNode() {
	if (node.value) {
		const updateInformation = {
			name: node.value.name,
			properties: {
				disabled: !node.value.disabled,
			},
		};

		workflowDocumentStore?.value?.updateNodeProperties(updateInformation);
	}
}

const shouldDisplayHtml = computed(
	() =>
		node.value?.type === HTML_NODE_TYPE &&
		node.value.parameters.operation === 'generateHtmlTemplate',
);

function setDisplayMode() {
	if (shouldDisplayHtml.value) {
		emit('displayModeChange', 'html');
	}
}

function activatePane() {
	emit('activatePane');
}

function onSearchClear() {
	search.value = '';
	document.dispatchEvent(new KeyboardEvent('keyup', { key: '/' }));
}

function executeNode(nodeName: string) {
	void runWorkflow({
		destinationNode: { nodeName, mode: 'inclusive' },
		source: 'schema-preview',
	});
}

defineExpose({ enterEditMode });
</script>

<template>
	<div
		:class="[
			'run-data',
			$style.container,
			{
				[$style['ndv-v2']]: isNDVV2,
				[$style.compact]: compact,
				[$style.showActionsOnHover]: showActionsOnHover && !search,
			},
		]"
		@mouseover="activatePane"
	>
		<N8nCallout
			v-if="
				!isPaneTypeInput &&
				pinnedData.hasData.value &&
				!editMode.enabled &&
				!isProductionExecutionPreview
			"
			theme="secondary"
			icon="pin"
			:class="$style.pinnedDataCallout"
			data-test-id="ndv-pinned-data-callout"
		>
			{{ i18n.baseText('runData.pindata.thisDataIsPinned') }}
			<span v-if="!isReadOnlyRoute && !isArchivedWorkflow && !readOnlyEnv" class="ml-4xs">
				<N8nLink
					theme="secondary"
					size="small"
					underline
					bold
					data-test-id="ndv-unpin-data"
					@click.stop="onTogglePinData({ source: 'banner-link' })"
				>
					{{ i18n.baseText('runData.pindata.unpin') }}
				</N8nLink>
			</span>
			<template #trailingContent>
				<N8nLink
					:to="DATA_PINNING_DOCS_URL"
					size="small"
					theme="secondary"
					bold
					underline
					@click="onClickDataPinningDocsLink"
				>
					{{ i18n.baseText('runData.pindata.learnMore') }}
				</N8nLink>
			</template>
		</N8nCallout>

		<div :class="$style.header">
			<div :class="$style.title">
				<slot name="header"></slot>
			</div>

			<div
				v-show="!hasRunError && !isTrimmedManualExecutionDataItem"
				:class="$style.displayModes"
				data-test-id="run-data-pane-header"
				@click.stop
			>
				<Suspense>
					<LazyRunDataSearch
						v-if="showIOSearch"
						v-model="search"
						:class="$style.search"
						:pane-type="paneType"
						:display-mode="displayMode"
						:shortcut="searchShortcut"
						@focus="activatePane"
					/>
				</Suspense>

				<N8nIconButton
					variant="ghost"
					v-if="displayMode === 'table' && collapsingTableColumnName !== null"
					:class="$style.resetCollapseButton"
					icon="chevrons-up-down"
					size="xsmall"
					@click="emit('collapsingTableColumnChanged', null)"
				/>

				<RunDataDisplayModeSelect
					v-if="!disableDisplayModeSelection"
					v-show="shouldShowDisplayModeSelect"
					:compact="props.compact"
					:value="displayMode"
					:has-binary-data="binaryData.length > 0"
					:pane-type="paneType"
					:node-generates-html="shouldDisplayHtml"
					:has-renderable-data="hasParsedAiContent"
					@change="onDisplayModeChange"
				/>

				<N8nIconButton
					variant="subtle"
					size="small"
					v-if="
						!props.disableEdit &&
						canPinData &&
						!isReadOnlyRoute &&
						!readOnlyEnv &&
						!isExecutionRedacted
					"
					v-show="!editMode.enabled"
					:title="i18n.baseText('runData.editOutput')"
					:circle="false"
					:disabled="node?.disabled"
					icon="pencil"
					data-test-id="ndv-edit-pinned-data"
					@click="enterEditMode({ origin: 'editIconButton' })"
				/>

				<RunDataPinButton
					v-if="showPinButton"
					:disabled="pinButtonDisabled"
					:tooltip-contents-visibility="{
						binaryDataTooltipContent: !!binaryData?.length,
						pinDataDiscoveryTooltipContent:
							isControlledPinDataTooltip && pinDataDiscoveryTooltipVisible,
					}"
					:data-pinning-docs-url="DATA_PINNING_DOCS_URL"
					:pinned-data="pinnedData"
					@toggle-pin-data="onTogglePinData({ source: 'pin-icon-click' })"
				/>

				<div v-if="!props.disableEdit" v-show="editMode.enabled" :class="$style.editModeActions">
					<N8nButton
						variant="subtle"
						:label="i18n.baseText('runData.editor.cancel')"
						@click="onClickCancelEdit"
					/>
					<N8nButton
						variant="solid"
						class="ml-2xs"
						:label="i18n.baseText('runData.editor.save')"
						@click="onClickSaveEdit"
					/>
				</div>
			</div>

			<slot name="header-end" v-bind="itemsCountProps" />
		</div>

		<div v-show="!binaryDataDisplayVisible">
			<div v-if="inputSelectLocation === 'header'" :class="$style.inputSelect">
				<slot name="input-select"></slot>
			</div>

			<div
				v-if="
					maxRunIndex > 0 &&
					!displaysMultipleNodes &&
					!props.disableRunIndexSelection &&
					!isExecutionRedacted
				"
				v-show="!editMode.enabled"
				:class="$style.runSelector"
			>
				<div :class="$style.runSelectorInner">
					<slot v-if="inputSelectLocation === 'runs'" name="input-select"></slot>

					<N8nSelect
						:model-value="runIndex"
						:class="$style.runSelectorSelect"
						size="small"
						teleported
						data-test-id="run-selector"
						@update:model-value="onRunIndexChange"
						@click.stop
					>
						<template #prepend>{{ i18n.baseText('ndv.output.run') }}</template>
						<N8nOption
							v-for="option in runSelectorOptionsCount"
							:key="option"
							:label="getRunLabel(option)"
							:value="option - 1"
							data-test-id="run-selection-option"
						></N8nOption>
					</N8nSelect>

					<N8nTooltip v-if="canLinkRuns" placement="right">
						<template #content>
							{{ i18n.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint') }}
						</template>
						<N8nIconButton
							variant="ghost"
							:icon="linkedRuns ? 'unlink' : 'link'"
							:class="['linkRun', linkedRuns ? 'linked' : '']"
							size="small"
							data-test-id="link-run"
							@click="toggleLinkRuns"
						/>
					</N8nTooltip>

					<slot name="run-info"></slot>
				</div>
				<ViewSubExecution
					v-if="activeTaskMetadata && !(paneType === 'input' && hasInputOverwrite)"
					:task-metadata="activeTaskMetadata"
					:display-mode="displayMode"
				/>
			</div>

			<slot v-if="!displaysMultipleNodes" name="before-data" />

			<div v-if="props.calloutMessage || $slots['callout-message']" :class="$style.hintCallout">
				<N8nCallout theme="info" data-test-id="run-data-callout">
					<slot name="callout-message">
						<N8nText v-n8n-html="props.calloutMessage" size="small"></N8nText>
					</slot>
				</N8nCallout>
			</div>
			<NodeSettingsHint
				v-if="!props.disableSettingsHint && props.paneType === 'output'"
				:node="node"
			/>
			<N8nCallout
				v-for="hint in nodeHints"
				:key="hint.message"
				:class="$style.hintCallout"
				:theme="hint.type || 'info'"
				data-test-id="node-hint"
			>
				<N8nText v-n8n-html="hint.message" size="small"></N8nText>
			</N8nCallout>

			<div
				v-if="showBranchSwitch && !isExecutionRedacted"
				:class="$style.outputs"
				data-test-id="branches"
			>
				<slot v-if="inputSelectLocation === 'outputs'" name="input-select"></slot>
				<ViewSubExecution
					v-if="activeTaskMetadata && !(paneType === 'input' && hasInputOverwrite)"
					:task-metadata="activeTaskMetadata"
					:display-mode="displayMode"
				/>

				<div :class="$style.tabs">
					<N8nTabs
						size="small"
						:model-value="currentOutputIndex"
						:options="branches"
						@update:model-value="onBranchChange"
					/>
				</div>
			</div>

			<div
				v-else-if="
					!props.compact &&
					hasNodeRun &&
					!isSearchInSchemaView &&
					((dataCount > 0 && maxRunIndex === 0) || search) &&
					!isArtificialRecoveredEventItem &&
					!displaysMultipleNodes &&
					!isExecutionRedacted
				"
				v-show="!editMode.enabled"
				:class="$style.itemsCount"
				data-test-id="ndv-items-count"
			>
				<slot v-if="inputSelectLocation === 'items'" name="input-select"></slot>

				<RunDataItemCount v-bind="itemsCountProps" />
				<ViewSubExecution
					v-if="activeTaskMetadata && !(paneType === 'input' && hasInputOverwrite)"
					:task-metadata="activeTaskMetadata"
					:display-mode="displayMode"
				/>
			</div>
		</div>

		<div
			ref="dataContainerRef"
			:class="$style.dataContainer"
			data-test-id="ndv-data-container"
			@wheel.capture="emit('captureWheelDataContainer', $event)"
		>
			<BinaryDataDisplay
				v-if="binaryDataDisplayData"
				:window-visible="binaryDataDisplayVisible"
				:display-data="binaryDataDisplayData"
				@close="closeBinaryDataDisplay"
			/>

			<div
				v-if="isExecuting && !isWaitNodeWaiting"
				:class="[$style.center, $style.executingMessage]"
				data-test-id="ndv-executing"
			>
				<div v-if="!props.compact" :class="$style.spinner">
					<N8nSpinner type="ring" />
				</div>
				<N8nText>{{ executingMessage }}</N8nText>
			</div>

			<div
				v-else-if="isTrimmedManualExecutionDataItem && !isExecutionInTerminalState"
				:class="[$style.center, $style.executingMessage]"
				data-test-id="ndv-trimmed-loading"
			>
				<div v-if="!props.compact" :class="$style.spinner">
					<N8nSpinner type="ring" />
				</div>
				<N8nText>
					{{ i18n.baseText('runData.trimmedData.loading') }}
				</N8nText>
			</div>

			<div
				v-else-if="isTrimmedManualExecutionDataItem && isExecutionInTerminalState"
				:class="[$style.center, $style.executingMessage]"
				data-test-id="ndv-trimmed-corrupted"
			>
				<N8nText>
					{{ i18n.baseText('runData.trimmedData.corrupted') }}
				</N8nText>
				<N8nButton
					v-if="pinnedData.hasData.value"
					class="mt-s"
					type="secondary"
					size="small"
					data-test-id="ndv-trimmed-corrupted-unpin"
					@click="onTogglePinData({ source: 'context-menu' })"
				>
					{{ i18n.baseText('runData.trimmedData.unpin') }}
				</N8nButton>
			</div>

			<div v-else-if="editMode.enabled" :class="$style.editMode">
				<N8nText v-if="previousExecutionDataUsedInEditMode" class="mb-2xs" size="small"
					>{{ i18n.baseText('runData.pinData.insertedExecutionData') }}
				</N8nText>
				<div :class="[$style.editModeBody, 'ignore-key-press-canvas']">
					<JsonEditor
						:model-value="editMode.value"
						:fill-parent="true"
						@update:model-value="ndvStore.setOutputPanelEditModeValue($event)"
					/>
				</div>
				<div :class="$style.editModeFooter">
					<N8nInfoTip :bold="false" :class="$style.editModeFooterInfotip">
						{{ i18n.baseText('runData.editor.copyDataInfo') }}
						<N8nLink :to="DATA_EDITING_DOCS_URL" size="small">
							{{ i18n.baseText('generic.learnMore') }}
						</N8nLink>
					</N8nInfoTip>
				</div>
			</div>

			<div v-else-if="isWaitNodeWaiting" :class="$style.center">
				<slot name="node-waiting">xxx</slot>
			</div>

			<div v-else-if="isExecutionRedacted" :class="$style.redactedContainer">
				<div :class="$style.redactedFakeData">
					<!-- Text-like fake content for AI / text output -->
					<div
						v-if="displayMode === 'ai' || node?.type?.includes('langchain')"
						:class="$style.redactedText"
					>
						<p>
							The process completed successfully and generated the following results based on the
							provided input parameters. The analysis identified several key patterns in the dataset
							that are worth highlighting.
						</p>
						<p>
							First, the primary metrics showed a consistent upward trend across all measured
							dimensions. The correlation between input variables and output quality remained strong
							throughout the evaluation period, with confidence intervals well within acceptable
							ranges.
						</p>
						<p>
							Additionally, the secondary analysis revealed three distinct clusters within the data,
							each exhibiting unique characteristics that align with the expected theoretical
							framework. These findings suggest that the underlying model is performing as intended.
						</p>
					</div>
					<!-- JSON-like fake content -->
					<pre v-else-if="displayMode === 'json'" :class="$style.redactedJson">
[
  {
    "id": 1007,
    "name": "John Smith",
    "email": "john@example.com",
    "status": "active",
    "created_at": "2024-01-15"
  },
  {
    "id": 1014,
    "name": "Jane Doe",
    "email": "jane@company.com",
    "status": "pending",
    "created_at": "2024-02-20"
  },
  {
    "id": 1021,
    "name": "Alex Johnson",
    "email": "alex@mail.com",
    "status": "active",
    "created_at": "2024-03-10"
  },
  {
    "id": 1028,
    "name": "Maria Garcia",
    "email": "maria@org.com",
    "status": "inactive",
    "created_at": "2024-04-05"
  },
  {
    "id": 1035,
    "name": "Chris Lee",
    "email": "chris@test.com",
    "status": "active",
    "created_at": "2024-05-18"
  }
]</pre
					>
					<!-- Schema-like fake content -->
					<div v-else-if="displayMode === 'schema'" :class="$style.redactedSchema">
						<div :class="$style.redactedSchemaField">
							<span :class="$style.redactedSchemaKey">id</span>
							<span :class="$style.redactedSchemaType">number</span>
						</div>
						<div :class="$style.redactedSchemaField">
							<span :class="$style.redactedSchemaKey">name</span>
							<span :class="$style.redactedSchemaType">string</span>
						</div>
						<div :class="$style.redactedSchemaField">
							<span :class="$style.redactedSchemaKey">email</span>
							<span :class="$style.redactedSchemaType">string</span>
						</div>
						<div :class="$style.redactedSchemaField">
							<span :class="$style.redactedSchemaKey">status</span>
							<span :class="$style.redactedSchemaType">string</span>
						</div>
						<div :class="$style.redactedSchemaField">
							<span :class="$style.redactedSchemaKey">created_at</span>
							<span :class="$style.redactedSchemaType">string</span>
						</div>
						<div :class="$style.redactedSchemaField">
							<span :class="$style.redactedSchemaKey">metadata</span>
							<span :class="$style.redactedSchemaType">object</span>
						</div>
						<div :class="$style.redactedSchemaField">
							<span :class="$style.redactedSchemaKey">tags</span>
							<span :class="$style.redactedSchemaType">array</span>
						</div>
					</div>
					<!-- Table-like fake content for structured data (default) -->
					<table v-else :class="$style.redactedTable">
						<thead>
							<tr>
								<th>id</th>
								<th>name</th>
								<th>email</th>
								<th>status</th>
								<th>created_at</th>
							</tr>
						</thead>
						<tbody>
							<tr v-for="row in 8" :key="row">
								<td>{{ 1000 + row * 7 }}</td>
								<td>
									{{
										[
											'John Smith',
											'Jane Doe',
											'Alex Johnson',
											'Maria Garcia',
											'Chris Lee',
											'Pat Taylor',
											'Sam Wilson',
											'Robin Brown',
										][row - 1]
									}}
								</td>
								<td>
									{{
										[
											'john@example',
											'jane@company',
											'alex@mail',
											'maria@org',
											'chris@test',
											'pat@demo',
											'sam@site',
											'robin@web',
										][row - 1]
									}}.com
								</td>
								<td>
									{{
										[
											'active',
											'pending',
											'active',
											'inactive',
											'active',
											'pending',
											'active',
											'inactive',
										][row - 1]
									}}
								</td>
								<td>
									{{
										[
											'2024-01-15',
											'2024-02-20',
											'2024-03-10',
											'2024-04-05',
											'2024-05-18',
											'2024-06-22',
											'2024-07-30',
											'2024-08-12',
										][row - 1]
									}}
								</td>
							</tr>
						</tbody>
					</table>
				</div>
				<div v-if="$slots['data-redacted']" :class="$style.redactedOverlay">
					<slot name="data-redacted"></slot>
				</div>
			</div>

			<div v-else-if="shouldShowNodeNotRunState" :class="$style.center">
				<slot name="node-not-run"></slot>
			</div>

			<div v-else-if="shouldShowDisabledNodeHint && node" :class="$style.center">
				<N8nText>
					{{ i18n.baseText('ndv.input.disabled', { interpolate: { nodeName: node.name } }) }}
					<N8nLink @click="enableNode">
						{{ i18n.baseText('ndv.input.disabled.cta') }}
					</N8nLink>
				</N8nText>
			</div>

			<div v-else-if="hasNodeRun && isArtificialRecoveredEventItem" :class="$style.center">
				<slot name="recovered-artificial-output-data"></slot>
			</div>

			<div v-else-if="hasNodeRun && hasRunError" :class="$style.stretchVertically">
				<NDVEmptyState
					v-if="isPaneTypeInput"
					:class="$style.center"
					:title="
						i18n.baseText('nodeErrorView.inputPanel.previousNodeError.title', {
							interpolate: { nodeName: node?.name ?? '' },
						})
					"
				/>
				<div v-else-if="$slots['content']">
					<NodeErrorView
						v-if="workflowRunErrorAsNodeError"
						:error="workflowRunErrorAsNodeError"
						:class="$style.inlineError"
						:compact="compact"
					/>
					<slot name="content"></slot>
				</div>
				<NodeErrorView
					v-else-if="workflowRunErrorAsNodeError"
					:error="workflowRunErrorAsNodeError"
					:class="$style.dataDisplay"
					:compact="compact"
					show-details
				/>
				<div v-else-if="hasRedactedError" :class="$style.center" data-test-id="ndv-redacted-error">
					<slot name="redacted-error"></slot>
				</div>
			</div>

			<div v-else-if="shouldShowNoDataInBranch" :class="$style.center">
				<NDVEmptyState v-if="search" :title="i18n.baseText('ndv.search.noMatch.title')">
					<I18nT keypath="ndv.search.noMatch.description" tag="span" scope="global">
						<template #link>
							<a href="#" @click.prevent="onSearchClear">
								{{ i18n.baseText('ndv.search.noMatch.description.link') }}
							</a>
						</template>
					</I18nT>
				</NDVEmptyState>
				<N8nText v-else>
					{{ noDataInBranchMessage }}
				</N8nText>
			</div>

			<div v-else-if="shouldShowNoOutputData" :class="$style.center">
				<slot name="no-output-data"></slot>
			</div>

			<div
				v-else-if="hasNodeRun && !showData"
				data-test-id="ndv-data-size-warning"
				:class="$style.center"
			>
				<div :class="$style.dataSizeWarning">
					<NDVEmptyState
						:title="
							i18n.baseText('ndv.tooMuchData.title', {
								interpolate: {
									size: dataSizeInMB,
								},
							})
						"
					>
						<span v-n8n-html="i18n.baseText('ndv.tooMuchData.message')" />
					</NDVEmptyState>

					<div :class="$style.warningActions">
						<N8nButton
							variant="outline"
							size="small"
							:label="i18n.baseText('runData.downloadBinaryData')"
							@click="downloadJsonData()"
						/>

						<N8nButton
							size="small"
							:label="i18n.baseText('ndv.tooMuchData.showDataAnyway')"
							@click="showTooMuchData"
						/>
					</div>
				</div>
			</div>

			<!-- V-else slot named content which only renders if $slots.content is passed and hasNodeRun -->
			<slot v-else-if="hasNodeRun && $slots['content']" name="content"></slot>

			<div v-else-if="shouldShowBinaryOnlyHint" :class="$style.center">
				<N8nText>
					{{ i18n.baseText('runData.switchToBinary.info') }}
					<a @click="switchToBinary">
						{{ i18n.baseText('runData.switchToBinary.binary') }}
					</a>
				</N8nText>
			</div>

			<NDVEmptyState
				v-else-if="showIoSearchNoMatchContent"
				:class="$style.center"
				:title="i18n.baseText('ndv.search.noMatch.title')"
			>
				<I18nT keypath="ndv.search.noMatch.description" tag="span" scope="global">
					<template #link>
						<a href="#" @click="onSearchClear">
							{{ i18n.baseText('ndv.search.noMatch.description.link') }}
						</a>
					</template>
				</I18nT>
			</NDVEmptyState>

			<Suspense v-else-if="hasNodeRun && displayMode === 'table' && node">
				<LazyRunDataTable
					:node="node"
					:input-data="inputDataPage"
					:mapping-enabled="mappingEnabled"
					:distance-from-active="distanceFromActive"
					:run-index="runIndex"
					:page-offset="currentPageOffset"
					:total-runs="maxRunIndex"
					:has-default-hover-state="paneType === 'input' && !search"
					:search="search"
					:header-bg-color="tableHeaderBgColor"
					:compact="props.compact"
					:disable-hover-highlight="props.disableHoverHighlight"
					:collapsing-column-name="collapsingTableColumnName"
					@mounted="emit('tableMounted', $event)"
					@active-row-changed="onItemHover"
					@display-mode-change="onDisplayModeChange"
					@collapsing-column-changed="emit('collapsingTableColumnChanged', $event)"
				/>
			</Suspense>

			<Suspense v-else-if="hasNodeRun && displayMode === 'json' && node">
				<LazyRunDataJson
					:pane-type="paneType"
					:edit-mode="editMode"
					:push-ref="pushRef"
					:node="node"
					:input-data="inputDataPage"
					:mapping-enabled="mappingEnabled"
					:distance-from-active="distanceFromActive"
					:run-index="runIndex"
					:output-index="currentOutputIndex"
					:total-runs="maxRunIndex"
					:search="search"
					:compact="props.compact"
					:execution="workflowExecution"
				/>
			</Suspense>

			<Suspense v-else-if="hasNodeRun && isPaneTypeOutput && displayMode === 'html'">
				<LazyRunDataHtml :input-html="inputHtml" />
			</Suspense>

			<Suspense v-else-if="hasNodeRun && displayMode === 'ai'">
				<LazyRunDataAi
					render-type="rendered"
					:compact="compact"
					:content="parsedAiContent"
					:search="search"
				/>
			</Suspense>

			<Suspense v-else-if="shouldShowSchemaView">
				<LazyRunDataSchema
					:nodes="nodes"
					:mapping-enabled="mappingEnabled"
					:node="node"
					:data="jsonData"
					:pane-type="paneType"
					:connection-type="connectionType"
					:output-index="currentOutputIndex"
					:search="search"
					:class="$style.schema"
					:compact="props.compact"
					:truncate-limit="props.truncateLimit"
					:preview-execution="lastSuccessfulExecution"
					@clear:search="onSearchClear"
					@execute="executeNode"
				/>
			</Suspense>

			<RunDataBinary
				v-else-if="displayMode === 'binary'"
				:binary-data="binaryData"
				@preview="displayBinaryData"
			/>

			<div v-else-if="!hasNodeRun" :class="$style.center">
				<slot name="node-not-run"></slot>
			</div>
		</div>
		<RunDataPaginationBar
			v-if="
				hidePagination === false &&
				hasNodeRun &&
				!hasRunError &&
				displayMode !== 'binary' &&
				dataCount > pageSize &&
				!isSchemaView &&
				!isArtificialRecoveredEventItem
			"
			v-show="!editMode.enabled"
			:current-page="currentPage"
			:page-size="pageSize"
			:total="dataCount"
			@update:current-page="onCurrentPageChange"
			@update:page-size="onPageSizeChange"
		/>
		<N8nBlockUi
			:show="blockUI"
			:class="{
				[$style.uiBlocker]: true,
				[$style.uiBlockerNdvV2]: isNDVV2,
			}"
		/>
	</div>
</template>

<style lang="scss" module>
.infoIcon {
	color: var(--color--foreground--shade-1);
}

.center {
	display: flex;
	height: 100%;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: var(--ndv--spacing) var(--ndv--spacing) var(--spacing--xl) var(--ndv--spacing);
	text-align: center;

	> * {
		max-width: 316px;
		margin-bottom: var(--spacing--2xs);
	}
}

.container {
	--ndv--spacing: var(--spacing--sm);
	position: relative;
	width: 100%;
	height: 100%;
	display: flex;
	flex-direction: column;
}

.pinnedDataCallout {
	border-radius: inherit;
	border-bottom-right-radius: 0;
	border-top: 0;
	border-left: 0;
	border-right: 0;
	height: 40px;
}

.header {
	display: flex;
	align-items: center;
	margin-bottom: var(--ndv--spacing);
	padding: var(--ndv--spacing) var(--spacing--3xs) 0 var(--ndv--spacing);
	position: relative;
	min-height: calc(30px + var(--ndv--spacing));
	scrollbar-width: thin;
	container-type: inline-size;

	.compact & {
		margin-bottom: var(--spacing--4xs);
		padding: var(--spacing--2xs);
		margin-bottom: 0;
		flex-shrink: 0;
		flex-grow: 0;
		min-height: auto;
		gap: var(--spacing--2xs);
	}

	> *:first-child {
		flex-grow: 1;
	}
}

.dataContainer {
	position: relative;
	overflow-y: auto;
	height: 100%;
}

.dataDisplay {
	position: absolute;
	top: 0;
	left: 0;
	padding: 0 var(--ndv--spacing) var(--spacing--3xl) var(--ndv--spacing);
	right: 0;
	overflow-y: auto;
	line-height: var(--line-height--xl);
	word-break: normal;
	height: 100%;

	.compact & {
		padding: 0 var(--spacing--2xs);
	}
}

.inlineError {
	line-height: var(--line-height--xl);
	padding-left: var(--ndv--spacing);
	padding-right: var(--ndv--spacing);
	padding-bottom: var(--ndv--spacing);
}

.outputs {
	display: flex;
	flex-direction: column;
	gap: var(--ndv--spacing);
	padding-left: var(--ndv--spacing);
	padding-right: var(--ndv--spacing);
	padding-bottom: var(--ndv--spacing);

	.compact & {
		padding-left: var(--spacing--2xs);
		padding-right: var(--spacing--2xs);
		padding-bottom: var(--spacing--2xs);
		font-size: var(--font-size--2xs);
	}
}

.tabs {
	display: flex;
	justify-content: space-between;
	align-items: center;
	min-height: 30px;
	--tabs--arrow-buttons--color: var(--run-data--color--background);
}

.itemsCount {
	display: flex;
	align-items: center;
	gap: var(--spacing--2xs);
	padding-left: var(--ndv--spacing);
	padding-right: var(--ndv--spacing);
	padding-bottom: var(--ndv--spacing);
	flex-flow: wrap;
}

.ndv-v2 .itemsCount {
	padding-left: var(--spacing--xs);
}

.inputSelect {
	padding-left: var(--ndv--spacing);
	padding-right: var(--ndv--spacing);
	padding-bottom: var(--ndv--spacing);
}

.runSelector {
	display: flex;
	align-items: center;
	flex-flow: wrap;
	padding-left: var(--ndv--spacing);
	padding-right: var(--ndv--spacing);
	margin-bottom: var(--ndv--spacing);
	gap: var(--spacing--3xs);

	:global(.el-input--suffix .el-input__inner) {
		padding-right: var(--spacing--lg);
	}
}

.runSelectorInner {
	display: flex;
	gap: var(--spacing--4xs);
	align-items: center;
}

.runSelectorSelect {
	max-width: 205px;
}

.search {
	margin-left: auto;
}

.displayModes {
	display: flex;
	justify-content: flex-end;
	align-items: center;
	flex-grow: 1;
	gap: var(--spacing--2xs);

	.compact & {
		/* let title text alone decide the height */
		height: 0;
	}

	.showActionsOnHover & {
		/* Using opacity instead of visibility so that search input can get focused through keyboard shortcut */
		opacity: 0;

		:global(.el-input__prefix) {
			transition-duration: 0ms;
		}
	}

	.showActionsOnHover:focus-within &,
	.showActionsOnHover:hover & {
		opacity: 1;
	}
}

.tooltipContain {
	max-width: 240px;
}

.spinner {
	display: flex;
	justify-content: center;
	margin-bottom: var(--ndv--spacing);

	* {
		color: var(--color--primary);
		min-height: 40px;
		min-width: 40px;
	}
}

.editMode {
	height: 100%;
	display: flex;
	flex-direction: column;
	justify-content: stretch;
	padding-left: var(--ndv--spacing);
	padding-right: var(--ndv--spacing);
}

.editModeBody {
	flex: 1 1 auto;
	max-height: 100%;
	width: 100%;
	overflow: auto;
}

.editModeFooter {
	flex: 0 1 auto;
	display: flex;
	width: 100%;
	justify-content: space-between;
	align-items: center;
	padding-top: var(--ndv--spacing);
	padding-bottom: var(--ndv--spacing);
}

.editModeFooterInfotip {
	display: flex;
	flex: 1;
	width: 100%;
}

.editModeActions {
	display: flex;
	justify-content: flex-end;
	align-items: center;
	margin-left: var(--ndv--spacing);
}

.stretchVertically {
	height: 100%;
}

.uiBlocker {
	border-top-left-radius: 0;
	border-bottom-left-radius: 0;
}

.uiBlockerNdvV2 {
	border-radius: 0;
}

.hintCallout {
	margin-bottom: var(--spacing--xs);
	margin-left: var(--ndv--spacing);
	margin-right: var(--ndv--spacing);

	.compact & {
		margin: 0 var(--spacing--2xs) var(--spacing--2xs) var(--spacing--2xs);
	}
}

.schema {
	padding: 0 var(--ndv--spacing);
}

.messageSection {
	display: flex;
	align-items: center;
	width: 100%;
}

.singleIcon {
	flex-direction: row;
	align-items: center;
}

.multipleIcons {
	flex-direction: column;
	align-items: flex-start;
	gap: var(--spacing--2xs, 8px);
}

.multipleIcons .iconStack {
	margin-right: 0;
	margin-bottom: 0;
}

.iconStack {
	display: flex;
	align-items: center;
	gap: var(--spacing--4xs, 4px);
	flex-shrink: 0;
	margin-right: var(--spacing--xs);
}

.icon {
	color: var(--callout--icon-color--info);
	line-height: 1;
	font-size: var(--font-size--xs);
}

.executingMessage {
	.compact & {
		color: var(--color--text--tint-1);
	}
}

.resetCollapseButton {
	color: var(--color--foreground--shade-2);
}

@container (max-width: 240px) {
	/* Hide title when the panel is too narrow */
	.compact:hover .title {
		visibility: hidden;
		width: 0;
	}
}

.ndv-v2,
.compact {
	--ndv--spacing: var(--spacing--2xs);
}

.dataSizeWarning {
	padding: var(--spacing--sm) var(--spacing--md);
	text-align: center;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--spacing--sm);
}

.warningActions {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--spacing--2xs);
	width: 100%;
	align-items: center;
}

.redactedContainer {
	position: relative;
	height: 100%;
	overflow: hidden;
}

.redactedFakeData {
	padding: var(--spacing--sm);
	opacity: 0.6;
	pointer-events: none;
	user-select: none;
	filter: blur(3px);
}

.redactedText {
	font-size: var(--font-size--sm);
	color: var(--color--text);
	line-height: var(--line-height--xl);

	p {
		margin-bottom: var(--spacing--sm);
	}
}

.redactedTable {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--font-size--2xs);
	color: var(--color--text);

	th,
	td {
		padding: var(--spacing--3xs) var(--spacing--xs);
		text-align: left;
		border-bottom: 1px solid var(--color--foreground);
	}

	th {
		font-weight: var(--font-weight--bold);
	}
}

.redactedJson {
	font-family: var(--font-family--monospace, 'Courier New', monospace);
	font-size: var(--font-size--2xs);
	color: var(--color--text);
	line-height: var(--line-height--xl);
	margin: 0;
	white-space: pre;
}

.redactedSchema {
	display: flex;
	flex-direction: column;
	gap: var(--spacing--4xs);
	padding-top: var(--spacing--2xs);
}

.redactedSchemaField {
	display: flex;
	align-items: center;
	gap: var(--spacing--xs);
	padding: var(--spacing--3xs) var(--spacing--xs);
	border-bottom: 1px solid var(--color--foreground);
}

.redactedSchemaKey {
	font-size: var(--font-size--sm);
	color: var(--color--text);
	font-weight: var(--font-weight--bold);
}

.redactedSchemaType {
	font-size: var(--font-size--2xs);
	color: var(--color--text--tint-2);
}

.redactedOverlay {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	display: flex;
	justify-content: center;
	align-items: flex-start;
	padding-top: var(--spacing--3xl);
	z-index: 1;
	backdrop-filter: blur(2px);

	&::before {
		content: '';
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		background: var(--color--foreground--tint-2);
		opacity: 0.5;
		z-index: -1;
	}
}
</style>

<style lang="scss" scoped>
.run-data {
	.code-node-editor {
		height: 100%;
	}
}
</style>

<style lang="scss" scoped>
:deep(.highlight) {
	background-color: #f7dc55;
	color: black;
	border-radius: var(--radius);
	padding: 0 1px;
	font-weight: var(--font-weight--regular);
	font-style: normal;
}
</style>
