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

/**
 * @function toggleTemplate
 * @description Generates the markup for a toggle in the admin.
 *
 * @since 1.1.16
 *
 * @param {object}                     options                  The options for the component template.
 * @param {object}                     options.customAttributes Any custom attributes.
 * @param {Array}                      options.customClasses    An array of additional classes for the toggle.
 * @param {boolean}                    options.disabled         If the toggle is disabled.
 * @param {boolean}                    options.icons            Do we use the checkmark and x icons for this instance.
 * @param {string}                     options.iconPrefix       Prefix for the icon class.
 * @param {string}                     options.iconBefore       Icon to display when toggle is off.
 * @param {string}                     options.iconAfter        Icon to display when toggle is on.
 * @param {string}                     options.id               Id for the toggle, auto generated if not passed.
 * @param {boolean}                    options.initialChecked   Is it checked on render?
 * @param {string}                     options.label            The label, required for accessibility.
 * @param {string}                     options.labelPosition    Position for the label, defaults to right.
 * @param {boolean}                    options.labelVisible     Is the label visible?
 * @param {string}                     options.name             Name for the input.
 * @param {string}                     options.size             Size for the toggle. Small (size-s), medium (size-m), or large (size-l).
 * @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.ariaDescribedby  Class of the element that describes this input.
 * @return {string}
 * @example
 * import { toggleTemplate } from '@gravityforms/components/html/admin/elements/Toggle';
 *
 * function Example() {
 *      const toggleHTML = toggleTemplate( options );
 *      document.body.insertAdjacentHTML( 'beforeend', toggleHTML );
 * }
 *
 */
export const toggleTemplate = ( {
	customAttributes = {},
	customClasses = [],
	disabled = false,
	icons = false,
	id = '',
	initialChecked = false,
	label = '',
	labelPosition = 'right',
	labelVisible = false,
	name = '',
	size = 'size-s',
	spacing = '',
	theme = 'primary',
	ariaDescribedby = '',
} ) => {
	const componentAttrs = objectToAttributes( {
		...customAttributes,
		class: [
			'gform-toggle',
			theme === 'cosmos' ? 'gform-toggle--theme-cosmos' : '',
			`gform-toggle--label-${ labelPosition }`,
			`gform-toggle--${ size }`,
			...Object.keys( spacerClasses( spacing ) ),
			...customClasses,
		],
	} );

	const labelClassName = [
		'gform-toggle__label',
		! labelVisible ? 'gform-visually-hidden' : '',
	];

	return `
		<div ${ componentAttrs }>
			<input
				${ initialChecked ? 'checked' : '' }
				class="gform-toggle__toggle${ icons ? ' gform-toggle__toggle--has-icons' : '' }"
				id="${ id }"
				name="${ name }"
				type="checkbox"
				${ disabled ? 'disabled' : '' }
				${ ariaDescribedby ? `aria-describedby="${ ariaDescribedby }"` : '' }
			/>
			<label
				class="${ labelClassName.join( ' ' ) }"
				for="${ id }"
			>
				${ label }
			</label>
		</div>
	`;
};

/**
 * @class Toggle
 * @description A toggle component to use wherever boolean based form fields are needed.
 *
 * @since 1.1.16
 *
 * @borrows toggleTemplate as toggleTemplate
 *
 * @param {object}  options                  The options for the component.
 * @param {object}  options.customAttributes Any custom attributes for the component.
 * @param {Array}   options.customClasses    An array of additional classes for the component.
 * @param {Array}   options.disabled         Is the toggle disabled on render?
 * @param {boolean} options.icons            Do we use the checkmark and x icons for this instance?
 * @param {string}  options.id               Id for the component, auto generated if not passed.
 * @param {boolean} options.initialChecked   Is it checked on render?
 * @param {string}  options.label            The label, required for accessibility.
 * @param {string}  options.labelPosition    Position for the label, defaults to right.
 * @param {boolean} options.labelVisible     Is the label visible?
 * @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}  options.name             Name for the input.
 * @param {string}  options.size             Size for the component. Small (size-s), medium (size-m), or large (size-l).
 * @param {string}  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.
 *
 * @return {Class} The class instance.
 * @example
 * import Toggle from '@gravityforms/components/html/admin/elements/Toggle';
 *
 * function Example() {
 *      const toggleInstance = new Toggle( {
 *          id: 'example-toggle',
 *          label: 'Example Toggle',
 *          name: 'example-toggle',
 *          renderOnInit: false,
 *          target: '#example-target',
 *          targetPosition: 'beforeend',
 *          theme: 'cosmos',
 *      } );
 *
 *      // Some time later we can render it. This is only done if we set renderOnInit to false.
 *      // If true it will render on initialization.
 *      toggleInstance.init();
 * }
 *
 */
export default class Toggle {
	constructor( options = {} ) {
		this.options = {};
		Object.assign(
			this.options,
			{
				customAttributes: {},
				customClasses: [],
				disabled: false,
				icons: false,
				id: '',
				initialChecked: false,
				label: '',
				labelPosition: 'right',
				labelVisible: false,
				name: '',
				rendered: false,
				renderOnInit: true,
				size: 'size-s',
				spacing: '',
				target: '',
				targetPosition: 'afterbegin',
				theme: 'cosmos',
			},
			options
		);

		/**
		 * @event gform/toggle/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/toggle/pre_init', native: false, data: { instance: this } } );

		this.options.id = this.options.id || uniqueId( 'toggle' );

		this.elements = {};

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

	/**
	 * @memberof Toggle
	 * @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,
				toggleTemplate( this.options )
			);
		}

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

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

		/**
		 * @event gform/toggle/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/toggle/post_render', native: false, data: { instance: this } } );

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