import {
	IExecuteFunctions,
} from 'n8n-core';

import {
	IDataObject,
	ILoadOptionsFunctions,
	INodeExecutionData,
	INodePropertyOptions,
	INodeType,
	INodeTypeDescription,
} from 'n8n-workflow';

import {
	googleApiRequest,
} from './GenericFunctions';

export class GoogleSlides implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Google Slides',
		name: 'googleSlides',
		icon: 'file:googleslides.svg',
		group: ['input', 'output'],
		version: 1,
		subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
		description: 'Consume the Google Slides API',
		defaults: {
			name: 'Google Slides',
			color: '#edba25',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'googleApi',
				required: true,
				displayOptions: {
					show: {
						authentication: [
							'serviceAccount',
						],
					},
				},
			},
			{
				name: 'googleSlidesOAuth2Api',
				required: true,
				displayOptions: {
					show: {
						authentication: [
							'oAuth2',
						],
					},
				},
			},
		],
		properties: [
			{
				displayName: 'Authentication',
				name: 'authentication',
				type: 'options',
				options: [
					{
						name: 'OAuth2',
						value: 'oAuth2',
					},
					{
						name: 'Service Account',
						value: 'serviceAccount',
					},
				],
				default: 'serviceAccount',
			},
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				options: [
					{
						name: 'Page',
						value: 'page',
					},
					{
						name: 'Presentation',
						value: 'presentation',
					},
				],
				default: 'presentation',
				description: 'Resource to operate on',
			},
			{
				displayName: 'Operation',
				name: 'operation',
				type: 'options',
				options: [
					{
						name: 'Create',
						value: 'create',
						description: 'Create a presentation',
					},
					{
						name: 'Get',
						value: 'get',
						description: 'Get a presentation',
					},
					{
						name: 'Get Slides',
						value: 'getSlides',
						description: 'Get presentation slides',
					},
					{
						name: 'Replace Text',
						value: 'replaceText',
						description: 'Replace text in a presentation',
					},
				],
				displayOptions: {
					show: {
						resource: [
							'presentation',
						],
					},
				},
				default: 'create',
				description: 'Operation to perform',
			},
			{
				displayName: 'Operation',
				name: 'operation',
				type: 'options',
				options: [
					{
						name: 'Get',
						value: 'get',
						description: 'Get a page',
					},
					{
						name: 'Get Thumbnail',
						value: 'getThumbnail',
						description: 'Get a thumbnail',
					},
				],
				displayOptions: {
					show: {
						resource: [
							'page',
						],
					},
				},
				default: 'get',
				description: 'Operation to perform',
			},
			{
				displayName: 'Title',
				name: 'title',
				description: 'Title of the presentation to create.',
				type: 'string',
				default: '',
				required: true,
				displayOptions: {
					show: {
						resource: [
							'presentation',
						],
						operation: [
							'create',
						],
					},
				},
			},
			{
				displayName: 'Presentation ID',
				name: 'presentationId',
				description: 'ID of the presentation to retrieve. Found in the presentation URL:<br><code>https://docs.google.com/presentation/d/PRESENTATION_ID/edit</code>',
				placeholder: '1wZtNFZ8MO-WKrxhYrOLMvyiqSgFwdSz5vn8_l_7eNqw',
				type: 'string',
				default: '',
				required: true,
				displayOptions: {
					show: {
						resource: [
							'presentation',
							'page',
						],
						operation: [
							'get',
							'getThumbnail',
							'getSlides',
							'replaceText',
						],
					},
				},
			},
			{
				displayName: 'Return All',
				name: 'returnAll',
				type: 'boolean',
				displayOptions: {
					show: {
						operation: [
							'getSlides',
						],
						resource: [
							'presentation',
						],
					},
				},
				default: false,
				description: 'If all results should be returned or only up to a given limit.',
			},
			{
				displayName: 'Limit',
				name: 'limit',
				type: 'number',
				displayOptions: {
					show: {
						operation: [
							'getSlides',
						],
						resource: [
							'presentation',
						],
						returnAll: [
							false,
						],
					},
				},
				typeOptions: {
					minValue: 1,
					maxValue: 500,
				},
				default: 100,
				description: 'How many results to return.',
			},
			{
				displayName: 'Page Object ID',
				name: 'pageObjectId',
				description: 'ID of the page object to retrieve.',
				type: 'string',
				default: '',
				required: true,
				displayOptions: {
					show: {
						resource: [
							'page',
						],
						operation: [
							'get',
							'getThumbnail',
						],
					},
				},
			},
			{
				displayName: 'Texts To Replace',
				name: 'textUi',
				placeholder: 'Add Text',
				type: 'fixedCollection',
				typeOptions: {
					multipleValues: true,
				},
				displayOptions: {
					show: {
						resource: [
							'presentation',
						],
						operation: [
							'replaceText',
						],
					},
				},
				default: {},
				options: [
					{
						name: 'textValues',
						displayName: 'Text',
						values: [
							{
								displayName: 'Match Case',
								name: 'matchCase',
								type: 'boolean',
								default: false,
								description: 'Indicates whether the search should respect case. True : the search is case sensitive. False : the search is case insensitive.',
							},
							{
								displayName: 'Page IDs',
								name: 'pageObjectIds',
								type: 'multiOptions',
								default: [],
								typeOptions: {
									loadOptionsMethod: 'getPages',
									loadOptionsDependsOn: [
										'presentationId',
									],
								},
								description: 'If non-empty, limits the matches to page elements only on the given pages.',
							},
							{
								displayName: 'Replace Text',
								name: 'replaceText',
								type: 'string',
								default: '',
								description: 'The text that will replace the matched text.',
							},
							{
								displayName: 'Text',
								name: 'text',
								type: 'string',
								default: '',
								description: 'The text to search for in the shape or table.',
							},
						],
					},
				],
			},
			{
				displayName: 'Options',
				name: 'options',
				type: 'collection',
				placeholder: 'Add Option',
				displayOptions: {
					show: {
						operation: [
							'replaceText',
						],
						resource: [
							'presentation',
						],
					},
				},
				default: {},
				options: [
					{
						displayName: 'Revision ID',
						name: 'revisionId',
						type: 'string',
						default: '',
						description: `The revision ID of the presentation required for the write request.</br>
						If specified and the requiredRevisionId doesn't exactly match the presentation's</br>
						current revisionId, the request will not be processed and will return a 400 bad request error.`,
					},
				],
			},

			{
				displayName: 'Download',
				name: 'download',
				type: 'boolean',
				default: false,
				displayOptions: {
					show: {
						resource: [
							'page',
						],
						operation: [
							'getThumbnail',
						],
					},
				},
				description: 'Name of the binary property to which to write the data of the read page.',
			},
			{
				displayName: 'Binary Property',
				name: 'binaryProperty',
				type: 'string',
				required: true,
				default: 'data',
				description: 'Name of the binary property to which to write to.',
				displayOptions: {
					show: {
						resource: [
							'page',
						],
						operation: [
							'getThumbnail',
						],
						download: [
							true,
						],
					},
				},
			},
		],
	};

	methods = {
		loadOptions: {
			// Get all the pages to display them to user so that he can
			// select them easily
			async getPages(
				this: ILoadOptionsFunctions,
			): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const presentationId = this.getCurrentNodeParameter('presentationId') as string;
				const { slides } = await googleApiRequest.call(this, 'GET', `/presentations/${presentationId}`, {}, { fields: 'slides' });
				for (const slide of slides) {
					returnData.push({
						name: slide.objectId,
						value: slide.objectId,
					});
				}
				return returnData;
			},
		},
	};


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

		const resource = this.getNodeParameter('resource', 0) as string;
		const operation = this.getNodeParameter('operation', 0) as string;

		let responseData;
		const returnData: INodeExecutionData[] = [];

		for (let i = 0; i < items.length; i++) {

			try {

				if (resource === 'page') {

					// *********************************************************************
					//                              page
					// *********************************************************************

					if (operation === 'get') {

						// ----------------------------------
						//            page: get
						// ----------------------------------

						const presentationId = this.getNodeParameter('presentationId', i) as string;
						const pageObjectId = this.getNodeParameter('pageObjectId', i) as string;
						responseData = await googleApiRequest.call(this, 'GET', `/presentations/${presentationId}/pages/${pageObjectId}`);
						returnData.push({ json: responseData });

					} else if (operation === 'getThumbnail') {

						// ----------------------------------
						//         page: getThumbnail
						// ----------------------------------

						const presentationId = this.getNodeParameter('presentationId', i) as string;
						const pageObjectId = this.getNodeParameter('pageObjectId', i) as string;
						responseData = await googleApiRequest.call(this, 'GET', `/presentations/${presentationId}/pages/${pageObjectId}/thumbnail`);

						const download = this.getNodeParameter('download', 0) as boolean;
						if (download === true) {
							const binaryProperty = this.getNodeParameter('binaryProperty', i) as string;

							const data = await this.helpers.request({
								uri: responseData.contentUrl,
								method: 'GET',
								json: false,
								encoding: null,
							});

							const fileName = pageObjectId + '.png';
							const binaryData = await this.helpers.prepareBinaryData(data, fileName || fileName);
							returnData.push({
								json: responseData,
								binary: {
									[binaryProperty]: binaryData,
								},
							});
						} else {
							returnData.push({ json: responseData });
						}
					}

				} else if (resource === 'presentation') {

					// *********************************************************************
					//                           presentation
					// *********************************************************************

					if (operation === 'create') {

						// ----------------------------------
						//       presentation: create
						// ----------------------------------

						const body = {
							title: this.getNodeParameter('title', i) as string,
						};

						responseData = await googleApiRequest.call(this, 'POST', '/presentations', body);
						returnData.push({ json: responseData });

					} else if (operation === 'get') {

						// ----------------------------------
						//         presentation: get
						// ----------------------------------

						const presentationId = this.getNodeParameter('presentationId', i) as string;
						responseData = await googleApiRequest.call(this, 'GET', `/presentations/${presentationId}`);
						returnData.push({ json: responseData });

					} else if (operation === 'getSlides') {

						// ----------------------------------
						//      presentation: getSlides
						// ----------------------------------
						const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
						const presentationId = this.getNodeParameter('presentationId', i) as string;
						responseData = await googleApiRequest.call(this, 'GET', `/presentations/${presentationId}`, {}, { fields: 'slides' });
						responseData = responseData.slides;
						if (returnAll === false) {
							const limit = this.getNodeParameter('limit', i) as number;
							responseData = responseData.slice(0, limit);
						}
						returnData.push(...this.helpers.returnJsonArray(responseData));

					} else if (operation === 'replaceText') {

						// ----------------------------------
						//      presentation: replaceText
						// ----------------------------------
						const presentationId = this.getNodeParameter('presentationId', i) as string;
						const texts = this.getNodeParameter('textUi.textValues', i, []) as IDataObject[];
						const options = this.getNodeParameter('options', i) as IDataObject;
						const requests = texts.map((text => {
							return {
								replaceAllText: {
									replaceText: text.replaceText,
									pageObjectIds: text.pageObjectIds || [],
									containsText: {
										text: text.text,
										matchCase: text.matchCase,
									},
								},
							};
						}));

						const body: IDataObject = {
							requests,
						};

						if (options.revisionId) {
							body['writeControl'] = {
								requiredRevisionId: options.revisionId as string,
							};
						}

						responseData = await googleApiRequest.call(this, 'POST', `/presentations/${presentationId}:batchUpdate`, { requests });
						returnData.push({ json: responseData });

					}
				}

			} catch (error) {
				if (this.continueOnFail()) {
					returnData.push({json:{ error: error.message }});
					continue;
				}
				throw error;
			}
		}

		return [returnData];
	}
}