import moment from 'moment-timezone';
import type {
	IDataObject,
	IExecuteFunctions,
	ILoadOptionsFunctions,
	INodeExecutionData,
	INodeListSearchItems,
	INodeListSearchResult,
	INodeParameterResourceLocator,
	INodePropertyOptions,
	INodeType,
	INodeTypeBaseDescription,
	INodeTypeDescription,
	JsonObject,
} from 'n8n-workflow';
import {
	BINARY_ENCODING,
	NodeConnectionTypes,
	NodeOperationError,
	SEND_AND_WAIT_OPERATION,
} from 'n8n-workflow';
import type { Readable } from 'stream';

import { channelFields, channelOperations } from './ChannelDescription';
import { fileFields, fileOperations } from './FileDescription';
import {
	slackApiRequest,
	slackApiRequestAllItems,
	getMessageContent,
	getTarget,
	createSendAndWaitMessageBody,
	processThreadOptions,
	slackApiRequestAllItemsWithRateLimit,
} from './GenericFunctions';
import {
	channelRLC,
	messageFields,
	messageOperations,
	sendToSelector,
	userRLC,
} from './MessageDescription';
import { reactionFields, reactionOperations } from './ReactionDescription';
import { starFields, starOperations } from './StarDescription';
import { userFields, userOperations } from './UserDescription';
import { userGroupFields, userGroupOperations } from './UserGroupDescription';
import { configureWaitTillDate } from '../../../utils/sendAndWait/configureWaitTillDate.util';
import { sendAndWaitWebhooksDescription } from '../../../utils/sendAndWait/descriptions';
import {
	getSendAndWaitProperties,
	SEND_AND_WAIT_WAITING_TOOLTIP,
	sendAndWaitWebhook,
} from '../../../utils/sendAndWait/utils';

export class SlackV2 implements INodeType {
	description: INodeTypeDescription;

	constructor(baseDescription: INodeTypeBaseDescription) {
		this.description = {
			...baseDescription,
			version: [2, 2.1, 2.2, 2.3, 2.4],
			defaults: {
				name: 'Slack',
			},
			inputs: [NodeConnectionTypes.Main],
			outputs: [NodeConnectionTypes.Main],
			usableAsTool: true,
			credentials: [
				{
					name: 'slackApi',
					required: true,
					displayOptions: {
						show: {
							authentication: ['accessToken'],
						},
					},
				},
				{
					name: 'slackOAuth2Api',
					required: true,
					displayOptions: {
						show: {
							authentication: ['oAuth2'],
						},
					},
				},
			],
			waitingNodeTooltip: SEND_AND_WAIT_WAITING_TOOLTIP,
			webhooks: sendAndWaitWebhooksDescription,
			properties: [
				{
					displayName: 'Authentication',
					name: 'authentication',
					type: 'options',
					options: [
						{
							name: 'Access Token',
							value: 'accessToken',
						},
						{
							name: 'OAuth2',
							value: 'oAuth2',
						},
					],
					default: 'accessToken',
				},

				{
					displayName: 'Resource',
					name: 'resource',
					type: 'options',
					noDataExpression: true,
					options: [
						{
							name: 'Channel',
							value: 'channel',
						},
						{
							name: 'File',
							value: 'file',
						},
						{
							name: 'Message',
							value: 'message',
						},
						{
							name: 'Reaction',
							value: 'reaction',
						},
						{
							name: 'Star',
							value: 'star',
						},
						{
							name: 'User',
							value: 'user',
						},
						{
							name: 'User Group',
							value: 'userGroup',
						},
					],
					default: 'message',
				},

				...channelOperations,
				...channelFields,
				...messageOperations,
				...messageFields,
				...getSendAndWaitProperties([
					{ ...sendToSelector, default: 'user' },
					{
						...channelRLC,
						displayOptions: {
							show: {
								select: ['channel'],
							},
						},
					},
					{
						...userRLC,
						displayOptions: {
							show: {
								select: ['user'],
							},
						},
					},
				]).filter((p) => p.name !== 'subject'),
				...starOperations,
				...starFields,
				...fileOperations,
				...fileFields,
				...reactionOperations,
				...reactionFields,
				...userOperations,
				...userFields,
				...userGroupOperations,
				...userGroupFields,
			],
		};
	}

