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

/**
 * @function inputTemplate
 * @description Generates the markup for a input 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 {string}                     options.id               Id for the input, auto generated if not passed.
 * @param {string}                     options.inputPrefix      The prefix for the input.
 * @param {string}                     options.inputSuffix      The suffix for the input.
 * @param {string}                     options.label            For radio or checkboxes, the label text.
 * @param {string}                     options.labelAttributes  Any other arbitrary attributes to add to the label.
 * @param {string}                     options.labelClasses     Space separated string of classes to add to the label.
 * @param {string}                     options.placeholder      Input placeholder text.
 * @param                              options.actionButton
 * @param                              options.actionButtonIcon
 * @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             Input type: `text`, `email`, `tel`, `url`, `checkbox` or `radio`.
 * @param {string}                     options.value            The value for the input.
 *
 * @return {string}
 * @example
 * import { inputTemplate } from '@gravityforms/components/html/admin/elements/Input';
 *
 * function Example() {
 *      const inputTemplateHTML = inputTemplateTemplate( options );
 *      document.body.insertAdjacentHTML( 'beforeend', inputTemplateHTML );
 * }
 *
 */
export const inputTemplate = ( {
	actionButton = false,
	actionButtonIcon = 'eye',
	customAttributes = {},
	customClasses = [],
	id = uniqueId( 'input' ),
	inputPrefix = '',
	inputSuffix = '',
	label = '',
	labelAttributes = '',
	labelClasses = '',
	placeholder = '',
	spacing = '',
	theme = 'primary',
	type = 'text',
	value = '',
} ) => {
	let labelHtml = '';
	const placeholderAttr = placeholder && ( type !== 'radio' || type !== 'checkbox' ) ? ` placeholder="${ placeholder }"` : '';
	const inputWrapperClasses = `${ inputPrefix ? 'gform-input-add-on-wrapper--prefix' : '' } ${ inputSuffix ? 'gform-input-add-on-wrapper--suffix' : '' } ${ actionButton ? 'gform-input-add-on-wrapper--action-button' : '' }`;
	const inputWrapperOpen = inputPrefix || inputSuffix || actionButton ? `<div class="gform-input-add-on-wrapper ${ inputWrapperClasses }">` : '';
	const inputWrapperClose = inputPrefix || inputSuffix || actionButton ? `</div>` : '';
	const inputPrefixContent = inputPrefix ? `<div class="gform-input__add-on gform-input__add-on--prefix">${ inputPrefix }</div>` : '';
	const inputSuffixContent = inputSuffix ? `<div class="gform-input__add-on gform-input__add-on--suffix">${ inputSuffix }</div>` : '';
	let actionButtonContent = '';

	if ( actionButton ) {
		actionButtonContent = `
			<button class="gform-button gform-button--size-sm gform-button--secondary gform-button--icon-leading gform-input__add-on gform-input__add-on--action-button">
				<i class="gform-button__icon gform-common-icon gform-common-icon--${ actionButtonIcon }" data-js="button-icon"></i>
			</button>
		`;
	}

	if ( label && ( type === 'radio' || type === 'checkbox' ) ) {
		const forAttr = id ? ` for="${ id }"` : '';

		labelHtml = `
			<label
				${ forAttr }
				class="gform-label gform-input__label gform-input__label--${ type } ${ labelClasses }"
				${ labelAttributes }
			>
				${ label }
			</label>
		`;
	}

	const componentAttrs = objectToAttributes( {
		...customAttributes,
		id,
		type,
		value,
		class: [
			'gform-input',
			`gform-input--theme-${ theme }`,
			( type !== 'radio' && type !== 'checkbox' ) && `gform-input`,
			( type !== 'radio' && type !== 'checkbox' ) && `gform-input--text`,
			( type === 'radio' || type === 'checkbox' ) && `gform-input--${ type }`,
			...Object.keys( spacerClasses( spacing ) ),
			...customClasses,
		],
	} );

	return `
		${ inputWrapperOpen }
			${ inputPrefixContent }
			<input
				${ placeholderAttr }
				${ componentAttrs }
			/>
			${ inputSuffixContent }
			${ actionButtonContent }
		${ inputWrapperClose }
		${ labelHtml }
	`;
};

/**
 * @class Input
 * @description A input component .
 *
 * @since 1.1.16
 *
 * @borrows inputTemplate as inputTemplate
 *
 * @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 {string} options.id               Id for the component, auto generated if not passed.
 * @param {string} options.inputPrefix      The prefix for the input.
 * @param {string} options.inputSuffix      The suffix for the input.
 * @param {string} options.label            For radio or checkboxes, the label text.
 * @param {string} options.labelAttributes  Any other arbitrary attributes to add to the label.
 * @param {string} options.labelClasses     Space separated string of classes to add to the label.
 * @param {string} options.placeholder      Input placeholder text.
 * @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.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             Input type: `text`, `email`, `tel`, `url`, `checkbox` or `radio`.
 * @param {string} options.value            The value for the input.
 *
 * @return {Class} The class instance.
 * @example
 * import Input from '@gravityforms/components/html/admin/elements/Input';
 *
 * function Example() {
 *      const inputInstance = new Input( {
 *          id: 'example-input',
 *          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.
 *      inputInstance.init();
 * }
 *
 */
export default class Input {
	constructor( options = {} ) {
		this.options = {};
		Object.assign(
			this.options,
			{
				actionButton: false,
				actionButtonIcon: 'eye',
				customAttributes: {},
				customClasses: [],
				id: '',
				inputPrefix: '',
				inputSuffix: '',
				label: '',
				labelAttributes: '',
				labelClasses: '',
				placeholder: '',
				rendered: false,
				renderOnInit: true,
				spacing: '',
				target: '',
				targetPosition: 'afterbegin',
				theme: 'cosmos',
				type: 'text',
				value: '',
			},
			options
		);

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

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

		this.elements = {};

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

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

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

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

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

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