import type {
	IDataObject,
	IExecuteFunctions,
	IHttpRequestMethods,
	INodeExecutionData,
	INodeProperties,
	JsonObject,
} from 'n8n-workflow';
import { NodeApiError, updateDisplayOptions } from 'n8n-workflow';

import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from '../../transport';

const searchOptions: INodeProperties = {
	displayName: 'Options',
	name: 'options',
	type: 'collection',
	default: {},
	placeholder: 'Add option',
	options: [
		{
			displayName: 'View Name or ID',
			name: 'viewId',
			type: 'resourceLocator',
			default: { mode: 'list', value: '' },
			description:
				'The view to operate on. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
			typeOptions: {
				loadOptionsDependsOn: ['table.value'],
			},
			modes: [
				{
					displayName: 'From List',
					name: 'list',
					type: 'list',
					typeOptions: {
						searchListMethod: 'getViews',
						searchable: true,
					},
				},
				{
					displayName: 'ID',
					name: 'id',
					type: 'string',
					placeholder: 'vw5t20qcex4d5zpk',
				},
			],
		},
		{
			// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
			displayName: 'Fields',
			name: 'fields',
			type: 'multiOptions',
			// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
			description: 'The select fields of the returned rows',
			typeOptions: {
				loadOptionsMethod: 'getFields',
				loadOptionsDependsOn: ['base.value', 'table.value'],
			},
			default: [],
			placeholder: 'Add field',
		},
		{
			displayName: 'Sort',
			name: 'sort',
			placeholder: 'Add Sort Rule',
			description: 'The sorting rules for the returned rows',
			type: 'fixedCollection',
			typeOptions: {
				multipleValues: true,
			},
			default: {},
			options: [
				{
					name: 'property',
					displayName: 'Property',
					values: [
						{
							displayName: 'Field Name or ID',
							name: 'field',
							type: 'resourceLocator',
							description: 'Name of the field to sort on',
							default: { mode: 'list', value: '' },
							typeOptions: {
								loadOptionsDependsOn: ['table.value'],
							},
							modes: [
								{
									displayName: 'From List',
									name: 'list',
									type: 'list',
									typeOptions: {
										searchListMethod: 'getFields',
										searchable: true,
									},
								},
								{
									displayName: 'ID',
									name: 'id',
									type: 'string',
									placeholder: 'c9xxmrtn2wfe39l',
								},
							],
						},
						{
							displayName: 'Direction',
							name: 'direction',
							type: 'options',
							options: [
								{
									name: 'ASC',
									value: 'asc',
									description: 'Sort in ascending order (small -> large)',
								},
								{
									name: 'DESC',
									value: 'desc',
									description: 'Sort in descending order (large -> small)',
								},
							],
							default: 'asc',
							description: 'The sort direction',
						},
					],
				},
			],
		},
		{
			displayName: 'Filter By Formula',
			name: 'where',
			type: 'string',
			default: '',
			placeholder: '(name,like,example%)~or(name,eq,test)',
			description: 'A formula used to filter rows',
		},
		{
			displayName: 'Page',
			name: 'page',
			type: 'number',
			default: '',
			description: 'The number of pages to skip from the beginning',
		},
		{
			displayName: 'Shuffle',
			name: 'shuffle',
			type: 'boolean',
			default: false,
			description: 'Whether to shuffle the results',
		},
	],
};

export const description: INodeProperties[] = updateDisplayOptions(
	{
		show: {
			operation: ['search'],
		},
	},
	[
		{
			displayName: 'Return All',
			name: 'returnAll',
			type: 'boolean',
			default: false,
			description: 'Whether to return all results or only up to a given limit',
		},
		{
			displayName: 'Limit',
			name: 'limit',
			type: 'number',
			displayOptions: {
				show: {
					returnAll: [false],
				},
			},
			typeOptions: {
				minValue: 1,
				maxValue: 100,
			},
			default: 50,
			description: 'Max number of results to return',
		},
		{
			displayName: 'Download Attachments',
			name: 'downloadAttachments',
			type: 'boolean',
			default: false,
			description: "Whether the attachment fields defined in 'Download Fields' will be downloaded",
		},
		{
			displayName: 'Download Field Names or IDs',
			name: 'downloadFieldNames',
			type: 'multiOptions',
			typeOptions: {
				loadOptionsMethod: 'getDownloadFields',
			},
			required: true,
			displayOptions: {
				show: {
					downloadAttachments: [true],
				},
			},
			default: [],
			description:
				'Names of the fields of type \'attachment\' that should be downloaded. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
		},
		searchOptions,
	],
);

export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
	const items = this.getInputData();
	const returnData: INodeExecutionData[] = [];
	let responseData;

	let requestMethod: IHttpRequestMethods;
	let endPoint = '';
	let qs: IDataObject = {};

	const baseId = this.getNodeParameter('projectId', 0, undefined, {
		extractValue: true,
	}) as string;

	for (let i = 0; i < items.length; i++) {
		try {
			requestMethod = 'GET';
			const table = this.getNodeParameter('table', i, undefined, {
				extractValue: true,
			}) as string;
			const returnAll = this.getNodeParameter('returnAll', i);
			qs = this.getNodeParameter('options', i, {});

			endPoint = `/api/v3/data/${baseId}/${table}/records`;

			if (qs.sort) {
				const properties = (qs.sort as IDataObject).property as Array<{
					field: {
						value: string;
					};
					direction: string;
				}>;
				qs.sort = JSON.stringify(
					properties.map((prop) => {
						return {
							field: prop.field.value,
							direction: prop.direction,
						};
					}),
				);
			}
			if (qs.fields) {
				qs.fields = (qs.fields as IDataObject[]).join(',');
			}
			if (qs.viewId && (qs.viewId as IDataObject).value) {
				qs.viewId = (qs.viewId as IDataObject).value;
			}
			if (qs.shuffle) {
				qs.shuffle = 1;
			}

			if (returnAll) {
				responseData = await apiRequestAllItems.call(this, requestMethod, endPoint, {}, qs);
			} else {
				qs.limit = this.getNodeParameter('limit', i);
				responseData = await apiRequest.call(this, requestMethod, endPoint, {}, qs);
				responseData = responseData.records;
			}

			const downloadAttachments = this.getNodeParameter('downloadAttachments', i) as boolean;

			if (downloadAttachments) {
				const downloadFieldNames = this.getNodeParameter('downloadFieldNames', i) as string[];
				const response = await downloadRecordAttachments.call(
					this,
					responseData as IDataObject[],
					downloadFieldNames,
					[{ item: i }],
				);
				returnData.push.apply(returnData, response);
			} else {
				const executionData = this.helpers.constructExecutionMetaData(
					this.helpers.returnJsonArray(responseData as IDataObject[]),
					{ itemData: { item: i } },
				);
				returnData.push.apply(returnData, executionData);
			}
		} catch (error) {
			if (this.continueOnFail()) {
				const executionData = this.helpers.constructExecutionMetaData(
					this.helpers.returnJsonArray({ error: error.toString() }),
					{ itemData: { item: i } },
				);
				returnData.push.apply(returnData, executionData);
			} else {
				throw new NodeApiError(this.getNode(), error as JsonObject);
			}
		}
	}

	return [returnData];
}
