<?php
/**
 * Admin / Backend for Estimated Delivery for WooCommerce.
 * Uses actions and filters so add-ons/custom code can extend settings, rules, product tab.
 *
 * @package EstimatedDeliveryDateWC
 */

declare( strict_types=1 );

defined( 'ABSPATH' ) || exit;

/**
 * Class EDFW_Backend
 *
 * @since 1.0.0
 */
final class EDFW_Backend {

	/**
	 * Singleton instance.
	 *
	 * @since 1.0.0
	 * @var EDFW_Backend|null
	 */
	private static ?EDFW_Backend $instance = null;

	/**
	 * Saved settings (raw, unmerged with defaults).
	 *
	 * @since 1.0.0
	 * @var array<string, mixed>
	 */
	private static array $settings = array();

	/**
	 * Saved delivery rules.
	 *
	 * @since 1.0.0
	 * @var array<int, array>
	 */
	private static array $rules = array();

	/**
	 * Get the singleton instance.
	 *
	 * @since 1.0.0
	 * @return EDFW_Backend
	 */
	public static function instance(): EDFW_Backend {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Load saved options and register admin hooks.
	 *
	 * @since 1.0.0
	 */
	private function __construct() {
		self::$settings = (array) get_option( 'edfw_settings', array() );
		self::$rules    = (array) get_option( 'edfw_rules', array() );

		add_action( 'admin_menu', array( $this, 'admin_menu' ), 20 );
		add_action( 'admin_init', array( $this, 'register_settings' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10 );
		add_filter( 'plugin_action_links_' . plugin_basename( EDFW_FILE ), array( $this, 'action_links' ) );

		add_filter( 'woocommerce_product_data_tabs', array( $this, 'product_data_tabs' ) );
		add_action( 'woocommerce_product_data_panels', array( $this, 'product_data_panels' ) );
		add_action( 'woocommerce_process_product_meta', array( $this, 'save_product_meta' ) );

		add_action( 'edfw_loaded', array( $this, 'register_addon_extension_hooks' ) );
	}

	/**
	 * Register hooks that add-ons can use to inject UI (settings, rules, product tab).
	 *
	 * @since 1.0.0
	 */
	public function register_addon_extension_hooks(): void {
		// add-ons can add_action( 'edfw_before_settings', ... ) etc.
	}

	/**
	 * Get all settings (merged with defaults). Filterable.
	 *
	 * @since 1.0.0
	 * @return array<string, mixed>
	 */
	public static function get_settings(): array {
		$defaults = EDFW_Helper::default_settings();
		$merged   = array_merge( $defaults, self::$settings );
		/**
		 * Filter the full settings array after defaults are merged in.
		 *
		 * @since 1.0.0
		 * @param array $merged Settings, keyed by setting name.
		 */
		return (array) apply_filters( 'edfw_get_settings', $merged );
	}

	/**
	 * Get single setting. Use edfw_setting_{$key} or edfw_get_setting to override.
	 *
	 * @since 1.0.0
	 * @param string $key           Setting key.
	 * @param mixed  $default_value Default if not set.
	 * @return mixed
	 */
	public static function get_setting( string $key, $default_value = null ) {
		$settings = self::get_settings();
		$value    = array_key_exists( $key, $settings ) ? $settings[ $key ] : $default_value;
		if ( has_filter( 'edfw_setting_' . $key ) ) {
			/**
			 * Filter one named setting as it is read.
			 * The hook name ends with the setting key, e.g. edfw_setting_cutoff_time.
			 *
			 * @since 1.0.0
			 * @param mixed $value Setting value.
			 */
			$value = apply_filters( 'edfw_setting_' . $key, $value );
		}
		/**
		 * Filter any single setting as it is read.
		 *
		 * @since 1.0.0
		 * @param mixed $value Setting value.
		 * @param string $key Setting key.
		 * @param mixed $default_value Value used when the setting is unset.
		 */
		return apply_filters( 'edfw_get_setting', $value, $key, $default_value );
	}

	/**
	 * Get rules. Filterable (Add-ons can add/modify).
	 *
	 * @since 1.0.0
	 * @return array<int, array>
	 */
	public static function get_rules(): array {
		/**
		 * Filter the delivery rules before they are matched.
		 * Add-ons can add, remove or rewrite rules here.
		 *
		 * @since 1.0.0
		 * @param array $rules Rules keyed by rule id.
		 */
		return (array) apply_filters( 'edfw_get_rules', self::$rules );
	}

	/**
	 * Re-read the saved options into the static cache.
	 *
	 * The constructor loads options once per request. After a REST save the statics are
	 * stale, so anything reading through get_settings() in the same request — including
	 * the response we hand back to the admin app — would return pre-save values.
	 *
	 * @since 1.0.0
	 */
	public static function refresh(): void {
		self::$settings = (array) get_option( 'edfw_settings', array() );
		self::$rules    = (array) get_option( 'edfw_rules', array() );
	}

	/**
	 * Cache-busting version for computed-date transients.
	 *
	 * @since 1.0.0
	 * @return int
	 */
	public static function cache_version(): int {
		return (int) get_option( 'edfw_cache_ver', 1 );
	}

	/**
	 * Invalidate all computed-date transients (called when settings/rules change).
	 *
	 * @since 1.0.0
	 */
	public static function bump_cache_version(): void {
		update_option( 'edfw_cache_ver', self::cache_version() + 1 );
	}

	/**
	 * Add the settings page under the WooCommerce menu.
	 *
	 * @since 1.0.0
	 */
	public function admin_menu(): void {
		add_submenu_page(
			'woocommerce',
			__( 'Estimated Delivery Date', 'wpactpro-estimated-delivery-date' ),
			__( 'Estimated Delivery Date', 'wpactpro-estimated-delivery-date' ),
			'manage_woocommerce',
			'edfw-settings',
			array( $this, 'settings_page' )
		);
	}

	/**
	 * Register the settings and rules options with their sanitize callbacks.
	 *
	 * @since 1.0.0
	 */
	public function register_settings(): void {
		register_setting(
			'edfw_settings',
			'edfw_settings',
			array(
				'type'              => 'array',
				'sanitize_callback' => array( $this, 'sanitize_settings' ),
			)
		);
		register_setting(
			'edfw_settings',
			'edfw_rules',
			array(
				'type'              => 'array',
				'sanitize_callback' => array( $this, 'sanitize_rules' ),
			)
		);
	}

	/**
	 * Sanitize settings. Add-ons can filter extra keys.
	 *
	 * @since 1.0.0
	 * @param array $input Raw input.
	 * @return array
	 */
	public function sanitize_settings( array $input ): array {
		$defaults = EDFW_Helper::default_settings();
		$allowed  = array_keys( $defaults );
		/**
		 * Filter which setting keys may be saved.
		 * Anything not listed is discarded, so an add-on storing keys in this plugin's
		 * option must add them here.
		 *
		 * @since 1.0.0
		 * @param array $allowed Allowed setting keys.
		 */
		$allowed = (array) apply_filters( 'edfw_settings_allowed_keys', $allowed );
		$out     = array();
		foreach ( $allowed as $key ) {
			if ( ! array_key_exists( $key, $input ) ) {
				continue;
			}
			$val = $input[ $key ];
			if ( 'skipped_dates' === $key && is_array( $val ) ) {
				$val = EDFW_Helper::sanitize_array( $val );
			} elseif ( 'message_template' === $key ) {
				// Allow the en-dash and tags; keep as a single line of text.
				$val = sanitize_text_field( (string) $val );
			} elseif ( is_array( $val ) ) {
				$val = EDFW_Helper::sanitize_array( $val );
			} else {
				$val = sanitize_text_field( (string) $val );
			}
			/**
			 * Filter one named setting as it is sanitized for storage.
			 * The hook name ends with the setting key.
			 *
			 * @since 1.0.0
			 * @param mixed $val Sanitized value.
			 */
			$out[ $key ] = apply_filters( 'edfw_sanitize_setting_' . $key, $val );
		}
		return $out;
	}

	/**
	 * Sanitize rules.
	 *
	 * @since 1.0.0
	 * @param array $input Raw rules.
	 * @return array
	 */
	public function sanitize_rules( array $input ): array {
		$out = array();
		foreach ( (array) $input as $key => $rule ) {
			$rule  = (array) $rule;
			$conds = array();
			foreach ( (array) ( $rule['conditions'] ?? array() ) as $c ) {
				$c       = (array) $c;
				$conds[] = array(
					'type' => sanitize_key( $c['type'] ?? 'product' ),
					'op'   => ( ( $c['op'] ?? 'is' ) === 'is_not' ) ? 'is_not' : 'is',
					'vals' => array_values( array_map( 'sanitize_text_field', (array) ( $c['vals'] ?? array() ) ) ),
				);
			}
			if ( empty( $conds ) ) {
				$conds[] = array(
					'type' => 'product',
					'op'   => 'is',
					'vals' => array(),
				);
			}
			$scheduled = '';
			if ( ! empty( $rule['scheduled'] ) && is_string( $rule['scheduled'] ) ) {
				$ts        = strtotime( $rule['scheduled'] );
				$scheduled = $ts ? gmdate( 'Y-m-d', $ts ) : '';
			}
			$r = array(
				'conditions' => $conds,
				'min'        => (string) max( 0, (int) ( $rule['min'] ?? 2 ) ),
				'max'        => (string) max( 0, (int) ( $rule['max'] ?? 5 ) ),
				'scheduled'  => $scheduled,
			);
			/**
			 * Filter a single rule after sanitization, before it is stored.
			 *
			 * @since 1.0.0
			 * @param array $r Sanitized rule.
			 * @param string|int $key Rule key.
			 */
			$r                                    = apply_filters( 'edfw_sanitize_rule', $r, $key );
			$out[ sanitize_key( (string) $key ) ] = $r;
		}
		/**
		 * Filter the whole rule set after sanitization, before it is stored.
		 *
		 * @since 1.0.0
		 * @param array $out Sanitized rules keyed by rule id.
		 */
		return apply_filters( 'edfw_sanitize_rules', $out );
	}

	/**
	 * Enqueue admin assets: the React app on the settings screen and the
	 * override toggle script on the product editor.
	 *
	 * @since 1.0.0
	 * @param string $hook Current admin page hook suffix.
	 */
	public function enqueue_scripts( string $hook ): void {
		if ( strpos( $hook, 'edfw-settings' ) === false ) {
			// On the product editor, load the small toggle script for the override tab.
			if ( in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
				$screen = get_current_screen();
				if ( $screen && 'product' === $screen->post_type ) {
					wp_enqueue_script( 'edfw-product-admin', EDFW_URI . 'assets/js/product-admin.js', array( 'jquery' ), EDFW_VERSION, true );
				}
			}
			/**
			 * Fires after this plugin's admin assets are enqueued.
			 * Add-ons enqueue their own admin assets here.
			 *
			 * @since 1.0.0
			 * @param string $hook Current admin page hook suffix.
			 */
			do_action( 'edfw_enqueue_admin_assets', $hook );
			return;
		}

		// The path is built from our own constant and a literal — no user input reaches
		// it. This is the standard @wordpress/scripts asset manifest, which carries the
		// script's dependency list and a content hash for cache-busting.
		$asset_file = EDFW_DIR . 'build/index.asset.php';
		// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path is plugin-constant + literal, not user input.
		$asset = file_exists( $asset_file ) ? require $asset_file : array(
			'dependencies' => array(),
			'version'      => EDFW_VERSION,
		);

		wp_enqueue_script(
			'edfw-admin',
			EDFW_URI . 'build/index.js',
			$asset['dependencies'],
			$asset['version'],
			true
		);
		wp_set_script_translations( 'edfw-admin', 'wpactpro-estimated-delivery-date' );

		if ( file_exists( EDFW_DIR . 'build/index.css' ) ) {
			wp_enqueue_style( 'edfw-admin', EDFW_URI . 'build/index.css', array( 'wp-components' ), $asset['version'] );
		}

		wp_add_inline_script(
			'edfw-admin',
			'window.edfwData = ' . wp_json_encode( $this->get_bootstrap_data() ) . ';',
			'before'
		);

		/**
		 * Fires after this plugin's admin assets are enqueued.
		 * Add-ons enqueue their own admin assets here.
		 *
		 * @since 1.0.0
		 * @param string $hook Current admin page hook suffix.
		 */
		do_action( 'edfw_enqueue_admin_assets', $hook );
	}

	/**
	 * Data handed to the React app on load.
	 *
	 * @since 1.0.0
	 * @return array<string, mixed>
	 */
	protected function get_bootstrap_data(): array {
		return array(
			'restUrl'      => esc_url_raw( rest_url( 'edfw/v1' ) ),
			'nonce'        => wp_create_nonce( 'wp_rest' ),
			'version'      => EDFW_VERSION,
			'settings'     => self::get_settings(),
			'rules'        => self::get_rules(),
			'meta'         => array(
				'zones'      => $this->get_zones(),
				'methods'    => $this->get_methods(),
				'categories' => $this->get_category_options(),
				'classes'    => $this->get_class_options(),
				'weekdays'   => EDFW_Helper::weekday_labels(),
				'timezone'   => wp_timezone_string(),
			),
			'capabilities' => EDFW_Helper::capabilities(),
			'schema'       => EDFW_Helper::settings_schema(),
		);
	}

	/**
	 * Product categories as value=>label for dropdowns.
	 *
	 * @since 1.0.0
	 * @return array<string, string>
	 */
	protected function get_category_options(): array {
		$terms = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
			)
		);
		$out   = array();
		if ( is_array( $terms ) ) {
			foreach ( $terms as $t ) {
				$out[ $t->slug ] = $t->name;
			}
		}
		return $out;
	}

