import { emit } from '../../../utilities/emit.js';
import { ready } from '../../../utilities/ready.js';

customElements.define('ajax-form', class extends HTMLElement {

	// Initialize on connect
	connectedCallback () {
		ready(this);
	}

	// Initialize the component
	readyInit () {

		// Don't run if already initialized
		if (this.hasAttribute('is-ready')) return;

		// Add a form status element
		this.announce = this.querySelector('[role="status"]') || document.createElement('div');
		this.announce.setAttribute('role', 'status');

		// Set base properties
		this.form = this.querySelector('form');
		// If there's no form, bail
		if ( !this.form) return;
		this.form.append(this.announce);

		// Define options
		this.msgSubmitting = this.getAttribute('msg-submitting') ?? 'Submitting...';
		this.msgDisappear = this.hasAttribute('msg-disappear');
		this.keepFields = this.hasAttribute('keep-fields');
		this.updateURL = this.getAttribute('update-url');
		this.removeElem = this.getAttribute('remove');
		this.closeDialog = this.hasAttribute('close-dialog');
		this.delay = this.hasAttribute('delay') ? 6000 : 0;

		// Listen for events
		this.form.addEventListener('submit', this);

		// Ready
		emit(this, 'ajax-form', 'ready');
		this.setAttribute('is-ready', '');
	}

	/**
	 * Handle events on the component
	 * @param  {Event} event The event object
	 */
	handleEvent(event) {
		this[`on${event.type}`](event);
	}

	/**
	 * Handle submit events
	 * @param  {Event} event The event object
	 */
	async onsubmit (event) {

		// Stop form from reloading the page
		event.preventDefault();

		// If the form is already submitting, do nothing
		// Otherwise, disable future submissions
		if (this.isDisabled()) return;
		this.disable();

		// Show status message
		this.showStatus(this.msgSubmitting, true);

		try {

			const formData = this.serialize();
			const action = this.form.getAttribute('action');
			const method = this.form.getAttribute('method');

			// Emit submit-before event
			// If cancelled, don't submit
			const event = emit(
				this,
				'ajax-form',
				'submit-before',
				{
					action,
					formData
				},
				true
			);
			if (!event) return;

			// Call the API
			const response = await fetch(action, {
				method,
				body: this.serialize(),
				headers: {
					'Accept': 'application/json, */*;q=0.1',
					'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
					'X-Requested-With': 'XMLHttpRequest'
				}
			});

			// Get the response data
			let data = await response.json();

			// If there's a fetch error, throw
			if (!response.ok) throw data;

			// Additional success flag from WP itself
			if (Object.hasOwn(data, 'success') && !data.success) {
				if (Object.hasOwn(data, 'data')) {
					data = data.data;
				}
				// Allow additional processing of the return
				emit(this, 'ajax-form', 'error', data);
				throw data;
			}

			if (Object.hasOwn(data, 'data')) {
				data = data.data;
			}

			// If message, display it
			if (data.message) {
				this.showStatus(data.message, true, true);
			}

			// If updateURL, update it
			if (this.updateURL) {
				history.replaceState(history.state, null, this.updateURL);
			}

			// If URL, redirect
			if (data.url) {
				window.location.href = data.url;
			}

			// Clear the form
			if (!this.keepFields) {
				this.reset();
			}

			// Emit custom event
			emit(this, 'ajax-form', 'success', data);

			// Optionally remove all HTML
			if (this.removeElem) {
				const elemToRemove = this.closest(this.removeElem) || this;
				elemToRemove.remove();
			}
		} catch (error) {
			console.warn(error);
			this.showStatus(error.message);
		} finally {
			setTimeout(() => {
				this.enable();
			}, this.delay);
		}

	}

	/**
	 * Disable a form so I can't be submitted while waiting for the API
	 */
	disable () {
		this.setAttribute('form-submitting', '');
	}

	/**
	 * Enable a form after the API returns
	 */
	enable () {
		this.removeAttribute('form-submitting');
	}

	/**
	 * Check if a form is submitting to the API
	 * @return {Boolean} If true, the form is submitting
	 */
	isDisabled () {
		return this.hasAttribute('form-submitting');
	}

	/**
	 * Update the form status in a field
	 * @param {String}  msg      The message to display
	 * @param {Boolean} success  If true, add success class
	 * @param {Boolean} complete If true, form submit is complete
	 */
	showStatus (msg, success, complete) {

		// Show the message
		this.announce.hidden = false;
		this.announce.innerHTML = msg;
		this.announce.className = success ? 'notice-message success-message' : 'notice-message error-message';

		// If content should be removed, switch to toast notification
		if (success && complete && this.removeElem) {
			this.toast();
		}

		// If success and message should disappear
		if (success && this.msgDisappear) {
			setTimeout(() => {
				this.announce.hidden = true;
				this.announce.innerHTML = '';
				this.announce.className = '';
			}, 6000);
		}

		// If success and the parent dialog should close
		if (success && complete && this.closeDialog) {
			setTimeout(() => {
				const dialogToClose = this.closest('dialog');
				if (dialogToClose) {
					dialogToClose.close('close');
				}
			}, 6000);
		}
	}

	/**
	 * Convert status message into a toast
	 */
	toast () {

		// Get the toast wrapper
		const wrapper = document.querySelector('.toast-wrapper');

		// If there is no wrapper, make one
		if (!wrapper) {
			wrapper = document.createElement('div');
			wrapper.className = 'toast-wrapper';
			document.body.append(wrapper);
		}

		// Add the toast class to the notification
		this.announce.className = 'toast';
		wrapper.prepend(this.announce);

		// Remove after 6 seconds
		setTimeout(() => {
			this.announce.remove();
		}, 6000);

	}

	/**
	 * Serialize all form data into an encoded query string
	 * @return {String} The serialized form data
	 */
	serialize () {
		const data = new FormData(this.form);
		const params = new URLSearchParams();
		for (let [key, val] of data) {
			params.append(key, val);
		}
		return params.toString();
	}

	/**
	 * Reset the form element values
	 */
	reset () {
		this.form.reset();
	}

});
