import {
	consoleInfo,
	cookieStorage,
	delegate,
	getClosest,
	getNode,
	objectToAttributes,
	spacerClasses,
	trigger,
	uniqueId,
} from '@gravityforms/utils';

/**
 * @function alertTemplate
 * @description Generates the markup for an alert in the admin.
 *
 * @since 1.1.16
 *
 * @param {object}                     options                      The options for the component template.
 * @param {string}                     options.content              The content of the alert.
 * @param {string}                     options.ctaLabel             The label for the call-to-action button.
 * @param {string}                     options.ctaLink              The link href for the call-to-action button.
 * @param {object}                     options.customAttributes     Any custom attributes.
 * @param {Array}                      options.customClasses        An array of additional classes for the toggle.
 * @param {string}                     options.dismissableAriaLabel The aria-label for the dismiss button.
 * @param {string}                     options.dismissableTitle     The title for the dismiss button.
 * @param {boolean}                    options.hasCta               Whether or not the alert has a call-to-action button.
 * @param {string}                     options.id                   Id for the alert, auto generated if not passed.
 * @param {boolean}                    options.isDismissable        Whether or not the alert is dismissable.
 * @param {boolean}                    options.isInline             Whether or not the alert is inline.
 * @param {string}                     options.selector             The data-js selector string for the alert.
 * @param {string|number|Array|object} options.spacing              The spacing for the component, string, number, object or array.
 * @param {string}                     options.theme                Theme for the toggle, primary or cosmos.
 * @param {string}                     options.type                 The type of the alert. Default, notice, success, error and accessibility.
 *
 * @return {string}
 * @example
 * import { alertTemplate } from '@gravityforms/components/html/admin/elements/Alert';
 *
 * function Example() {
 *      const alertTemplateHTML = alertTemplateTemplate( options );
 *      document.body.insertAdjacentHTML( 'beforeend', alertTemplateHTML );
 * }
 *
 */
export const alertTemplate = ( {
	content = '',
	ctaLabel = '',
	ctaLink = '',
	customAttributes = {},
	customClasses = [],
	dismissableAriaLabel = '',
	dismissableTitle = '',
	hasCta = false,
	id = uniqueId( 'alert' ),
	isDismissable = false,
	isInline = false,
	spacing = '',
	theme = 'primary',
	type = 'default',
} ) => {
	const componentAttrs = objectToAttributes( {
		...customAttributes,
		id,
		class: [
			'gform-alert',
			`gform-alert--${ type }`,
			`gform-alert--theme-${ theme }`,
			isInline && 'gform-alert--inline',
			...Object.keys( spacerClasses( spacing ) ),
			...customClasses,
		],
	} );

	const iconAttrs = objectToAttributes( {
		'aria-hidden': 'true',
		class: [
			'gform-alert__icon',
			'gform-icon',
			type === 'default' && 'gform-icon--campaign',
			type === 'notice' && 'gform-icon--circle-notice-fine',
			type === 'success' && 'gform-icon--circle-check-fine',
			type === 'error' && 'gform-icon--circle-error-fine',
			type === 'accessibility' && 'gform-icon--accessibility',
		],
	} );

	return `
		<div ${ componentAttrs }>
			<span ${ iconAttrs }></span>
			<div class="gform-alert__message-wrap">
				<p class="gform-alert__message">${ content }</p>
				${ hasCta ? `
					<a
						class="gform-alert__cta gform-button gform-button--white gform-button--size-xs"
						href="${ ctaLink }"
						target="_blank"
						title="title for a11y"
					>
						${ ctaLabel }
					</a>
				` : '' }
			</div>
			${ isDismissable ? `
				<button
					class="gform-alert__dismiss"
					aria-label="${ dismissableAriaLabel }"
					title="${ dismissableTitle }"
					data-js="gform-alert-dismiss-trigger"
				>
					<span class="gform-icon gform-icon--delete"></span>
				</button>
			` : '' }
		</div>
	`;
};

