import type {
	IDataObject,
	IExecuteFunctions,
	IHookFunctions,
	ILoadOptionsFunctions,
	INodeExecutionData,
	IWebhookFunctions,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';

import type {
	OnfleetAdmins,
	OnfleetCloneOverrideTaskOptions,
	OnfleetCloneTask,
	OnfleetCloneTaskOptions,
	OnfleetDestination,
	OnfleetHubs,
	OnfleetListTaskFilters,
	OnfleetRecipient,
	OnfleetTask,
	OnfleetTaskComplete,
	OnfleetTaskUpdate,
	OnfleetTeamAutoDispatch,
	OnfleetTeams,
	OnfleetWebhook,
	OnfleetWorker,
	OnfleetWorkerEstimates,
	OnfleetWorkerFilter,
	OnfleetWorkerSchedule,
	OnfleetWorkerScheduleEntry,
} from './interfaces';

import { onfleetApiRequest, onfleetApiRequestAllItems } from './GenericFunctions';

import moment from 'moment-timezone';

const formatAddress = (
	unparsed: boolean,
	address: string | undefined,
	addressNumber: string | undefined,
	addressStreet: string | undefined,
	addressCity: string | undefined,
	addressCountry: string | undefined,
	additionalFields: IDataObject,
): OnfleetDestination => {
	let destination: OnfleetDestination;
	if (unparsed) {
		destination = { address: { unparsed: address } };
	} else {
		destination = {
			address: {
				number: addressNumber,
				street: addressStreet,
				city: addressCity,
				country: addressCountry,
			},
		};
	}

	// Adding destination extra fields
	if (additionalFields.addressName) {
		destination.address.name = additionalFields.addressName as string;
	}
	if (additionalFields.addressApartment) {
		destination.address.apartment = additionalFields.addressApartment as string;
	}
	if (additionalFields.addressState) {
		destination.address.state = additionalFields.addressState as string;
	}
	if (additionalFields.addressPostalCode) {
		destination.address.postalCode = additionalFields.addressPostalCode as string;
	}
	if (additionalFields.addressNotes) {
		destination.notes = additionalFields.addressNotes as string;
	}
	return destination;
};

export class Onfleet {
	/**
	 * Returns a valid formatted destination object
	 * @param unparsed Whether the address is parsed or not
	 * @param address Destination address
	 * @param addressNumber Destination number
	 * @param addressStreet Destination street
	 * @param addressCity Destination city
	 * @param addressCountry Destination country
	 * @param additionalFields Destination additional fields
	 */

	/**
	 * Gets the properties of a destination according to the operation chose
	 * @param item Current execution data
	 * @param operation Current destination operation
	 * @param shared Whether the collection is in other resource or not
	 */
	static getDestinationFields(
		this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
		item: number,
		operation: string,
		shared: { parent: string } | boolean = false,
	): OnfleetDestination | OnfleetDestination[] | null {
		if (['create', 'update'].includes(operation)) {
			/* -------------------------------------------------------------------------- */
			/*               Get fields for create and update a destination               */
			/* -------------------------------------------------------------------------- */
			if (shared !== false) {
				let destination;
				if (typeof shared === 'boolean' && shared) {
					const { destinationProperties = {} } = this.getNodeParameter(
						'destination',
						item,
					) as IDataObject;
					destination = destinationProperties;
				} else if (typeof shared !== 'boolean') {
					const { destination: destinationCollection = {} } = this.getNodeParameter(
						shared.parent,
						item,
					) as IDataObject;
					destination = (destinationCollection as IDataObject).destinationProperties;
				}

				if (!destination || Object.keys(destination as IDataObject).length === 0) {
					return [];
				}

				const {
					unparsed,
					address,
					addressNumber,
					addressStreet,
					addressCity,
					addressCountry,
					...additionalFields
				} = destination as IDataObject;
				return formatAddress(
					unparsed as boolean,
					address as string,
					addressNumber as string,
					addressStreet as string,
					addressCity as string,
					addressCountry as string,
					additionalFields as IDataObject,
				);
			} else {
				let address, addressNumber, addressStreet, addressCity, addressCountry;
				const unparsed = this.getNodeParameter('unparsed', item) as boolean;
				if (unparsed) {
					address = this.getNodeParameter('address', item) as string;
				} else {
					addressNumber = this.getNodeParameter('addressNumber', item) as string;
					addressStreet = this.getNodeParameter('addressStreet', item) as string;
					addressCity = this.getNodeParameter('addressCity', item) as string;
					addressCountry = this.getNodeParameter('addressCountry', item) as string;
				}
				const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject;

				return formatAddress(
					unparsed,
					address,
					addressNumber,
					addressStreet,
					addressCity,
					addressCountry,
					additionalFields,
				);
			}
		}
		return null;
	}

	/**
	 * Gets the properties of an administrator according to the operation chose
	 * @param item Current execution data
	 * @param operation Current administrator operation
	 */
	static getAdminFields(
		this: IExecuteFunctions,
		item: number,
		operation: string,
	): OnfleetAdmins | null {
		if (operation === 'create') {
			/* -------------------------------------------------------------------------- */
			/*                         Get fields for create admin                        */
			/* -------------------------------------------------------------------------- */
			const name = this.getNodeParameter('name', item) as string;
			const email = this.getNodeParameter('email', item) as string;
			const additionalFields = this.getNodeParameter('additionalFields', item);

			const adminData: OnfleetAdmins = { name, email };
			// Adding additional fields
			Object.assign(adminData, additionalFields);

			return adminData;
		} else if (operation === 'update') {
			/* -------------------------------------------------------------------------- */
			/*                         Get fields for update admin                        */
			/* -------------------------------------------------------------------------- */
			const updateFields = this.getNodeParameter('updateFields', item);
			const adminData: OnfleetAdmins = {};
			if (!Object.keys(updateFields).length) {
				throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated');
			}
			// Adding additional fields
			Object.assign(adminData, updateFields);
			return adminData;
		}
		return null;
	}

	/**
	 * Gets the properties of a hub according to the operation chose
	 * @param item Current execution data
	 * @param operation Current hub operation
	 */
	static getHubFields(
		this: IExecuteFunctions,
		item: number,
		operation: string,
	): OnfleetHubs | null {
		if (operation === 'create') {
			/* -------------------------------------------------------------------------- */
			/*                          Get fields for create hub                         */
			/* -------------------------------------------------------------------------- */
			const destination = Onfleet.getDestinationFields.call(
				this,
				item,
				operation,
				true,
			) as OnfleetDestination;
			const name = this.getNodeParameter('name', item) as string;
			const additionalFields = this.getNodeParameter('additionalFields', item);

			const hubData: OnfleetHubs = { name, ...destination };

			// Adding additional fields
			Object.assign(hubData, additionalFields);

			return hubData;
		} else if (operation === 'update') {
			/* -------------------------------------------------------------------------- */
			/*                          Get fields for update hub                         */
			/* -------------------------------------------------------------------------- */
			const destination = Onfleet.getDestinationFields.call(this, item, operation, {
				parent: 'updateFields',
			}) as OnfleetDestination;
			const hubData: OnfleetHubs = { ...destination };

			// Adding additional fields
			const updateFields = this.getNodeParameter('updateFields', item);

			if (!Object.keys(updateFields).length) {
				throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated');
			}

			Object.assign(hubData, updateFields);
			return hubData;
		}
		return null;
	}

	/**
	 * Gets the properties of a worker according to the operation chose
	 * @param item Current execution data
	 * @param operation Current worker operation
	 */
	static getWorkerFields(
		this: IExecuteFunctions,
		item: number,
		operation: string,
	): OnfleetWorker | OnfleetWorkerFilter | OnfleetWorkerSchedule | null {
		if (operation === 'create') {
			/* -------------------------------------------------------------------------- */
			/*                        Get fields for create worker                        */
			/* -------------------------------------------------------------------------- */
			const name = this.getNodeParameter('name', item) as string;
			const phone = this.getNodeParameter('phone', item) as string;
			const teams = this.getNodeParameter('teams', item) as string[];
			const workerData: OnfleetWorker = { name, phone, teams };

			// Adding additional fields
			const additionalFields = this.getNodeParameter('additionalFields', item);

			if (additionalFields.vehicle) {
				const { vehicleProperties } = additionalFields.vehicle as IDataObject;
				Object.assign(workerData, { vehicle: vehicleProperties });
				delete additionalFields.vehicle;
			}

			Object.assign(workerData, additionalFields);

			return workerData;
		} else if (operation === 'update') {
			/* -------------------------------------------------------------------------- */
			/*                        Get fields for update worker                        */
			/* -------------------------------------------------------------------------- */

			const workerData: OnfleetWorker = {};

			// Adding additional fields
			const updateFields = this.getNodeParameter('updateFields', item);

			if (!Object.keys(updateFields).length) {
				throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated');
			}

			Object.assign(workerData, updateFields);
			return workerData;
		} else if (operation === 'get') {
			const options = this.getNodeParameter('options', item, {});
			const workerFilter: OnfleetWorkerFilter = {};
			if (options.filter) {
				options.filter = (options.filter as string[]).join(',');
			}
			if (typeof options.analytics === 'boolean') {
				options.analytics = options.analytics ? 'true' : 'false';
			}
			Object.assign(workerFilter, options);
			return workerFilter;
		} else if (operation === 'getAll') {
			/* -------------------------------------------------------------------------- */
			/*                    Get fields for get and getAll workers                   */
			/* -------------------------------------------------------------------------- */

			const options = this.getNodeParameter('options', item, {});
			const filters = this.getNodeParameter('filters', item, {});
			const workerFilter: OnfleetWorkerFilter = {};

			if (filters.states) {
				filters.states = (filters.states as number[]).join(',');
			}
			if (filters.teams) {
				filters.teams = (filters.teams as string[]).join(',');
			}
			if (filters.phones) {
				filters.phones = (filters.phones as string[]).join(',');
			}
			if (options.filter) {
				options.filter = (options.filter as string[]).join(',');
			}

			Object.assign(workerFilter, options);
			Object.assign(workerFilter, filters);
			return workerFilter;
		} else if (operation === 'setSchedule') {
			/* -------------------------------------------------------------------------- */
			/*                            Set a worker schedule                           */
			/* -------------------------------------------------------------------------- */
			const { scheduleProperties } = this.getNodeParameter('schedule', item) as IDataObject;
			const entries = ((scheduleProperties as IDataObject[]) || []).map((entry) => {
				const { timezone, date, shifts } = entry;
				const { shiftsProperties } = shifts as IDataObject;
				return {
					timezone: timezone as string,
					date: moment(date as Date).format('YYYY-MM-DD'),
					shifts: (shiftsProperties as IDataObject[]).map(({ start, end }) => [
						new Date(start as Date).getTime(),
						new Date(end as Date).getTime(),
					]),
				} as OnfleetWorkerScheduleEntry;
			});
			return { entries } as OnfleetWorkerSchedule;
		}
		return null;
	}

	/**
	 * Gets the properties of a webhooks according to the operation chose
	 * @param item Current execution data
	 * @param operation Current webhooks operation
	 */
	static getWebhookFields(
		this: IExecuteFunctions,
		item: number,
		operation: string,
	): OnfleetWebhook | null {
		if (operation === 'create') {
			/* -------------------------------------------------------------------------- */
			/*                        Get fields for create webhook                       */
			/* -------------------------------------------------------------------------- */
			const url = this.getNodeParameter('url', item) as string;
			const name = this.getNodeParameter('name', item) as string;
			const trigger = this.getNodeParameter('trigger', item) as number;
			const additionalFields = this.getNodeParameter('additionalFields', item);

			const webhookData: OnfleetWebhook = { url, name, trigger };
			// Adding additional fields
			Object.assign(webhookData, additionalFields);

			return webhookData;
		}
		return null;
	}

	/**
	 * Returns a valid formatted recipient object
	 * @param name Recipient name
	 * @param phone Recipient phone
	 * @param additionalFields Recipient additional fields
	 */
	static formatRecipient(
		name: string,
		phone: string,
		additionalFields: IDataObject,
		options: IDataObject = {},
	): OnfleetRecipient {
		const recipient: OnfleetRecipient = { name, phone };

		// Adding recipient extra fields
		if (additionalFields.recipientNotes) {
			recipient.notes = additionalFields.recipientNotes as string;
		}
		if (additionalFields.recipientSkipSMSNotifications) {
			recipient.skipSMSNotifications = additionalFields.recipientSkipSMSNotifications as boolean;
		}
		if ('recipientSkipPhoneNumberValidation' in options) {
			recipient.skipPhoneNumberValidation =
				(options.recipientSkipPhoneNumberValidation as boolean) || false;
		}

		return recipient;
	}

	/**
	 * Gets the properties of a recipient according to the operation chose
	 * @param item Current execution data
	 * @param operation Current recipient operation
	 * @param shared Whether the collection is in other resource or not
	 */
	static getRecipientFields(
		this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
		item: number,
		operation: string,
		shared = false,
	): OnfleetRecipient | OnfleetRecipient[] | null {
		if (operation === 'create') {
			/* -------------------------------------------------------------------------- */
			/*                       Get fields to create recipient                       */
			/* -------------------------------------------------------------------------- */
			if (shared) {
				const { recipient: recipientData = {} } = this.getNodeParameter(
					'additionalFields',
					item,
					{},
				) as IDataObject;
				const options = this.getNodeParameter('options', item, {}) as IDataObject;
				const { recipientProperties: recipient = {} } = recipientData as IDataObject;
				if (!recipient || Object.keys(recipient).length === 0) {
					return null;
				}
				const {
					recipientName: name,
					recipientPhone: phone,
					...additionalFields
				} = recipient as IDataObject;
				return Onfleet.formatRecipient(
					name as string,
					phone as string,
					additionalFields as IDataObject,
					options,
				);
			} else {
				const name = this.getNodeParameter('recipientName', item) as string;
				const phone = this.getNodeParameter('recipientPhone', item) as string;
				const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject;
				const options = this.getNodeParameter('options', item) as IDataObject;
				return Onfleet.formatRecipient(name, phone, additionalFields, options);
			}
		} else if (operation === 'update') {
			/* -------------------------------------------------------------------------- */
			/*                       Get fields to update recipient                       */
			/* -------------------------------------------------------------------------- */
			const {
				recipientName: name = '',
				recipientPhone: phone = '',
				...additionalFields
			} = this.getNodeParameter('updateFields', item) as IDataObject;

			const recipientData: OnfleetRecipient = {};

			// Adding additional fields
			if (name) {
				recipientData.name = name as string;
			}
			if (phone) {
				recipientData.phone = phone as string;
			}
			Object.assign(recipientData, additionalFields);
			return recipientData;
		}
		return null;
	}

	/**
	 * Gets the properties of a task according to the operation chose
	 * @param item Current execution data
	 * @param operation Current task operation
	 */
	static getTaskFields(
		this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
		item: number,
		operation: string,
	):
		| OnfleetListTaskFilters
		| OnfleetTask
		| OnfleetCloneTask
		| OnfleetTaskComplete
		| OnfleetTaskUpdate
		| null {
		if (operation === 'create') {
			/* -------------------------------------------------------------------------- */
			/*                         Get fields to create a task                        */
			/* -------------------------------------------------------------------------- */
			const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject;
			const destination = Onfleet.getDestinationFields.call(
				this,
				item,
				operation,
				true,
			) as OnfleetDestination;

			// Adding recipients information
			const recipient = Onfleet.getRecipientFields.call(
				this,
				item,
				operation,
				true,
			) as OnfleetRecipient;

			const taskData: OnfleetTask = { destination, recipients: [recipient] };
			const { completeAfter = null, completeBefore = null, ...extraFields } = additionalFields;
			if (completeAfter) taskData.completeAfter = new Date(completeAfter as Date).getTime();
			if (completeBefore) taskData.completeBefore = new Date(completeBefore as Date).getTime();

			Object.assign(taskData, extraFields);
			return taskData;
		} else if (operation === 'update') {
			/* -------------------------------------------------------------------------- */
			/*                          Get fields to update task                         */
			/* -------------------------------------------------------------------------- */
			const updateFields = this.getNodeParameter('updateFields', item) as IDataObject;
			const taskData: OnfleetTaskUpdate = {};

			if (!Object.keys(updateFields).length) {
				throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated');
			}

			const { completeAfter = null, completeBefore = null, ...extraFields } = updateFields;
			if (completeAfter) taskData.completeAfter = new Date(completeAfter as Date).getTime();
			if (completeBefore) taskData.completeBefore = new Date(completeBefore as Date).getTime();
			Object.assign(taskData, extraFields);
			return taskData;
		} else if (operation === 'clone') {
			/* -------------------------------------------------------------------------- */
			/*                          Get fields to clone task                          */
			/* -------------------------------------------------------------------------- */
			const overrideFields = this.getNodeParameter('overrideFields', item) as IDataObject;

			const options: OnfleetCloneTaskOptions = {};
			if (overrideFields.includeMetadata) {
				options.includeMetadata = overrideFields.includeMetadata as boolean;
			}

			if (overrideFields.includeBarcodes) {
				options.includeBarcodes = overrideFields.includeBarcodes as boolean;
			}

			if (overrideFields.includeDependencies) {
				options.includeDependencies = overrideFields.includeDependencies as boolean;
			}

			// Adding overrides data
			const { notes, pickupTask, serviceTime, completeAfter, completeBefore } = overrideFields;
			const overridesData = {} as OnfleetCloneOverrideTaskOptions;

			if (notes) overridesData.notes = notes as string;
			if (typeof pickupTask !== 'undefined') overridesData.pickupTask = pickupTask as boolean;
			if (serviceTime) overridesData.serviceTime = serviceTime as number;
			if (completeAfter) overridesData.completeAfter = new Date(completeAfter as Date).getTime();
			if (completeBefore) overridesData.completeBefore = new Date(completeBefore as Date).getTime();

			if (overridesData && Object.keys(overridesData).length > 0) {
				options.overrides = overridesData;
			}

			return { options } as OnfleetCloneTask;
		} else if (operation === 'getAll') {
			/* -------------------------------------------------------------------------- */
			/*                          Get fields to list tasks                          */
			/* -------------------------------------------------------------------------- */
			const filters = this.getNodeParameter('filters', item) as IDataObject;
			const listTaskData: OnfleetListTaskFilters = {};

			const allStates = '0,1,2,3';

			const twoWeeksInMilisecods = () => 604800 * 1000;

			// Adding extra fields to search tasks
			if (filters.from) {
				listTaskData.from = new Date(filters.from as Date).getTime();
			} else {
				listTaskData.from = new Date().getTime() - twoWeeksInMilisecods();
			}
			if (filters.to) {
				listTaskData.to = new Date(filters.to as Date).getTime();
			}
			if (filters.state) {
				listTaskData.state = (filters.state as number[]).join(',');
				if (listTaskData.state.includes('all')) {
					listTaskData.state = allStates;
				}
			}

			return listTaskData;
		} else if (operation === 'complete') {
			/* -------------------------------------------------------------------------- */
			/*                        Get fields to complete a task                       */
			/* -------------------------------------------------------------------------- */
			const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject;
			const success = this.getNodeParameter('success', item) as boolean;
			const taskData: OnfleetTaskComplete = { completionDetails: { success } };
			if (additionalFields.notes) {
				taskData.completionDetails.notes = additionalFields.notes as string;
			}
			return taskData;
		}
		return null;
	}

	/**
	 * Gets the properties of a team according to the operation chose
	 * @param item Current execution data
	 * @param operation Current team operation
	 */
	static getTeamFields(
		this: IExecuteFunctions,
		item: number,
		operation: string,
	): OnfleetTeams | OnfleetWorkerEstimates | OnfleetTeamAutoDispatch | null {
		if (operation === 'create') {
			/* -------------------------------------------------------------------------- */
			/*                         Get fields to create a team                        */
			/* -------------------------------------------------------------------------- */
			const name = this.getNodeParameter('name', item) as string;
			const workers = this.getNodeParameter('workers', item) as string[];
			const managers = this.getNodeParameter('managers', item) as string[];
			const additionalFields = this.getNodeParameter('additionalFields', item);

			const teamData: OnfleetTeams = { name, workers, managers };
			// Adding additional fields
			Object.assign(teamData, additionalFields);

			return teamData;
		} else if (operation === 'update') {
			/* -------------------------------------------------------------------------- */
			/*                         Get fields to update a team                        */
			/* -------------------------------------------------------------------------- */
			const teamData: OnfleetTeams = {};
			// Adding additional fields
			const updateFields = this.getNodeParameter('updateFields', item);

			if (!Object.keys(updateFields).length) {
				throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated');
			}

			Object.assign(teamData, updateFields);
			return teamData;
		} else if (operation === 'getTimeEstimates') {
			/* -------------------------------------------------------------------------- */
			/*      Get driver time estimates for tasks that haven't been created yet     */
			/* -------------------------------------------------------------------------- */
			const {
				dropOff = {},
				pickUp = {},
				...additionalFields
			} = this.getNodeParameter('filters', item);
			const { dropOffProperties = {} } = dropOff as IDataObject;
			const { pickUpProperties = {} } = pickUp as IDataObject;
			const hasPickUp = pickUp && Object.keys(pickUpProperties as IDataObject).length > 0;
			const hasDropOff =
				dropOffProperties && Object.keys(dropOffProperties as IDataObject).length > 0;

			if (!hasPickUp && !hasDropOff) {
				throw new NodeOperationError(
					this.getNode(),
					'At least 1 of Drop-Off location or Pick-Up location must be selected',
				);
			}

			const workerTimeEstimates = {} as OnfleetWorkerEstimates;
			if (hasPickUp) {
				const {
					pickupLongitude: longitude,
					pickupLatitude: latitude,
					pickupTime,
				} = pickUpProperties as IDataObject;
				workerTimeEstimates.pickupLocation = `${longitude},${latitude}`;
				if (pickupTime) {
					workerTimeEstimates.pickupTime = moment(new Date(pickupTime as Date))
						.local()
						.unix();
				}
			}
			if (hasDropOff) {
				const { dropOffLongitude: longitude, dropOffLatitude: latitude } =
					dropOffProperties as IDataObject;
				workerTimeEstimates.dropoffLocation = `${longitude},${latitude}`;
			}

			Object.assign(workerTimeEstimates, additionalFields);
			return workerTimeEstimates;
		} else if (operation === 'autoDispatch') {
			/* -------------------------------------------------------------------------- */
			/*                  Dynamically dispatching tasks on the fly                  */
			/* -------------------------------------------------------------------------- */
			const teamAutoDispatch = {} as OnfleetTeamAutoDispatch;
			const {
				scheduleTimeWindow = {},
				taskTimeWindow = {},
				endingRoute = {},
				...additionalFields
			} = this.getNodeParameter('additionalFields', item);
			const { endingRouteProperties = {} } = endingRoute as IDataObject;
			const { scheduleTimeWindowProperties = {} } = scheduleTimeWindow as IDataObject;
			const { taskTimeWindowProperties = {} } = taskTimeWindow as IDataObject;

			if (
				scheduleTimeWindowProperties &&
				Object.keys(scheduleTimeWindowProperties as IDataObject).length > 0
			) {
				const { startTime, endTime } = scheduleTimeWindowProperties as IDataObject;
				teamAutoDispatch.scheduleTimeWindow = [
					moment(new Date(startTime as Date))
						.local()
						.unix(),
					moment(new Date(endTime as Date))
						.local()
						.unix(),
				];
			}

			if (endingRouteProperties && Object.keys(endingRouteProperties as IDataObject).length > 0) {
				const { routeEnd, hub } = endingRouteProperties as IDataObject;
				teamAutoDispatch.routeEnd = {
					anywhere: null,
					hub: `hub://${hub}`,
					team_hub: 'teams://DEFAULT',
					worker_routing_address: 'workers://ROUTING_ADDRESS',
				}[routeEnd as string] as string;
			}

			if (
				taskTimeWindowProperties &&
				Object.keys(taskTimeWindowProperties as IDataObject).length > 0
			) {
				const { startTime, endTime } = taskTimeWindowProperties as IDataObject;
				teamAutoDispatch.taskTimeWindow = [
					moment(new Date(startTime as Date))
						.local()
						.unix(),
					moment(new Date(endTime as Date))
						.local()
						.unix(),
				];
			}

			Object.assign(teamAutoDispatch, additionalFields);
			return teamAutoDispatch;
		}
		return null;
	}

	/**
	 * Execute the task operations
	 * @param resource Resource to be executed (Task)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeTaskOperations(
		this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		if (operation === 'create' && Object.keys(items).length > 1) {
			/* -------------------------------------------------------------------------- */
			/*                       Create multiple tasks by batch                       */
			/* -------------------------------------------------------------------------- */
			const path = `${resource}/batch`;
			const tasksData = {
				tasks: items.map((_item, index) => Onfleet.getTaskFields.call(this, index, operation)),
			};
			//@ts-ignore
			const { tasks: tasksCreated } = await onfleetApiRequest.call(this, 'POST', path, tasksData);
			return tasksCreated;
		}

		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'create') {
					/* -------------------------------------------------------------------------- */
					/*                              Create a new task                             */
					/* -------------------------------------------------------------------------- */
					const taskData = Onfleet.getTaskFields.call(this, index, operation);
					if (!taskData) {
						continue;
					}
					responseData.push(await onfleetApiRequest.call(this, 'POST', resource, taskData));
				} else if (operation === 'get') {
					/* -------------------------------------------------------------------------- */
					/*                              Get a single task                             */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const shortId = String(id).length <= 8;
					const path = `${resource}${shortId ? '/shortId' : ''}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'GET', path));
				} else if (operation === 'clone') {
					/* -------------------------------------------------------------------------- */
					/*                                Clone a task                                */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;

					const taskData = Onfleet.getTaskFields.call(this, index, operation) as any;
					if (!taskData) {
						continue;
					}
					const path = `${resource}/${id}/clone`;
					responseData.push(await onfleetApiRequest.call(this, 'POST', path, taskData));
				} else if (operation === 'delete') {
					/* -------------------------------------------------------------------------- */
					/*                            Delete a single task                            */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					await onfleetApiRequest.call(this, 'DELETE', path);
					responseData.push({ success: true });
				} else if (operation === 'getAll') {
					/* -------------------------------------------------------------------------- */
					/*                                Get all tasks                               */
					/* -------------------------------------------------------------------------- */
					const taskData = Onfleet.getTaskFields.call(this, 0, operation) as IDataObject;
					if (!taskData) return [];
					const returnAll = this.getNodeParameter('returnAll', 0, false);
					const path = `${resource}/all`;
					let tasks;
					if (returnAll === true) {
						tasks = await onfleetApiRequestAllItems.call(this, 'tasks', 'GET', path, {}, taskData);
					} else {
						const limit = this.getNodeParameter('limit', 0);
						tasks = await onfleetApiRequest.call(this, 'GET', path, {}, taskData);
						tasks = tasks.tasks;
						tasks = tasks.splice(0, limit);
					}
					responseData.push(...tasks);
				} else if (operation === 'complete') {
					/* -------------------------------------------------------------------------- */
					/*                            Force complete a task                           */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const taskData = Onfleet.getTaskFields.call(this, index, operation);
					if (!taskData) {
						continue;
					}
					const path = `${resource}/${id}/complete`;
					await onfleetApiRequest.call(this, 'POST', path, taskData);
					responseData.push({ success: true });
				} else if (operation === 'update') {
					/* -------------------------------------------------------------------------- */
					/*                                Update a task                               */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', 0) as string;
					const path = `${resource}/${id}`;
					const taskData = Onfleet.getTaskFields.call(this, index, operation);
					if (!taskData) {
						continue;
					}
					responseData.push(await onfleetApiRequest.call(this, 'PUT', path, taskData));
				}
			} catch (error) {
				//@ts-ignore
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}
		return responseData;
	}

	/**
	 * Execute the destination operations
	 * @param resource Resource to be executed (Destination)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeDestinationOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'create') {
					/* -------------------------------------------------------------------------- */
					/*                             Create destination                             */
					/* -------------------------------------------------------------------------- */
					const destinationData = Onfleet.getDestinationFields.call(this, index, operation);
					if (!destinationData) {
						continue;
					}
					responseData.push(await onfleetApiRequest.call(this, 'POST', resource, destinationData));
				} else if (operation === 'get') {
					/* -------------------------------------------------------------------------- */
					/*                           Get single destination                           */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'GET', path));
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}

		return responseData;
	}

	/**
	 * Execute the organization operations
	 * @param resource Resource to be executed (Organization)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeOrganizationOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'get') {
					/* -------------------------------------------------------------------------- */
					/*                          Get organization details                          */
					/* -------------------------------------------------------------------------- */
					const path = 'organization';
					responseData.push(await onfleetApiRequest.call(this, 'GET', path));
				} else if (operation === 'getDelegatee') {
					/* -------------------------------------------------------------------------- */
					/*                         Get organization delegatee                         */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'GET', path));
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}

		return responseData;
	}

	/**
	 * Execute the recipient operations
	 * @param resource Resource to be executed (Recipient)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeRecipientOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'create') {
					/* -------------------------------------------------------------------------- */
					/*                           Create a new recipient                           */
					/* -------------------------------------------------------------------------- */
					const recipientData = Onfleet.getRecipientFields.call(this, index, operation);
					if (!recipientData) {
						continue;
					}
					responseData.push(await onfleetApiRequest.call(this, 'POST', resource, recipientData));
				} else if (operation === 'update') {
					/* -------------------------------------------------------------------------- */
					/*                             Update a recipient                             */
					/* -------------------------------------------------------------------------- */
					const recipientData = Onfleet.getRecipientFields.call(this, index, operation);
					if (!recipientData) {
						continue;
					}
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'PUT', path, recipientData));
				} else if (operation === 'get') {
					/* -------------------------------------------------------------------------- */
					/*                          Get recipient information                         */
					/* -------------------------------------------------------------------------- */
					const lookupBy = this.getNodeParameter('getBy', index) as string;
					const lookupByValue = this.getNodeParameter(lookupBy, index) as string;
					const path = `${resource}${lookupBy === 'id' ? '' : '/' + lookupBy}/${lookupByValue}`;
					responseData.push(await onfleetApiRequest.call(this, 'GET', path));
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}

		return responseData;
	}

	/**
	 * Execute the administrator operations
	 * @param resource Resource to be executed (Administrator)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeAdministratorOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'getAll') {
					/* -------------------------------------------------------------------------- */
					/*                             Get administrators                             */
					/* -------------------------------------------------------------------------- */

					const returnAll = this.getNodeParameter('returnAll', 0, false);
					let adminUsers = await onfleetApiRequest.call(this, 'GET', resource);
					if (!returnAll) {
						const limit = this.getNodeParameter('limit', 0);
						adminUsers = adminUsers.slice(0, limit);
					}
					responseData.push(...adminUsers);
				} else if (operation === 'create') {
					/* -------------------------------------------------------------------------- */
					/*                             Create a new admin                             */
					/* -------------------------------------------------------------------------- */
					const adminData = Onfleet.getAdminFields.call(this, index, operation);
					responseData.push(await onfleetApiRequest.call(this, 'POST', resource, adminData));
				} else if (operation === 'update') {
					/* -------------------------------------------------------------------------- */
					/*                                Update admin                                */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const adminData = Onfleet.getAdminFields.call(this, index, operation);
					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'PUT', path, adminData));
				} else if (operation === 'delete') {
					/* -------------------------------------------------------------------------- */
					/*                                Delete admin                                */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					await onfleetApiRequest.call(this, 'DELETE', path);
					responseData.push({ success: true });
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}
		return responseData;
	}

	/**
	 * Execute the hub operations
	 * @param resource Resource to be executed (Hub)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeHubOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'getAll') {
					/* -------------------------------------------------------------------------- */
					/*                                Get all hubs                                */
					/* -------------------------------------------------------------------------- */

					const returnAll = this.getNodeParameter('returnAll', 0, false);
					let hubs = await onfleetApiRequest.call(this, 'GET', resource);
					if (!returnAll) {
						const limit = this.getNodeParameter('limit', 0);
						hubs = hubs.slice(0, limit);
					}
					responseData.push(...hubs);
				} else if (operation === 'create') {
					/* -------------------------------------------------------------------------- */
					/*                              Create a new hub                              */
					/* -------------------------------------------------------------------------- */
					const hubData = Onfleet.getHubFields.call(this, index, operation);
					responseData.push(await onfleetApiRequest.call(this, 'POST', resource, hubData));
				}
				if (operation === 'update') {
					/* -------------------------------------------------------------------------- */
					/*                                Update a hub                                */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const hubData = Onfleet.getHubFields.call(this, index, operation);
					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'PUT', path, hubData));
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}

		return responseData;
	}

	/**
	 * Execute the worker operations
	 * @param resource Resource to be executed (Worker)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeWorkerOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'getAll') {
					/* -------------------------------------------------------------------------- */
					/*                               Get all workers                              */
					/* -------------------------------------------------------------------------- */
					const byLocation = this.getNodeParameter('byLocation', index) as boolean;
					const returnAll = this.getNodeParameter('returnAll', index, false);
					let workers;

					if (byLocation) {
						const longitude = this.getNodeParameter('longitude', index) as string;
						const latitude = this.getNodeParameter('latitude', index) as number;
						const filters = this.getNodeParameter('filters', index);
						const path = `${resource}/location`;
						workers = await onfleetApiRequest.call(
							this,
							'GET',
							path,
							{},
							{ longitude, latitude, ...filters },
						);
						workers = workers.workers;
					} else {
						const workerFilters = Onfleet.getWorkerFields.call(
							this,
							0,
							operation,
						) as OnfleetWorkerFilter;
						workers = await onfleetApiRequest.call(this, 'GET', resource, {}, workerFilters);
					}

					if (!returnAll) {
						const limit = this.getNodeParameter('limit', index);
						workers = workers.slice(0, limit);
					}

					responseData.push(...workers);
				} else if (operation === 'get') {
					/* -------------------------------------------------------------------------- */
					/*                                Get a worker                                */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const workerFilters = Onfleet.getWorkerFields.call(
						this,
						index,
						operation,
					) as OnfleetWorkerFilter;

					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'GET', path, {}, workerFilters));
				} else if (operation === 'create') {
					/* -------------------------------------------------------------------------- */
					/*                             Create a new worker                            */
					/* -------------------------------------------------------------------------- */
					const workerData = Onfleet.getWorkerFields.call(this, index, operation);
					responseData.push(await onfleetApiRequest.call(this, 'POST', resource, workerData));
				} else if (operation === 'update') {
					/* -------------------------------------------------------------------------- */
					/*                                Update worker                               */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const workerData = Onfleet.getWorkerFields.call(this, index, operation);
					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'PUT', path, workerData));
				} else if (operation === 'delete') {
					/* -------------------------------------------------------------------------- */
					/*                                Delete worker                               */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					await onfleetApiRequest.call(this, 'DELETE', path);
					responseData.push({ success: true });
				} else if (operation === 'getSchedule') {
					/* -------------------------------------------------------------------------- */
					/*                             Get worker schedule                            */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}/schedule`;
					responseData.push(await onfleetApiRequest.call(this, 'GET', path));
				} else if (operation === 'setSchedule') {
					/* -------------------------------------------------------------------------- */
					/*                            Set a worker schedule                           */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const workerSchedule = Onfleet.getWorkerFields.call(
						this,
						index,
						operation,
					) as OnfleetWorkerSchedule;
					const path = `${resource}/${id}/schedule`;
					responseData.push(await onfleetApiRequest.call(this, 'POST', path, workerSchedule));
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}
		return responseData;
	}

	/**
	 * Execute the webhook operations
	 * @param resource Resource to be executed (Webhook)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeWebhookOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'getAll') {
					/* -------------------------------------------------------------------------- */
					/*                              Get all webhooks                              */
					/* -------------------------------------------------------------------------- */
					responseData.push(...(await onfleetApiRequest.call(this, 'GET', resource)));
				} else if (operation === 'create') {
					/* -------------------------------------------------------------------------- */
					/*                            Create a new webhook                            */
					/* -------------------------------------------------------------------------- */
					const webhookData = Onfleet.getWebhookFields.call(this, index, operation);
					responseData.push(await onfleetApiRequest.call(this, 'POST', resource, webhookData));
				} else if (operation === 'delete') {
					/* -------------------------------------------------------------------------- */
					/*                              Delete a webhook                              */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					await onfleetApiRequest.call(this, 'DELETE', path);
					responseData.push({ success: true });
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}

		return responseData;
	}

	/**
	 * Execute the containers operations
	 * @param resource Resource to be executed (Container)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeContainerOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'get') {
					/* -------------------------------------------------------------------------- */
					/*                        Get container by id and type                        */
					/* -------------------------------------------------------------------------- */
					const containerId = this.getNodeParameter('containerId', index) as string;
					const containerType = this.getNodeParameter('containerType', index) as string;
					const path = `${resource}/${containerType}/${containerId}`;
					responseData.push(await onfleetApiRequest.call(this, 'GET', path));
				} else if (['addTask', 'updateTask'].includes(operation)) {
					/* -------------------------------------------------------------------------- */
					/*                      Add or update tasks to container                      */
					/* -------------------------------------------------------------------------- */
					const containerId = this.getNodeParameter('containerId', index) as string;
					const containerType = this.getNodeParameter('containerType', index, 'workers') as string;
					const options = this.getNodeParameter('options', index);

					const tasks = this.getNodeParameter('tasks', index) as Array<string | number>;
					if (operation === 'addTask') {
						const type = this.getNodeParameter('type', index) as number;
						if (type === 1) {
							const tasksIndex = this.getNodeParameter('index', index) as number;
							tasks.unshift(tasksIndex);
						} else {
							tasks.unshift(type);
						}
					}

					const path = `${resource}/${containerType}/${containerId}`;
					responseData.push(await onfleetApiRequest.call(this, 'PUT', path, { tasks, ...options }));
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}

		return responseData;
	}

	/**
	 * Execute the team operations
	 * @param resource Resource to be executed (Team)
	 * @param operation Operation to be executed
	 * @param items Number of items to process by the node
	 */
	static async executeTeamOperations(
		this: IExecuteFunctions,
		resource: string,
		operation: string,
		items: INodeExecutionData[],
	): Promise<IDataObject | IDataObject[]> {
		const responseData = [];
		for (const key of Object.keys(items)) {
			const index = Number(key);
			try {
				if (operation === 'getAll') {
					/* -------------------------------------------------------------------------- */
					/*                                Get all teams                               */
					/* -------------------------------------------------------------------------- */
					const returnAll = this.getNodeParameter('returnAll', 0, false);
					let teams = await onfleetApiRequest.call(this, 'GET', resource);
					if (!returnAll) {
						const limit = this.getNodeParameter('limit', 0);
						teams = teams.slice(0, limit);
					}

					responseData.push(...teams);
				} else if (operation === 'get') {
					/* -------------------------------------------------------------------------- */
					/*                              Get a single team                             */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'GET', path));
				} else if (operation === 'create') {
					/* -------------------------------------------------------------------------- */
					/*                              Create a new team                             */
					/* -------------------------------------------------------------------------- */
					const teamData = Onfleet.getTeamFields.call(this, index, operation);
					responseData.push(await onfleetApiRequest.call(this, 'POST', resource, teamData));
				} else if (operation === 'update') {
					/* -------------------------------------------------------------------------- */
					/*                                Update a team                               */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const teamData = Onfleet.getTeamFields.call(this, index, operation);
					const path = `${resource}/${id}`;
					responseData.push(await onfleetApiRequest.call(this, 'PUT', path, teamData));
				} else if (operation === 'delete') {
					/* -------------------------------------------------------------------------- */
					/*                                Delete a team                               */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const path = `${resource}/${id}`;
					await onfleetApiRequest.call(this, 'DELETE', path);
					responseData.push({ success: true });
				} else if (operation === 'getTimeEstimates') {
					/* -------------------------------------------------------------------------- */
					/*      Get driver time estimates for tasks that haven't been created yet     */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const workerTimeEstimates = Onfleet.getTeamFields.call(
						this,
						index,
						operation,
					) as OnfleetWorkerSchedule;
					const path = `${resource}/${id}/estimate`;
					responseData.push(
						await onfleetApiRequest.call(this, 'GET', path, {}, workerTimeEstimates),
					);
				} else if (operation === 'autoDispatch') {
					/* -------------------------------------------------------------------------- */
					/*                  Dynamically dispatching tasks on the fly                  */
					/* -------------------------------------------------------------------------- */
					const id = this.getNodeParameter('id', index) as string;
					const teamAutoDispatch = Onfleet.getTeamFields.call(
						this,
						index,
						operation,
					) as OnfleetWorkerSchedule;
					const path = `${resource}/${id}/dispatch`;
					responseData.push(await onfleetApiRequest.call(this, 'POST', path, teamAutoDispatch));
				}
			} catch (error) {
				if (this.continueOnFail()) {
					responseData.push({ error: (error as IDataObject).toString() });
					continue;
				}
				throw error;
			}
		}

		return responseData;
	}
}
