<?php namespace ProcessWire;

/**
 * An Inputfield for handling "submit" buttons
 * 
 * ProcessWire 3.x, Copyright 2021 by Ryan Cramer
 * https://processwire.com
 * License: MPL 2.0
 * 
 * @property bool $header Whether or not button will also appear in header (default=false). 
 * @property bool $secondary Whether or not button is secondary (default=false). 
 * @property bool $small Whether or not button should be small, where supported (default=false). 
 * @property string $dropdownInputName Name of input to receive selected dropdown value (default='_action_value') #pw-internal
 * @property bool $dropdownSubmit Selected dropdown value becomes submit value? (default=true) #pw-internal 
 * @property bool $dropdownRequired Require dropdown selection, when true, click submit without selection opens dropdown @since 3.0.180 #pw-internal
 * @property string $html Button inner HTML label (default='') @since 3.0.134
 * @property string $text Button inner TEXT label, if $html not provided. (default='') @since 3.0.134
 * @property string $value Button value attribute and inner TEXT label, if $text it provided (default='Submit')
 * @property string $textClass Class applied to span for inner html/text, omitted if blank (default='ui-button-text') @since 3.0.134
 * @property-read string|false $submitValue Value that was submitted if clicked (default=false) @since 3.0.134
 *
 */
class InputfieldSubmit extends Inputfield {

	public static function getModuleInfo() {
		return array(
			'title' => 'Submit', // Module Title
			'summary' => __('Form submit button', __FILE__), // Module Summary
			'version' => 103,
			'permanent' => true, 
		);
	}

	/**
	 * Names of submit buttons created
	 * 
	 * @var array
	 * 
	 */ 
	protected static $submitNames = array();

	/**
	 * Additional dropdown actions added to the button
	 * 
	 * @var array
	 * 
	 */
	protected $dropdownItems = array();

	/**
	 * Init
	 * 
	 */
	public function init() {
		parent::init();
		$this->attr('type', 'submit'); 
		$this->attr('name', 'submit'); 
		$this->attr('value', $this->_('Submit')); // Standard submit button label
		$this->set('submitValue', false); // becomes string after processInput
		$this->attr('class', 'ui-button ui-widget ui-state-default ui-corner-all');
		$this->set('textClass', 'ui-button-text');
		$this->skipLabel = Inputfield::skipLabelBlank; 
		$this->set('small', false);
		$this->set('html', '');
		$this->set('text', '');
		
		// name of 'hidden' input that will receive the clicked dropdown item value
		$this->set('dropdownInputName', '_action_value');
		
		// Selected dropdown value becomes submit value? If set to false, then only the 
		// dropdownInputName above will contain the selected value
		$this->set('dropdownSubmit', true);
		
		// dropdown selection required? (when true, clicking submit without dropdown selection opens dropdown)
		$this->set('dropdownRequired', false);
	}
	
	public function set($key, $value) {
		if($key == 'header') {
			$this->showInHeader($value);
		} else if($key == 'secondary') {
			$this->setSecondary($value);
		}
		return parent::set($key, $value);
	}
	
	public function get($key) {
		if($key == 'header') return $this->hasClass('pw-head-button');
		if($key == 'secondary') return $this->hasClass('ui-priority-secondary');
		return parent::get($key);
	}
	
	public function setAttribute($key, $value) {
		if($key === 'name') self::$submitNames[$value] = $value;
		return parent::setAttribute($key, $value);
	}	
		
	/**
	 * Show another copy of this button in the header? 
	 * 
	 * @param bool $show True=yes, false=no (default=true)
	 * @return $this
	 * 
	 */
	public function showInHeader($show = true) {
		if($show) {
			$this->addClass('pw-head-button');
		} else {
			$this->removeClass('pw-head-button');
		}
		return $this;
	}

	/**
	 * Make this button secondary? (slightly faded)
	 * 
	 * Note: by default, buttons are not secondary
	 * 
	 * @param bool $secondary Default=true
	 * @return $this
	 * 
	 */
	public function setSecondary($secondary = true) {
		if($secondary) {
			$this->addClass('ui-priority-secondary');
		} else {
			$this->removeClass('ui-priority-secondary');
		}
		return $this;
	}
	
	/**
	 * Make this button small?
	 * 
	 * By default, buttons are regular size. This makes them small.
	 * Supported only for non-dropdown, non-header buttons. 
	 *
	 * @param bool $small Default=true
	 * @return $this
	 *
	 */
	public function setSmall($small = true) {
		$this->set('small', $small ? true : false);
		return $this;
	}