	methods = {
		listSearch: {
			async getChannels(
				this: ILoadOptionsFunctions,
				filter?: string,
				paginationToken?: string,
			): Promise<INodeListSearchResult> {
				const qs = {
					types: 'public_channel,private_channel',
					limit: 1000,
					cursor: paginationToken,
				};
				// in case of too many rate limit errors, return cursor and allow user to lazy load by scrolling
				const { data: channels, cursor } = await slackApiRequestAllItemsWithRateLimit<{
					id: string;
					name: string;
				}>(this, 'channels', 'GET', '/conversations.list', {}, qs, {
					onFail: 'stop',
					maxRetries: 2,
					fallbackDelay: 30_000,
				});
				const results: INodeListSearchItems[] = channels
					.map((c) => ({
						name: c.name,
						value: c.id,
					}))
					.filter(
						(c) =>
							!filter ||
							c.name.toLowerCase().includes(filter.toLowerCase()) ||
							c.value?.toString() === filter,
					)
					.sort((a, b) => {
						if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
						if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
						return 0;
					});
				return { results, paginationToken: cursor };
			},
			async getUsers(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult> {
				const users = (await slackApiRequestAllItems.call(
					this,
					'members',
					'GET',
					'/users.list',
				)) as Array<{ id: string; name: string }>;
				const results: INodeListSearchItems[] = users
					.map((c) => ({
						name: c.name,
						value: c.id,
					}))
					.filter(
						(c) =>
							!filter ||
							c.name.toLowerCase().includes(filter.toLowerCase()) ||
							c.value?.toString() === filter,
					)
					.sort((a, b) => {
						if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
						if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
						return 0;
					});
				return { results };
			},
		},
		loadOptions: {
			// Get all the users to display them to user so that they can
			// select them easily
			async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const users = await slackApiRequestAllItems.call(this, 'members', 'GET', '/users.list');
				for (const user of users) {
					const userName = user.name;
					const userId = user.id;
					returnData.push({
						name: userName,
						value: userId,
					});
				}

				returnData.sort((a, b) => {
					if (a.name < b.name) {
						return -1;
					}
					if (a.name > b.name) {
						return 1;
					}
					return 0;
				});

				return returnData;
			},
			// Get all the users to display them to user so that they can
			// select them easily
			async getChannels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const qs = { types: 'public_channel,private_channel', limit: 1000 };
				const channels = await slackApiRequestAllItems.call(
					this,
					'channels',
					'GET',
					'/conversations.list',
					{},
					qs,
				);
				for (const channel of channels) {
					const channelName = channel.name;
					const channelId = channel.id;
					returnData.push({
						name: channelName,
						value: channelId,
					});
				}

				returnData.sort((a, b) => {
					if (a.name < b.name) {
						return -1;
					}
					if (a.name > b.name) {
						return 1;
					}
					return 0;
				});

				return returnData;
			},
			// Get all the users to display them to user so that they can
			// select them easily
			async getChannelsName(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const qs = { types: 'public_channel,private_channel', limit: 1000 };
				const channels = await slackApiRequestAllItems.call(
					this,
					'channels',
					'GET',
					'/conversations.list',
					{},
					qs,
				);
				for (const channel of channels) {
					const channelName = channel.name;
					returnData.push({
						name: channelName,
						value: channelName,
					});
				}

				returnData.sort((a, b) => {
					if (a.name < b.name) {
						return -1;
					}
					if (a.name > b.name) {
						return 1;
					}
					return 0;
				});

				return returnData;
			},
			// Get all the team fields to display them to user so that they can
			// select them easily
			async getTeamFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const {
					profile: { fields },
				} = await slackApiRequest.call(this, 'GET', '/team.profile.get');
				for (const field of fields) {
					const fieldName = field.label;
					const fieldId = field.id;
					returnData.push({
						name: fieldName,
						value: fieldId,
					});
				}
				return returnData;
			},
		},
	};

	webhook = sendAndWaitWebhook;

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		const returnData: INodeExecutionData[] = [];
		const length = items.length;
		let qs: IDataObject;
		let responseData;
		const authentication = this.getNodeParameter('authentication', 0) as string;
		const resource = this.getNodeParameter('resource', 0);
		const operation = this.getNodeParameter('operation', 0);

		const nodeVersion = this.getNode().typeVersion;
		const instanceId = this.getInstanceId();

		if (resource === 'message' && operation === SEND_AND_WAIT_OPERATION) {
			await slackApiRequest.call(
				this,
				'POST',
				'/chat.postMessage',
				createSendAndWaitMessageBody(this),
			);

			const waitTill = configureWaitTillDate(this);

			await this.putExecutionToWait(waitTill);
			return [this.getInputData()];
		}

		for (let i = 0; i < length; i++) {
			try {
				responseData = {
					error: 'Resource ' + resource + ' / operation ' + operation + ' not found!',
				};
				qs = {};
				if (resource === 'channel') {
					//https://api.slack.com/methods/conversations.archive
					if (operation === 'archive') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const body: IDataObject = {
							channel,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.archive',
							body,
							qs,
						);
					}
					//https://api.slack.com/methods/conversations.close
					if (operation === 'close') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const body: IDataObject = {
							channel,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.close',
							body,
							qs,
						);
					}
					//https://api.slack.com/methods/conversations.create
					if (operation === 'create') {
						let channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						channel = channel[0] === '#' ? channel.slice(1) : channel;
						const channelVisibility = this.getNodeParameter('channelVisibility', i) as string;
						const body: IDataObject = {
							name: channel,
							is_private: channelVisibility === 'private' ? true : false,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.create',
							body,
							qs,
						);
						responseData = responseData.channel;
					}
					//https://api.slack.com/methods/conversations.kick
					if (operation === 'kick') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const userId = this.getNodeParameter('userId', i) as string;
						const body: IDataObject = {
							channel,
							user: userId,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.kick',
							body,
							qs,
						);
					}
					//https://api.slack.com/methods/conversations.join
					if (operation === 'join') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const body: IDataObject = {
							channel,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.join',
							body,
							qs,
						);
						responseData = responseData.channel;
					}
					//https://api.slack.com/methods/conversations.info
					if (operation === 'get') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						qs.channel = channel;
						responseData = await slackApiRequest.call(this, 'POST', '/conversations.info', {}, qs);
						responseData = responseData.channel;
					}
					//https://api.slack.com/methods/conversations.list
					if (operation === 'getAll') {
						const returnAll = this.getNodeParameter('returnAll', i);
						const filters = this.getNodeParameter('filters', i);
						if (filters.types) {
							qs.types = (filters.types as string[]).join(',');
						}
						if (filters.excludeArchived) {
							qs.exclude_archived = filters.excludeArchived as boolean;
						}
						if (returnAll) {
							responseData = await slackApiRequestAllItems.call(
								this,
								'channels',
								'GET',
								'/conversations.list',
								{},
								qs,
							);
						} else {
							qs.limit = this.getNodeParameter('limit', i);
							responseData = await slackApiRequest.call(this, 'GET', '/conversations.list', {}, qs);
							responseData = responseData.channels;
						}
					}
					//https://api.slack.com/methods/conversations.history
					if (operation === 'history') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const returnAll = this.getNodeParameter('returnAll', i);
						const filters = this.getNodeParameter('filters', i);
						qs.channel = channel;
						if (filters.inclusive) {
							qs.inclusive = filters.inclusive as boolean;
						}
						if (filters.latest) {
							qs.latest = new Date(filters.latest as string).getTime() / 1000;
						}
						if (filters.oldest) {
							qs.oldest = new Date(filters.oldest as string).getTime() / 1000;
						}
						if (returnAll) {
							responseData = await slackApiRequestAllItems.call(
								this,
								'messages',
								'GET',
								'/conversations.history',
								{},
								qs,
							);
						} else {
							qs.limit = this.getNodeParameter('limit', i);
							responseData = await slackApiRequest.call(
								this,
								'GET',
								'/conversations.history',
								{},
								qs,
							);
							responseData = responseData.messages;
						}

						// Slack API "feature" - messages sorting breaks in-between pages when oldest is provided
						// Always sort manually in descending order to ensure consistent sorting
						if (nodeVersion >= 2.4) {
							responseData.sort((a: IDataObject, b: IDataObject) => +(b.ts ?? 0) - +(a.ts ?? 0));
						}
					}
					//https://api.slack.com/methods/conversations.invite
					if (operation === 'invite') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const userIds = (this.getNodeParameter('userIds', i) as string[]).join(',');
						const body: IDataObject = {
							channel,
							users: userIds,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.invite',
							body,
							qs,
						);
						responseData = responseData.channel;
					}
					//https://api.slack.com/methods/conversations.leave
					if (operation === 'leave') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const body: IDataObject = {
							channel,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.leave',
							body,
							qs,
						);
					}
					//https://api.slack.com/methods/conversations.members
					if (operation === 'member') {
						const returnAll = this.getNodeParameter('returnAll', 0);
						const resolveData = this.getNodeParameter('resolveData', 0);
						qs.channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						if (returnAll) {
							responseData = await slackApiRequestAllItems.call(
								this,
								'members',
								'GET',
								'/conversations.members',
								{},
								qs,
							);
							responseData = responseData.map((member: string) => ({ member }));
						} else {
							qs.limit = this.getNodeParameter('limit', i);
							responseData = await slackApiRequest.call(
								this,
								'GET',
								'/conversations.members',
								{},
								qs,
							);
							responseData = responseData.members.map((member: string) => ({ member }));
						}

						if (resolveData) {
							const data: IDataObject[] = [];
							for (const { member } of responseData) {
								const { user } = await slackApiRequest.call(
									this,
									'GET',
									'/users.info',
									{},
									{ user: member },
								);
								data.push(user as IDataObject);
							}
							responseData = data;
						}
					}
					//https://api.slack.com/methods/conversations.open
					if (operation === 'open') {
						const options = this.getNodeParameter('options', i);
						const body: IDataObject = {};
						if (options.channelId) {
							body.channel = options.channelId as string;
						}
						if (options.returnIm) {
							body.return_im = options.returnIm as boolean;
						}
						if (options.users) {
							body.users = (options.users as string[]).join(',');
						}
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.open',
							body,
							qs,
						);
						responseData = responseData.channel;
					}
					//https://api.slack.com/methods/conversations.rename
					if (operation === 'rename') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as IDataObject;
						const name = this.getNodeParameter('name', i) as IDataObject;
						const body: IDataObject = {
							channel,
							name,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.rename',
							body,
							qs,
						);
						responseData = responseData.channel;
					}
					//https://api.slack.com/methods/conversations.replies
					if (operation === 'replies') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const ts = this.getNodeParameter('ts', i)?.toString() as string;
						const returnAll = this.getNodeParameter('returnAll', i);
						const filters = this.getNodeParameter('filters', i);
						qs.channel = channel;
						qs.ts = ts;
						if (filters.inclusive) {
							qs.inclusive = filters.inclusive as boolean;
						}
						if (filters.latest) {
							qs.latest = new Date(filters.latest as string).getTime() / 1000;
						}
						if (filters.oldest) {
							qs.oldest = new Date(filters.oldest as string).getTime() / 1000;
						}
						if (returnAll) {
							responseData = await slackApiRequestAllItems.call(
								this,
								'messages',
								'GET',
								'/conversations.replies',
								{},
								qs,
							);
						} else {
							qs.limit = this.getNodeParameter('limit', i);
							responseData = await slackApiRequest.call(
								this,
								'GET',
								'/conversations.replies',
								{},
								qs,
							);
							responseData = responseData.messages;
						}
					}
					//https://api.slack.com/methods/conversations.setPurpose
					if (operation === 'setPurpose') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as IDataObject;
						const purpose = this.getNodeParameter('purpose', i) as IDataObject;
						const body: IDataObject = {
							channel,
							purpose,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.setPurpose',
							body,
							qs,
						);
						responseData = responseData.channel;
					}
					//https://api.slack.com/methods/conversations.setTopic
					if (operation === 'setTopic') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as IDataObject;
						const topic = this.getNodeParameter('topic', i) as IDataObject;
						const body: IDataObject = {
							channel,
							topic,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.setTopic',
							body,
							qs,
						);
						responseData = responseData.channel;
					}
					//https://api.slack.com/methods/conversations.unarchive
					if (operation === 'unarchive') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const body: IDataObject = {
							channel,
						};
						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/conversations.unarchive',
							body,
							qs,
						);
					}
				}
				if (resource === 'message') {
					//https://api.slack.com/methods/chat.postMessage
					if (operation === 'post') {
						const select = this.getNodeParameter('select', i) as 'user' | 'channel';
						const target = getTarget(this, i, select);
						const { sendAsUser } = this.getNodeParameter('otherOptions', i) as IDataObject;
						const content = getMessageContent.call(this, i, nodeVersion, instanceId);

						const body: IDataObject = {
							channel: target,
							...content,
						};
						if (authentication === 'accessToken' && sendAsUser !== '' && sendAsUser !== undefined) {
							body.username = sendAsUser;
						}

						// Add all the other options to the request
						const otherOptions = this.getNodeParameter('otherOptions', i) as IDataObject;
						let action = 'postMessage';
						if (otherOptions.ephemeral) {
							const ephemeral = otherOptions.ephemeral as IDataObject;
							if (select === 'channel') {
								const ephemeralValues = ephemeral.ephemeralValues as IDataObject;
								const userRlc = ephemeralValues.user as INodeParameterResourceLocator;
								body.user =
									userRlc.value?.toString().slice(0, 1) !== '@' && userRlc.mode === 'username'
										? `@${userRlc.value}`
										: userRlc.value;
								action = 'postEphemeral';
							} else if (select === 'user') {
								body.user = target;
								action = 'postEphemeral';
							}
						}

						const threadParams = processThreadOptions(otherOptions.thread_ts as IDataObject);
						Object.assign(body, threadParams);
						delete otherOptions.thread_ts;
						delete otherOptions.ephemeral;
						if (otherOptions.botProfile) {
							const botProfile = otherOptions.botProfile as IDataObject;
							const botProfileValues = botProfile.imageValues as IDataObject;
							Object.assign(
								body,
								botProfileValues.profilePhotoType === 'image'
									? { icon_url: botProfileValues.icon_url }
									: { icon_emoji: botProfileValues.icon_emoji },
							);
						}
						delete otherOptions.botProfile;
						Object.assign(body, otherOptions);
						if (
							select === 'user' &&
							action === 'postEphemeral' &&
							(this.getNodeParameter('user', i) as INodeParameterResourceLocator)?.mode ===
								'username'
						) {
							throw new NodeOperationError(
								this.getNode(),
								'You cannot send ephemeral messages using User type "By username". Please use "From List" or "By ID".',
							);
						} else {
							responseData = await slackApiRequest.call(this, 'POST', `/chat.${action}`, body, qs);
						}
					}
					//https://api.slack.com/methods/chat.update
					if (operation === 'update') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const ts = this.getNodeParameter('ts', i)?.toString() as string;
						const content = getMessageContent.call(this, i, nodeVersion, instanceId);

						const body: IDataObject = {
							channel,
							ts,
							...content,
						};

						// Add all the other options to the request
						const updateFields = this.getNodeParameter('updateFields', i);
						Object.assign(body, updateFields);
						responseData = await slackApiRequest.call(this, 'POST', '/chat.update', body, qs);
					}
					//https://api.slack.com/methods/chat.delete
					if (operation === 'delete') {
						const select = this.getNodeParameter('select', i) as string;
						let target =
							select === 'channel'
								? (this.getNodeParameter('channelId', i, undefined, {
										extractValue: true,
									}) as string)
								: (this.getNodeParameter('user', i, undefined, {
										extractValue: true,
									}) as string);

						if (
							select === 'user' &&
							(this.getNodeParameter('user', i) as IDataObject).mode === 'username'
						) {
							target = target.slice(0, 1) === '@' ? target : `@${target}`;
						}
						const timestamp = this.getNodeParameter('timestamp', i)?.toString() as string;
						const body: IDataObject = {
							channel: target,
							ts: timestamp,
						};
						// Add all the other options to the request
						responseData = await slackApiRequest.call(this, 'POST', '/chat.delete', body, qs);
					}
					//https://api.slack.com/methods/chat.getPermalink
					if (operation === 'getPermalink') {
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const timestamp = this.getNodeParameter('timestamp', i)?.toString() as string;
						qs = {
							channel,
							message_ts: timestamp,
						};
						responseData = await slackApiRequest.call(this, 'GET', '/chat.getPermalink', {}, qs);
					}
					//https://api.slack.com/methods/search.messages
					if (operation === 'search') {
						let query = this.getNodeParameter('query', i) as string;
						const sort = this.getNodeParameter('sort', i) as string;
						const returnAll = this.getNodeParameter('returnAll', i);
						const options = this.getNodeParameter('options', i);
						if (options.searchChannel) {
							const channel = options.searchChannel as IDataObject[];
							for (const channelItem of channel) {
								query += ` in:${channelItem}`;
							}
						}
						qs = {
							query,
							sort: sort === 'relevance' ? 'score' : 'timestamp',
							sort_dir: sort === 'asc' ? 'asc' : 'desc',
						};
						if (returnAll) {
							responseData = await slackApiRequestAllItems.call(
								this,
								'messages',
								'GET',
								'/search.messages',
								{},
								qs,
							);
						} else {
							qs.count = this.getNodeParameter('limit', i);
							responseData = await slackApiRequest.call(this, 'POST', '/search.messages', {}, qs);
							responseData = responseData.messages.matches;
						}
					}
				}
				if (resource === 'reaction') {
					const channel = this.getNodeParameter(
						'channelId',
						i,
						{},
						{ extractValue: true },
					) as string;
					const timestamp = this.getNodeParameter('timestamp', i)?.toString() as string;
					//https://api.slack.com/methods/reactions.add
					if (operation === 'add') {
						const name = this.getNodeParameter('name', i) as string;
						const body: IDataObject = {
							channel,
							name,
							timestamp,
						};
						responseData = await slackApiRequest.call(this, 'POST', '/reactions.add', body, qs);
					}
					//https://api.slack.com/methods/reactions.remove
					if (operation === 'remove') {
						const name = this.getNodeParameter('name', i) as string;
						const body: IDataObject = {
							channel,
							name,
							timestamp,
						};
						responseData = await slackApiRequest.call(this, 'POST', '/reactions.remove', body, qs);
					}
					//https://api.slack.com/methods/reactions.get
					if (operation === 'get') {
						qs.channel = channel;
						qs.timestamp = timestamp;
						responseData = await slackApiRequest.call(this, 'GET', '/reactions.get', {}, qs);
					}
				}
				if (resource === 'star') {
					//https://api.slack.com/methods/stars.add
					if (operation === 'add') {
						const options = this.getNodeParameter('options', i);
						const target = this.getNodeParameter('target', i) as string;
						const channel = this.getNodeParameter(
							'channelId',
							i,
							{},
							{ extractValue: true },
						) as string;
						const body: IDataObject = {};
						body.channel = channel;

						if (target === 'message') {
							const timestamp = this.getNodeParameter('timestamp', i)?.toString() as string;
							body.timestamp = timestamp;
						}
						if (target === 'file') {
							const file = this.getNodeParameter('fileId', i) as string;
							body.file = file;
						}
						if (options.fileComment) {
							body.file_comment = options.fileComment as string;
						}
						responseData = await slackApiRequest.call(this, 'POST', '/stars.add', body, qs);
					}
					//https://api.slack.com/methods/stars.remove
					if (operation === 'delete') {
						const options = this.getNodeParameter('options', i);
						const body: IDataObject = {};
						if (options.channelId) {
							body.channel = options.channelId as string;
						}
						if (options.fileId) {
							body.file = options.fileId as string;
						}
						if (options.fileComment) {
							body.file_comment = options.fileComment as string;
						}
						if (options.timestamp) {
							body.timestamp = options.timestamp as string;
						}
						responseData = await slackApiRequest.call(this, 'POST', '/stars.remove', body, qs);
					}
					//https://api.slack.com/methods/stars.list
					if (operation === 'getAll') {
						const returnAll = this.getNodeParameter('returnAll', i);
						if (returnAll) {
							responseData = await slackApiRequestAllItems.call(
								this,
								'items',
								'GET',
								'/stars.list',
								{},
								qs,
							);
						} else {
							qs.limit = this.getNodeParameter('limit', i);
							responseData = await slackApiRequest.call(this, 'GET', '/stars.list', {}, qs);
							responseData = responseData.items;
						}
					}
				}
				if (resource === 'file') {
					//https://api.slack.com/methods/files.upload
					if (operation === 'upload') {
						const options = this.getNodeParameter('options', i);
						const body: IDataObject = {};
						const fileBody: IDataObject = {};

						if (options.channelIds) {
							body.channels = (options.channelIds as string[]).join(',');
						}
						if (options.channelId) {
							body.channel_id = options.channelId as string;
						}
						if (options.initialComment) {
							body.initial_comment = options.initialComment as string;
						}
						if (options.threadTs) {
							body.thread_ts = options.threadTs as string;
						}
						if (options.title) {
							if (nodeVersion <= 2.1) {
								body.title = options.title as string;
							}
						}

						if (this.getNodeParameter('binaryData', i, false) || nodeVersion > 2.1) {
							const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
							const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);

							let fileSize: number;
							let uploadData: Buffer | Readable;
							if (binaryData.id) {
								uploadData = await this.helpers.getBinaryStream(binaryData.id);
								const metadata = await this.helpers.getBinaryMetadata(binaryData.id);
								fileSize = metadata.fileSize;
							} else {
								uploadData = Buffer.from(binaryData.data, BINARY_ENCODING);
								fileSize = uploadData.length;
							}

							if (nodeVersion <= 2.1) {
								body.file = {
									value: uploadData,
									options: {
										filename: binaryData.fileName,
										contentType: binaryData.mimeType,
									},
								};

								responseData = await slackApiRequest.call(
									this,
									'POST',
									'/files.upload',
									{},
									qs,
									{ 'Content-Type': 'multipart/form-data' },
									{ formData: body },
								);
								responseData = responseData.file;
							} else {
								fileBody.file = {
									value: uploadData,
									options: {
										filename: binaryData.fileName,
										contentType: binaryData.mimeType,
									},
								};

								const uploadUrl = await slackApiRequest.call(
									this,
									'GET',
									'/files.getUploadURLExternal',
									{},
									{
										filename: options.fileName ? options.fileName : binaryData.fileName,
										length: fileSize,
									},
								);
								await slackApiRequest.call(
									this,
									'POST',
									uploadUrl.upload_url,
									{},
									qs,
									{ 'Content-Type': 'multipart/form-data' },
									{ formData: fileBody },
								);
								body.files = [
									{
										id: uploadUrl.file_id,
										title: options.title ? options.title : binaryData.fileName,
									},
								];
								responseData = await slackApiRequest.call(
									this,
									'POST',
									'/files.completeUploadExternal',
									body,
								);
								responseData = responseData.files;
							}
						} else {
							const fileContent = this.getNodeParameter('fileContent', i) as string;
							body.content = fileContent;
							responseData = await slackApiRequest.call(
								this,
								'POST',
								'/files.upload',
								body,
								qs,
								{ 'Content-Type': 'application/x-www-form-urlencoded' },
								{ form: body },
							);
							responseData = responseData.file;
						}
					}
					//https://api.slack.com/methods/files.list
					if (operation === 'getAll') {
						const returnAll = this.getNodeParameter('returnAll', i);
						const filters = this.getNodeParameter('filters', i);
						if (filters.channelId) {
							qs.channel = filters.channelId as string;
						}
						if (filters.showFilesHidden) {
							qs.show_files_hidden_by_limit = filters.showFilesHidden as boolean;
						}
						if (filters.tsFrom) {
							qs.ts_from = filters.tsFrom as string;
						}
						if (filters.tsTo) {
							qs.ts_to = filters.tsTo as string;
						}
						if (filters.types) {
							qs.types = (filters.types as string[]).join(',');
						}
						if (filters.userId) {
							qs.user = filters.userId as string;
						}
						if (returnAll) {
							responseData = await slackApiRequestAllItems.call(
								this,
								'files',
								'GET',
								'/files.list',
								{},
								qs,
							);
						} else {
							qs.count = this.getNodeParameter('limit', i);
							responseData = await slackApiRequest.call(this, 'GET', '/files.list', {}, qs);
							responseData = responseData.files;
						}
					}
					//https://api.slack.com/methods/files.info
					if (operation === 'get') {
						const fileId = this.getNodeParameter('fileId', i) as string;
						qs.file = fileId;
						responseData = await slackApiRequest.call(this, 'GET', '/files.info', {}, qs);
						responseData = responseData.file;
					}
				}
				if (resource === 'user') {
					//https://api.slack.com/methods/users.info
					if (operation === 'info') {
						qs.user = this.getNodeParameter('user', i, undefined, { extractValue: true }) as string;
						responseData = await slackApiRequest.call(this, 'GET', '/users.info', {}, qs);
						responseData = responseData.user;
					}
					//https://api.slack.com/methods/users.list
					if (operation === 'getAll') {
						const returnAll = this.getNodeParameter('returnAll', i);
						if (returnAll) {
							responseData = await slackApiRequestAllItems.call(
								this,
								'members',
								'GET',
								'/users.list',
								{},
								qs,
							);
						} else {
							qs.limit = this.getNodeParameter('limit', i);
							responseData = await slackApiRequest.call(this, 'GET', '/users.list', {}, qs);
							responseData = responseData.members;
						}
					}
					//https://api.slack.com/methods/users.getPresence
					if (operation === 'getPresence') {
						qs.user = this.getNodeParameter('user', i, undefined, { extractValue: true }) as string;
						responseData = await slackApiRequest.call(this, 'GET', '/users.getPresence', {}, qs);
					}
					if (operation === 'getProfile') {
						qs.user = this.getNodeParameter('user', i, undefined, { extractValue: true }) as string;
						responseData = await slackApiRequest.call(this, 'GET', '/users.profile.get', {}, qs);
						responseData = responseData.profile;
					}
					if (operation === 'updateProfile') {
						const options = this.getNodeParameter('options', i);
						const timezone = this.getTimezone();

						const body: IDataObject = {};
						let status;
						if (options.status) {
							status = ((options.status as IDataObject)?.set_status as IDataObject[])[0];
							if (status.status_expiration === undefined) {
								status.status_expiration = 0;
							} else {
								status.status_expiration = moment
									.tz(status.status_expiration as string, timezone)
									.unix();
							}
							Object.assign(body, status);
							delete options.status;
						}

						if (options.customFieldUi) {
							const customFields = (options.customFieldUi as IDataObject)
								.customFieldValues as IDataObject[];

							const fields: IDataObject = {};

							for (const customField of customFields) {
								fields[customField.id as string] = {
									value: customField.value,
									alt: customField.alt,
								};
							}

							options.fields = fields;
						}

						Object.assign(body, options);
						let requestBody: IDataObject = { profile: body };

						let userId;
						if (options.user) {
							userId = options.user;
							delete body.user;
							requestBody = { profile: body, user: userId };
						}

						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/users.profile.set',
							requestBody,
							qs,
						);

						responseData = responseData.profile;
					}
				}

				if (resource === 'userGroup') {
					//https://api.slack.com/methods/usergroups.create
					if (operation === 'create') {
						const name = this.getNodeParameter('name', i) as string;

						const options = this.getNodeParameter('Options', i);

						const body: IDataObject = {
							name,
						};

						Object.assign(body, options);

						responseData = await slackApiRequest.call(this, 'POST', '/usergroups.create', body, qs);

						responseData = responseData.usergroup;
					}
					//https://api.slack.com/methods/usergroups.enable
					if (operation === 'enable') {
						const userGroupId = this.getNodeParameter('userGroupId', i) as string;

						const options = this.getNodeParameter('option', i);

						const body: IDataObject = {
							usergroup: userGroupId,
						};

						Object.assign(body, options);

						responseData = await slackApiRequest.call(this, 'POST', '/usergroups.enable', body, qs);

						responseData = responseData.usergroup;
					}
					//https://api.slack.com/methods/usergroups.disable
					if (operation === 'disable') {
						const userGroupId = this.getNodeParameter('userGroupId', i) as string;

						const options = this.getNodeParameter('options', i);

						const body: IDataObject = {
							usergroup: userGroupId,
						};

						Object.assign(body, options);

						responseData = await slackApiRequest.call(
							this,
							'POST',
							'/usergroups.disable',
							body,
							qs,
						);

						responseData = responseData.usergroup;
					}

					//https://api.slack.com/methods/usergroups.list
					if (operation === 'getAll') {
						const returnAll = this.getNodeParameter('returnAll', i);

						const options = this.getNodeParameter('options', i);

						Object.assign(qs, options);

						responseData = await slackApiRequest.call(this, 'GET', '/usergroups.list', {}, qs);

						responseData = responseData.usergroups;

						if (!returnAll) {
							const limit = this.getNodeParameter('limit', i);

							responseData = responseData.slice(0, limit);
						}
					}

					//https://api.slack.com/methods/usergroups.update
					if (operation === 'update') {
						const userGroupId = this.getNodeParameter('userGroupId', i) as string;

						const updateFields = this.getNodeParameter('updateFields', i);

						const body: IDataObject = {
							usergroup: userGroupId,
						};

						Object.assign(body, updateFields);

						responseData = await slackApiRequest.call(this, 'POST', '/usergroups.update', body, qs);

						responseData = responseData.usergroup;
					}
				}
				const executionData = this.helpers.constructExecutionMetaData(
					this.helpers.returnJsonArray(responseData as IDataObject[]),
					{ itemData: { item: i } },
				);
				returnData.push(...executionData);
			} catch (error) {
				if (this.continueOnFail()) {
					returnData.push({ json: { error: (error as JsonObject).message } });
					continue;
				}
				throw error;
			}
		}
		return [returnData];
	}
}
