/**
 * Addons for WPBakery - editor enhancements.
 *
 * Makes the font cards in the "Styles" tab collapsible. The collapse-by-default
 * CSS is gated on the "cafw-accordion-on" class so the controls stay visible
 * if this script fails to load.
 */
(function ($) {
	'use strict';

	// Runtime field alignment. A half-width pair in an edit panel can fall out
	// of line because a description makes one heading taller, or a dropdown is a
	// touch taller than a text field. CSS guesses at this kept failing, so we
	// measure the real boxes instead: for each row of half-width fields, give
	// every heading the row's tallest heading height and every input the row's
	// tallest input height. That lines the boxes up whatever the cause, and it
	// is harmless on other elements' panels (it just aligns those too).
	(function fieldAligner() {
		// Line up a pair by matching three things between the two columns:
		//   1. column padding-top - WPBakery's ".vc_column + .vc_column" rule
		//      gives every column after the first a 15px top pad, so the left
		//      field of a top row (the very first column, no pad) sits higher
		//      than its right neighbour. This is the main offender.
		//   2. heading height - a description makes one heading taller.
		//   3. input height - a dropdown is a touch taller than a text field.
		// Equalising all three lines the boxes up top and bottom, with each
		// heading moving down with its box.
		function equalizePair(a, b) {
			// Padding: read the columns' current top padding and raise the smaller to
			// the larger. We never zero it first - resetting to '' then writing back can
			// briefly expose 0 (e.g. while WPBakery re-renders a field on a dependency
			// change) and pin a field at no-padding. Equal pairs are left untouched, so a
			// naturally-aligned row (both carry WPBakery's 15px .vc_column+.vc_column pad)
			// stays put; only a first-column field with no pad is raised to match.
			var padA = parseFloat(window.getComputedStyle(a).paddingTop) || 0;
			var padB = parseFloat(window.getComputedStyle(b).paddingTop) || 0;
			var pad = Math.max(padA, padB);
			if (pad && padA < pad) {
				a.style.paddingTop = pad + 'px';
			}
			if (pad && padB < pad) {
				b.style.paddingTop = pad + 'px';
			}
			
			// Heading + input heights: a description can make one heading taller and a
			// dropdown is a touch taller than a text field. Reset to read the natural
			// height (safe - a transient just re-measures), then give both the taller.
			var parts = [a, b].map(function (col) {
				var head = col.querySelector('.wpb-param-heading');
				var input = col.querySelector(
					'.edit_form_line .wpb-textinput, .edit_form_line .wpb-select'
				);
				if (head) {
					head.style.height = '';
					// Pin a description's hidden .tooltip-content out of flow before
					// measuring (WPBakery's Popper does this eventually) so it cannot
					// inflate the heading height.
					var tip = head.querySelector('.tooltip-content');
					if (tip) {
						tip.style.position = 'absolute';
					}
				}
				if (input) {
					input.style.height = '';
				}
				return { col: col, head: head, input: input };
			});
			var maxHead = 0;
			var maxInput = 0;
			parts.forEach(function (p) {
				if (p.head) {
					maxHead = Math.max(maxHead, p.head.offsetHeight);
				}
				if (p.input) {
					maxInput = Math.max(maxInput, p.input.offsetHeight);
				}
			});
			parts.forEach(function (p) {
				if (p.head && maxHead) {
					p.head.style.boxSizing = 'border-box';
					p.head.style.height = maxHead + 'px';
				}
				if (p.input && maxInput) {
					p.input.style.boxSizing = 'border-box';
					p.input.style.height = maxInput + 'px';
				}
			});
		}

		function alignForms() {
			var forms = document.querySelectorAll('.vc_edit-form-tab');
			Array.prototype.forEach.call(forms, function (form) {
				if (!form.offsetParent) {
					return;
				}
				var fw = form.offsetWidth;
				if (!fw) {
					return;
				}
				// Walk the fields in DOM order. Consecutive half-width fields pair
				// up two-per-row (the flex grid wraps them that way); a full-width
				// or hidden field ends the current run. Pairing by source order -
				// not by current vertical position - is what lets us realign a pair
				// that has already drifted apart.
				var run = [];
				var flush = function () {
					for (var j = 0; j + 1 < run.length; j += 2) {
						equalizePair(run[j], run[j + 1]);
					}
					run = [];
				};
				Array.prototype.forEach.call(form.children, function (col) {
					if (!col.classList || !col.classList.contains('vc_shortcode-param')) {
						return;
					}
					if (!col.offsetParent || col.offsetWidth === 0) {
						return; // hidden (e.g. a dependency) - skip, don't break the run
					}
					if (col.offsetWidth > fw * 0.7) {
						flush(); // full-width field ends the run
						return;
					}
					run.push(col);
				});
				flush();
			});
		}

		var pending;
		function schedule() {
			window.clearTimeout(pending);
			pending = window.setTimeout(alignForms, 120);
			// Also run a couple of times as the panel/tab settles in: when it
			// first opens or animates in a form can still be hidden (so it is
			// skipped), and switching tabs only toggles a class, not the DOM.
			window.setTimeout(alignForms, 350);
			window.setTimeout(alignForms, 700);
		}

		if (window.MutationObserver) {
			// childList catches the panel opening; the "class" attribute filter
			// catches tab switches (vc_active toggles) - and excludes the style
			// attribute we write ourselves, so there is no feedback loop.
			new MutationObserver(schedule).observe(document.documentElement, {
				childList: true,
				subtree: true,
				attributes: true,
				attributeFilter: ['class']
			});
		}
		window.addEventListener('resize', schedule);
		schedule();
	})();

	document.documentElement.classList.add('cafw-accordion-on');

	var HEADER = '.wpb-modal-section [data-param_type="font_container"][data-vc-shortcode-param-name$="_font"] > .wpb-param-heading';

	$(document).on('click', HEADER, function (e) {
		e.preventDefault();
		$(this).closest('.wpb-modal-section').toggleClass('cafw-open');
	});

	// Layout picker (cafw_layout param): clicking a thumbnail selects it and
	// writes its value into the hidden .wpb_vc_param_value input. Params named
	// in data-resets snap back to their first option, so the element lands in
	// exactly the arrangement the thumbnail shows (still editable afterwards).
	$(document).on('click', '.cafw-layout-option', function (e) {
		e.preventDefault();
		var $option = $(this);
		var $picker = $option.closest('.cafw-layout-picker');
		$picker.find('.cafw-layout-option').removeClass('is-selected').attr('aria-pressed', 'false');
		$option.addClass('is-selected').attr('aria-pressed', 'true');
		$picker.find('input.wpb_vc_param_value').val($option.data('value')).trigger('change');

		var $panel = $picker.closest('.vc_ui-panel-window, .wpb-modal');
		if (!$panel.length) {
			$panel = $(document.body);
		}
		($picker.attr('data-resets') || '').split(',').forEach(function (paramName) {
			if (!paramName) {
				return;
			}
			var $field = $panel.find('[data-vc-shortcode-param-name="' + paramName + '"]')
				.find('select.wpb_vc_param_value').first();
			if ($field.length) {
				var firstValue = $field.find('option').first().val();
				if ($field.val() !== firstValue) {
					$field.val(firstValue).trigger('change');
				}
			}
		});
	});

	// ----- Backend-editor live previews -----------------------------------
	// WPBakery's backend editor shows every element as a plain grey block, so
	// setting changes are invisible. For our elements we render the shortcode
	// for real (via admin-ajax) and show the result inside the block, updated
	// whenever the element's params change.
	(function () {
		if (typeof window.cafwEditor === 'undefined') {
			return;
		}

		var debounceTimers = {};

		var requestPreview = function (model) {
			var params = model.get('params') || {};
			var clean = {};
			Object.keys(params).forEach(function (key) {
				if (typeof params[key] === 'string' || typeof params[key] === 'number') {
					clean[key] = params[key];
				}
			});

			$.post(cafwEditor.ajaxUrl, {
				action: 'cafw_render_preview',
				nonce: cafwEditor.nonce,
				tag: model.get('shortcode'),
				params: JSON.stringify(clean)
			}).done(function (response) {
				if (!response || !response.success) {
					return;
				}
				var $wrapper = $('[data-model-id="' + model.get('id') + '"]')
					.find('.wpb_element_wrapper').first();
				if (!$wrapper.length) {
					return;
				}
				var $preview = $wrapper.children('.cafw-backend-preview');
				if (!$preview.length) {
					$preview = $('<div class="cafw-backend-preview" aria-hidden="true"></div>').appendTo($wrapper);
				}
				$preview.html(response.data);
			});
		};

		var schedule = function (model) {
			if (!model || !model.get || cafwEditor.previewTags.indexOf(model.get('shortcode')) === -1) {
				return;
			}
			var id = model.get('id');
			window.clearTimeout(debounceTimers[id]);
			debounceTimers[id] = window.setTimeout(function () {
				requestPreview(model);
			}, 300);
		};

		var bind = function () {
			if (!window.vc || !vc.shortcodes || typeof vc.shortcodes.on !== 'function') {
				return false;
			}
			// Backend editor only - in the frontend editor the page itself is
			// the live preview. (#wpbakery_content since WPBakery 8,
			// #visual_composer_content before it.)
			if (!document.getElementById('wpbakery_content') && !document.getElementById('visual_composer_content')) {
				return true;
			}
			vc.shortcodes.on('add change', schedule);
			vc.shortcodes.each(schedule);
			// One late pass for blocks whose DOM was still building above.
			window.setTimeout(function () {
				vc.shortcodes.each(schedule);
			}, 2000);
			return true;
		};

		// WPBakery's editor app boots after us; poll briefly until it exists.
		var tries = 0;
		var timer = window.setInterval(function () {
			tries++;
			if (bind() || tries > 60) {
				window.clearInterval(timer);
			}
		}, 250);
	})();
})(jQuery);

( function () {
	'use strict';
	document.addEventListener( 'click', function ( e ) {
		var sw = e.target.closest( '.cafw-visibility-switch' );
		if ( ! sw ) {
			return;
		}
		e.preventDefault();
		var row = sw.closest( '.cafw-visibility-row' );
		var picker = sw.closest( '.cafw-visibility' );
		row.classList.toggle( 'is-hidden' );
		var hidden = row.classList.contains( 'is-hidden' );
		sw.setAttribute( 'aria-checked', hidden ? 'false' : 'true' );
		var state = row.querySelector( '.cafw-visibility-row__state' );
		if ( state ) {
			state.textContent = hidden ? ( picker.dataset.hidden || 'Hidden' ) : ( picker.dataset.shown || 'Visible' );
		}
		var keys = Array.prototype.map.call(
			picker.querySelectorAll( '.cafw-visibility-row.is-hidden' ),
			function ( el ) { return el.getAttribute( 'data-key' ); }
		);
		var input = picker.querySelector( '.wpb_vc_param_value' );
		input.value = keys.join( ',' );
		input.dispatchEvent( new Event( 'change', { bubbles: true } ) );
	} );
}() );
