/**
 * WrenchPress confirmation dialogs: progressive enhancement only.
 *
 * Replaces the browser's native confirm dialog, which cannot be labelled, cannot
 * be styled, and is a SC 4.1.2 failure. What replaces it is a native <dialog>
 * opened with showModal().
 *
 * The native call is deliberately not spelled out anywhere in this repo, because
 * a regression gate greps for it. A comment that names it would keep that gate
 * permanently red and hide the day someone reintroduces a real one.
 *
 * READ THIS BEFORE YOU "IMPROVE" THIS FILE.
 *
 * showModal() already gives you, correctly and for free:
 *  - a focus trap (Tab and Shift+Tab stay inside the dialog),
 *  - background inertness (everything outside the dialog stops being clickable
 *    and stops being reachable by assistive tech),
 *  - dismissal on the Esc key,
 *  - focus restoration to the invoker on close,
 *  - top-layer stacking, which paints above the WP admin bar with no stacking
 *    override of any kind.
 *
 * So this file MUST NOT contain a hand-rolled focus trap, a Tab-key handler, an
 * inert sweep over sibling nodes, an Esc keydown listener, or a stacking
 * override. If you find yourself writing any of those, stop: you are rebuilding
 * something the browser already ships, and you will rebuild it worse.
 *
 * Two further rules that are not negotiable:
 *  - The <h2> is the FIRST child of the dialog. A close button placed before it
 *    triggers a known VoiceOver bug in Chrome and Firefox where the
 *    aria-labelledby target is skipped and the dialog announces anonymously.
 *  - Copy is written with textContent, never parsed as markup.
 *
 * This is a courtesy layer, not a security boundary. Every trigger keeps its
 * original mechanics: a <button> stays inside its <form>, a link keeps its
 * href. If this script never loads, both actions still work, and both still hit
 * the server-side nonce and capability checks that are the real gate.
 *
 * Copy is supplied by the trigger's data attributes so it stays translatable in
 * PHP and is never hardcoded here:
 *
 *   data-wrenchpress-confirm         the body text (its presence is the hook)
 *   data-wrenchpress-confirm-title   the <h2>
 *   data-wrenchpress-confirm-cancel  the cancel button label
 *   data-wrenchpress-confirm-ok      the confirm button label
 */
( function () {
	'use strict';

	var TITLE_ID = 'wrenchpress-dialog-title';

	var dialog = null;
	var titleEl = null;
	var bodyEl = null;
	var cancelBtn = null;
	var confirmBtn = null;
	var invoker = null;

	/**
	 * Build the single dialog this page uses and append it to the body.
	 *
	 * One dialog is reused by every trigger. Building one per trigger would put
	 * N copies of the same title id in the document and break aria-labelledby.
	 *
	 * @return {void}
	 */
	function build() {
		dialog = document.createElement( 'dialog' );
		dialog.className = 'wrenchpress-dialog';
		dialog.setAttribute( 'aria-labelledby', TITLE_ID );

		// The heading is the first child. Nothing may precede it.
		titleEl = document.createElement( 'h2' );
		titleEl.id = TITLE_ID;
		dialog.appendChild( titleEl );

		bodyEl = document.createElement( 'p' );
		bodyEl.className = 'wrenchpress-dialog__body';
		dialog.appendChild( bodyEl );

		var actions = document.createElement( 'div' );
		actions.className = 'wrenchpress-dialog__actions';

		/*
		 * Cancel is first in DOM order, so it takes the browser's default
		 * initial focus. That is the safe landing spot for a confirmation that
		 * sits next to a destructive action.
		 */
		cancelBtn = document.createElement( 'button' );
		cancelBtn.type = 'button';
		cancelBtn.className = 'button';
		cancelBtn.setAttribute( 'data-dialog-cancel', '' );
		actions.appendChild( cancelBtn );

		confirmBtn = document.createElement( 'button' );
		confirmBtn.type = 'button';
		confirmBtn.className = 'button button-primary';
		confirmBtn.setAttribute( 'data-dialog-confirm', '' );
		actions.appendChild( confirmBtn );

		dialog.appendChild( actions );
		document.body.appendChild( dialog );

		cancelBtn.addEventListener( 'click', function () {
			dialog.close();
		} );

		confirmBtn.addEventListener( 'click', function () {
			var target = invoker;

			dialog.close();
			proceed( target );
		} );

		/*
		 * Fires on every close path: the cancel button, the Esc key, and the
		 * confirm button. Browsers restore focus to the invoker per spec; doing
		 * it explicitly costs two lines and removes all doubt.
		 */
		dialog.addEventListener( 'close', function () {
			if ( invoker && typeof invoker.focus === 'function' ) {
				invoker.focus();
			}
		} );
	}

	/**
	 * Carry out the action the trigger would have carried out on its own.
	 *
	 * The trigger's native mechanics are preserved, not replaced: a submit
	 * button submits its form, a link navigates to its href.
	 *
	 * @param {Element} trigger The element that opened the dialog.
	 * @return {void}
	 */
	function proceed( trigger ) {
		if ( ! trigger ) {
			return;
		}

		var form = trigger.form || trigger.closest( 'form' );

		if ( 'BUTTON' === trigger.tagName && form ) {
			form.submit();
			return;
		}

		if ( 'A' === trigger.tagName && trigger.href ) {
			window.location.assign( trigger.href );
		}
	}

	/**
	 * Open the dialog for a trigger, taking its copy from the data attributes.
	 *
	 * @param {Element} trigger The element that was activated.
	 * @return {void}
	 */
	function open( trigger ) {
		invoker = trigger;

		titleEl.textContent =
			trigger.getAttribute( 'data-wrenchpress-confirm-title' ) || '';
		bodyEl.textContent =
			trigger.getAttribute( 'data-wrenchpress-confirm' ) || '';
		cancelBtn.textContent =
			trigger.getAttribute( 'data-wrenchpress-confirm-cancel' ) || '';
		confirmBtn.textContent =
			trigger.getAttribute( 'data-wrenchpress-confirm-ok' ) || '';

		dialog.showModal();
	}

	function init() {
		/*
		 * A browser without native dialog support keeps the unenhanced
		 * behaviour: the button submits and the link navigates, with no
		 * confirmation step. Degrading to "the action just happens" is correct,
		 * because the confirmation was never the thing protecting the action.
		 */
		if ( typeof window.HTMLDialogElement === 'undefined' ) {
			return;
		}

		build();

		// Delegated, so triggers rendered after load are picked up too.
		document.addEventListener( 'click', function ( event ) {
			var trigger = event.target.closest( '[data-wrenchpress-confirm]' );

			if ( ! trigger ) {
				return;
			}

			event.preventDefault();
			open( trigger );
		} );
	}

	if ( 'loading' === document.readyState ) {
		document.addEventListener( 'DOMContentLoaded', init );
	} else {
		init();
	}
} )();