	/**
	 * Shipping classes as slug=>label for dropdowns.
	 *
	 * @since 1.0.0
	 * @return array<string, string>
	 */
	protected function get_class_options(): array {
		$out = array();
		if ( function_exists( 'WC' ) && WC()->shipping() ) {
			foreach ( WC()->shipping()->get_shipping_classes() as $c ) {
				$out[ $c->slug ] = $c->name;
			}
		}
		return $out;
	}

	/**
	 * Add a Settings link on the Plugins screen.
	 *
	 * @since 1.0.0
	 * @param array $links Existing action links.
	 * @return array
	 */
	public function action_links( array $links ): array {
		$url = admin_url( 'admin.php?page=edfw-settings' );
		array_unshift( $links, '<a href="' . esc_url( $url ) . '">' . esc_html__( 'Settings', 'wpactpro-estimated-delivery-date' ) . '</a>' );
		/**
		 * Filter the plugin's action links on the Plugins screen.
		 *
		 * @since 1.0.0
		 * @param array $links Action links as HTML.
		 */
		return (array) apply_filters( 'edfw_plugin_action_links', $links );
	}

	/**
	 * Add the Estimated Delivery Date tab to the product data tabs.
	 *
	 * @since 1.0.0
	 * @param array $tabs Product data tabs.
	 * @return array
	 */
	public function product_data_tabs( array $tabs ): array {
		$tabs['edfw'] = array(
			'label'  => __( 'Estimated Delivery Date', 'wpactpro-estimated-delivery-date' ),
			'target' => 'edfw_product_data',
		);
		/**
		 * Filter the WooCommerce product data tabs.
		 *
		 * @since 1.0.0
		 * @param array $tabs Product data tabs.
		 */
		return apply_filters( 'edfw_product_data_tabs', $tabs );
	}

