/**
 * DrezSync booking field.
 *
 * Renders the booking field's date and time picker, loads the open times for
 * the chosen date from the server, and keeps the selection in a hidden input
 * for submission. After a failed submit the field reloads its list of times, so
 * nothing has to be re-entered. Styles are inline so they render the same
 * regardless of theme or CSS caching.
 */
( function( $ ) {
	$( function() {
		if ( typeof drezsyncBooking === 'undefined' ) {
			return;
		}

		var pillBase = 'appearance:none;-webkit-appearance:none;border:1px solid #d5d5d5;background:#fff;color:#434343;padding:8px 15px;border-radius:999px;font-size:14px;line-height:1.2;cursor:pointer;';

		// Every booking field on the form registers here so they can see each
		// other's current selection and re-mark when anything changes.
		var registry = [];

		function ymd( d ) {
			return d.getFullYear() + '-' +
				String( d.getMonth() + 1 ).padStart( 2, '0' ) + '-' +
				String( d.getDate() ).padStart( 2, '0' );
		}

		var min = ymd( new Date() );

		function addDays( dateStr, n ) {
			var p = dateStr.split( '-' );
			var d = new Date( parseInt( p[0], 10 ), parseInt( p[1], 10 ) - 1, parseInt( p[2], 10 ) );
			d.setDate( d.getDate() + n );
			return ymd( d );
		}

		function pretty( t ) {
			var parts = t.split( ':' );
			var h     = parseInt( parts[0], 10 );
			var ampm  = h >= 12 ? 'PM' : 'AM';
			var h12   = h % 12;
			if ( 0 === h12 ) {
				h12 = 12;
			}
			return h12 + ':' + parts[1] + ' ' + ampm;
		}

		function fieldIdFrom( $value ) {
			var name    = $value.attr( 'name' ) || '';
			var matched = name.match( /input_(\d+)/ );
			return matched ? matched[1] : 0;
		}

		// Seconds since epoch for a date and time, in the browser's local clock.
		// Only used to compare intervals against each other, so the zone cancels out.
		function ts( dateStr, timeStr ) {
			var d = dateStr.split( '-' );
			var t = timeStr.split( ':' );
			return new Date( parseInt( d[0], 10 ), parseInt( d[1], 10 ) - 1, parseInt( d[2], 10 ), parseInt( t[0], 10 ), parseInt( t[1], 10 ), 0 ).getTime() / 1000;
		}


		// Ask every field to re-mark itself after any change.
		function recompute() {
			$.each( registry, function( i, f ) {
				f.refresh();
			} );
		}

		function initSlot( $wrap ) {
			var $value  = $wrap.find( '.drezsync-booking__value' );
			var $date   = $wrap.find( '.drezsync-booking__date' );
			var $times  = $wrap.find( '.drezsync-booking__times' );
			var color   = $wrap.attr( 'data-color' ) || '#ff4e43';
			var slotLen = parseInt( $wrap.attr( 'data-slot' ), 10 ) || 30;
			var pillOn  = pillBase + 'background:' + color + ';border-color:' + color + ';color:#fff;';
			var pillErr = pillBase + 'background:#b32d23;border-color:#b32d23;color:#fff;';
			var pillDim = pillBase + 'opacity:.4;cursor:not-allowed;text-decoration:line-through;';
			var fieldId = fieldIdFrom( $value );
			var calField = $wrap.attr( 'data-cal-field' ) || '';
			var srcSel   = calField ? '[name="input_' + calField + '"],[name^="input_' + calField + '."]' : '';
			var $msg    = $( '<p class="drezsync-booking__msg" style="margin:6px 0 0;font-size:14px;"></p>' ).appendTo( $wrap );

			function sourceVal() {
				if ( ! calField ) {
					return '';
				}
				var $els = $( srcSel );
				var $chk = $els.filter( ':checked' );
				if ( $chk.length ) {
					return $chk.first().val();
				}
				var v = $els.val();
				return ( null == v ) ? '' : v;
			}

			$date.attr( 'min', min );

			function hint( text ) {
				$times.html( '<p class="drezsync-booking__hint" style="margin:0;font-size:14px;opacity:.65;"></p>' );
				$times.find( '.drezsync-booking__hint' ).text( text );
			}

			function setMsg( text, col ) {
				if ( ! text ) {
					$msg.text( '' ).hide();
					return;
				}
				$msg.text( text ).css( 'color', col || '' ).show();
			}

			function chosen() {
				var v = $value.val() || '';
				var m = v.match( /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})$/ );
				return m ? { date: m[1], time: m[2] } : null;
			}


			// Dim the times that clash with another field, warn if our own pick clashes.
			function refresh() {
				var date = $date.val();
				var pick = chosen();

				$times.find( '.drezsync-booking__time-pill' ).each( function() {
					var $b = $( this );
					var t  = $b.attr( 'data-time' );
					if ( ! t || ! date ) {
						return;
					}
					var isPick  = pick && pick.time === t;
					var clashes = false;

					if ( isPick ) {
						$b.attr( 'style', clashes ? pillErr : pillOn ).attr( 'aria-pressed', 'true' ).removeAttr( 'data-clash' ).removeAttr( 'aria-disabled' );
					} else if ( clashes ) {
						$b.attr( 'style', pillDim ).attr( 'data-clash', '1' ).attr( 'aria-disabled', 'true' ).attr( 'aria-pressed', 'false' );
					} else {
						$b.attr( 'style', pillBase ).attr( 'aria-pressed', 'false' ).removeAttr( 'data-clash' ).removeAttr( 'aria-disabled' );
					}
				} );

				setMsg( '' );
			}

			function render( times ) {
				$times.empty();
				$.each( times, function( i, t ) {
					$( '<button type="button" class="drezsync-booking__time-pill"></button>' )
						.attr( 'data-time', t )
						.attr( 'aria-pressed', 'false' )
						.attr( 'style', pillBase )
						.text( pretty( t ) )
						.appendTo( $times );
				} );
				recompute();
			}

			// Load open times for the current date. Optionally keep a prior pick
			// selected if it is still available; otherwise clear it.
			function load( keepTime ) {
				var date = $date.val();
				if ( ! date ) {
					hint( drezsyncBooking.pickDate );
					recompute();
					return;
				}
				var data = {
					action:  'drezsync_availability',
					nonce:   drezsyncBooking.nonce,
					formId:  drezsyncBooking.formId,
					fieldId: fieldId,
					date:    date
				};
				if ( calField ) {
					var sv = sourceVal();
					if ( ! sv ) {
						$value.val( '' );
						hint( drezsyncBooking.pickSource );
						recompute();
						return;
					}
					data.calValue = sv;
				}
				hint( drezsyncBooking.loading );
				$.post( drezsyncBooking.ajaxUrl, data ).done( function( res ) {
					if ( res && res.success && res.data.times && res.data.times.length ) {
						render( res.data.times );
						if ( keepTime ) {
							$value.val( res.data.times.indexOf( keepTime ) !== -1 ? ( date + ' ' + keepTime ) : '' );
							recompute();
						}
					} else {
						hint( drezsyncBooking.none );
						recompute();
					}
				} ).fail( function() {
					hint( drezsyncBooking.none );
					recompute();
				} );
			}

			$date.on( 'change', function() {
				$value.val( '' );
				load();
			} );

			$times.on( 'click', '.drezsync-booking__time-pill', function() {
				var $btn = $( this );
				if ( '1' === $btn.attr( 'data-clash' ) ) {
					return; // A clashing time cannot be picked.
				}
				var date = $date.val();
				var time = $btn.attr( 'data-time' );
				$value.val( ( date && time ) ? ( date + ' ' + time ) : '' );
				recompute();
			} );

			if ( calField ) {
				$( document ).on( 'change', srcSel, function() {
					$value.val( '' );
					if ( $date.val() ) {
						load();
					} else {
						hint( drezsyncBooking.pickDate );
					}
					recompute();
				} );
			}

			var reg = { refresh: refresh };
			registry.push( reg );

			// On first load, if a date is already set (a saved value or a
			// re-render after a failed submit), reload the full list of times
			// and keep the prior pick, so nothing has to be re-entered.
			$times.on( 'focusin', '.drezsync-booking__time-pill', function() { $( this ).css( { 'outline': '3px solid ' + color, 'outline-offset': '2px' } ); } ).on( 'focusout', '.drezsync-booking__time-pill', function() { $( this ).css( { 'outline': '', 'outline-offset': '' } ); } );

				var start = chosen();
			if ( $date.val() ) {
				load( start ? start.time : null );
			}
		}


		$( '.drezsync-booking' ).each( function() {
			var $wrap = $( this );
			initSlot( $wrap );
		} );
	} );
}( jQuery ) );