	/**
	 * Render ready
	 * 
	 * @param Inputfield|InputfieldWrapper|null The parent InputfieldWrapper that is rendering it, or null if no parent.
	 * @param bool $renderValueMode Specify true only if this is for `Inputfield::renderValue()` rather than `Inputfield::render()`.
	 * @return bool True if assets were just added, false if already added.
	 *
	 */
	public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
		$class = $this->attr('class');	
		if(strpos($class, 'head_button_clone') !== false) {
			// if legacy class name used, convert to updated pw- class name to accomodate 3rd party usages
			$class = str_replace('head_button_clone', 'pw-head-button', $class);
			$this->attr('class', $class);
		}
		if($this->getSetting('small')) {
			$this->addClass('InputfieldSubmitSmall', 'wrapClass');
		}
		return parent::renderReady($parent, $renderValueMode);
	}

	/**
	 * Render the button
	 * 
	 * @return string
	 * 
	 */
	public function ___render() {
		$sanitizer = $this->wire()->sanitizer;
		$attrs  = $this->getAttributesString();
		$icon = $this->icon ? $sanitizer->name($this->icon) : '';
		$icon = $icon ? wireIconMarkup($icon) . ' ' : '';
		$buttonText = $this->getSetting('html'); // option for non-encoded button text
		if(empty($buttonText)) {
			$buttonText = $this->getSetting('text');
			if(empty($buttonText)) $buttonText = $this->attr('value');
			$buttonText = $this->entityEncode($buttonText);
		}
		$buttonText = $icon . $buttonText;
		$textClass = $sanitizer->entities($this->getSetting('textClass'));
		if(!empty($textClass)) $buttonText = "<span class='$textClass'>$buttonText</span>";
		$out = "<button $attrs>$buttonText</button>";
		if($this->getSetting('small')) $out = "<small>$out</small>";
		if(count($this->dropdownItems)) $out .= $this->renderDropdown();
		return $out; 
	}

	/**
	 * Render the dropdown to accompany the button
	 * 
	 * @return string
	 * 
	 */
	protected function renderDropdown() {

		if($this->wire('input')->get('modal')) return '';
		
		$config = $this->wire()->config;
		$file = $config->debug ? 'dropdown.js' : 'dropdown.min.js';
		$config->scripts->add($config->urls->InputfieldSubmit . $file);
		$numValues = 0;
		$dropdownID = $this->attr('id') . '_dropdown';
		$out  = "<ul id='$dropdownID' class='pw-button-dropdown' data-my='left top' data-at='left bottom+1'>";

		foreach($this->dropdownItems as $item) {
			
			// entity encode all the labels in the dropdown
			foreach($item as $k => $v) {
				if($k == 'type') continue;
				$item[$k] = htmlentities($v, ENT_QUOTES, "UTF-8");
			}
			
			if($item['type'] == 'link') {
				// direct link
				$out .= "<li><a href='$item[value]'>";
			} else {
				// populate hidden input with value before submit
				$out .= "<li><a data-pw-dropdown-value='$item[value]' href='#'>";
				$numValues++;
			}
			// icon to accompany label
			if($item['icon']) $out .= "<i class='fa fa-fw fa-$item[icon]'></i>";
					
			// label and finish item
			$out .= "$item[label]</a></li>";
		}
		
		$out .= "</ul>";
		
		if($numValues) {
			// there are values that can be populated to a hidden input
			$inputID = $dropdownID . '_value';
			$attr = "type='hidden' name='{$this->dropdownInputName}' id='$inputID' value='' ";
			// copy the submitted dropdown value to the submit button value?
			if($this->dropdownSubmit) $attr .= "data-pw-dropdown-submit='1' ";
			// render the output
			$out = "<input $attr />" . str_replace("<ul ", "<ul data-pw-dropdown-input='#$inputID' ", $out);
		}
	
		$required = $this->dropdownRequired ? 'true' : 'false';
		// script to initialize this dropdown immediately
		$out .= "<script" . ">InputfieldSubmitDropdown.init('#{$this->id}', null, $required);</script>";
		
		return $out; 
	}

	/**
	 * Process input
	 * 
	 * @param WireInputData $input
	 * @return $this
	 * 
	 */
	public function ___processInput(WireInputData $input) {
		
		$this->submitValue = '';
		$name = $this->attr('name');
		$value = $input->$name;
		
		if($value === $this->attr('value')) {
			$this->submitValue = $value;
			return $this;
		} 
		
		if(!count($this->dropdownItems)) return $this;
		
		if(!$this->dropdownSubmit) {
			$name = $this->dropdownInputName;
			$value = $input->$name;
		}
		
		if($value === null) return $this;
		
		foreach($this->dropdownItems as $item) {
			if($value !== $item['value']) continue;
			$this->submitValue = $item['value'];
			break;
		}
		
		return $this; 
	}

	/**
	 * Add a dropdown item to this button
	 * 
	 * @param string $type Either 'link' or 'value'
	 * @param string|int $value
	 * @param string $label
	 * @param string $icon
	 * 
	 */
	protected function addActionItem($type, $value, $label, $icon) {
		if(!$icon) $icon = 'angle-double-right';
		$this->dropdownItems[] = array(
			'type' => $type,
			'value' => $value,
			'label' => $label,
			'icon' => $icon,
		);
	}

	/**
	 * Add a dropdown action item that populates a new 'value' for the submit button
	 * 
	 * This also populates the value to $_POST['_action_value']
	 * 
	 * @param string $value Value to populate to hidden input when dropdown item is selected/clicked.
	 * @param string $label Text label to accompany the item
	 * @param string $icon Icon name (optional)
	 * 
	 */
	public function addActionValue($value, $label, $icon = '') {
		$this->addActionItem('value', $value, $label, $icon);
	}

	/**
	 * Add a dropdown action item that links to a URL
	 *
	 * @param string $url URL to link to
	 * @param string $label Text label to accompany the item
	 * @param string $icon Icon name (optional)
	 *
	 */
	public function addActionLink($url, $label, $icon = '') {
		$this->addActionItem('link', $url, $label, $icon);
	}
	
	/**
	 * #pw-internal
	 * 
	 * @return array()
	 * 
	 */
	static public function getSubmitNames() {
		return self::$submitNames;
	}
	
}