	/**
	 * Render the Estimated Delivery Date product data panel.
	 *
	 * @since 1.0.0
	 */
	public function product_data_panels(): void {
		global $post;
		$product_id = $post && isset( $post->ID ) ? (int) $post->ID : 0;
		if ( ! $product_id ) {
			return;
		}

		/**
		 * Before product tab content. Add-ons can add notices or extra UI.
		 *
		 * @since 1.0.0
		 */
		do_action( 'edfw_product_tab_before', $product_id );

		?>
		<div id="edfw_product_data" class="panel woocommerce_options_panel">
			<?php
			wp_nonce_field( 'edfw_product', 'edfw_product_nonce' );
			$this->render_product_tab( $product_id );
			?>
		</div>
		<?php

		/**
		 * After product tab content. Add-ons can add more fields.
		 *
		 * @since 1.0.0
		 */
		do_action( 'edfw_product_tab_after', $product_id );
	}

	/**
	 * Render product Estimated Delivery tab.
	 *
	 * @since 1.0.0
	 * @param int $product_id Product ID.
	 */
	protected function render_product_tab( int $product_id ): void {
		$enable    = (string) get_post_meta( $product_id, '_edfw_enable', true );
		$enable    = '' !== $enable ? $enable : 'global';
		$rules     = (array) get_post_meta( $product_id, '_edfw_rules', true );
		$rule      = ! empty( $rules ) ? (array) reset( $rules ) : array();
		$min       = isset( $rule['min'] ) ? (string) $rule['min'] : '';
		$max       = isset( $rule['max'] ) ? (string) $rule['max'] : '';
		$scheduled = isset( $rule['scheduled'] ) ? (string) $rule['scheduled'] : '';

		woocommerce_wp_select(
			array(
				'id'          => '_edfw_enable',
				'label'       => __( 'Delivery estimate', 'wpactpro-estimated-delivery-date' ),
				'options'     => array(
					'global'   => __( 'Use store settings', 'wpactpro-estimated-delivery-date' ),
					'disable'  => __( 'Hide for this product', 'wpactpro-estimated-delivery-date' ),
					'override' => __( 'Custom estimate for this product', 'wpactpro-estimated-delivery-date' ),
				),
				'value'       => $enable,
				'desc_tip'    => true,
				'description' => __( 'Use store settings follows your Delivery Rules. Hide removes the estimate for this product. Custom estimate shows this product’s own Min/Max below — useful for showing an estimate on just a few products when there is no store-wide default rule.', 'wpactpro-estimated-delivery-date' ),
			)
		);

		echo '<div class="edfw-product-override" style="' . ( 'override' !== $enable ? 'display:none' : '' ) . '">';

		/**
		 * Before override fields. Add-ons can add extra fields.
		 *
		 * @since 1.0.0
		 */
		do_action( 'edfw_product_tab_before_override_rules', $product_id );

		woocommerce_wp_text_input(
			array(
				'id'                => '_edfw_min',
				'label'             => __( 'Minimum (business days)', 'wpactpro-estimated-delivery-date' ),
				'type'              => 'number',
				'custom_attributes' => array(
					'min'  => '0',
					'step' => '1',
				),
				'value'             => $min,
				'placeholder'       => (string) self::get_setting( 'min_days', '2' ),
			)
		);
		woocommerce_wp_text_input(
			array(
				'id'                => '_edfw_max',
				'label'             => __( 'Maximum (business days)', 'wpactpro-estimated-delivery-date' ),
				'type'              => 'number',
				'custom_attributes' => array(
					'min'  => '0',
					'step' => '1',
				),
				'value'             => $max,
				'placeholder'       => (string) self::get_setting( 'max_days', '5' ),
			)
		);
		woocommerce_wp_text_input(
			array(
				'id'          => '_edfw_scheduled',
				'label'       => __( 'Scheduled start date', 'wpactpro-estimated-delivery-date' ),
				'type'        => 'date',
				'value'       => $scheduled,
				'desc_tip'    => true,
				'description' => __( 'Optional. Count business days from this date instead of today — useful for pre-orders, restocks and launches.', 'wpactpro-estimated-delivery-date' ),
			)
		);

		/**
		 * After override fields. Add-ons can add extra fields.
		 *
		 * @since 1.0.0
		 */
		do_action( 'edfw_product_tab_after_override_rules', $product_id );

		echo '</div>';
	}

