<?php
/**
 * Forms admin page.
 *
 * @package SubmissionInbox
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class SUBIN_FormsPage {
	const PAGE_SLUG = 'submission-inbox-forms';

	/**
	 * Form repository.
	 *
	 * @var \SUBIN\Forms\FormRepository
	 */
	private $forms;

	/**
	 * Field repository.
	 *
	 * @var \SUBIN\Forms\FormFieldRepository
	 */
	private $fields;

	/**
	 * Template repository.
	 *
	 * @var \SUBIN\Forms\FormTemplateRepository
	 */
	private $templates;

	/**
	 * Auto-reply template repository.
	 *
	 * @var \SUBIN\AutoReplies\AutoReplyTemplateRepository
	 */
	private $auto_reply_templates;

	/**
	 * Auto-reply rule repository.
	 *
	 * @var \SUBIN\AutoReplies\AutoReplyRuleRepository
	 */
	private $auto_reply_rules;

	/**
	 * Auto-reply provider registry.
	 *
	 * @var \SUBIN\AutoReplies\AutoReplyFormProviderRegistry
	 */
	private $auto_reply_providers;

	/**
	 * Settings repository.
	 *
	 * @var \SUBIN_SettingsRepository
	 */
	private $settings;

	/**
	 * Whether state-changing request handling has already run.
	 *
	 * @var bool
	 */
	private $request_handled = false;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->forms          = new \SUBIN\Forms\FormRepository();
		$this->fields         = new \SUBIN\Forms\FormFieldRepository();
		$this->templates      = new \SUBIN\Forms\FormTemplateRepository( $this->forms, $this->fields );
		$this->auto_reply_templates = new \SUBIN\AutoReplies\AutoReplyTemplateRepository();
		$this->auto_reply_rules = new \SUBIN\AutoReplies\AutoReplyRuleRepository();
		$this->auto_reply_providers = new \SUBIN\AutoReplies\AutoReplyFormProviderRegistry( $this->forms, $this->fields );
		$this->settings       = new \SUBIN_SettingsRepository();
	}

	/**
	 * Render page.
	 *
	 * @return void
	 */
	public function render() {
		if ( ! current_user_can( SUBIN_AdminMenu::CAPABILITY ) ) {
			wp_die( esc_html__( 'You do not have permission to perform this action.', 'submission-inbox' ) );
		}

		$this->handle_request();

		$view = SUBIN_Request::get_key( 'view' );

		if ( 'templates' === $view ) {
			$this->render_templates_view();
			return;
		}

		if ( 'template-preview' === $view ) {
			$this->render_template_preview_view();
			return;
		}

		if ( 'edit' === $view ) {
			$this->render_edit_view();
			return;
		}

		if ( 'preview' === $view ) {
			$this->render_preview_view();
			return;
		}

		$this->render_default_view();
	}

	/**
	 * Handle redirects and state changes before admin HTML is sent.
	 *
	 * @return void
	 */
	public function handle_request() {
		if ( $this->request_handled ) {
			return;
		}

		$this->request_handled = true;

		if ( ! current_user_can( SUBIN_AdminMenu::CAPABILITY ) ) {
			wp_die( esc_html__( 'You do not have permission to perform this action.', 'submission-inbox' ) );
		}

		if ( $this->is_legacy_auto_replies_tab_request() ) {
			$this->safe_admin_redirect( $this->auto_replies_url() );
		}

		$this->handle_post_action();
		$this->handle_action();
	}

	/**
	 * Save GrapesJS project data for the Design Studio editor.
	 *
	 * @return void
	 */
	public function ajax_save_grapesjs_project() {
		if ( ! current_user_can( SUBIN_AdminMenu::CAPABILITY ) ) {
			$this->send_grapesjs_project_error( __( 'Design Studio could not be saved.', 'submission-inbox' ), 403 );
		}

		$form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
		$nonce   = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';

		if ( $form_id <= 0 || ! wp_verify_nonce( $nonce, 'subin_save_grapesjs_project_' . $form_id ) ) {
			$this->send_grapesjs_project_error( __( 'Design Studio could not be saved.', 'submission-inbox' ), 403 );
		}

		$form = $this->forms->get( $form_id );
		if ( ! $form ) {
			$this->send_grapesjs_project_error( __( 'Form not found.', 'submission-inbox' ), 404 );
		}

		$raw_project = isset( $_POST['project_json'] ) ? wp_unslash( $_POST['project_json'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Raw JSON is decoded and sanitized recursively below.
		if ( ! is_string( $raw_project ) || '' === trim( $raw_project ) ) {
			$this->send_grapesjs_project_error( __( 'Project data is missing.', 'submission-inbox' ), 400 );
		}

		$project = json_decode( $raw_project, true );
		if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $project ) ) {
			$this->send_grapesjs_project_error( __( 'Project data is not valid JSON.', 'submission-inbox' ), 400 );
		}

		$project = $this->sanitize_grapesjs_project( $project );
		if ( is_wp_error( $project ) ) {
			$this->send_grapesjs_project_error( $project->get_error_message(), 400 );
		}

		if ( ! $this->sync_grapesjs_project_fields( $form_id, $project ) ) {
			$this->send_grapesjs_project_error( __( 'Design Studio fields could not be saved.', 'submission-inbox' ), 500 );
		}

		$settings = $this->forms->decode_settings( $form );
		if ( ! is_array( $settings['design_studio'] ?? null ) ) {
			$settings['design_studio'] = array();
		}

		$settings['design_studio']['grapesjs_project'] = $project;
		$messages_update = $this->messages_from_grapesjs_project( $project );
		if ( ! empty( $messages_update['messages'] ) ) {
			$settings['messages'] = array_merge( is_array( $settings['messages'] ?? null ) ? $settings['messages'] : array(), $messages_update['messages'] );
		}

		$updates = array(
			'settings_json' => $settings,
		);
		if ( isset( $messages_update['success_message'] ) ) {
			$updates['success_message'] = $messages_update['success_message'];
		}
		if ( isset( $messages_update['header_title'] ) ) {
			$updates['header_title'] = $messages_update['header_title'];
		}
		if ( isset( $messages_update['header_description'] ) ) {
			$updates['header_description'] = $messages_update['header_description'];
		}

		if ( ! $this->forms->update_partial( $form_id, $updates ) ) {
			$this->send_grapesjs_project_error( __( 'Design Studio could not be saved.', 'submission-inbox' ), 500 );
		}

		wp_send_json_success(
			array(
				'message' => __( 'Design Studio saved.', 'submission-inbox' ),
			)
		);
	}

	/**
	 * Send a normalized GrapesJS project AJAX error.
	 *
	 * @param string $message Error message.
	 * @param int    $status_code HTTP status code.
	 * @return void
	 */
	private function send_grapesjs_project_error( $message, $status_code = 400 ) {
		wp_send_json_error(
			array(
				'message' => $message,
			),
			absint( $status_code )
		);
	}

	/**
	 * Extract saved global messages from the controlled GrapesJS form wrapper.
	 *
	 * @param array<string,mixed> $project Project data.
	 * @return array{messages?:array<string,string>,success_message?:string}
	 */
	private function messages_from_grapesjs_project( array $project ) {
		$attributes = $this->grapesjs_form_wrapper_attributes( $project );
		$messages   = array();
		$result     = array();

		if ( isset( $attributes['data-subin-validation-error-message'] ) ) {
			$messages['validation_error'] = sanitize_textarea_field( $attributes['data-subin-validation-error-message'] );
		}
		if ( isset( $attributes['data-subin-required-error-message'] ) ) {
			$messages['required_field_error'] = sanitize_text_field( $attributes['data-subin-required-error-message'] );
		}
		if ( isset( $attributes['data-subin-spam-error-message'] ) ) {
			$messages['spam_protection_error'] = sanitize_textarea_field( $attributes['data-subin-spam-error-message'] );
		}
		if ( isset( $attributes['data-subin-success-message'] ) ) {
			$result['success_message'] = sanitize_textarea_field( $attributes['data-subin-success-message'] );
		}
		if ( isset( $attributes['data-subin-form-title'] ) ) {
			$result['header_title'] = sanitize_text_field( $attributes['data-subin-form-title'] );
		}
		if ( isset( $attributes['data-subin-form-description'] ) ) {
			$result['header_description'] = sanitize_textarea_field( $attributes['data-subin-form-description'] );
		}
		if ( ! empty( $messages ) ) {
			$result['messages'] = $messages;
		}

		return $result;
	}

	/**
	 * Find form wrapper attributes in native GrapesJS project data.
	 *
	 * @param mixed $node Project node.
	 * @return array<string,string>
	 */
	private function grapesjs_form_wrapper_attributes( $node ) {
		if ( ! is_array( $node ) ) {
			return array();
		}

		if ( $this->is_grapesjs_component_array( $node ) ) {
			$attributes = is_array( $node['attributes'] ?? null ) ? $node['attributes'] : array();
			$type       = sanitize_key( (string) ( $attributes['data-subin-component'] ?? $this->grapesjs_component_key_from_type( $node['type'] ?? '' ) ) );
			if ( 'form-wrapper' === $type ) {
				return $attributes;
			}
		}

		foreach ( $node as $child ) {
			$attributes = $this->grapesjs_form_wrapper_attributes( $child );
			if ( ! empty( $attributes ) ) {
				return $attributes;
			}
		}

		return array();
	}

	/**
	 * Attach new controlled GrapesJS field blocks to real form fields.
	 *
	 * @param int                 $form_id Form ID.
	 * @param array<string,mixed> $project Project data.
	 * @return bool
	 */
	private function sync_grapesjs_project_fields( $form_id, array &$project ) {
		$existing_fields = $this->fields->get_by_form_id( $form_id );
		$field_map       = array();
		$used_field_ids  = array();

		foreach ( $existing_fields as $field ) {
			$field_id  = absint( $field['id'] ?? 0 );
			$field_key = sanitize_key( $field['field_key'] ?? '' );
			if ( '' !== $field_key && $field_id > 0 ) {
				$field_map[ $field_key ] = $field;
			}
		}

		return $this->sync_grapesjs_components_fields( $form_id, $project, $field_map, $used_field_ids );
	}

	/**
	 * Recursively attach controlled GrapesJS field components to fields.
	 *
	 * @param int                                      $form_id Form ID.
	 * @param mixed                                    $node Project node.
	 * @param array<string,array<string,mixed>>        $field_map Existing fields by key.
	 * @param array<int,bool>                          $used_field_ids Field IDs already referenced.
	 * @return bool
	 */
	private function sync_grapesjs_components_fields( $form_id, &$node, array &$field_map, array &$used_field_ids ) {
		if ( ! is_array( $node ) ) {
			return true;
		}

		if ( $this->is_grapesjs_component_array( $node ) ) {
			if ( ! $this->sync_grapesjs_field_component( $form_id, $node, $field_map, $used_field_ids ) ) {
				return false;
			}
		}

		foreach ( $node as &$child ) {
			if ( is_array( $child ) && ! $this->sync_grapesjs_components_fields( $form_id, $child, $field_map, $used_field_ids ) ) {
				return false;
			}
		}
		unset( $child );

		return true;
	}

	/**
	 * Attach one controlled GrapesJS field component to a stored form field.
	 *
	 * @param int                               $form_id Form ID.
	 * @param array<string,mixed>               $component Component.
	 * @param array<string,array<string,mixed>> $field_map Existing fields by key.
	 * @param array<int,bool>                   $used_field_ids Field IDs already referenced.
	 * @return bool
	 */
	private function sync_grapesjs_field_component( $form_id, array &$component, array &$field_map, array &$used_field_ids ) {
		$attributes = is_array( $component['attributes'] ?? null ) ? $component['attributes'] : array();
		$subin_type   = sanitize_key( (string) ( $attributes['data-subin-component'] ?? $this->grapesjs_component_key_from_type( $component['type'] ?? '' ) ) );

		if ( 'field' !== $subin_type ) {
			return true;
		}

		$field_id = absint( $attributes['data-subin-field-id'] ?? 0 );
		if ( $field_id > 0 ) {
			$existing = $this->fields->get( $field_id );
			if ( $existing ) {
				$field_data = $this->grapesjs_field_defaults_from_attributes( $attributes, $form_id, $existing );
				if ( ! $this->fields->update( $field_id, $field_data ) ) {
					return false;
				}
				$updated = $this->fields->get( $field_id );
				if ( $updated ) {
					$component['attributes']['data-subin-field-key'] = sanitize_key( $updated['field_key'] ?? $field_data['field_key'] );
					$field_map[ sanitize_key( $updated['field_key'] ?? $field_data['field_key'] ) ] = $updated;
				}
				$used_field_ids[ $field_id ] = true;
				return true;
			}
		}

		$field_key = sanitize_key( $attributes['data-subin-field-key'] ?? '' );
		if ( '' !== $field_key && isset( $field_map[ $field_key ] ) ) {
			$existing_id = absint( $field_map[ $field_key ]['id'] ?? 0 );
			if ( $existing_id > 0 && empty( $used_field_ids[ $existing_id ] ) ) {
				$field_data = $this->grapesjs_field_defaults_from_attributes( $attributes, $form_id, $field_map[ $field_key ] );
				if ( ! $this->fields->update( $existing_id, $field_data ) ) {
					return false;
				}
				$updated = $this->fields->get( $existing_id );
				$component['attributes']['data-subin-field-id'] = (string) $existing_id;
				$component['attributes']['data-subin-field-key'] = sanitize_key( $updated['field_key'] ?? $field_data['field_key'] );
				$used_field_ids[ $existing_id ] = true;
				return true;
			}
		}

		$field_data = $this->grapesjs_field_defaults_from_attributes( $attributes, $form_id );
		$new_id     = $this->fields->create( $form_id, $field_data );
		if ( $new_id <= 0 ) {
			return false;
		}

		$new_field = $this->fields->get( $new_id );
		if ( ! $new_field ) {
			return false;
		}

		$new_key = sanitize_key( $new_field['field_key'] ?? $field_data['field_key'] );
		$component['attributes']['data-subin-field-id'] = (string) $new_id;
		$component['attributes']['data-subin-field-key'] = $new_key;
		$component['attributes']['data-subin-style-id'] = 'field-' . $new_id;
		$field_map[ $new_key ] = $new_field;
		$used_field_ids[ $new_id ] = true;

		return true;
	}

	/**
	 * Build form field defaults from controlled GrapesJS field metadata.
	 *
	 * @param array<string,mixed> $attributes Component attributes.
	 * @param int                 $form_id Form ID.
	 * @param array<string,mixed> $existing Existing field row.
	 * @return array<string,mixed>
	 */
	private function grapesjs_field_defaults_from_attributes( array $attributes, $form_id, array $existing = array() ) {
		$type       = $this->sanitize_choice( $attributes['data-subin-field-type'] ?? 'text', array_keys( $this->field_type_labels() ), 'text' );
		$field_data = empty( $existing ) ? $this->library_field_defaults( $type, $form_id ) : array(
			'field_type'    => $type,
			'field_key'     => sanitize_key( $existing['field_key'] ?? '' ),
			'field_label'   => sanitize_text_field( $existing['field_label'] ?? '' ),
			'placeholder'   => sanitize_text_field( $existing['placeholder'] ?? '' ),
			'help_text'     => sanitize_textarea_field( $existing['help_text'] ?? '' ),
			'is_required'   => ! empty( $existing['is_required'] ),
			'field_options' => is_string( $existing['field_options'] ?? null ) ? $existing['field_options'] : array(),
			'sort_order'    => absint( $existing['sort_order'] ?? 0 ),
		);
		$label      = sanitize_text_field( $attributes['data-subin-label'] ?? ( $attributes['data-subin-field-label'] ?? '' ) );
		$key        = sanitize_key( $attributes['data-subin-field-key'] ?? '' );

		if ( '' !== $label ) {
			$field_data['field_label'] = $label;
		}
		if ( '' !== $key ) {
			$field_data['field_key'] = $key;
		}
		if ( isset( $attributes['data-subin-placeholder'] ) ) {
			$field_data['placeholder'] = sanitize_text_field( $attributes['data-subin-placeholder'] );
		}
		if ( isset( $attributes['data-subin-help-text'] ) ) {
			$field_data['help_text'] = sanitize_textarea_field( $attributes['data-subin-help-text'] );
		}
		if ( 'consent' === $type && isset( $attributes['data-subin-consent-text'] ) ) {
			$field_data['field_label'] = sanitize_text_field( $attributes['data-subin-consent-text'] );
		}

		if ( isset( $attributes['data-subin-required'] ) ) {
			$field_data['is_required'] = '1' === (string) $attributes['data-subin-required'];
		}

		if ( in_array( $type, array( 'select', 'radio', 'checkbox' ), true ) && isset( $attributes['data-subin-options'] ) ) {
			$options = $this->grapesjs_options_from_attribute( $attributes['data-subin-options'] );
			if ( ! empty( $options ) ) {
				$field_data['field_options'] = $options;
			}
		}

		return $field_data;
	}

	/**
	 * Handle POST editor actions.
	 *
	 * @return void
	 */
	private function handle_post_action() {
		$request_method = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) );
		if ( 'POST' !== strtoupper( $request_method ) ) {
			return;
		}

		if ( ! current_user_can( SUBIN_AdminMenu::CAPABILITY ) ) {
			$this->redirect_with_notice( 'permission_denied' );
		}

		$action  = isset( $_POST['subin_editor_action'] ) ? sanitize_key( wp_unslash( $_POST['subin_editor_action'] ) ) : '';
		if ( '' === $action ) {
			return;
		}

		if ( 'use_template' === $action ) {
			$this->handle_use_template_post();
			return;
		}

		$form_id = isset( $_POST['form_id'] ) ? absint( wp_unslash( $_POST['form_id'] ) ) : 0;
		$tab     = isset( $_POST['tab'] ) ? sanitize_key( wp_unslash( $_POST['tab'] ) ) : 'fields';
		$field_id = isset( $_POST['field_id'] ) ? absint( wp_unslash( $_POST['field_id'] ) ) : 0;
		$nonce   = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';

		if ( ! wp_verify_nonce( $nonce, $this->editor_nonce_name( $action, $form_id, $field_id ) ) ) {
			$this->redirect_to_edit( $form_id, $tab, 'security_failed' );
		}

		$form = $this->forms->get( $form_id );
		if ( ! $form ) {
			$this->redirect_with_notice( 'form_not_found' );
		}

		if ( in_array( $action, array( 'save_notification', 'save_settings' ), true ) ) {
			$settings = $this->forms->decode_settings( $form );
			$notify = is_array( $settings['notification'] ?? null ) ? $settings['notification'] : array();
			$recipient_mode = $this->sanitize_notification_recipient_mode(
				sanitize_text_field( wp_unslash( $_POST['notification_recipient_mode'] ?? ( $notify['recipient_mode'] ?? $this->default_notification_recipient_mode_for_form( $form ) ) ) )
			);
			$custom_recipient = sanitize_email( wp_unslash( $_POST['notification_email_preserved'] ?? ( $form['notification_email'] ?? '' ) ) );
			if ( array_key_exists( 'notification_email', $_POST ) ) {
				$custom_recipient = sanitize_email( wp_unslash( $_POST['notification_email'] ) );
			}

			if ( ! empty( $_POST['notification_enabled'] ) ) {
				if ( 'default' === $recipient_mode && ! is_email( $this->default_admin_notification_recipient() ) ) {
					$this->redirect_to_edit( $form_id, 'settings', 'default_notification_recipient_missing' );
				}

				if ( 'custom' === $recipient_mode && ! is_email( $custom_recipient ) ) {
					$this->redirect_to_edit( $form_id, 'settings', 'custom_notification_recipient_invalid' );
				}
			}

			$settings['notification'] = array(
				'use_lead_email_reply_to' => ! empty( $_POST['use_lead_email_reply_to'] ),
				'recipient_mode'          => $recipient_mode,
			);
			if ( array_key_exists( 'auto_reply_enabled', $_POST ) || array_key_exists( 'auto_reply_template_id', $_POST ) || array_key_exists( 'auto_reply_recipient_field_key', $_POST ) || array_key_exists( 'auto_reply_recipient_field_key_preserved', $_POST ) ) {
				$auto_reply = is_array( $settings['auto_reply'] ?? null ) ? $settings['auto_reply'] : array();
				$posted_auto_reply_recipient = $auto_reply['recipient_field_key'] ?? '';
				if ( array_key_exists( 'auto_reply_recipient_field_key_preserved', $_POST ) ) {
					$posted_auto_reply_recipient = sanitize_text_field( wp_unslash( $_POST['auto_reply_recipient_field_key_preserved'] ) );
				}
				if ( array_key_exists( 'auto_reply_recipient_field_key', $_POST ) ) {
					$posted_auto_reply_recipient = sanitize_text_field( wp_unslash( $_POST['auto_reply_recipient_field_key'] ) );
				}
				$auto_reply_enabled   = ! empty( $_POST['auto_reply_enabled'] );
				$auto_reply_recipient = $this->sanitize_auto_reply_recipient_field_key( $form_id, $posted_auto_reply_recipient );
				if ( $auto_reply_enabled && '' === $auto_reply_recipient ) {
					$this->redirect_to_edit( $form_id, 'settings', 'auto_reply_email_field_required' );
				}
				$settings['auto_reply'] = array(
					'enabled'             => $auto_reply_enabled,
					'template_id'         => $this->sanitize_auto_reply_template_id( absint( $_POST['auto_reply_template_id'] ?? ( $auto_reply['template_id'] ?? 0 ) ) ),
					'recipient_field_key' => $auto_reply_recipient,
				);
			}
			$updates = array(
				'notification_enabled' => ! empty( $_POST['notification_enabled'] ),
				'notification_email'   => $custom_recipient,
				'notification_subject' => sanitize_text_field( wp_unslash( $_POST['notification_subject'] ?? '' ) ),
				'settings_json'        => $settings,
			);

			if ( 'save_settings' === $action ) {
				$updates['slug']       = sanitize_title( wp_unslash( $_POST['slug'] ?? '' ) );
				$updates['status']     = sanitize_key( wp_unslash( $_POST['status'] ?? '' ) );
				$updates['save_leads'] = ! empty( $_POST['save_leads'] );
			}

			$updated = $this->forms->update_partial( $form_id, $updates );
			$this->redirect_to_edit( $form_id, 'settings', $updated ? ( 'save_settings' === $action ? 'form_settings_saved' : 'form_email_saved' ) : 'form_save_failed' );
		}

		if ( 'save_publish' === $action ) {
			$updated = $this->forms->update_partial(
				$form_id,
				array(
					'slug'       => sanitize_title( wp_unslash( $_POST['slug'] ?? '' ) ),
					'status'     => sanitize_key( wp_unslash( $_POST['status'] ?? '' ) ),
					'save_leads' => ! empty( $_POST['save_leads'] ),
				)
			);
			$this->redirect_to_edit( $form_id, 'settings', $updated ? 'form_publish_saved' : 'form_save_failed' );
		}

	}

	/**
	 * Handle template creation from the redesigned chooser.
	 *
	 * @return void
	 */
	private function handle_use_template_post() {
		$template_key = isset( $_POST['template_key'] ) ? sanitize_key( wp_unslash( $_POST['template_key'] ) ) : '';
		$nonce        = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';

		if ( ! $this->templates->get_template( $template_key ) ) {
			$this->redirect_to_templates( 'template_not_found' );
		}

		if ( ! wp_verify_nonce( $nonce, 'subin_use_template_' . $template_key ) ) {
			$this->redirect_to_templates( 'security_failed' );
		}

		$form_id = $this->templates->create_form_from_template( $template_key );
		if ( $form_id > 0 ) {
			$this->redirect_to_edit( $form_id, 'design_studio', 'form_created' );
		}

		$this->redirect_to_templates( 'form_create_failed' );
	}

	/**
	 * Handle nonce-protected GET actions.
	 *
	 * @return void
	 */
	private function handle_action() {
		$action = SUBIN_Request::action_key( 'subin_action' );
		if ( '' === $action ) {
			return;
		}

		if ( ! current_user_can( SUBIN_AdminMenu::CAPABILITY ) ) {
			$this->redirect_with_notice( 'permission_denied' );
		}

		if ( 'create_from_template' === $action ) {
			$nonce        = SUBIN_Request::action_text( '_wpnonce' );
			$template_key = SUBIN_Request::action_key( 'template_key' );
			if ( ! $this->templates->get_template( $template_key ) ) {
				$this->redirect_to_templates( 'template_not_found' );
			}

			if ( ! wp_verify_nonce( $nonce, 'subin_use_template_' . $template_key ) ) {
				$this->redirect_to_templates( 'security_failed' );
			}

			$form_id = $this->templates->create_form_from_template( $template_key );
			if ( $form_id > 0 ) {
				$this->redirect_to_edit( $form_id, 'design_studio', 'form_created' );
			}

			$this->redirect_to_templates( 'form_create_failed' );
		}

		$form_id  = SUBIN_Request::action_absint( 'form_id' );
		$field_id = SUBIN_Request::action_absint( 'field_id' );
		$nonce    = SUBIN_Request::action_text( '_wpnonce' );

		if ( ! wp_verify_nonce( $nonce, $this->action_nonce_name( $action, $form_id, $field_id ) ) ) {
			$this->redirect_with_notice( 'security_failed' );
		}

		if ( 'duplicate' === $action ) {
			$new_form_id = $this->forms->duplicate( $form_id );
			$this->redirect_with_notice( $new_form_id > 0 ? 'form_duplicated' : 'form_duplicate_failed' );
		}

		if ( 'activate' === $action || 'deactivate' === $action ) {
			$status  = 'activate' === $action ? \SUBIN\Forms\FormRepository::STATUS_ACTIVE : \SUBIN\Forms\FormRepository::STATUS_INACTIVE;
			$updated = $this->forms->update_status( $form_id, $status );
			$this->redirect_with_notice( $updated ? 'form_' . $status : 'form_status_failed' );
		}

		if ( 'delete' === $action ) {
			$deleted = $this->forms->delete( $form_id );
			$this->redirect_with_notice( $deleted ? 'form_deleted' : 'form_delete_failed' );
		}

		$this->redirect_with_notice( 'unknown_action' );
	}

	/**
	 * Render default Forms view.
	 *
	 * @return void
	 */
	private function render_default_view() {
		$forms = $this->forms->get_paginated( array( 'per_page' => 200 ) );

		SUBIN_View::page_header(
			__( 'Forms', 'submission-inbox' ),
			__( 'Create, customize, and publish lead forms.', 'submission-inbox' ),
			array(
				'wrapper_class'  => 'subin-forms-page',
				'primary_action' => array(
					'label' => __( 'Create Form', 'submission-inbox' ),
					'url'   => $this->templates_url(),
				),
			)
		);

		$this->render_notice();
		$this->render_my_forms_tab( $forms );

		SUBIN_View::page_footer();
	}

	/**
	 * Render My Forms tab content.
	 *
	 * @param array<int,array<string,mixed>> $forms Forms.
	 * @return void
	 */
	private function render_my_forms_tab( array $forms ) {
		?>
		<div class="subin-forms-section">
			<div class="subin-section-heading">
				<h2><?php esc_html_e( 'My Forms', 'submission-inbox' ); ?></h2>
				<p><?php esc_html_e( 'Manage your forms and check the most important setup details.', 'submission-inbox' ); ?></p>
			</div>

			<?php if ( empty( $forms ) ) : ?>
				<div class="subin-forms-empty-state subin-empty-state">
					<h2><?php esc_html_e( 'No forms yet', 'submission-inbox' ); ?></h2>
					<p><?php esc_html_e( 'Create your first form from a ready-made template and customize it in Design Studio.', 'submission-inbox' ); ?></p>
					<div class="subin-forms-empty-state__actions">
						<a class="button button-primary subin-btn subin-btn-primary" href="<?php echo esc_url( $this->templates_url() ); ?>"><?php esc_html_e( 'Create Form', 'submission-inbox' ); ?></a>
						<a class="button subin-btn" href="<?php echo esc_url( $this->templates_url() ); ?>"><?php esc_html_e( 'Browse Templates', 'submission-inbox' ); ?></a>
					</div>
				</div>
			<?php else : ?>
				<?php $this->render_forms_table( $forms ); ?>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Render template chooser.
	 *
	 * @return void
	 */
	private function render_templates_view() {
		SUBIN_View::page_header(
			__( 'Choose a form template', 'submission-inbox' ),
			__( 'Start with a ready-made form and customize it before publishing.', 'submission-inbox' ),
			array(
				'wrapper_class' => 'subin-forms-page subin-form-templates-page',
			)
		);

		$this->render_notice();
		?>
		<a class="subin-back-link" href="<?php echo esc_url( $this->forms_url() ); ?>"><?php esc_html_e( 'Back to Forms', 'submission-inbox' ); ?></a>

		<div class="subin-template-grid">
			<?php foreach ( $this->templates->get_templates() as $template_key => $template ) : ?>
				<?php
				$fields      = is_array( $template['fields'] ?? null ) ? $template['fields'] : array();
				$field_names = $this->template_field_labels( $template );
				?>
				<div class="subin-template-card">
					<div class="subin-template-card__badge"><?php echo esc_html( $this->template_category( $template_key, $template ) ); ?></div>
					<h2 class="subin-template-card__title"><?php echo esc_html( $template['title'] ?? '' ); ?></h2>
					<p class="subin-template-card__description"><?php echo esc_html( $this->template_description( $template_key, $template ) ); ?></p>
					<div class="subin-template-card__count">
						<?php echo esc_html( $this->format_template_field_count( count( $fields ) ) ); ?>
					</div>
					<p class="subin-template-card__fields">
						<strong><?php esc_html_e( 'Fields:', 'submission-inbox' ); ?></strong>
						<?php echo esc_html( implode( ', ', $field_names ) ); ?>
					</p>
					<?php $this->render_template_visual_preview( $template ); ?>
					<div class="subin-template-card__actions">
						<?php $this->render_use_template_form( $template_key ); ?>
						<a class="button subin-btn subin-btn-secondary" href="<?php echo esc_url( $this->template_preview_url( $template_key ) ); ?>"><?php esc_html_e( 'Preview', 'submission-inbox' ); ?></a>
					</div>
				</div>
			<?php endforeach; ?>
		</div>
		<?php
		SUBIN_View::page_footer();
	}

	/**
	 * Render a larger view-only template preview.
	 *
	 * @return void
	 */
	private function render_template_preview_view() {
		$template_key = SUBIN_Request::get_key( 'template' );
		$template     = $this->templates->get_template( $template_key );

		SUBIN_View::page_header(
			$template ? sprintf(
				/* translators: %s: Template title. */
				__( 'Preview Template: %s', 'submission-inbox' ),
				$template['title']
			) : __( 'Preview Template', 'submission-inbox' ),
			__( 'Review the starter form before creating it.', 'submission-inbox' ),
			array(
				'wrapper_class' => 'subin-forms-page subin-form-templates-page',
			)
		);

		if ( ! $template ) {
			$this->render_notice( 'template_not_found' );
			echo '<p><a class="button subin-btn subin-btn-secondary" href="' . esc_url( $this->templates_url() ) . '">' . esc_html__( 'Back to Templates', 'submission-inbox' ) . '</a></p>';
			SUBIN_View::page_footer();
			return;
		}

		$field_names = $this->template_field_labels( $template );
		$fields      = is_array( $template['fields'] ?? null ) ? $template['fields'] : array();
		?>
		<div class="subin-page-actions">
			<?php $this->render_use_template_form( $template_key ); ?>
			<a class="button subin-btn subin-btn-secondary" href="<?php echo esc_url( $this->templates_url() ); ?>"><?php esc_html_e( 'Back to Templates', 'submission-inbox' ); ?></a>
		</div>
		<div class="subin-notice-box">
			<?php esc_html_e( 'This is a template preview. Create the form before placing it on a page.', 'submission-inbox' ); ?>
		</div>

		<div class="subin-template-preview-screen">
			<div class="subin-template-preview-screen__content">
				<div class="subin-template-card__badge"><?php echo esc_html( $this->template_category( $template_key, $template ) ); ?></div>
				<h2><?php echo esc_html( $template['title'] ?? '' ); ?></h2>
				<p><?php echo esc_html( $this->template_description( $template_key, $template ) ); ?></p>
				<p class="subin-template-card__fields">
					<strong><?php esc_html_e( 'Fields:', 'submission-inbox' ); ?></strong>
					<?php echo esc_html( implode( ', ', $field_names ) ); ?>
				</p>
				<dl class="subin-template-details">
					<dt><?php esc_html_e( 'Field count', 'submission-inbox' ); ?></dt>
					<dd><?php echo esc_html( $this->format_template_field_count( count( $fields ) ) ); ?></dd>
					<?php if ( ! empty( $template['header_title'] ) ) : ?>
						<dt><?php esc_html_e( 'Header title', 'submission-inbox' ); ?></dt>
						<dd><?php echo esc_html( $template['header_title'] ); ?></dd>
					<?php endif; ?>
					<?php if ( ! empty( $template['header_description'] ) ) : ?>
						<dt><?php esc_html_e( 'Header description', 'submission-inbox' ); ?></dt>
						<dd><?php echo esc_html( $template['header_description'] ); ?></dd>
					<?php endif; ?>
					<?php if ( ! empty( $template['footer_note'] ) ) : ?>
						<dt><?php esc_html_e( 'Footer note', 'submission-inbox' ); ?></dt>
						<dd><?php echo esc_html( $template['footer_note'] ); ?></dd>
					<?php endif; ?>
					<?php if ( ! empty( $template['success_message'] ) ) : ?>
						<dt><?php esc_html_e( 'Success message', 'submission-inbox' ); ?></dt>
						<dd><?php echo esc_html( $template['success_message'] ); ?></dd>
					<?php endif; ?>
				</dl>
			</div>
			<div class="subin-template-preview-screen__visual">
				<?php $this->render_template_visual_preview( $template, true ); ?>
			</div>
		</div>
		<?php
		SUBIN_View::page_footer();
	}

	/**
	 * Render admin preview screen.
	 *
	 * @return void
	 */
	private function render_preview_view() {
		$form_id = SUBIN_Request::get_absint( 'form_id' );
		$form    = $this->forms->get( $form_id );

		if ( ! $form ) {
			SUBIN_View::page_header( __( 'Form Preview', 'submission-inbox' ), __( 'Form was not found.', 'submission-inbox' ) );
			$this->render_notice( 'form_not_found' );
			echo '<p><a class="button" href="' . esc_url( $this->forms_url() ) . '">' . esc_html__( 'Back to Forms', 'submission-inbox' ) . '</a></p>';
			SUBIN_View::page_footer();
			return;
		}

		$fields   = $this->fields->get_by_form_id( $form_id );
		$renderer = new \SUBIN\Forms\FormRenderer();
		$shortcode = '[submission_inbox_form id="' . $form_id . '"]';
		$status    = sanitize_key( $form['status'] ?? '' );
		$is_active = \SUBIN\Forms\FormRepository::STATUS_ACTIVE === $status;

		SUBIN_View::page_header(
			sprintf(
				/* translators: %s: Form title. */
				__( 'Preview Form: %s', 'submission-inbox' ),
				$form['title']
			),
			__( 'Preview the saved form before placing it on a public page.', 'submission-inbox' )
		);
		?>
		<div class="subin-preview-page">
			<div class="subin-preview-page__bar">
				<div>
					<?php SUBIN_View::badge( $is_active ? __( 'Active', 'submission-inbox' ) : __( 'Inactive', 'submission-inbox' ), $is_active ? 'success' : 'muted' ); ?>
					<?php if ( ! $is_active ) : ?>
						<p class="description"><?php esc_html_e( 'This form is inactive. Regular visitors will not see it.', 'submission-inbox' ); ?></p>
					<?php endif; ?>
				</div>
				<div class="subin-editor-actions">
					<a class="button" href="<?php echo esc_url( $this->edit_url( $form_id ) ); ?>"><?php esc_html_e( 'Back to editor', 'submission-inbox' ); ?></a>
					<a class="button" href="<?php echo esc_url( $this->forms_url() ); ?>"><?php esc_html_e( 'Back to Forms', 'submission-inbox' ); ?></a>
				</div>
			</div>

			<?php $this->render_shortcode_box( $shortcode, __( 'Shortcode', 'submission-inbox' ), __( 'Copy this shortcode and paste it into any page or post.', 'submission-inbox' ) ); ?>

			<div class="subin-notice-box subin-preview-notice">
				<?php esc_html_e( 'This is an admin preview. To collect leads, place the shortcode on a public page.', 'submission-inbox' ); ?>
			</div>
			<div class="subin-preview-frame">
				<?php echo $renderer->render( $form, $fields, array( 'preview' => true ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</div>
		</div>
		<?php
		SUBIN_View::page_footer();
	}

	/**
	 * Render edit screen.
	 *
	 * @return void
	 */
	private function render_edit_view() {
		$form_id = SUBIN_Request::get_absint( 'form_id' );
		$form    = $this->forms->get( $form_id );

		if ( ! $form ) {
			SUBIN_View::page_header( __( 'Edit Form', 'submission-inbox' ), __( 'Form was not found.', 'submission-inbox' ) );
			$this->render_notice( 'form_not_found' );
			echo '<p><a class="button" href="' . esc_url( $this->forms_url() ) . '">' . esc_html__( 'Back to Forms', 'submission-inbox' ) . '</a></p>';
			SUBIN_View::page_footer();
			return;
		}

		$requested_tab = SUBIN_Request::get_key( 'tab' );
		$visible_tabs  = array( 'design_studio', 'settings' );
		$legacy_tabs   = array( 'content', 'fields', 'design', 'messages', 'advanced_html' );
		$tab           = '' === $requested_tab ? 'design_studio' : $requested_tab;
		if ( in_array( $tab, array( 'email', 'publish' ), true ) ) {
			$this->safe_admin_redirect( $this->edit_url( $form_id, 'settings' ) );
		}
		if ( in_array( $tab, $legacy_tabs, true ) ) {
			$this->safe_admin_redirect( $this->edit_url( $form_id, 'design_studio' ) );
		}
		$tab = in_array( $tab, $visible_tabs, true ) ? $tab : 'design_studio';
		$shortcode = '[submission_inbox_form id="' . absint( $form['id'] ) . '"]';

		SUBIN_View::page_header(
			sprintf(
				/* translators: %s: Form title. */
				__( 'Edit Form: %s', 'submission-inbox' ),
				$form['title']
			),
			__( 'Customize your form, review the saved preview, and copy the shortcode when you are ready to publish.', 'submission-inbox' ),
			array( 'wrapper_class' => 'subin-forms-page subin-form-editor-page' )
		);

		$this->render_notice();
		$editor_classes = array( 'subin-form-editor' );
		if ( 'design_studio' === $tab ) {
			$editor_classes[] = 'subin-form-editor--design-studio';
		}
		?>
		<div class="<?php echo esc_attr( implode( ' ', $editor_classes ) ); ?>">
			<div class="subin-form-editor__body">
				<div class="subin-form-editor__main">
					<nav class="nav-tab-wrapper subin-editor-tabs" aria-label="<?php esc_attr_e( 'Form editor tabs', 'submission-inbox' ); ?>">
						<?php $this->editor_tab( $form_id, 'design_studio', __( 'Design Studio', 'submission-inbox' ), $tab ); ?>
						<?php $this->editor_tab( $form_id, 'settings', __( 'Settings', 'submission-inbox' ), $tab ); ?>
					</nav>

					<?php
					if ( 'design_studio' === $tab ) {
						$this->render_design_studio_tab( $form );
					} elseif ( 'settings' === $tab ) {
						$this->render_settings_tab( $form, $shortcode );
					} else {
						$this->render_design_studio_tab( $form );
					}
					?>
				</div>
			</div>
		</div>
		<?php

		SUBIN_View::page_footer();
	}

	/**
	 * Render Design Studio foundation tab.
	 *
	 * @param array<string,mixed> $form Form.
	 * @return void
	 */
	private function render_design_studio_tab( array $form ) {
		$form_id     = absint( $form['id'] );
		$shortcode   = '[submission_inbox_form id="' . $form_id . '"]';
		$status      = sanitize_key( $form['status'] ?? '' );
		$is_active   = \SUBIN\Forms\FormRepository::STATUS_ACTIVE === $status;
		$editor_data = $this->grapesjs_editor_form_data( $form );
		?>
		<div class="subin-panel subin-editor-panel subin-design-studio-panel">
			<a class="subin-design-back-link" href="<?php echo esc_url( $this->forms_url() ); ?>"><?php esc_html_e( 'Back to Forms', 'submission-inbox' ); ?></a>
			<div class="subin-design-studio subin-design-studio--grapesjs-shell">
				<div class="subin-design-studio__toolbar">
					<div class="subin-design-toolbar__left">
						<div class="subin-design-toolbar__identity">
							<h2>
								<?php echo esc_html( $form['title'] ?? '' ); ?>
								<?php SUBIN_View::help_tooltip( __( 'Click an element in the preview to edit its content or style.', 'submission-inbox' ) ); ?>
							</h2>
							<?php SUBIN_View::badge( $is_active ? __( 'Active', 'submission-inbox' ) : __( 'Inactive', 'submission-inbox' ), $is_active ? 'success' : 'muted' ); ?>
						</div>
					</div>
					<div class="subin-design-toolbar__center" aria-label="<?php esc_attr_e( 'Design Studio', 'submission-inbox' ); ?>">
						<div class="subin-gjs-preview-state-switcher" role="group" aria-label="<?php esc_attr_e( 'Preview state', 'submission-inbox' ); ?>">
							<button type="button" class="button button-small subin-gjs-preview-state-button is-active" data-subin-gjs-preview-state="normal" aria-pressed="true"><?php esc_html_e( 'Normal', 'submission-inbox' ); ?></button>
							<button type="button" class="button button-small subin-gjs-preview-state-button" data-subin-gjs-preview-state="error" aria-pressed="false"><?php esc_html_e( 'Error', 'submission-inbox' ); ?></button>
							<button type="button" class="button button-small subin-gjs-preview-state-button" data-subin-gjs-preview-state="success" aria-pressed="false"><?php esc_html_e( 'Success', 'submission-inbox' ); ?></button>
						</div>
					</div>
					<div class="subin-design-toolbar__right">
						<div class="subin-design-toolbar__shortcode">
							<code class="subin-shortcode-pill"><?php echo esc_html( $shortcode ); ?></code>
							<?php SUBIN_View::help_tooltip( __( 'Preview changes are not saved until you save the design.', 'submission-inbox' ) ); ?>
							<button type="button" class="button button-small subin-shortcode-copy subin-copy-shortcode" data-subin-copy="<?php echo esc_attr( $shortcode ); ?>"><?php esc_html_e( 'Copy', 'submission-inbox' ); ?></button>
							<span class="subin-copy-feedback" aria-live="polite"></span>
						</div>
						<button type="button" class="button button-primary" data-subin-grapesjs-save><?php esc_html_e( 'Save', 'submission-inbox' ); ?></button>
						<span class="subin-grapesjs-save-status" data-subin-grapesjs-save-status aria-live="polite"></span>
					</div>
				</div>

				<div id="subin-grapesjs-editor" class="subin-grapesjs-editor"></div>
				<script type="application/json" id="subin-grapesjs-form-data"><?php echo str_replace( '</script', '<\/script', wp_json_encode( $editor_data ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON is encoded and script-close escaped for an admin-only data island. ?></script>
				<?php $this->render_grapesjs_html_editor_modal(); ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Render the WordPress editor modal used by the controlled GrapesJS HTML block.
	 *
	 * @return void
	 */
	private function render_grapesjs_html_editor_modal() {
		$editor_id = 'subin_gjs_html_editor';
		?>
		<div class="subin-gjs-html-modal" data-subin-gjs-html-modal hidden>
			<div class="subin-gjs-html-modal__backdrop" data-subin-gjs-html-close></div>
			<div class="subin-gjs-html-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="subin-gjs-html-modal-title">
				<div class="subin-gjs-html-modal__header">
					<h2 id="subin-gjs-html-modal-title"><?php esc_html_e( 'HTML block editor', 'submission-inbox' ); ?></h2>
					<button type="button" class="button-link subin-gjs-html-modal__close" data-subin-gjs-html-close aria-label="<?php esc_attr_e( 'Close editor', 'submission-inbox' ); ?>">&times;</button>
				</div>
				<div class="subin-gjs-html-modal__body">
					<?php
					wp_editor(
						'',
						$editor_id,
						array(
							'textarea_name' => 'subin_gjs_html_editor_content',
							'textarea_rows' => 14,
							'media_buttons' => false,
							'teeny'         => false,
							'quicktags'     => true,
							'tinymce'       => array(
								'toolbar1' => 'formatselect,bold,italic,bullist,numlist,blockquote,alignleft,aligncenter,alignright,link,unlink,undo,redo',
								'toolbar2' => '',
							),
						)
					);
					?>
					<p class="description"><?php esc_html_e( 'Scripts, forms, embeds, iframes, shortcodes, and unsafe attributes are stripped when the block is saved or rendered.', 'submission-inbox' ); ?></p>
				</div>
				<div class="subin-gjs-html-modal__footer">
					<button type="button" class="button" data-subin-gjs-html-close><?php esc_html_e( 'Cancel', 'submission-inbox' ); ?></button>
					<button type="button" class="button button-primary" data-subin-gjs-html-apply><?php esc_html_e( 'Apply HTML', 'submission-inbox' ); ?></button>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Build the Design Studio GrapesJS editor preview data.
	 *
	 * @param array<string,mixed> $form Form.
	 * @return array<string,mixed>
	 */
	private function grapesjs_editor_form_data( array $form ) {
		$form_id     = absint( $form['id'] ?? 0 );
		$fields      = array();
		$title       = sanitize_text_field( $form['header_title'] ?? '' );
		$description = sanitize_textarea_field( $form['header_description'] ?? '' );
		$settings     = $this->forms->decode_settings( $form );
		$designer     = new \SUBIN\Forms\DesignSettings();
		$design       = $designer->get_for_form( $form );
		$messages     = is_array( $settings['messages'] ?? null ) ? $settings['messages'] : array();
		$field_styles = $designer->sanitize_field_styles( is_array( $settings['design_studio_field_styles'] ?? null ) ? $settings['design_studio_field_styles'] : array() );
		$project      = $this->saved_grapesjs_project_for_editor( $settings );

		if ( '' === $title ) {
			$title = sanitize_text_field( $form['title'] ?? '' );
		}

		if ( '' === $description ) {
			$description = sanitize_textarea_field( $form['description'] ?? '' );
		}

		foreach ( $this->fields->get_by_form_id( $form_id ) as $field ) {
			$fields[] = array(
				'id'          => absint( $field['id'] ?? 0 ),
				'key'         => sanitize_key( $field['field_key'] ?? '' ),
				'type'        => sanitize_key( $field['field_type'] ?? 'text' ),
				'label'       => sanitize_text_field( $field['field_label'] ?? '' ),
				'placeholder' => sanitize_text_field( $field['placeholder'] ?? '' ),
				'help_text'   => sanitize_textarea_field( $field['help_text'] ?? '' ),
				'required'    => ! empty( $field['is_required'] ),
				'options'     => $this->get_preview_options( $field ),
			);
		}

		return array(
			'id'                 => $form_id,
			'title'              => $title,
			'description'        => $description,
			'footer_note'        => sanitize_textarea_field( $form['footer_note'] ?? '' ),
			'privacy_text'       => sanitize_textarea_field( $form['privacy_text'] ?? '' ),
			'submit_button_text' => sanitize_text_field( $form['submit_button_text'] ?? __( 'Submit', 'submission-inbox' ) ),
			'fields'             => $fields,
			'design_styles'      => $design,
			'field_styles'       => $field_styles,
			'messages'           => array(
				'validation_error'      => sanitize_textarea_field( $messages['validation_error'] ?? __( 'Please check the highlighted fields and try again.', 'submission-inbox' ) ),
				'required_field_error'  => sanitize_text_field( $messages['required_field_error'] ?? __( 'This field is required.', 'submission-inbox' ) ),
				'spam_protection_error' => sanitize_textarea_field( $messages['spam_protection_error'] ?? __( 'Your submission could not be accepted.', 'submission-inbox' ) ),
				'success_message'       => sanitize_textarea_field( $form['success_message'] ?: __( 'Thanks, your message has been sent.', 'submission-inbox' ) ),
			),
			'grapesjs_project'   => $project,
			'ajax'               => array(
				'action'       => 'subin_save_grapesjs_project',
				'url'          => admin_url( 'admin-ajax.php' ),
				'nonce'        => wp_create_nonce( 'subin_save_grapesjs_project_' . $form_id ),
				'media_action' => 'subin_upload_design_studio_media',
				'media_nonce'  => wp_create_nonce( 'subin_upload_design_studio_media_' . $form_id ),
			),
		);
	}

	/**
	 * Upload a Design Studio image into the WordPress Media Library.
	 *
	 * @return void
	 */
	public function ajax_upload_design_studio_media() {
		if ( ! current_user_can( SUBIN_AdminMenu::CAPABILITY ) || ! current_user_can( 'upload_files' ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Image could not be uploaded.', 'submission-inbox' ),
				),
				403
			);
		}

		$form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
		$nonce   = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';

		if ( $form_id <= 0 || ! wp_verify_nonce( $nonce, 'subin_upload_design_studio_media_' . $form_id ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Image could not be uploaded.', 'submission-inbox' ),
				),
				403
			);
		}

		if ( ! $this->forms->get( $form_id ) || empty( $_FILES['file'] ) || ! is_array( $_FILES['file'] ) ) {
			wp_send_json_error(
				array(
					'message' => __( 'Image file is missing.', 'submission-inbox' ),
				),
				400
			);
		}

		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';
		require_once ABSPATH . 'wp-admin/includes/media.php';

		$attachment_id = media_handle_upload( 'file', 0, array(), array( 'test_form' => false ) );
		if ( is_wp_error( $attachment_id ) ) {
			wp_send_json_error(
				array(
					'message' => $attachment_id->get_error_message(),
				),
				400
			);
		}

		$url = wp_get_attachment_image_url( $attachment_id, 'full' );
		if ( ! $url ) {
			$url = wp_get_attachment_url( $attachment_id );
		}

		if ( ! $url ) {
			wp_send_json_error(
				array(
					'message' => __( 'Uploaded image URL could not be processed.', 'submission-inbox' ),
				),
				500
			);
		}

		wp_send_json_success(
			array(
				'id'  => absint( $attachment_id ),
				'url' => esc_url_raw( $url ),
			)
		);
	}

	/**
	 * Get saved GrapesJS project data for the admin editor.
	 *
	 * @param array<string,mixed> $settings Decoded settings.
	 * @return array<string,mixed>
	 */
	private function saved_grapesjs_project_for_editor( array $settings ) {
		$project = is_array( $settings['design_studio']['grapesjs_project'] ?? null ) ? $settings['design_studio']['grapesjs_project'] : array();
		if ( empty( $project ) ) {
			return array();
		}

		$project = $this->sanitize_grapesjs_project( $project );
		return is_wp_error( $project ) ? array() : $project;
	}

	/**
	 * Sanitize GrapesJS project data while preserving GrapesJS key names.
	 *
	 * @param array<string,mixed> $project Project data.
	 * @return array<string,mixed>|\WP_Error
	 */
	private function sanitize_grapesjs_project( array $project ) {
		$clean = $this->sanitize_grapesjs_value( $project );
		if ( is_wp_error( $clean ) ) {
			return $clean;
		}

		if ( ! is_array( $clean ) ) {
			return new \WP_Error( 'subin_grapesjs_project_invalid', __( 'Project data is invalid.', 'submission-inbox' ) );
		}

		return $clean;
	}

	/**
	 * Sanitize a GrapesJS project value.
	 *
	 * @param mixed $value Value.
	 * @return mixed|\WP_Error
	 */
	private function sanitize_grapesjs_value( $value ) {
		if ( is_array( $value ) ) {
			if ( $this->is_grapesjs_component_array( $value ) ) {
				return $this->sanitize_grapesjs_component( $value );
			}

			$clean = array();
			foreach ( $value as $key => $item ) {
				$clean_key = is_int( $key ) ? $key : $this->sanitize_grapesjs_project_key( $key );
				if ( '' === $clean_key && ! is_int( $key ) ) {
					continue;
				}

				$clean_item = $this->sanitize_grapesjs_value( $item );
				if ( is_wp_error( $clean_item ) ) {
					return $clean_item;
				}

				$clean[ $clean_key ] = $clean_item;
			}

			return $clean;
		}

		if ( is_bool( $value ) || is_int( $value ) || is_float( $value ) || null === $value ) {
			return $value;
		}

		$value = (string) $value;
		if ( $this->grapesjs_string_is_unsafe( $value ) ) {
			return '';
		}

		return sanitize_text_field( $value );
	}

	/**
	 * Check whether an array looks like a GrapesJS component.
	 *
	 * @param array<mixed> $value Value.
	 * @return bool
	 */
	private function is_grapesjs_component_array( array $value ) {
		return isset( $value['tagName'] ) || isset( $value['type'] ) || isset( $value['attributes'] ) || isset( $value['components'] );
	}

	/**
	 * Sanitize one GrapesJS component.
	 *
	 * @param array<string,mixed> $component Component.
	 * @return array<string,mixed>|\WP_Error
	 */
	private function sanitize_grapesjs_component( array $component ) {
		$type       = sanitize_key( (string) ( $component['type'] ?? '' ) );
		$tag_name   = strtolower( sanitize_key( (string) ( $component['tagName'] ?? '' ) ) );
		$attributes = is_array( $component['attributes'] ?? null ) ? $this->sanitize_grapesjs_attributes( $component['attributes'] ) : array();
		$subin_type   = sanitize_key( (string) ( $attributes['data-subin-component'] ?? $this->grapesjs_component_key_from_type( $type ) ) );

		if ( in_array( $tag_name, array( 'script', 'iframe', 'object', 'embed', 'form' ), true ) ) {
			return new \WP_Error( 'subin_grapesjs_project_unsafe', __( 'Project data contains unsupported markup.', 'submission-inbox' ) );
		}

		if ( in_array( $tag_name, array( 'input', 'select', 'textarea' ), true ) && '' === sanitize_key( $attributes['data-subin-preview-control'] ?? '' ) ) {
			return new \WP_Error( 'subin_grapesjs_project_unsafe', __( 'Project data contains unsupported markup.', 'submission-inbox' ) );
		}

		if ( 'button' === $tag_name && 'submit-button' !== $subin_type ) {
			return new \WP_Error( 'subin_grapesjs_project_unsafe', __( 'Project data contains unsupported markup.', 'submission-inbox' ) );
		}

		if ( in_array( $type, array( 'script', 'iframe', 'object', 'embed', 'form', 'button' ), true ) && 'submit-button' !== $subin_type ) {
			return new \WP_Error( 'subin_grapesjs_project_unsafe', __( 'Project data contains unsupported components.', 'submission-inbox' ) );
		}

		$clean = array();
		foreach ( $component as $key => $value ) {
			$key = $this->sanitize_grapesjs_project_key( $key );
			if ( '' === $key ) {
				continue;
			}

			if ( 'attributes' === $key ) {
				$clean[ $key ] = $attributes;
				continue;
			}

			if ( 'components' === $key ) {
				$children = $this->sanitize_grapesjs_components( $value );
				if ( is_wp_error( $children ) ) {
					return $children;
				}
				$clean[ $key ] = $children;
				continue;
			}

			if ( 'style' === $key && is_array( $value ) ) {
				$clean[ $key ] = $this->sanitize_grapesjs_style_map( $value );
				continue;
			}

			$clean_value = $this->sanitize_grapesjs_value( $value );
			if ( is_wp_error( $clean_value ) ) {
				return $clean_value;
			}
			$clean[ $key ] = $clean_value;
		}

		return $clean;
	}

	/**
	 * Sanitize component children.
	 *
	 * @param mixed $components Components.
	 * @return mixed|\WP_Error
	 */
	private function sanitize_grapesjs_components( $components ) {
		if ( is_string( $components ) ) {
			return wp_kses( $components, $this->grapesjs_allowed_preview_tags(), array() );
		}

		if ( ! is_array( $components ) ) {
			return array();
		}

		$clean = array();
		foreach ( $components as $component ) {
			$clean_component = is_array( $component ) ? $this->sanitize_grapesjs_component( $component ) : $this->sanitize_grapesjs_value( $component );
			if ( is_wp_error( $clean_component ) ) {
				return $clean_component;
			}
			$clean[] = $clean_component;
		}

		return $clean;
	}

	/**
	 * Sanitize GrapesJS component attributes.
	 *
	 * @param array<string,mixed> $attributes Attributes.
	 * @return array<string,string>
	 */
	private function sanitize_grapesjs_attributes( array $attributes ) {
		$clean = array();
		foreach ( $attributes as $key => $value ) {
			$key = strtolower( (string) $key );
			if ( '' === $key || preg_match( '/^on[a-z]/', $key ) ) {
				continue;
			}

			$key = preg_replace( '/[^a-z0-9_:\\.-]/', '', $key );
			if ( '' === $key ) {
				continue;
			}

			if ( 'data-subin-options' === $key ) {
				$value = $this->sanitize_grapesjs_options_attribute( $value );
				if ( '' !== $value ) {
					$clean[ $key ] = $value;
				}
				continue;
			}

			if ( 'data-subin-field-key' === $key ) {
				$value = sanitize_key( (string) $value );
				if ( '' !== $value ) {
					$clean[ $key ] = $value;
				}
				continue;
			}

			if ( in_array( $key, array( 'data-subin-help-text', 'data-subin-consent-text', 'data-subin-default-value', 'data-subin-required-message', 'data-subin-form-description', 'data-subin-validation-error-message', 'data-subin-required-error-message', 'data-subin-spam-error-message', 'data-subin-success-message' ), true ) ) {
				$value = sanitize_textarea_field( (string) $value );
				if ( $this->grapesjs_string_is_unsafe( $value ) ) {
					continue;
				}
				$clean[ $key ] = $value;
				continue;
			}

			if ( 'data-subin-form-title' === $key ) {
				$value = sanitize_text_field( (string) $value );
				if ( $this->grapesjs_string_is_unsafe( $value ) ) {
					continue;
				}
				$clean[ $key ] = $value;
				continue;
			}

			if ( in_array( $key, array( 'data-subin-content', 'data-subin-html-content' ), true ) ) {
				$value = wp_kses( (string) $value, $this->grapesjs_allowed_content_tags(), array( 'http', 'https', 'mailto', 'tel' ) );
				if ( $this->grapesjs_string_is_unsafe( $value ) ) {
					continue;
				}
				$clean[ $key ] = $value;
				continue;
			}

			if ( 'data-subin-bg-image-id' === $key ) {
				$clean[ $key ] = (string) absint( $value );
				continue;
			}

			if ( 'data-subin-bg-image-url' === $key ) {
				$value = esc_url_raw( (string) $value, array( 'http', 'https' ) );
				if ( '' !== $value && ! preg_match( '/^(javascript|data):/i', $value ) && ! preg_match( '/\\.(svg|svgz)(?:[?#].*)?$/i', $value ) && preg_match( '/\\.(jpe?g|png|gif|webp|avif)(?:[?#].*)?$/i', $value ) ) {
					$clean[ $key ] = $value;
				}
				continue;
			}

			if ( 'data-subin-bg-size' === $key ) {
				$value = $this->sanitize_choice( $value, array( 'cover', 'contain', 'auto' ), 'cover' );
				$clean[ $key ] = $value;
				continue;
			}

			if ( 'data-subin-bg-position' === $key ) {
				$value = $this->sanitize_choice( $value, array( 'center', 'top', 'bottom', 'left', 'right' ), 'center' );
				$clean[ $key ] = $value;
				continue;
			}

			if ( 'data-subin-bg-repeat' === $key ) {
				$value = $this->sanitize_choice( $value, array( 'no-repeat', 'repeat', 'repeat-x', 'repeat-y' ), 'no-repeat' );
				$clean[ $key ] = $value;
				continue;
			}

			$value = sanitize_text_field( (string) $value );
			if ( $this->grapesjs_string_is_unsafe( $value ) ) {
				continue;
			}

			$clean[ $key ] = $value;
		}

		return $clean;
	}

	/**
	 * Sanitize a serialized GrapesJS options attribute.
	 *
	 * @param mixed $value Attribute value.
	 * @return string
	 */
	private function sanitize_grapesjs_options_attribute( $value ) {
		$options = $this->grapesjs_options_from_attribute( $value );
		if ( empty( $options ) ) {
			return '';
		}

		$encoded = wp_json_encode( $options );

		return is_string( $encoded ) ? $encoded : '';
	}

	/**
	 * Decode and sanitize choice field options from a GrapesJS attribute.
	 *
	 * @param mixed $value Attribute value.
	 * @return array<int,array{label:string,value:string}>
	 */
	private function grapesjs_options_from_attribute( $value ) {
		if ( is_string( $value ) ) {
			$decoded = json_decode( wp_unslash( $value ), true );
			$value   = is_array( $decoded ) ? $decoded : array();
		}

		if ( ! is_array( $value ) ) {
			return array();
		}

		$options = array();
		foreach ( $value as $index => $option ) {
			if ( is_scalar( $option ) ) {
				$label = sanitize_text_field( wp_strip_all_tags( (string) $option ) );
				$raw_value = $label;
			} elseif ( is_array( $option ) ) {
				$label = sanitize_text_field( wp_strip_all_tags( (string) ( $option['label'] ?? '' ) ) );
				$raw_value = (string) ( $option['value'] ?? $label );
			} else {
				continue;
			}

			if ( preg_match( '/^\\s*(javascript|data)\\s*:/i', $raw_value ) ) {
				$raw_value = '';
			}

			$value_key = sanitize_key( '' !== $raw_value ? $raw_value : $label );
			if ( '' === $value_key ) {
				$value_key = 'option_' . ( absint( $index ) + 1 );
			}

			if ( '' === $label ) {
				$label = $value_key;
			}

			$options[] = array(
				'label' => $label,
				'value' => $value_key,
			);
		}

		return $options;
	}

	/**
	 * Sanitize CSS style map values.
	 *
	 * @param array<string,mixed> $style Style declarations.
	 * @return array<string,string>
	 */
	private function sanitize_grapesjs_style_map( array $style ) {
		$clean = array();
		foreach ( $style as $property => $value ) {
			$property = strtolower( preg_replace( '/[^a-z0-9_-]/', '', (string) $property ) );
			$value    = sanitize_text_field( (string) $value );
			if ( '' === $property || $this->grapesjs_string_is_unsafe( $value ) ) {
				continue;
			}

			$clean[ $property ] = $value;
		}

		return $clean;
	}

	/**
	 * Sanitize a GrapesJS project key without forcing lowercase.
	 *
	 * @param mixed $key Key.
	 * @return string
	 */
	private function sanitize_grapesjs_project_key( $key ) {
		return preg_replace( '/[^A-Za-z0-9_\\-:]/', '', (string) $key );
	}

	/**
	 * Detect unsafe strings in project data.
	 *
	 * @param string $value Value.
	 * @return bool
	 */
	private function grapesjs_string_is_unsafe( $value ) {
		return (bool) preg_match( '/(<\\s*\\/?\\s*(script|iframe|object|embed|form|input)\\b|on[a-z]+\\s*=|javascript\\s*:|data\\s*:|expression\\s*\\(|@import|<\\s*\\/?\\s*style\\b)/i', $value );
	}

	/**
	 * Map GrapesJS type names to Submission Inbox component keys.
	 *
	 * @param string $type Type.
	 * @return string
	 */
	private function grapesjs_component_key_from_type( $type ) {
		$map = array(
			'subin-row'           => 'row',
			'subin-field'         => 'field',
			'subin-submit-button' => 'submit-button',
			'subin-text-block'    => 'text-block',
			'subin-html-block'    => 'html-block',
			'subin-notice-block'  => 'notice',
			'subin-divider-block' => 'divider',
			'subin-privacy-block' => 'privacy',
			'subin-section-block' => 'section',
			'subin-spacer-block'  => 'spacer',
			'subin-image-block'   => 'image-block',
			'wrapper'           => 'form-wrapper',
		);

		return $map[ sanitize_key( (string) $type ) ] ?? '';
	}

	/**
	 * Get allowed markup for text-like GrapesJS component content.
	 *
	 * @return array<string,array<string,bool>>
	 */
	private function grapesjs_allowed_preview_tags() {
		$global = array(
			'class'                    => true,
			'data-subin-component'       => true,
			'data-subin-preview-control' => true,
			'data-subin-style-id'        => true,
			'aria-hidden'              => true,
		);
		$control = array_merge(
			$global,
			array(
				'disabled'    => true,
				'readonly'    => true,
				'type'        => true,
				'value'       => true,
				'placeholder' => true,
			)
		);

		return array(
			'div'      => $global,
			'p'        => $global,
			'h2'       => $global,
			'label'    => $global,
			'select'   => $control,
			'option'   => $control,
			'input'    => $control,
			'textarea' => $control,
			'span'     => $global,
			'strong'   => $global,
			'em'       => $global,
			'br'       => array(),
			'hr'       => $global,
		);
	}

	/**
	 * Get allowed markup for controlled content block attributes.
	 *
	 * @return array<string,array<string,bool>>
	 */
	private function grapesjs_allowed_content_tags() {
		return array(
			'p'      => array( 'class' => true, 'aria-label' => true ),
			'br'     => array(),
			'strong' => array( 'class' => true ),
			'em'     => array( 'class' => true ),
			'b'      => array( 'class' => true ),
			'i'      => array( 'class' => true ),
			'span'   => array( 'class' => true, 'aria-label' => true ),
			'div'    => array( 'class' => true, 'aria-label' => true ),
			'small'  => array( 'class' => true ),
			'ul'     => array( 'class' => true ),
			'ol'     => array( 'class' => true ),
			'li'     => array( 'class' => true ),
			'a'      => array(
				'href'   => true,
				'title'  => true,
				'target' => true,
				'rel'    => true,
				'class'  => true,
			),
			'hr'     => array( 'class' => true ),
		);
	}

	/**
	 * Render merged Settings tab.
	 *
	 * @param array<string,mixed> $form Form.
	 * @param string              $shortcode Shortcode.
	 * @return void
	 */
	private function render_settings_tab( array $form, $shortcode ) {
		$form_id      = absint( $form['id'] );
		$settings     = $this->forms->decode_settings( $form );
		$notify       = is_array( $settings['notification'] ?? null ) ? $settings['notification'] : array();
		$default_notification_recipient = $this->default_admin_notification_recipient();
		$notification_recipient_mode    = $this->sanitize_notification_recipient_mode( $notify['recipient_mode'] ?? $this->default_notification_recipient_mode_for_form( $form ) );
		$notification_email_value       = 'custom' === $notification_recipient_mode ? sanitize_email( $form['notification_email'] ?? '' ) : $default_notification_recipient;
		$auto_reply_rule = $this->auto_reply_rules->find_rule( 'submission_inbox_form', (string) $form_id );
		$auto_reply_template = $auto_reply_rule ? $this->auto_reply_templates->get( absint( $auto_reply_rule['template_id'] ?? 0 ) ) : null;
		$auto_reply_field = $auto_reply_rule ? $this->auto_reply_providers->get_email_field( 'submission_inbox_form', (string) $form_id, (string) ( $auto_reply_rule['visitor_email_field'] ?? '' ) ) : null;
		?>
		<div class="subin-form-settings">
			<form id="subin-editor-form-settings" method="post" action="<?php echo esc_url( $this->edit_url( $form_id, 'settings' ) ); ?>">
				<input type="hidden" name="subin_editor_action" value="save_settings" />
				<input type="hidden" name="form_id" value="<?php echo esc_attr( (string) $form_id ); ?>" />
				<input type="hidden" name="tab" value="settings" />
				<?php wp_nonce_field( $this->editor_nonce_name( 'save_settings', $form_id ) ); ?>

				<section class="subin-settings-card">
					<header class="subin-settings-card__header">
						<div>
							<h2><?php esc_html_e( 'Publish & shortcode', 'submission-inbox' ); ?></h2>
							<p><?php esc_html_e( 'Copy the shortcode and place this form on any page, post, or widget area that supports shortcodes.', 'submission-inbox' ); ?></p>
						</div>
						<a class="button subin-settings-button" href="<?php echo esc_url( $this->preview_url( $form_id ) ); ?>"><?php esc_html_e( 'Preview form', 'submission-inbox' ); ?></a>
					</header>
					<div class="subin-settings-card__body">
						<div class="subin-settings-field">
							<label><?php esc_html_e( 'Shortcode', 'submission-inbox' ); ?></label>
							<div class="subin-settings-shortcode-row">
								<code class="subin-shortcode-pill"><?php echo esc_html( $shortcode ); ?></code>
								<button type="button" class="button subin-settings-button subin-copy-shortcode" data-subin-copy="<?php echo esc_attr( $shortcode ); ?>"><?php esc_html_e( 'Copy shortcode', 'submission-inbox' ); ?></button>
								<span class="subin-copy-feedback" aria-live="polite"></span>
							</div>
						</div>
						<div class="subin-settings-grid">
							<div class="subin-settings-field">
								<label for="subin-form-status">
									<?php esc_html_e( 'Status', 'submission-inbox' ); ?>
									<?php SUBIN_View::help_tooltip( __( 'Inactive forms are not shown to regular visitors.', 'submission-inbox' ) ); ?>
								</label>
								<select id="subin-form-status" name="status">
									<option value="active" <?php selected( $form['status'] ?? '', 'active' ); ?>><?php esc_html_e( 'Active', 'submission-inbox' ); ?></option>
									<option value="inactive" <?php selected( $form['status'] ?? '', 'inactive' ); ?>><?php esc_html_e( 'Inactive', 'submission-inbox' ); ?></option>
								</select>
							</div>
							<div class="subin-settings-field">
								<label for="subin-form-slug"><?php esc_html_e( 'Slug', 'submission-inbox' ); ?></label>
								<input id="subin-form-slug" type="text" name="slug" value="<?php echo esc_attr( $form['slug'] ?? '' ); ?>" />
							</div>
						</div>
					</div>
				</section>

				<section class="subin-settings-card">
					<header class="subin-settings-card__header">
						<div>
							<h2><?php esc_html_e( 'Lead storage', 'submission-inbox' ); ?></h2>
							<p><?php esc_html_e( 'Choose whether form submissions should be stored in Submission Inbox.', 'submission-inbox' ); ?></p>
						</div>
					</header>
					<div class="subin-settings-card__body">
						<label class="subin-settings-toggle">
							<input type="checkbox" name="save_leads" value="1" <?php checked( ! empty( $form['save_leads'] ) ); ?> />
							<span></span>
							<strong><?php esc_html_e( 'Save submissions in Submission Inbox', 'submission-inbox' ); ?></strong>
						</label>
						<p class="subin-settings-help"><?php esc_html_e( 'When enabled, submissions are stored and can be reviewed later.', 'submission-inbox' ); ?></p>
					</div>
				</section>

				<section class="subin-settings-card">
					<header class="subin-settings-card__header">
						<div>
							<h2><?php esc_html_e( 'Admin notification', 'submission-inbox' ); ?></h2>
							<p><?php esc_html_e( 'Send an email to the site owner when a new lead is submitted.', 'submission-inbox' ); ?></p>
						</div>
					</header>
					<div class="subin-settings-card__body">
						<label class="subin-settings-toggle">
							<input type="checkbox" name="notification_enabled" value="1" <?php checked( ! empty( $form['notification_enabled'] ) ); ?> />
							<span></span>
							<strong><?php esc_html_e( 'Send admin notification', 'submission-inbox' ); ?></strong>
						</label>
						<div class="subin-settings-grid">
							<div class="subin-settings-field">
								<label><?php esc_html_e( 'Send to', 'submission-inbox' ); ?></label>
								<label class="subin-settings-radio">
									<input type="radio" name="notification_recipient_mode" value="default" <?php checked( $notification_recipient_mode, 'default' ); ?> data-subin-notification-recipient-mode />
									<span>
										<?php
										if ( is_email( $default_notification_recipient ) ) {
											printf(
												/* translators: %s: default notification recipient email. */
												esc_html__( 'Default recipient: %s', 'submission-inbox' ),
												esc_html( $default_notification_recipient )
											);
										} else {
											esc_html_e( 'Default admin notification recipient is not configured.', 'submission-inbox' );
										}
										?>
									</span>
								</label>
								<label class="subin-settings-radio">
									<input type="radio" name="notification_recipient_mode" value="custom" <?php checked( $notification_recipient_mode, 'custom' ); ?> data-subin-notification-recipient-mode />
									<span><?php esc_html_e( 'Custom recipient for this form', 'submission-inbox' ); ?></span>
								</label>
								<p><?php esc_html_e( 'The default recipient is configured in Settings → Sending.', 'submission-inbox' ); ?></p>
							</div>
						</div>
						<div class="subin-settings-grid">
							<div class="subin-settings-field" data-subin-notification-custom-recipient data-subin-default-notification-recipient="<?php echo esc_attr( $default_notification_recipient ); ?>">
								<label for="subin-notification-email"><?php esc_html_e( 'Recipient email', 'submission-inbox' ); ?></label>
								<input type="hidden" name="notification_email_preserved" value="<?php echo esc_attr( $notification_email_value ); ?>" data-subin-notification-email-preserved />
								<input id="subin-notification-email" type="email" name="notification_email" value="<?php echo esc_attr( $notification_email_value ); ?>" <?php disabled( 'custom' !== $notification_recipient_mode ); ?> data-subin-notification-email />
							</div>
							<div class="subin-settings-field">
								<label for="subin-notification-subject">
									<?php esc_html_e( 'Email subject', 'submission-inbox' ); ?>
									<?php SUBIN_View::help_tooltip( __( 'You can use {form_name}. Submission Inbox replaces it with this form’s title before sending the admin notification.', 'submission-inbox' ) ); ?>
								</label>
								<input id="subin-notification-subject" type="text" name="notification_subject" value="<?php echo esc_attr( $form['notification_subject'] ?: __( 'New lead from {form_name}', 'submission-inbox' ) ); ?>" />
							</div>
						</div>
						<label class="subin-settings-checkbox">
							<input type="checkbox" name="use_lead_email_reply_to" value="1" <?php checked( ! empty( $notify['use_lead_email_reply_to'] ) ); ?> />
							<span><?php esc_html_e( 'Use submitted email as Reply-To when available', 'submission-inbox' ); ?></span>
						</label>
					</div>
				</section>

				<section class="subin-settings-card">
					<header class="subin-settings-card__header">
						<div>
							<h2><?php esc_html_e( 'Visitor auto-reply', 'submission-inbox' ); ?></h2>
							<p><?php esc_html_e( 'Visitor confirmations are managed from Auto-reply Rules.', 'submission-inbox' ); ?></p>
						</div>
					</header>
					<div class="subin-settings-card__body">
						<?php if ( $auto_reply_rule ) : ?>
							<div class="subin-auto-reply-status-block">
								<?php SUBIN_View::badge( ! empty( $auto_reply_rule['enabled'] ) ? __( 'Enabled via Auto-reply Rule', 'submission-inbox' ) : __( 'Rule disabled', 'submission-inbox' ), ! empty( $auto_reply_rule['enabled'] ) ? 'success' : 'muted' ); ?>
								<dl class="subin-compact-details">
									<div>
										<dt><?php esc_html_e( 'Template', 'submission-inbox' ); ?></dt>
										<dd><?php echo esc_html( $auto_reply_template ? (string) ( $auto_reply_template['name'] ?? '' ) : __( 'Missing template', 'submission-inbox' ) ); ?></dd>
									</div>
									<div>
										<dt><?php esc_html_e( 'Visitor email field', 'submission-inbox' ); ?></dt>
										<dd><?php echo esc_html( $auto_reply_field ? $auto_reply_field['label'] : (string) ( $auto_reply_rule['visitor_email_field_label'] ?: $auto_reply_rule['visitor_email_field'] ) ); ?></dd>
									</div>
								</dl>
								<a class="button subin-settings-button" href="<?php echo esc_url( $this->auto_reply_rule_url( absint( $auto_reply_rule['id'] ?? 0 ), $form_id ) ); ?>"><?php esc_html_e( 'Manage rule', 'submission-inbox' ); ?></a>
							</div>
						<?php else : ?>
							<div class="subin-auto-reply-status-block">
								<p><?php esc_html_e( 'No auto-reply rule configured for this form.', 'submission-inbox' ); ?></p>
								<a class="button button-primary subin-btn subin-btn-primary" href="<?php echo esc_url( $this->auto_reply_rule_url( 0, $form_id ) ); ?>"><?php esc_html_e( 'Create rule', 'submission-inbox' ); ?></a>
							</div>
						<?php endif; ?>
					</div>
				</section>

				<div class="subin-settings-actions">
					<?php submit_button( __( 'Save settings', 'submission-inbox' ), 'primary', 'submit', false ); ?>
				</div>
			</form>

		</div>
		<?php
	}

	/**
	 * Render selected auto-reply template card.
	 *
	 * @param array<string,mixed>|null $template Template.
	 * @param int                      $template_count Template count.
	 * @param bool                     $disabled Whether controls should be disabled.
	 * @return void
	 */
	private function render_selected_auto_reply_template_card( $template, $template_count, $disabled = false ) {
		if ( ! $template ) {
			?>
			<div class="subin-auto-reply-template-empty<?php echo $disabled ? ' is-disabled' : ''; ?>">
				<h3><?php esc_html_e( 'No template selected', 'submission-inbox' ); ?></h3>
				<p><?php esc_html_e( 'Choose which auto-reply template should be sent after a successful submission.', 'submission-inbox' ); ?></p>
				<?php if ( $template_count > 0 ) : ?>
					<div class="subin-auto-reply-template-empty__actions">
						<button type="button" class="button button-primary" data-subin-open-template-picker <?php disabled( $disabled ); ?>><?php esc_html_e( 'Change template', 'submission-inbox' ); ?></button>
						<?php if ( $disabled ) : ?>
							<span class="button disabled" aria-disabled="true"><?php esc_html_e( 'Edit template', 'submission-inbox' ); ?></span>
						<?php else : ?>
							<a class="button" href="<?php echo esc_url( $this->auto_replies_create_url() ); ?>"><?php esc_html_e( 'Edit template', 'submission-inbox' ); ?></a>
						<?php endif; ?>
					</div>
				<?php else : ?>
					<?php if ( $disabled ) : ?>
						<span class="button button-primary disabled" aria-disabled="true"><?php esc_html_e( 'Change template', 'submission-inbox' ); ?></span>
					<?php else : ?>
						<a class="button button-primary" href="<?php echo esc_url( $this->auto_replies_create_url() ); ?>"><?php esc_html_e( 'Change template', 'submission-inbox' ); ?></a>
					<?php endif; ?>
				<?php endif; ?>
			</div>
			<?php
			return;
		}

		$template_id = absint( $template['id'] ?? 0 );
		?>
		<div class="subin-auto-reply-template-selected<?php echo $disabled ? ' is-disabled' : ''; ?>" data-subin-selected-template-card="<?php echo esc_attr( (string) $template_id ); ?>">
			<div class="subin-auto-reply-template-selected__preview">
				<?php $this->render_auto_reply_template_mini_preview( $template ); ?>
			</div>
			<div class="subin-auto-reply-template-selected__details">
				<div class="subin-auto-reply-template-selected__top">
					<div>
						<h3><?php echo esc_html( $template['name'] ?? __( 'Auto-reply template', 'submission-inbox' ) ); ?></h3>
					</div>
					<div class="subin-auto-reply-template-selected__actions">
						<button type="button" class="button" data-subin-open-template-picker <?php disabled( $disabled ); ?>><?php esc_html_e( 'Change template', 'submission-inbox' ); ?></button>
						<a class="button<?php echo $disabled ? ' disabled' : ''; ?>" href="<?php echo esc_url( $this->auto_reply_edit_url( $template_id ) ); ?>" data-subin-auto-reply-link-control <?php echo $disabled ? 'aria-disabled="true" tabindex="-1"' : ''; ?>><?php esc_html_e( 'Edit template', 'submission-inbox' ); ?></a>
					</div>
				</div>
				<dl class="subin-template-meta-list">
					<dt><?php esc_html_e( 'Subject', 'submission-inbox' ); ?></dt>
					<dd><?php echo esc_html( $template['subject'] ?? __( 'We received your request', 'submission-inbox' ) ); ?></dd>
					<dt><?php esc_html_e( 'Preheader', 'submission-inbox' ); ?></dt>
					<dd><?php echo esc_html( $template['preheader'] ?? __( 'Not set', 'submission-inbox' ) ); ?></dd>
					<dt><?php esc_html_e( 'Used for', 'submission-inbox' ); ?></dt>
					<dd><?php esc_html_e( 'Visitor confirmation after successful submission', 'submission-inbox' ); ?></dd>
					<dt><?php esc_html_e( 'Variables', 'submission-inbox' ); ?></dt>
					<dd><?php esc_html_e( 'Supports form and submission variables.', 'submission-inbox' ); ?></dd>
				</dl>
			</div>
		</div>
		<?php
	}

	/**
	 * Render auto-reply picker modal.
	 *
	 * @param array<int,array<string,mixed>> $templates Templates.
	 * @return void
	 */
	private function render_auto_reply_template_picker_modal( array $templates ) {
		?>
		<div class="subin-template-picker-modal" data-subin-template-picker-modal hidden>
			<div class="subin-template-picker-modal__backdrop" data-subin-close-template-picker></div>
			<div class="subin-template-picker-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="subin-template-picker-title">
				<header class="subin-template-picker-modal__header">
					<div>
						<h2 id="subin-template-picker-title"><?php esc_html_e( 'Choose auto-reply template', 'submission-inbox' ); ?></h2>
						<p><?php esc_html_e( 'Preview the email before attaching it to this form.', 'submission-inbox' ); ?></p>
					</div>
					<div class="subin-template-picker-modal__header-actions">
						<a class="button" href="<?php echo esc_url( $this->auto_replies_create_url() ); ?>"><?php esc_html_e( 'Create template', 'submission-inbox' ); ?></a>
						<button type="button" class="button-link" data-subin-close-template-picker><?php esc_html_e( 'Close', 'submission-inbox' ); ?></button>
					</div>
				</header>
				<div class="subin-template-picker-modal__grid">
					<?php if ( empty( $templates ) ) : ?>
						<div class="subin-template-picker-modal__empty">
							<p><?php esc_html_e( 'No auto-reply templates are available yet.', 'submission-inbox' ); ?></p>
							<a class="button button-primary" href="<?php echo esc_url( $this->auto_replies_create_url() ); ?>"><?php esc_html_e( 'Create template', 'submission-inbox' ); ?></a>
						</div>
					<?php else : ?>
						<?php foreach ( $templates as $template ) : ?>
							<?php $this->render_auto_reply_template_picker_card( $template ); ?>
						<?php endforeach; ?>
					<?php endif; ?>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Render one picker card.
	 *
	 * @param array<string,mixed> $template Template.
	 * @return void
	 */
	private function render_auto_reply_template_picker_card( array $template ) {
		$template_id = absint( $template['id'] ?? 0 );
		?>
		<article class="subin-template-picker-card" data-subin-template-picker-card="<?php echo esc_attr( (string) $template_id ); ?>">
			<?php $this->render_auto_reply_template_mini_preview( $template ); ?>
			<div class="subin-template-picker-card__body">
				<div class="subin-template-picker-card__title">
					<h3><?php echo esc_html( $template['name'] ?? __( 'Auto-reply template', 'submission-inbox' ) ); ?></h3>
				</div>
				<p><?php echo esc_html( $template['preheader'] ?? __( 'Confirmation email template.', 'submission-inbox' ) ); ?></p>
				<div class="subin-template-picker-card__subject"><?php echo esc_html( $template['subject'] ?? __( 'We received your request', 'submission-inbox' ) ); ?></div>
				<button type="button" class="button button-primary" data-subin-use-template="<?php echo esc_attr( (string) $template_id ); ?>"><?php esc_html_e( 'Use this template', 'submission-inbox' ); ?></button>
			</div>
		</article>
		<?php
	}

	/**
	 * Render safe compact email preview.
	 *
	 * @param array<string,mixed> $template Template.
	 * @return void
	 */
	private function render_auto_reply_template_mini_preview( array $template ) {
		$srcdoc = $this->auto_reply_template_preview_srcdoc( $template );
		if ( '' !== $srcdoc ) {
			?>
			<div class="subin-template-mini-preview">
				<iframe
					<?php /* translators: %s: auto-reply template name. */ ?>
					title="<?php echo esc_attr( sprintf( __( 'Preview: %s', 'submission-inbox' ), $template['name'] ?? __( 'Auto-reply template', 'submission-inbox' ) ) ); ?>"
					sandbox=""
					loading="lazy"
					scrolling="no"
					srcdoc="<?php echo esc_attr( $srcdoc ); ?>"
				></iframe>
			</div>
			<?php
			return;
		}

		?>
		<div class="subin-template-mini-preview subin-template-mini-preview--fallback" aria-hidden="true">
			<div class="subin-template-mini-preview__hero"></div>
			<div class="subin-template-mini-preview__card">
				<span></span>
				<span></span>
				<span></span>
			</div>
			<div class="subin-template-mini-preview__subject"><?php echo esc_html( wp_trim_words( (string) ( $template['subject'] ?? __( 'We received your request', 'submission-inbox' ) ), 7 ) ); ?></div>
		</div>
		<?php
	}

	/**
	 * Build sandboxed srcdoc for a real auto-reply preview.
	 *
	 * @param array<string,mixed> $template Template.
	 * @return string
	 */
	private function auto_reply_template_preview_srcdoc( array $template ) {
		$html = (string) ( $template['rendered_html'] ?? $template['body'] ?? '' );
		$css  = (string) ( $template['rendered_css'] ?? '' );

		if ( '' === trim( $html ) ) {
			return '';
		}

		$html = wp_kses(
			$html,
			\SUBIN\AutoReplies\AutoReplyTemplateRepository::allowed_email_html(),
			array( 'http', 'https', 'mailto', 'tel' )
		);

		if ( '' === trim( $html ) ) {
			return '';
		}

		$css = wp_strip_all_tags( $css );
		$css = preg_replace( '/@import[^;]+;/i', '', $css );
		$css = preg_replace( '/(?:expression|javascript|data|behavior|-moz-binding)\s*[:(][^;}]+[;}]?/i', '', (string) $css );

		$preview_css = '
			html,body{margin:0;padding:0;background:#eef3f8;overflow:hidden;}
			body{width:620px;min-height:420px;font-family:Arial,sans-serif;transform-origin:top left;}
			a{pointer-events:none;}
			*{box-sizing:border-box;}
		';

		return '<!doctype html><html><head><meta charset="utf-8"><base target="_blank"><style>' . esc_html( $preview_css . "\n" . $css ) . '</style></head><body>' . $html . '</body></html>';
	}

	/**
	 * Find template in a list.
	 *
	 * @param array<int,array<string,mixed>> $templates Templates.
	 * @param int                           $template_id Template id.
	 * @return array<string,mixed>|null
	 */
	private function find_auto_reply_template( array $templates, $template_id ) {
		$template_id = absint( $template_id );
		foreach ( $templates as $template ) {
			if ( absint( $template['id'] ?? 0 ) === $template_id ) {
				return $template;
			}
		}

		return null;
	}

	/**
	 * Build auto-reply edit URL.
	 *
	 * @param int $template_id Template id.
	 * @return string
	 */
	private function auto_reply_edit_url( $template_id ) {
		return add_query_arg(
			array(
				'page'                 => 'submission-inbox-auto-replies',
				'subin_auto_replies_tab' => 'templates',
				'view'                 => 'edit',
				'id'                   => absint( $template_id ),
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Build auto-reply rule URL.
	 *
	 * @param int $rule_id Rule id.
	 * @param int $form_id Form id.
	 * @return string
	 */
	private function auto_reply_rule_url( $rule_id, $form_id ) {
		$args = array(
			'page'                 => 'submission-inbox-auto-replies',
			'subin_auto_replies_tab' => 'rules',
			'rule_view'            => $rule_id > 0 ? 'edit' : 'new',
			'provider'             => 'submission_inbox_form',
			'form_id'              => absint( $form_id ),
		);
		if ( $rule_id > 0 ) {
			$args['id'] = absint( $rule_id );
		}

		return add_query_arg( $args, admin_url( 'admin.php' ) );
	}

	/**
	 * Build auto-reply create URL.
	 *
	 * @return string
	 */
	private function auto_replies_create_url() {
		return add_query_arg(
			array(
				'page'                 => 'submission-inbox-auto-replies',
				'subin_auto_replies_tab' => 'templates',
				'view'                 => 'new',
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Render a shortcode copy block.
	 *
	 * @param string $shortcode Shortcode.
	 * @param string $title Title.
	 * @param string $description Description.
	 * @return void
	 */
	private function render_shortcode_box( $shortcode, $title, $description = '' ) {
		?>
		<div class="subin-shortcode-box">
			<div class="subin-shortcode-box__header">
				<h3><?php echo esc_html( $title ); ?></h3>
				<?php if ( '' !== $description ) : ?>
					<p><?php echo esc_html( $description ); ?></p>
				<?php endif; ?>
			</div>
			<div class="subin-shortcode-box__copy">
				<code class="subin-shortcode-pill"><?php echo esc_html( $shortcode ); ?></code>
				<button type="button" class="button button-primary subin-shortcode-copy subin-copy-shortcode" data-subin-copy="<?php echo esc_attr( $shortcode ); ?>"><?php esc_html_e( 'Copy shortcode', 'submission-inbox' ); ?></button>
				<span class="subin-copy-feedback" aria-live="polite"></span>
			</div>
		</div>
		<?php
	}

	/**
	 * Get preview options from a saved field.
	 *
	 * @param array<string,mixed> $field Field.
	 * @return array<int,array{label:string,value:string}>
	 */
	private function get_preview_options( array $field ) {
		if ( empty( $field['field_options'] ) || ! is_string( $field['field_options'] ) ) {
			return array();
		}

		$decoded = json_decode( $field['field_options'], true );
		if ( ! is_array( $decoded ) ) {
			return array();
		}

		$options = array();
		foreach ( $decoded as $option ) {
			if ( ! is_array( $option ) ) {
				continue;
			}

			$label = sanitize_text_field( $option['label'] ?? '' );
			$value = sanitize_key( $option['value'] ?? $label );
			if ( '' === $label || '' === $value ) {
				continue;
			}

			$options[] = array(
				'label' => $label,
				'value' => $value,
			);
		}

		return $options;
	}

	/**
	 * Get email fields for auto-reply recipient choices.
	 *
	 * @param int $form_id Form id.
	 * @return array<int,array{id:int,key:string,label:string}>
	 */
	private function email_field_choices( $form_id ) {
		$choices = array();
		foreach ( $this->fields->get_by_form_id( $form_id ) as $field ) {
			if ( 'email' !== sanitize_key( $field['field_type'] ?? '' ) ) {
				continue;
			}

			$key = sanitize_key( $field['field_key'] ?? '' );
			if ( '' === $key ) {
				continue;
			}

			$label = sanitize_text_field( $field['field_label'] ?? '' );
			$choices[] = array(
				'id'    => absint( $field['id'] ?? 0 ),
				'key'   => $key,
				'label' => '' !== $label ? $label : $key,
			);
		}

		return $choices;
	}

	/**
	 * Normalize saved auto-reply recipient selection for the editor.
	 *
	 * @param array<int,array{id:int,key:string,label:string}> $email_fields Real email field choices.
	 * @param mixed                                     $selected Selected key or legacy id.
	 * @return string
	 */
	private function normalize_auto_reply_recipient_field_key( array $email_fields, $selected ) {
		if ( empty( $email_fields ) ) {
			return '';
		}

		$selected_key = sanitize_key( (string) $selected );
		$selected_id  = absint( $selected );
		foreach ( $email_fields as $field ) {
			if ( $selected_key === $field['key'] || ( $selected_id > 0 && $selected_id === absint( $field['id'] ?? 0 ) ) ) {
				return $field['key'];
			}
		}

		if ( '' !== $selected_key || 1 === count( $email_fields ) ) {
			return $email_fields[0]['key'];
		}

		return '';
	}

	/**
	 * Format date.
	 *
	 * @param string $date Date.
	 * @return string
	 */
	private function format_date( $date ) {
		return $date ? mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $date ) : '';
	}

	/**
	 * Format template key.
	 *
	 * @param string $template_key Template key.
	 * @return string
	 */
	private function format_template_label( $template_key ) {
		$template = $this->templates->get_template( sanitize_key( $template_key ) );

		return $template ? (string) $template['title'] : __( 'Custom', 'submission-inbox' );
	}

	/**
	 * Template description for chooser cards.
	 *
	 * @param string              $template_key Template key.
	 * @param array<string,mixed> $template Template.
	 * @return string
	 */
	private function template_description( $template_key, array $template ) {
		$descriptions = array(
			'contact_us'       => __( 'A simple form for general messages.', 'submission-inbox' ),
			'request_quote'    => __( 'Collect project or service requests with helpful details.', 'submission-inbox' ),
			'callback_request' => __( 'Let visitors request a phone call.', 'submission-inbox' ),
			'support_request'  => __( 'Collect support questions with a subject and message.', 'submission-inbox' ),
			'booking_inquiry'  => __( 'Collect booking requests or appointment inquiries.', 'submission-inbox' ),
			'job_application'  => __( 'Collect basic job application details without file uploads.', 'submission-inbox' ),
		);

		return $descriptions[ sanitize_key( $template_key ) ] ?? sanitize_text_field( $template['header_description'] ?? '' );
	}

	/**
	 * Recommended use text for a template.
	 *
	 * @param string $template_key Template key.
	 * @return string
	 */
	private function template_recommended_use( $template_key ) {
		$uses = array(
			'contact_us'       => __( 'General inquiries', 'submission-inbox' ),
			'request_quote'    => __( 'Service requests', 'submission-inbox' ),
			'callback_request' => __( 'Phone follow-up', 'submission-inbox' ),
			'support_request'  => __( 'Support intake', 'submission-inbox' ),
			'booking_inquiry'  => __( 'Booking requests', 'submission-inbox' ),
			'job_application'  => __( 'Hiring inquiries', 'submission-inbox' ),
		);

		return $uses[ sanitize_key( $template_key ) ] ?? __( 'Custom form', 'submission-inbox' );
	}

	/**
	 * Template category label.
	 *
	 * @param string              $template_key Template key.
	 * @param array<string,mixed> $template Template.
	 * @return string
	 */
	private function template_category( $template_key, array $template ) {
		if ( ! empty( $template['category'] ) ) {
			return sanitize_text_field( $template['category'] );
		}

		return $this->template_recommended_use( $template_key );
	}

	/**
	 * Get readable template field labels.
	 *
	 * @param array<string,mixed> $template Template.
	 * @return array<int,string>
	 */
	private function template_field_labels( array $template ) {
		$fields = is_array( $template['fields'] ?? null ) ? $template['fields'] : array();
		$labels = array();

		foreach ( $fields as $field ) {
			if ( ! empty( $field['field_label'] ) ) {
				$labels[] = sanitize_text_field( $field['field_label'] );
			}
		}

		return $labels;
	}

	/**
	 * Format template field count.
	 *
	 * @param int $count Field count.
	 * @return string
	 */
	private function format_template_field_count( $count ) {
		$count = absint( $count );

		return sprintf(
			/* translators: %d: Field count. */
			_n( '%d field', '%d fields', $count, 'submission-inbox' ),
			$count
		);
	}

	/**
	 * Render a real, non-submitting form template preview.
	 *
	 * @param array<string,mixed> $template Template.
	 * @param bool                $large Whether to render the larger version.
	 * @return void
	 */
	private function render_template_visual_preview( array $template, $large = false ) {
		$renderer = new \SUBIN\Forms\FormRenderer();
		$form     = $this->template_preview_form( $template );
		$fields   = $this->template_preview_fields( $template );
		?>
		<div class="subin-template-preview subin-template-preview--real<?php echo $large ? ' subin-template-preview--large' : ''; ?>" aria-hidden="true">
			<div class="subin-template-preview__viewport">
				<div class="subin-template-preview__scale">
					<?php echo $renderer->render( $form, $fields, array( 'preview' => true ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- FormRenderer escapes fields and outputs sanitized preview markup. ?>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Build a temporary form row for template preview rendering.
	 *
	 * @param array<string,mixed> $template Template.
	 * @return array<string,mixed>
	 */
	private function template_preview_form( array $template ) {
		$seed = absint( sprintf( '%u', crc32( (string) ( $template['title'] ?? 'template' ) ) ) );
		$form_id = 900000 + ( $seed % 90000 );

		return array(
			'id'                 => $form_id,
			'title'              => sanitize_text_field( $template['title'] ?? __( 'Form template', 'submission-inbox' ) ),
			'slug'               => sanitize_title( $template['title'] ?? 'form-template' ),
			'header_title'       => sanitize_text_field( $template['header_title'] ?? $template['title'] ?? '' ),
			'header_description' => sanitize_textarea_field( $template['header_description'] ?? '' ),
			'footer_note'        => sanitize_textarea_field( $template['footer_note'] ?? '' ),
			'success_message'    => sanitize_textarea_field( $template['success_message'] ?? '' ),
			'submit_button_text' => sanitize_text_field( $template['submit_button_text'] ?? __( 'Send', 'submission-inbox' ) ),
			'layout_template'    => \SUBIN\Forms\FormRepository::LAYOUT_CLEAN_CARD,
			'style_preset'       => \SUBIN\Forms\FormRepository::STYLE_DEFAULT,
			'settings_json'      => '',
		);
	}

	/**
	 * Build temporary field rows for template preview rendering.
	 *
	 * @param array<string,mixed> $template Template.
	 * @return array<int,array<string,mixed>>
	 */
	private function template_preview_fields( array $template ) {
		$fields = is_array( $template['fields'] ?? null ) ? $template['fields'] : array();
		$rows   = array();
		$order  = 0;

		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) ) {
				continue;
			}

			++$order;
			$options = $field['field_options'] ?? array();
			if ( is_array( $options ) ) {
				$options = wp_json_encode( $options );
				$options = is_string( $options ) ? $options : '';
			}

			$rows[] = array(
				'id'            => $order,
				'field_key'     => sanitize_key( $field['field_key'] ?? 'field_' . $order ),
				'field_type'    => sanitize_key( $field['field_type'] ?? 'text' ),
				'field_label'   => sanitize_text_field( $field['field_label'] ?? '' ),
				'placeholder'   => sanitize_text_field( $field['placeholder'] ?? '' ),
				'help_text'     => sanitize_textarea_field( $field['help_text'] ?? '' ),
				'is_required'   => ! empty( $field['is_required'] ) ? 1 : 0,
				'field_options' => $options,
				'sort_order'    => $order,
			);
		}

		return $rows;
	}

	/**
	 * Render Use Template form.
	 *
	 * @param string $template_key Template key.
	 * @return void
	 */
	private function render_use_template_form( $template_key ) {
		$template_key = sanitize_key( $template_key );
		?>
		<form class="subin-template-use-form" method="post" action="<?php echo esc_url( $this->templates_url() ); ?>">
			<input type="hidden" name="subin_editor_action" value="use_template" />
			<input type="hidden" name="template_key" value="<?php echo esc_attr( $template_key ); ?>" />
			<?php wp_nonce_field( 'subin_use_template_' . $template_key ); ?>
			<button type="submit" class="button button-primary subin-btn subin-btn-primary"><?php esc_html_e( 'Use Template', 'submission-inbox' ); ?></button>
		</form>
		<?php
	}

	/**
	 * Sanitize an allowlisted choice.
	 *
	 * @param mixed             $value Value.
	 * @param array<int,string> $allowed Allowed values.
	 * @param string            $fallback Fallback.
	 * @return string
	 */
	private function sanitize_choice( $value, array $allowed, $fallback ) {
		$value = sanitize_key( (string) $value );
		return in_array( $value, $allowed, true ) ? $value : $fallback;
	}

	/**
	 * Render built-in forms table.
	 *
	 * @param array<int,array<string,mixed>> $forms Forms.
	 * @return void
	 */
	private function render_forms_table( array $forms ) {
		$plugin_settings = $this->settings->all();
		?>
		<div class="subin-form-list">
			<?php foreach ( $forms as $form ) : ?>
				<?php
				$form_id       = absint( $form['id'] ?? 0 );
				$fields        = $this->fields->get_by_form_id( $form_id );
				$settings      = $this->forms->decode_settings( $form );
				$auto_reply_rule = $this->auto_reply_rules->find_rule( 'submission_inbox_form', (string) $form_id );
				$auto_reply_template = $auto_reply_rule ? $this->auto_reply_templates->get( absint( $auto_reply_rule['template_id'] ?? 0 ) ) : null;
				$indicators    = $this->form_setup_indicators( $form, $fields, $settings, $plugin_settings );
				$status        = sanitize_key( $form['status'] ?? '' );
				$is_active     = \SUBIN\Forms\FormRepository::STATUS_ACTIVE === $status;
				$shortcode     = '[submission_inbox_form id="' . $form_id . '"]';
				$last_edited   = ! empty( $form['updated_at'] ) ? $form['updated_at'] : ( $form['created_at'] ?? '' );
				$field_count   = count( $fields );
				$email_found   = $this->form_has_email_field( $fields );
				$save_leads    = ! empty( $form['save_leads'] );
				$auto_reply_summary = $this->form_auto_reply_summary( $auto_reply_rule, $auto_reply_template, $email_found );
				?>
				<div class="subin-form-card<?php echo $is_active ? ' subin-form-card--active' : ' subin-form-card--inactive'; ?>">
					<div class="subin-form-main">
						<div class="subin-card-top">
							<span class="subin-card-top__icon dashicons dashicons-feedback" aria-hidden="true"></span>
							<div class="subin-card-top__content">
								<div class="subin-title-line">
									<h3 class="subin-form-card__title"><?php echo esc_html( $form['title'] ?? '' ); ?></h3>
									<?php SUBIN_View::badge( $is_active ? __( 'Active', 'submission-inbox' ) : __( 'Inactive', 'submission-inbox' ), $is_active ? 'success' : 'muted' ); ?>
								</div>
								<div class="subin-meta">
									<span><?php echo esc_html( $form['slug'] ?? '' ); ?></span>
									<?php if ( $last_edited ) : ?>
										<span><?php esc_html_e( 'Last edited', 'submission-inbox' ); ?>: <?php echo esc_html( $this->format_date( $last_edited ) ); ?></span>
									<?php endif; ?>
								</div>
							</div>
							<a class="button button-primary subin-btn subin-btn-primary" href="<?php echo esc_url( $this->edit_url( $form_id ) ); ?>"><?php esc_html_e( 'Edit in Design Studio', 'submission-inbox' ); ?></a>
						</div>

						<div class="subin-shortcode-row">
							<span class="subin-shortcode-label"><?php esc_html_e( 'Shortcode', 'submission-inbox' ); ?></span>
							<span class="subin-shortcode subin-shortcode-pill"><?php echo esc_html( $shortcode ); ?></span>
							<?php SUBIN_View::help_tooltip( __( 'Copy this shortcode and paste it into a page or post to display the form.', 'submission-inbox' ) ); ?>
							<button type="button" class="button button-small subin-shortcode-copy subin-copy-shortcode" data-subin-copy="<?php echo esc_attr( $shortcode ); ?>"><?php esc_html_e( 'Copy', 'submission-inbox' ); ?></button>
							<span class="subin-copy-feedback" aria-live="polite"></span>
						</div>

						<div class="subin-card-grid">
							<section class="subin-panel">
								<h4 class="subin-panel-title"><?php esc_html_e( 'Form setup', 'submission-inbox' ); ?></h4>
								<div class="subin-setup-list">
									<?php foreach ( $indicators as $indicator ) : ?>
										<div class="subin-setup-row subin-setup-row-<?php echo esc_attr( $indicator['type'] ); ?>">
											<span class="subin-dot" aria-hidden="true"></span>
											<div>
												<strong>
													<?php echo esc_html( $indicator['label'] ); ?>
													<?php if ( ! empty( $indicator['help'] ) ) : ?>
														<?php SUBIN_View::help_tooltip( $indicator['help'] ); ?>
													<?php endif; ?>
												</strong>
												<span><?php echo esc_html( $indicator['text'] ); ?></span>
											</div>
										</div>
									<?php endforeach; ?>
								</div>
							</section>

							<section class="subin-panel subin-autoreply">
								<div class="subin-panel-title-row">
									<h4 class="subin-panel-title"><?php esc_html_e( 'Visitor auto-reply', 'submission-inbox' ); ?></h4>
									<?php SUBIN_View::badge( $auto_reply_summary['status_label'], $auto_reply_summary['badge_type'] ); ?>
								</div>
								<p><?php echo esc_html( $auto_reply_summary['text'] ); ?></p>
								<?php if ( ! empty( $auto_reply_summary['template_name'] ) ) : ?>
									<div class="subin-template-box">
										<span><?php esc_html_e( 'Template', 'submission-inbox' ); ?></span>
										<a href="<?php echo esc_url( $this->auto_reply_edit_url( absint( $auto_reply_template['id'] ?? 0 ) ) ); ?>"><?php echo esc_html( $auto_reply_summary['template_name'] ); ?></a>
									</div>
								<?php endif; ?>
								<a class="subin-action-chip" href="<?php echo esc_url( $this->auto_reply_rule_url( absint( $auto_reply_rule['id'] ?? 0 ), $form_id ) ); ?>"><?php echo esc_html( $auto_reply_rule ? __( 'Manage rule', 'submission-inbox' ) : __( 'Create rule', 'submission-inbox' ) ); ?></a>
							</section>
						</div>

						<div class="subin-info-row">
							<span class="subin-info-pill"><strong><?php esc_html_e( 'Fields', 'submission-inbox' ); ?>:</strong> <?php echo esc_html( (string) $field_count ); ?></span>
							<span class="subin-info-pill"><strong><?php esc_html_e( 'Email field', 'submission-inbox' ); ?>:</strong> <?php echo esc_html( $email_found ? __( 'Found', 'submission-inbox' ) : __( 'Missing', 'submission-inbox' ) ); ?></span>
							<span class="subin-info-pill"><strong><?php esc_html_e( 'Save leads', 'submission-inbox' ); ?>:</strong> <?php echo esc_html( $save_leads ? __( 'Enabled', 'submission-inbox' ) : __( 'Disabled', 'submission-inbox' ) ); ?></span>
						</div>

						<div class="subin-card-actions">
							<a class="subin-action-chip" href="<?php echo esc_url( $this->edit_url( $form_id, 'settings' ) ); ?>"><?php esc_html_e( 'Settings', 'submission-inbox' ); ?></a>
							<a class="subin-action-chip" href="<?php echo esc_url( $this->action_url( 'duplicate', $form_id ) ); ?>"><?php esc_html_e( 'Duplicate', 'submission-inbox' ); ?></a>
							<?php if ( $is_active ) : ?>
								<a class="subin-action-chip subin-action-chip-warning" href="<?php echo esc_url( $this->action_url( 'deactivate', $form_id ) ); ?>"><?php esc_html_e( 'Deactivate', 'submission-inbox' ); ?></a>
							<?php else : ?>
								<a class="subin-action-chip subin-action-chip-warning" href="<?php echo esc_url( $this->action_url( 'activate', $form_id ) ); ?>"><?php esc_html_e( 'Activate', 'submission-inbox' ); ?></a>
							<?php endif; ?>
							<a class="subin-action-chip subin-action-chip-danger" href="<?php echo esc_url( $this->action_url( 'delete', $form_id ) ); ?>" onclick="return confirm('<?php echo esc_js( __( 'Delete this form? Existing saved leads will remain in the Inbox.', 'submission-inbox' ) ); ?>');"><?php esc_html_e( 'Delete', 'submission-inbox' ); ?></a>
						</div>
					</div>
				</div>
			<?php endforeach; ?>
		</div>
		<?php
	}

	/**
	 * Get attached auto-reply template for form settings.
	 *
	 * @param array<string,mixed>          $settings Form settings.
	 * @param array<int,array<string,mixed>> $templates Templates.
	 * @return array<string,mixed>|null
	 */
	private function auto_reply_template_for_form_settings( array $settings, array $templates ) {
		$auto_reply = is_array( $settings['auto_reply'] ?? null ) ? $settings['auto_reply'] : array();
		$template_id = absint( $auto_reply['template_id'] ?? 0 );

		if ( $template_id <= 0 ) {
			return null;
		}

		$template = $this->find_auto_reply_template( $templates, $template_id );
		if ( $template ) {
			return $template;
		}

		return $this->auto_reply_templates->get( $template_id );
	}

	/**
	 * Render attached auto-reply template info.
	 *
	 * @param int                       $form_id Form id.
	 * @param array<string,mixed>       $settings Form settings.
	 * @param array<string,mixed>|null  $template Attached template.
	 * @param bool                      $inside_setup Whether the row is nested inside a setup item.
	 * @return void
	 */
	private function render_form_auto_reply_template_line( $form_id, array $settings, $template, $inside_setup = false ) {
		$auto_reply   = is_array( $settings['auto_reply'] ?? null ) ? $settings['auto_reply'] : array();
		$template_id  = absint( $auto_reply['template_id'] ?? 0 );
		$settings_url = $this->edit_url( $form_id, 'settings' );
		?>
		<div class="subin-form-auto-reply-line<?php echo $inside_setup ? ' subin-form-auto-reply-line--inside' : ''; ?>">
			<span class="subin-form-auto-reply-line__label"><?php echo esc_html( $inside_setup ? __( 'Template', 'submission-inbox' ) : __( 'Auto-reply template', 'submission-inbox' ) ); ?></span>
			<?php if ( $template ) : ?>
				<a class="subin-form-auto-reply-line__template" href="<?php echo esc_url( $this->auto_reply_edit_url( absint( $template['id'] ?? 0 ) ) ); ?>">
					<span><?php echo esc_html( (string) ( $template['name'] ?? __( 'Untitled template', 'submission-inbox' ) ) ); ?></span>
				</a>
			<?php elseif ( $template_id > 0 ) : ?>
				<a class="subin-form-auto-reply-line__missing" href="<?php echo esc_url( $settings_url ); ?>"><?php esc_html_e( 'Selected template was not found', 'submission-inbox' ); ?></a>
			<?php else : ?>
				<a class="subin-form-auto-reply-line__empty" href="<?php echo esc_url( $settings_url ); ?>"><?php esc_html_e( 'No template attached', 'submission-inbox' ); ?></a>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Build auto-reply summary for a form card.
	 *
	 * @param array<string,mixed>|null $rule Rule row.
	 * @param array<string,mixed>|null $template Attached template.
	 * @param bool                     $email_found Whether the form has an email field.
	 * @return array<string,string>
	 */
	private function form_auto_reply_summary( $rule, $template, $email_found ) {
		$enabled            = is_array( $rule ) && ! empty( $rule['enabled'] );
		$template_id        = is_array( $rule ) ? absint( $rule['template_id'] ?? 0 ) : 0;
		$template_name      = is_array( $template ) ? sanitize_text_field( $template['name'] ?? '' ) : '';

		if ( ! is_array( $rule ) ) {
			return array(
				'status_label'  => __( 'Disabled', 'submission-inbox' ),
				'badge_type'    => 'muted',
				'text'          => __( 'No auto-reply rule configured for this form.', 'submission-inbox' ),
				'template_name' => '',
			);
		}

		if ( $enabled && ! $email_found ) {
			return array(
				'status_label'  => __( 'Needs email field', 'submission-inbox' ),
				'badge_type'    => 'warning',
				'text'          => __( 'Auto-reply requires a valid email field in the form.', 'submission-inbox' ),
				'template_name' => $template_name,
			);
		}

		if ( $enabled && ( $template_id <= 0 || '' === $template_name ) ) {
			return array(
				'status_label'  => __( 'Needs setup', 'submission-inbox' ),
				'badge_type'    => 'warning',
				'text'          => __( 'Select an auto-reply template before sending visitor confirmations.', 'submission-inbox' ),
				'template_name' => '',
			);
		}

		if ( $enabled && '' !== $template_name ) {
			return array(
				'status_label'  => __( 'Enabled', 'submission-inbox' ),
				'badge_type'    => 'success',
				'text'          => __( 'Sends a confirmation message to the visitor if the form has a valid email field.', 'submission-inbox' ),
				'template_name' => $template_name,
			);
		}

		return array(
			'status_label'  => __( 'Disabled', 'submission-inbox' ),
			'badge_type'    => 'muted',
			'text'          => __( 'The auto-reply rule exists but is disabled.', 'submission-inbox' ),
			'template_name' => $template_name,
		);
	}

	/**
	 * Check if a form has an email field.
	 *
	 * @param array<int,array<string,mixed>> $fields Fields.
	 * @return bool
	 */
	private function form_has_email_field( array $fields ) {
		foreach ( $fields as $field ) {
			if ( \SUBIN\Forms\FormFieldRepository::TYPE_EMAIL === sanitize_key( $field['field_type'] ?? '' ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Build setup indicators for a form card.
	 *
	 * @param array<string,mixed> $form Form row.
	 * @param array<int,array<string,mixed>> $fields Fields.
	 * @param array<string,mixed> $settings Form settings.
	 * @param array<string,mixed> $plugin_settings Plugin settings.
	 * @return array<int,array<string,mixed>>
	 */
	private function form_setup_indicators( array $form, array $fields, array $settings, array $plugin_settings ) {
		$indicators       = array();
		$email_found      = $this->form_has_email_field( $fields );
		$notify_enabled   = ! empty( $form['notification_enabled'] );
		$notify_recipient = sanitize_email( $form['notification_email'] ?? '' );
		if ( '' === $notify_recipient ) {
			$notify_recipient = sanitize_email( $plugin_settings['notification_recipient_email'] ?? '' );
		}
		$smtp_enabled           = ! empty( $plugin_settings['enable_smtp'] );
		$smtp_configured        = $smtp_enabled && '' !== (string) ( $plugin_settings['smtp_host'] ?? '' );
		if ( $smtp_configured && ! empty( $plugin_settings['smtp_auth'] ) && '' === (string) ( $plugin_settings['smtp_username'] ?? '' ) ) {
			$smtp_configured = false;
		}

		$indicators[] = ! empty( $form['save_leads'] )
			? array( 'label' => __( 'Leads are saved', 'submission-inbox' ), 'text' => __( 'Submissions are stored in Submission Inbox.', 'submission-inbox' ), 'type' => 'success', 'help' => __( 'When enabled, submissions are stored in Submission Inbox.', 'submission-inbox' ) )
			: array( 'label' => __( 'Lead storage disabled', 'submission-inbox' ), 'text' => __( 'Submissions are not stored in Submission Inbox.', 'submission-inbox' ), 'type' => 'warning', 'help' => __( 'Enable lead storage if you want submissions saved even when email notification fails.', 'submission-inbox' ) );

		if ( $notify_enabled && is_email( $notify_recipient ) ) {
			$indicators[] = array( 'label' => __( 'Admin notification enabled', 'submission-inbox' ), 'text' => __( 'Admin notification has a recipient.', 'submission-inbox' ), 'type' => 'success', 'help' => __( 'Sends an email notification when this form is submitted.', 'submission-inbox' ) );
		} elseif ( $notify_enabled ) {
			$indicators[] = array( 'label' => __( 'Notification needs recipient', 'submission-inbox' ), 'text' => __( 'Add a recipient before using admin notifications.', 'submission-inbox' ), 'type' => 'warning', 'help' => __( 'Sends an email notification when this form is submitted.', 'submission-inbox' ) );
		} else {
			$indicators[] = array( 'label' => __( 'Notification disabled', 'submission-inbox' ), 'text' => __( 'No admin notification is configured.', 'submission-inbox' ), 'type' => 'muted', 'help' => __( 'Sends an email notification when this form is submitted.', 'submission-inbox' ) );
		}

		$indicators[] = $smtp_configured
			? array( 'label' => __( 'SMTP configured', 'submission-inbox' ), 'text' => __( 'WordPress uses the configured SMTP connection.', 'submission-inbox' ), 'type' => 'success', 'help' => __( 'Controls how WordPress sends email from this site.', 'submission-inbox' ) )
			: array( 'label' => __( 'SMTP not configured', 'submission-inbox' ), 'text' => __( 'WordPress will use the default mail method.', 'submission-inbox' ), 'type' => 'warning', 'help' => __( 'WordPress will use the default mail method until SMTP is configured.', 'submission-inbox' ) );

		$indicators[] = $email_found
			? array( 'label' => __( 'Email field found', 'submission-inbox' ), 'text' => __( 'Reply-To and auto-reply can use a submitted email.', 'submission-inbox' ), 'type' => 'success', 'help' => __( 'Auto-replies and Reply-To use the submitted email field.', 'submission-inbox' ) )
			: array( 'label' => __( 'Email field missing', 'submission-inbox' ), 'text' => __( 'Reply-To and auto-reply may not work as expected.', 'submission-inbox' ), 'type' => 'warning', 'help' => __( 'Auto-replies and Reply-To may not work without an email field.', 'submission-inbox' ) );

		return $indicators;
	}

	/**
	 * Render auto-reply templates section.
	 *
	 * @return void
	 */
	private function render_auto_reply_templates_section() {
		$templates = $this->auto_reply_templates->get_paginated(
			array(
				'per_page' => 50,
				'offset'   => 0,
			)
		);
		?>
		<div class="subin-forms-section">
			<div class="subin-section-heading">
				<h2><?php esc_html_e( 'Auto-reply Templates', 'submission-inbox' ); ?></h2>
				<p><?php esc_html_e( 'Create reusable visitor auto-reply messages and attach them to forms from the form editor.', 'submission-inbox' ); ?></p>
			</div>
			<div class="subin-auto-reply-manager">
				<div class="subin-auto-reply-manager__header">
					<p><?php esc_html_e( 'Create reusable visitor auto-reply templates and attach them to forms from the form editor.', 'submission-inbox' ); ?></p>
					<a class="button button-primary subin-btn subin-btn-primary" href="<?php echo esc_url( $this->auto_replies_new_url() ); ?>"><?php esc_html_e( 'Create auto-reply template', 'submission-inbox' ); ?></a>
				</div>
				<?php if ( empty( $templates ) ) : ?>
					<div class="subin-forms-empty-state subin-empty-state">
						<h2><?php esc_html_e( 'No auto-reply templates yet', 'submission-inbox' ); ?></h2>
						<p><?php esc_html_e( 'Create a reusable confirmation message for visitors after they submit a form.', 'submission-inbox' ); ?></p>
						<div class="subin-forms-empty-state__actions">
							<a class="button button-primary subin-btn subin-btn-primary" href="<?php echo esc_url( $this->auto_replies_new_url() ); ?>"><?php esc_html_e( 'Create auto-reply template', 'submission-inbox' ); ?></a>
						</div>
					</div>
				<?php else : ?>
					<div class="subin-auto-reply-card-list">
						<?php foreach ( $templates as $template ) : ?>
							<?php
							$template_id = absint( $template['id'] ?? 0 );
							?>
							<div class="subin-auto-reply-card">
								<div class="subin-auto-reply-card__main">
									<div class="subin-auto-reply-card__title-row">
										<h3><a href="<?php echo esc_url( $this->auto_replies_edit_url( $template_id ) ); ?>"><?php echo esc_html( $template['name'] ?? '' ); ?></a></h3>
									</div>
									<p class="subin-auto-reply-card__subject"><?php echo esc_html( $template['subject'] ?? '' ); ?></p>
									<?php if ( ! empty( $template['updated_at'] ) ) : ?>
										<p class="subin-auto-reply-card__meta"><?php esc_html_e( 'Last edited', 'submission-inbox' ); ?>: <?php echo esc_html( $this->format_date( $template['updated_at'] ) ); ?></p>
									<?php endif; ?>
								</div>
								<div class="subin-action-row subin-auto-reply-card__actions">
									<a class="button button-primary subin-btn subin-btn-primary" href="<?php echo esc_url( $this->auto_replies_edit_url( $template_id ) ); ?>"><?php esc_html_e( 'Edit', 'submission-inbox' ); ?></a>
									<a class="subin-action-chip" href="<?php echo esc_url( $this->auto_replies_action_url( 'duplicate', $template_id ) ); ?>"><?php esc_html_e( 'Duplicate', 'submission-inbox' ); ?></a>
									<a class="subin-action-chip subin-action-chip-danger" href="<?php echo esc_url( $this->auto_replies_action_url( 'delete', $template_id ) ); ?>" onclick="return confirm('<?php echo esc_js( __( 'Delete this auto-reply template?', 'submission-inbox' ) ); ?>');"><?php esc_html_e( 'Delete', 'submission-inbox' ); ?></a>
								</div>
							</div>
						<?php endforeach; ?>
					</div>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Build auto-reply template new URL.
	 *
	 * @return string
	 */
	private function auto_replies_new_url() {
		return add_query_arg( array( 'page' => 'submission-inbox-auto-replies', 'view' => 'new' ), admin_url( 'admin.php' ) );
	}

	/**
	 * Build auto-reply template edit URL.
	 *
	 * @param int $template_id Template id.
	 * @return string
	 */
	private function auto_replies_edit_url( $template_id ) {
		return add_query_arg( array( 'page' => 'submission-inbox-auto-replies', 'view' => 'edit', 'id' => absint( $template_id ) ), admin_url( 'admin.php' ) );
	}

	/**
	 * Build auto-reply template action URL.
	 *
	 * @param string $action Action.
	 * @param int    $template_id Template id.
	 * @return string
	 */
	private function auto_replies_action_url( $action, $template_id ) {
		$action      = sanitize_key( $action );
		$template_id = absint( $template_id );

		return wp_nonce_url(
			add_query_arg(
				array(
					'page'       => 'submission-inbox-auto-replies',
					'subin_action' => $action,
					'id'         => $template_id,
				),
				admin_url( 'admin.php' )
			),
			'subin_auto_reply_' . $action . '_' . $template_id
		);
	}

	/**
	 * Render notice from query arg.
	 *
	 * @param string $forced_notice Optional forced notice.
	 * @return void
	 */
	private function render_notice( $forced_notice = '' ) {
		$notice = $forced_notice ? sanitize_key( $forced_notice ) : SUBIN_Request::get_key( 'subin_notice' );
		if ( '' === $notice ) {
			return;
		}

		$messages = array(
			'form_created'          => array( 'success', __( 'Form created. You can now customize it.', 'submission-inbox' ) ),
			'form_create_failed'    => array( 'error', __( 'Form could not be created.', 'submission-inbox' ) ),
			'template_not_found'    => array( 'error', __( 'Template was not found.', 'submission-inbox' ) ),
			'form_duplicated'       => array( 'success', __( 'Form duplicated.', 'submission-inbox' ) ),
			'form_duplicate_failed' => array( 'error', __( 'Form could not be duplicated.', 'submission-inbox' ) ),
			'form_active'           => array( 'success', __( 'Form activated.', 'submission-inbox' ) ),
			'form_inactive'         => array( 'success', __( 'Form deactivated.', 'submission-inbox' ) ),
			'form_status_failed'    => array( 'error', __( 'Form status could not be updated.', 'submission-inbox' ) ),
			'form_deleted'          => array( 'success', __( 'Form deleted.', 'submission-inbox' ) ),
			'form_delete_failed'    => array( 'error', __( 'Form could not be deleted.', 'submission-inbox' ) ),
			'form_saved'            => array( 'success', __( 'Form saved.', 'submission-inbox' ) ),
			'form_content_saved'    => array( 'success', __( 'Form content saved.', 'submission-inbox' ) ),
			'header_title_saved'    => array( 'success', __( 'Header title saved.', 'submission-inbox' ) ),
			'header_description_saved' => array( 'success', __( 'Header description saved.', 'submission-inbox' ) ),
			'footer_note_saved'     => array( 'success', __( 'Footer note saved.', 'submission-inbox' ) ),
			'privacy_text_saved'    => array( 'success', __( 'Privacy text saved.', 'submission-inbox' ) ),
			'button_text_saved'     => array( 'success', __( 'Button text saved.', 'submission-inbox' ) ),
			'design_content_invalid' => array( 'error', __( 'Content could not be saved.', 'submission-inbox' ) ),
			'form_design_saved'     => array( 'success', __( 'Form design saved.', 'submission-inbox' ) ),
			'form_style_applied'    => array( 'success', __( 'Form style applied.', 'submission-inbox' ) ),
			'form_style_not_found'  => array( 'error', __( 'Form style was not found.', 'submission-inbox' ) ),
			'form_style_apply_failed' => array( 'error', __( 'Form style could not be applied.', 'submission-inbox' ) ),
			'background_applied'       => array( 'success', __( 'Background applied.', 'submission-inbox' ) ),
			'background_preset_not_found' => array( 'error', __( 'Background preset was not found.', 'submission-inbox' ) ),
			'background_apply_failed'  => array( 'error', __( 'Background could not be applied.', 'submission-inbox' ) ),
			'gradient_applied'         => array( 'success', __( 'Gradient applied.', 'submission-inbox' ) ),
			'gradient_preset_not_found' => array( 'error', __( 'Gradient preset was not found.', 'submission-inbox' ) ),
			'gradient_apply_failed'    => array( 'error', __( 'Gradient could not be applied.', 'submission-inbox' ) ),
			'button_applied'           => array( 'success', __( 'Button style applied.', 'submission-inbox' ) ),
			'button_preset_not_found'  => array( 'error', __( 'Button style was not found.', 'submission-inbox' ) ),
			'button_apply_failed'      => array( 'error', __( 'Button style could not be applied.', 'submission-inbox' ) ),
			'input_applied'            => array( 'success', __( 'Input style applied.', 'submission-inbox' ) ),
			'input_preset_not_found'   => array( 'error', __( 'Input style was not found.', 'submission-inbox' ) ),
			'input_apply_failed'       => array( 'error', __( 'Input style could not be applied.', 'submission-inbox' ) ),
			'design_element_saved'     => array( 'success', __( 'Element style saved.', 'submission-inbox' ) ),
			'design_element_cleared'   => array( 'success', __( 'Element style cleared.', 'submission-inbox' ) ),
			'design_element_invalid'   => array( 'error', __( 'Element could not be customized.', 'submission-inbox' ) ),
			'design_element_save_failed' => array( 'error', __( 'Element style could not be saved.', 'submission-inbox' ) ),
			'form_email_saved'      => array( 'success', __( 'Email notification settings saved.', 'submission-inbox' ) ),
			'default_notification_recipient_missing' => array( 'error', __( 'Default admin notification recipient is not configured. Add it in Settings → Sending or use a custom recipient for this form.', 'submission-inbox' ) ),
			'custom_notification_recipient_invalid' => array( 'error', __( 'Enter a valid custom recipient email address.', 'submission-inbox' ) ),
			'auto_reply_email_field_required' => array( 'error', __( 'Visitor auto-reply requires an Email field. Add an Email field to the form or turn auto-reply off.', 'submission-inbox' ) ),
			'form_messages_saved'   => array( 'success', __( 'Form messages saved.', 'submission-inbox' ) ),
			'form_publish_saved'       => array( 'success', __( 'Publish settings saved.', 'submission-inbox' ) ),
			'form_settings_saved'      => array( 'success', __( 'Form settings saved.', 'submission-inbox' ) ),
			'form_advanced_html_saved' => array( 'success', __( 'Advanced HTML settings saved.', 'submission-inbox' ) ),
			'custom_css_invalid'       => array( 'error', __( 'Custom CSS must include .subin-form-{id} so it only affects this form.', 'submission-inbox' ) ),
			'form_save_failed'         => array( 'error', __( 'Form could not be saved.', 'submission-inbox' ) ),
			'form_not_found'           => array( 'error', __( 'Form was not found.', 'submission-inbox' ) ),
			'field_added'              => array( 'success', __( 'Field added.', 'submission-inbox' ) ),
			'field_saved'              => array( 'success', __( 'Field saved.', 'submission-inbox' ) ),
			'field_updated'            => array( 'success', __( 'Field updated.', 'submission-inbox' ) ),
			'field_duplicated'         => array( 'success', __( 'Field duplicated.', 'submission-inbox' ) ),
			'field_deleted'            => array( 'success', __( 'Field deleted.', 'submission-inbox' ) ),
			'field_order_updated'      => array( 'success', __( 'Field order updated.', 'submission-inbox' ) ),
			'field_not_found'          => array( 'error', __( 'Field was not found.', 'submission-inbox' ) ),
			'field_save_failed'        => array( 'error', __( 'Field could not be saved.', 'submission-inbox' ) ),
			'block_added'              => array( 'success', __( 'Block added.', 'submission-inbox' ) ),
			'block_updated'            => array( 'success', __( 'Block updated.', 'submission-inbox' ) ),
			'block_deleted'            => array( 'success', __( 'Block deleted.', 'submission-inbox' ) ),
			'block_order_updated'      => array( 'success', __( 'Block order updated.', 'submission-inbox' ) ),
			'block_status_updated'     => array( 'success', __( 'Block status updated.', 'submission-inbox' ) ),
			'block_save_failed'        => array( 'error', __( 'Block could not be saved.', 'submission-inbox' ) ),
			'permission_denied'        => array( 'error', __( 'You do not have permission to perform this action.', 'submission-inbox' ) ),
			'security_failed'          => array( 'error', __( 'Security check failed. Please try again.', 'submission-inbox' ) ),
		);

		if ( empty( $messages[ $notice ] ) ) {
			return;
		}

		$type    = $messages[ $notice ][0];
		$message = $messages[ $notice ][1];
		?>
		<div class="notice notice-<?php echo esc_attr( $type ); ?> is-dismissible">
			<p><?php echo esc_html( $message ); ?></p>
		</div>
		<?php
	}

	/**
	 * Get field type labels.
	 *
	 * @return array<string,string>
	 */
	private function field_type_labels() {
		return array(
			'text'     => __( 'Text', 'submission-inbox' ),
			'email'    => __( 'Email', 'submission-inbox' ),
			'phone'    => __( 'Phone', 'submission-inbox' ),
			'date'     => __( 'Date field', 'submission-inbox' ),
			'textarea' => __( 'Textarea', 'submission-inbox' ),
			'select'   => __( 'Select', 'submission-inbox' ),
			'radio'    => __( 'Radio', 'submission-inbox' ),
			'checkbox' => __( 'Checkbox', 'submission-inbox' ),
			'consent'  => __( 'Consent checkbox', 'submission-inbox' ),
			'hidden'   => __( 'Hidden', 'submission-inbox' ),
		);
	}

	/**
	 * Get the next sort order for a form field.
	 *
	 * @param int $form_id Form ID.
	 * @return int
	 */
	private function next_field_sort_order( $form_id ) {
		$max = -1;
		foreach ( $this->fields->get_by_form_id( $form_id ) as $field ) {
			$max = max( $max, absint( $field['sort_order'] ?? 0 ) );
		}

		return $max + 1;
	}

	/**
	 * Get Elements Library field defaults.
	 *
	 * @param string $field_type Field type.
	 * @param int    $form_id Form ID.
	 * @return array<string,mixed>
	 */
	private function library_field_defaults( $field_type, $form_id ) {
		$field_type = $this->sanitize_choice( $field_type, array_keys( $this->field_type_labels() ), 'text' );
		$defaults   = array(
			'text'     => array(
				'field_label'   => __( 'Text field', 'submission-inbox' ),
				'field_key'     => 'text_field',
				'placeholder'   => '',
				'is_required'   => false,
				'field_options' => array(),
			),
			'email'    => array(
				'field_label'   => __( 'Email', 'submission-inbox' ),
				'field_key'     => 'email',
				'placeholder'   => 'email@example.com',
				'is_required'   => true,
				'field_options' => array(),
			),
			'phone'    => array(
				'field_label'   => __( 'Phone', 'submission-inbox' ),
				'field_key'     => 'phone',
				'placeholder'   => '',
				'is_required'   => false,
				'field_options' => array(),
			),
			'date'     => array(
				'field_label'   => __( 'Date', 'submission-inbox' ),
				'field_key'     => 'date',
				'placeholder'   => '',
				'is_required'   => false,
				'field_options' => array(),
			),
			'textarea' => array(
				'field_label'   => __( 'Message', 'submission-inbox' ),
				'field_key'     => 'message',
				'placeholder'   => '',
				'is_required'   => true,
				'field_options' => array(),
			),
			'select'   => array(
				'field_label'   => __( 'Select field', 'submission-inbox' ),
				'field_key'     => 'select_field',
				'placeholder'   => '',
				'is_required'   => false,
				'field_options' => $this->default_choice_options(),
			),
			'radio'    => array(
				'field_label'   => __( 'Radio field', 'submission-inbox' ),
				'field_key'     => 'radio_field',
				'placeholder'   => '',
				'is_required'   => false,
				'field_options' => $this->default_choice_options(),
			),
			'checkbox' => array(
				'field_label'   => __( 'Checkbox field', 'submission-inbox' ),
				'field_key'     => 'checkbox_field',
				'placeholder'   => '',
				'is_required'   => false,
				'field_options' => $this->default_choice_options(),
			),
			'consent'  => array(
				'field_label'   => __( 'I agree to be contacted about this request.', 'submission-inbox' ),
				'field_key'     => 'consent',
				'placeholder'   => '',
				'is_required'   => false,
				'field_options' => array(),
			),
			'hidden'   => array(
				'field_label'   => __( 'Hidden field', 'submission-inbox' ),
				'field_key'     => 'hidden_field',
				'placeholder'   => '',
				'is_required'   => false,
				'field_options' => array(),
			),
		);

		$field = $defaults[ $field_type ];
		$field['field_type'] = $field_type;
		$field['help_text']  = '';
		$field['sort_order'] = $this->next_field_sort_order( $form_id );

		return $field;
	}

	/**
	 * Get default options for library choice fields.
	 *
	 * @return array<int,array{label:string,value:string}>
	 */
	private function default_choice_options() {
		return array(
			array(
				'label' => __( 'Option 1', 'submission-inbox' ),
				'value' => 'option_1',
			),
			array(
				'label' => __( 'Option 2', 'submission-inbox' ),
				'value' => 'option_2',
			),
		);
	}

	/**
	 * Render editor tab.
	 *
	 * @param int    $form_id Form id.
	 * @param string $tab Tab key.
	 * @param string $label Tab label.
	 * @param string $active Active tab.
	 * @return void
	 */
	private function editor_tab( $form_id, $tab, $label, $active ) {
		$is_active = $tab === $active;

		printf(
			'<a class="nav-tab subin-editor-tab %1$s" href="%2$s"%3$s>%4$s</a>',
			$is_active ? 'nav-tab-active is-active subin-editor-tab--active' : '',
			esc_url( $this->edit_url( $form_id, $tab ) ),
			$is_active ? ' aria-current="page"' : '',
			esc_html( $label )
		);
	}

	/**
	 * Get the active internal Forms tab.
	 *
	 * @return string
	 */
	private function current_forms_tab() {
		$tab          = SUBIN_Request::get_key( 'subin_forms_tab', 'forms' );
		$allowed_tabs = array( 'forms' );

		return in_array( $tab, $allowed_tabs, true ) ? $tab : 'forms';
	}

	/**
	 * Check if an old internal auto-replies tab URL was requested.
	 *
	 * @return bool
	 */
	private function is_legacy_auto_replies_tab_request() {
		return 'auto_replies' === SUBIN_Request::get_key( 'subin_forms_tab' );
	}

	/**
	 * Render Forms product navigation.
	 *
	 * @param string $active Active tab key.
	 * @return void
	 */
	private function render_management_tabs( $active ) {
		SUBIN_View::admin_tabs(
			array(
				array(
					'key'   => 'forms',
					'label' => __( 'Forms', 'submission-inbox' ),
					'url'   => $this->forms_url(),
				),
			),
			$active
		);
	}

	/**
	 * Build base Forms URL.
	 *
	 * @return string
	 */
	private function forms_url() {
		return add_query_arg( array( 'page' => self::PAGE_SLUG ), admin_url( 'admin.php' ) );
	}

	/**
	 * Build auto-reply templates manager URL.
	 *
	 * @return string
	 */
	private function auto_replies_url() {
		return add_query_arg( array( 'page' => 'submission-inbox-auto-replies' ), admin_url( 'admin.php' ) );
	}

	/**
	 * Build an internal Forms tab URL.
	 *
	 * @param string $tab Tab key.
	 * @return string
	 */
	private function forms_tab_url( $tab ) {
		$allowed_tabs = array( 'forms' );
		$tab          = in_array( $tab, $allowed_tabs, true ) ? $tab : 'forms';

		return add_query_arg(
			array(
				'page'          => self::PAGE_SLUG,
				'subin_forms_tab' => $tab,
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Build template chooser URL.
	 *
	 * @return string
	 */
	private function templates_url() {
		return add_query_arg(
			array(
				'page' => self::PAGE_SLUG,
				'view' => 'templates',
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Build template preview URL.
	 *
	 * @param string $template_key Template key.
	 * @return string
	 */
	private function template_preview_url( $template_key ) {
		return add_query_arg(
			array(
				'page'     => self::PAGE_SLUG,
				'view'     => 'template-preview',
				'template' => sanitize_key( $template_key ),
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Build preview URL.
	 *
	 * @param int $form_id Form id.
	 * @return string
	 */
	private function preview_url( $form_id ) {
		return add_query_arg(
			array(
				'page'    => self::PAGE_SLUG,
				'view'    => 'preview',
				'form_id' => absint( $form_id ),
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Build edit URL.
	 *
	 * @param int                 $form_id Form id.
	 * @param string              $tab Tab key.
	 * @param array<string,mixed> $extra Extra args.
	 * @return string
	 */
	private function edit_url( $form_id, $tab = 'content', array $extra = array() ) {
		$tab = sanitize_key( $tab );
		if ( 'content' === $tab ) {
			$tab = 'design_studio';
		}
		if ( in_array( $tab, array( 'email', 'publish' ), true ) ) {
			$tab = 'settings';
		}

		return add_query_arg(
			array_merge(
				array(
					'page'    => self::PAGE_SLUG,
					'view'    => 'edit',
					'form_id' => absint( $form_id ),
					'tab'     => $tab,
				),
				$extra
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Build action URL.
	 *
	 * @param string              $action Action.
	 * @param int                 $form_id Form id.
	 * @param array<string,mixed> $extra Extra args.
	 * @return string
	 */
	private function action_url( $action, $form_id = 0, array $extra = array() ) {
		$action = sanitize_key( $action );
		$args   = array_merge(
			array(
				'page'       => self::PAGE_SLUG,
				'subin_action' => $action,
			),
			$extra
		);

		if ( $form_id > 0 ) {
			$args['form_id'] = absint( $form_id );
		}

		$field_id = absint( $extra['field_id'] ?? 0 );
		$nonce_action = $this->action_nonce_name( $action, $form_id, $field_id );
		if ( 'create_from_template' === $action && ! empty( $extra['template_key'] ) ) {
			$nonce_action = 'subin_use_template_' . sanitize_key( $extra['template_key'] );
		}

		return wp_nonce_url( add_query_arg( $args, admin_url( 'admin.php' ) ), $nonce_action );
	}

	/**
	 * Get nonce action name for state-changing admin links.
	 *
	 * @param string $action Action.
	 * @param int    $form_id Form id.
	 * @param int    $field_id Field id.
	 * @return string
	 */
	private function action_nonce_name( $action, $form_id = 0, $field_id = 0 ) {
		$action   = sanitize_key( $action );
		$form_id  = absint( $form_id );
		$field_id = absint( $field_id );

		if ( 'create_from_template' === $action ) {
			return 'subin_create_form_from_template';
		}

		if ( 'duplicate' === $action ) {
			return 'subin_duplicate_form_' . $form_id;
		}

		if ( in_array( $action, array( 'activate', 'deactivate' ), true ) ) {
			return 'subin_update_form_' . $form_id;
		}

		if ( 'delete' === $action ) {
			return 'subin_delete_form_' . $form_id;
		}

		return 'subin_forms_' . $action;
	}

	/**
	 * Get nonce action name for editor POST actions.
	 *
	 * @param string $action Action.
	 * @param int    $form_id Form id.
	 * @param int    $field_id Field id.
	 * @return string
	 */
	private function editor_nonce_name( $action, $form_id, $field_id = 0 ) {
		$action   = sanitize_key( $action );
		$form_id  = absint( $form_id );
		$field_id = absint( $field_id );

		if ( in_array( $action, array( 'save_notification', 'save_publish', 'save_settings' ), true ) ) {
			return 'subin_update_form_' . $form_id;
		}

		return 'subin_form_editor_' . $form_id;
	}

	/**
	 * Sanitize auto-reply recipient field against this form's email fields.
	 *
	 * @param int    $form_id Form id.
	 * @param string $field_key Field key.
	 * @return string
	 */
	private function sanitize_auto_reply_recipient_field_key( $form_id, $field_key ) {
		$field_key = sanitize_key( $field_key );
		$field_id  = absint( $field_key );
		if ( '' === $field_key ) {
			return '';
		}

		foreach ( $this->email_field_choices( $form_id ) as $field ) {
			if ( $field_key === $field['key'] || ( $field_id > 0 && $field_id === absint( $field['id'] ?? 0 ) ) ) {
				return $field['key'];
			}
		}

		return '';
	}

	/**
	 * Sanitize selected auto-reply template id.
	 *
	 * @param int $template_id Template id.
	 * @return int
	 */
	private function sanitize_auto_reply_template_id( $template_id ) {
		$template_id = absint( $template_id );
		if ( $template_id <= 0 ) {
			return 0;
		}

		return $this->auto_reply_templates->get( $template_id ) ? $template_id : 0;
	}

	/**
	 * Sanitize admin notification recipient mode.
	 *
	 * @param string $mode Recipient mode.
	 * @return string
	 */
	private function sanitize_notification_recipient_mode( $mode ) {
		$mode = sanitize_key( $mode );

		return in_array( $mode, array( 'default', 'custom' ), true ) ? $mode : 'default';
	}

	/**
	 * Determine default notification recipient mode for existing forms.
	 *
	 * @param array<string,mixed> $form Form row.
	 * @return string
	 */
	private function default_notification_recipient_mode_for_form( array $form ) {
		$recipient = sanitize_email( $form['notification_email'] ?? '' );

		return is_email( $recipient ) ? 'custom' : 'default';
	}

	/**
	 * Get the global default admin notification recipient.
	 *
	 * @return string
	 */
	private function default_admin_notification_recipient() {
		$settings  = get_option( \SUBIN_SettingsRepository::OPTION_KEY, array() );
		$recipient = is_array( $settings ) ? sanitize_email( $settings['notification_recipient_email'] ?? '' ) : '';
		if ( is_email( $recipient ) ) {
			return $recipient;
		}

		$admin_email = sanitize_email( (string) get_option( 'admin_email' ) );
		return is_email( $admin_email ) ? $admin_email : '';
	}

	/**
	 * Redirect to Forms page with notice.
	 *
	 * @param string $notice Notice key.
	 * @return void
	 */
	private function redirect_with_notice( $notice ) {
		$this->safe_admin_redirect(
			add_query_arg(
				array(
					'page'       => self::PAGE_SLUG,
					'subin_notice' => sanitize_key( $notice ),
				),
				admin_url( 'admin.php' )
			)
		);
	}

	/**
	 * Redirect to template chooser with notice.
	 *
	 * @param string $notice Notice key.
	 * @return void
	 */
	private function redirect_to_templates( $notice ) {
		$this->safe_admin_redirect(
			add_query_arg(
				array(
					'subin_notice' => sanitize_key( $notice ),
				),
				$this->templates_url()
			)
		);
	}

	/**
	 * Redirect to edit screen with notice.
	 *
	 * @param int    $form_id Form id.
	 * @param string $tab Tab key.
	 * @param string $notice Notice key.
	 * @return void
	 */
	private function redirect_to_edit( $form_id, $tab, $notice ) {
		$this->safe_admin_redirect(
			add_query_arg(
				array(
					'subin_notice' => sanitize_key( $notice ),
				),
				$this->edit_url( $form_id, $tab )
			)
		);
	}

	/**
	 * Redirect from an admin page callback without header warnings.
	 *
	 * @param string $url Target URL.
	 * @return void
	 */
	private function safe_admin_redirect( $url ) {
		$url = esc_url_raw( $url );
		if ( ! headers_sent() ) {
			wp_safe_redirect( $url );
			exit;
		}

		printf(
			'<script>window.location.href=%1$s;</script><noscript><meta http-equiv="refresh" content="0;url=%2$s" /></noscript>',
			wp_json_encode( $url ),
			esc_url( $url )
		);
		exit;
	}
}