/**
 * @class Alert
 * @description A alert component with 5 styles, default, notice, success, error and accessibility. It also allows for a cookie to store dismissed state.
 *
 * @since 1.1.16
 *
 * @borrows alertTemplate as alertTemplate
 *
 * @param {object}                     options                      The options for the component.
 * @param {string}                     options.content              The content of the alert.
 * @param {string}                     options.ctaLabel             The label for the call-to-action button.
 * @param {string}                     options.ctaLink              The link href for the call-to-action button.
 * @param {object}                     options.customAttributes     Any custom attributes for the component.
 * @param {Array}                      options.customClasses        An array of additional classes for the component.
 * @param {string}                     options.dismissableAriaLabel The aria-label for the dismiss button.
 * @param {string}                     options.dismissableTitle     The title for the dismiss button.
 * @param {boolean}                    options.hasCta               Whether or not the alert has a call-to-action button.
 * @param {string}                     options.id                   Id for the component, auto generated if not passed.
 * @param {boolean}                    options.isDismissable        Whether or not the alert is dismissable.
 * @param {boolean}                    options.isInline             Whether or not the alert is inline.
 * @param {string}                     options.rendered             Is the component already rendered in the dom, eg by php?
 * @param {string}                     options.renderOnInit         Render the component on init of the class?
 * @param {string|number|Array|object} options.spacing              Spacing for the component.
 * @param {string}                     options.target               The target to render to. Any valid css selector string.
 * @param {string}                     options.targetPosition       The insert position for the component relative to the target.
 * @param {string}                     options.theme                Theme for the component, primary or cosmos.
 * @param {string}                     options.type                 The type of the alert. Default, notice, success, error and accessibility.
 *
 * @return {Class} The class instance.
 * @example
 * import Alert from '@gravityforms/components/html/admin/elements/Alert';
 *
 * function Example() {
 *      const alertInstance = new Alert( {
 *          content: 'This is an alert',
 *          id: 'my-alert',
 *          renderOnInit: false,
 *          target: '#example-target',
 *          targetPosition: 'beforeend',
 *          type: 'error',
 *      } );
 *
 *      // Some time later we can render it. This is only done if we set renderOnInit to false.
 *      // If true it will render on initialization.
 *      alertInstance.init();
 * }
 *
 */
export default class Alert {
	constructor( options = {} ) {
		this.options = {};
		Object.assign(
			this.options,
			{
				container: document,
				content: '',
				cookieName: '',
				ctaLabel: '',
				ctaLink: '',
				customAttributes: {},
				customClasses: [],
				dismissableAriaLabel: '',
				dismissableTitle: '',
				hasCta: false,
				id: uniqueId( 'alert' ),
				isDismissable: false,
				isInline: false,
				rendered: false,
				renderOnInit: true,
				spacing: '',
				target: '',
				targetPosition: 'afterbegin',
				theme: 'cosmos',
				type: 'default',
			},
			options
		);

		/**
		 * @event gform/alert/pre_init
		 * @type {object}
		 * @description Fired before the component has started any internal init functions. A great chance to augment the options.
		 *
		 * @since 1.1.16
		 *
		 * @property {object} instance The Component class instance.
		 */
		trigger( { event: 'gform/alert/pre_init', native: false, data: { instance: this } } );

		this.elements = {};

		if ( this.options.renderOnInit ) {
			this.init();
		}
	}

	/**
	 * @memberof Alert
	 * @description Renders the component into the dom.
	 *
	 * @since 1.1.16
	 *
	 * @return {void}
	 */
	render() {
		const { rendered, target, targetPosition } = this.options;

		if ( ! rendered ) {
			const renderTarget = getNode( target, document, true );

			renderTarget.insertAdjacentHTML(
				targetPosition,
				alertTemplate( this.options )
			);
		}

		this.elements.alert = getNode( `#${ this.options.id }`, document, true );
	}

	/**
	 * @param  e
	 * @memberof Alert
	 * @description Dismiss the alert and store a cookie if name is set.
	 *
	 * @since 1.1.16
	 *
	 * @return {void}
	 */
	dismissAlert( e ) {
		const parentEl = getClosest( e.target, `#${ this.options.id }` );
		parentEl.style.display = 'none';
		if ( this.options.cookieName ) {
			const cookieValue = uniqueId( 'gform-alert' );
			cookieStorage.set( this.options.cookieName, cookieValue, 1, true );
		}
	}

	/**
	 * @memberof Alert
	 * @description Bind event listeners for the alert.
	 *
	 * @since 1.1.16
	 *
	 * @return {void}
	 */
	bindEvents() {
		delegate(
			`#${ this.options.id }`,
			'[data-js="gform-alert-dismiss-trigger"]',
			'click',
			this.dismissAlert.bind( this )
		);
	}

	/**
	 * @memberof Alert
	 * @description Initialize the component.
	 *
	 * @fires gform/alert/post_render
	 *
	 * @since 1.1.16
	 *
	 * @return {void}
	 */
	init() {
		this.bindEvents();
		this.render();

		/**
		 * @event gform/alert/post_render
		 * @type {object}
		 * @description Fired when the component has completed rendering and all class init functions have completed.
		 *
		 * @since 1.1.16
		 *
		 * @property {object} instance The Component class instance.
		 */
		trigger( { event: 'gform/alert/post_render', native: false, data: { instance: this } } );

		consoleInfo( `Gravity Forms Admin: Initialized alert component.` );
	}
}