	/**
	 * Get shipping zones for dropdown.
	 *
	 * @since 1.0.0
	 * @return array<int, string>
	 */
	protected function get_zones(): array {
		$zones = WC_Shipping_Zones::get_zones();
		$out   = array();
		foreach ( $zones as $z ) {
			$obj = WC_Shipping_Zones::get_zone( $z['zone_id'] );
			if ( is_object( $obj ) && count( $obj->get_shipping_methods( true ) ) > 0 ) {
				$out[ $z['zone_id'] ] = $obj->get_zone_name();
			}
		}
		/**
		 * Filter the shipping zones offered in the rule builder.
		 *
		 * @since 1.0.0
		 * @param array $out Zone id => zone name.
		 */
		return (array) apply_filters( 'edfw_admin_zones', $out );
	}

	/**
	 * Get shipping methods (instance_id => zone, name, title).
	 *
	 * @since 1.0.0
	 * @return array<int, array>
	 */
	protected function get_methods(): array {
		$zones   = $this->get_zones();
		$methods = array();
		foreach ( array_keys( $zones ) as $zone_id ) {
			$zone = WC_Shipping_Zones::get_zone( $zone_id );
			if ( ! is_object( $zone ) ) {
				continue;
			}
			foreach ( $zone->get_shipping_methods( true ) as $m ) {
				$methods[ $m->instance_id ] = array(
					'zone'  => $zone_id,
					'name'  => $m->id,
					'title' => $m->get_title(),
				);
			}
		}
		/**
		 * Filter the shipping methods offered in the rule builder.
		 *
		 * @since 1.0.0
		 * @param array $methods Instance id => zone, name and title.
		 */
		return (array) apply_filters( 'edfw_admin_methods', $methods );
	}

	/**
	 * Save the per-product override fields from the product editor.
	 *
	 * @since 1.0.0
	 * @param int $post_id Product ID.
	 */
	public function save_product_meta( int $post_id ): void {
		if ( ! isset( $_POST['_edfw_enable'] ) || ! isset( $_POST['edfw_product_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['edfw_product_nonce'] ) ), 'edfw_product' ) ) {
			return;
		}
		$enable = sanitize_text_field( wp_unslash( $_POST['_edfw_enable'] ) );
		if ( ! in_array( $enable, array( 'global', 'disable', 'override' ), true ) ) {
			$enable = 'global';
		}
		update_post_meta( $post_id, '_edfw_enable', $enable );

		if ( 'override' === $enable ) {
			$raw_min       = isset( $_POST['_edfw_min'] ) ? sanitize_text_field( wp_unslash( $_POST['_edfw_min'] ) ) : '';
			$raw_max       = isset( $_POST['_edfw_max'] ) ? sanitize_text_field( wp_unslash( $_POST['_edfw_max'] ) ) : '';
			$raw_scheduled = isset( $_POST['_edfw_scheduled'] ) ? sanitize_text_field( wp_unslash( $_POST['_edfw_scheduled'] ) ) : '';
			$min           = '' !== $raw_min ? (string) absint( $raw_min ) : (string) self::get_setting( 'min_days', '2' );
			$max           = '' !== $raw_max ? (string) absint( $raw_max ) : (string) self::get_setting( 'max_days', '5' );
			$scheduled_ts  = '' !== $raw_scheduled ? strtotime( $raw_scheduled ) : false;
			$scheduled     = $scheduled_ts ? gmdate( 'Y-m-d', $scheduled_ts ) : '';
			update_post_meta(
				$post_id,
				'_edfw_rules',
				array(
					'default' => array(
						'conditions' => array(
							array(
								'type' => 'product',
								'op'   => 'is',
								'vals' => array(),
							),
						),
						'min'        => $min,
						'max'        => $max,
						'scheduled'  => $scheduled,
					),
				)
			);
		} else {
			delete_post_meta( $post_id, '_edfw_rules' );
		}

		/**
		 * After saving product estimated delivery meta. Add-ons can save extra fields.
		 *
		 * @param int $post_id Product ID.
		 * @since 1.0.0
		 */
		do_action( 'edfw_save_product_meta', $post_id );
	}

	/**
	 * Output nonce for product tab. Call in render_product_tab.
	 *
	 * @since 1.0.0
	 */
	public static function product_nonce(): void {
		wp_nonce_field( 'edfw_product', 'edfw_product_nonce' );
	}

	/**
	 * Output the settings page shell the React app mounts into.
	 *
	 * @since 1.0.0
	 */
	public function settings_page(): void {
		/**
		 * Fires immediately before the settings screen is rendered.
		 *
		 * @since 1.0.0
		 * @param string $context Render context. Always 'app'.
		 */
		do_action( 'edfw_before_settings_page', 'app' );
		// A real .wrap > h1 gives WordPress a title anchor; the wp-header-end
		// marker tells core exactly where to relocate admin notices, so other
		// plugins' notices land above our app instead of inside the header.
		echo '<div class="wrap edfw-wrap">';
		echo '<h1 class="screen-reader-text">' . esc_html__( 'Estimated Delivery Date', 'wpactpro-estimated-delivery-date' ) . '</h1>';
		echo '<hr class="wp-header-end">';
		echo '<div id="edfw-admin-app"></div>';
		echo '</div>';
		/**
		 * Fires immediately after the settings screen is rendered.
		 *
		 * @since 1.0.0
		 * @param string $context Render context. Always 'app'.
		 */
		do_action( 'edfw_after_settings_page', 'app' );
	}
}
