<?php
/**
 * UI Settings Admin Page
 *
 * Manages chat widget UI configuration: centerstage, positions, language, and greeting text.
 *
 * @package Niwa_AI
 * @since   0.2.1
 */

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

class Niwa_AI_UI_Settings {

	use Niwa_AI_Admin_Field_Helpers;

	const OPTION_KEY = Niwa_AI_Constants::OPT_UI_SETTINGS;
	const FORM_FIELD = '_niwa_ui_form';
	const FORM_UI = 'ui';
	const FORM_LEAVE_STOPER = 'leave_stoper';
	const MOBILE_FLYOUT_WIDTH_DEFAULT = 95;
	const MOBILE_FLYOUT_HEIGHT_DEFAULT = 95;

	/**
	 * UI field keys that belong to this option (used by migration).
	 *
	 * @var string[]
	 */
	private static $ui_keys = array(
		'ui_language', 'greeting_text', 'chat_theme_mode', 'enable_flyout',
		'hide_niwa_on_blog_pages', 'hide_niwa_on_custom_pages', 'hide_niwa_custom_pages',
		'frontend_avatar_url',
		'launcher_label', 'launcher_label_color', 'launcher_use_default_settings',
		'centerstage_mode', 'centerstage_pill_width', 'centerstage_pill_height',
		'centerstage_pill_text', 'centerstage_pill_text_color', 'centerstage_pill_arrow_color',
		'desktop_centerstage_pill_width', 'desktop_centerstage_pill_height',
		'tablet_centerstage_pill_width', 'tablet_centerstage_pill_height',
		'mobile_centerstage_pill_width', 'mobile_centerstage_pill_height',
		'desktop_centerstage_morph_width', 'desktop_centerstage_morph_height',
		'tablet_centerstage_morph_width', 'tablet_centerstage_morph_height',
		'desktop_launcher_position', 'desktop_launcher_x', 'desktop_launcher_y', 'desktop_launcher_width', 'desktop_launcher_height',
		'tablet_launcher_position', 'tablet_launcher_x', 'tablet_launcher_y', 'tablet_launcher_width', 'tablet_launcher_height',
		'mobile_launcher_position', 'mobile_launcher_x', 'mobile_launcher_y', 'mobile_launcher_width', 'mobile_launcher_height',
		'desktop_width', 'desktop_height',
		'tablet_width', 'tablet_height',
		'mobile_width_vw', 'mobile_height_dvh', 'mobile_fullscreen',
		'delivery_days', 'product_card_action_mode', 'product_card_footer_mode', 'product_card_banner_type',
		'product_card_banner_text',
		'product_card_banner_text_en', 'product_card_banner_text_sr', 'product_card_banner_text_hr',
		'product_card_banner_text_de', 'product_card_banner_text_es', 'product_card_banner_text_fr',
		'product_card_banner_bg_color', 'product_card_banner_bg_opacity',
		'leave_stoper_enabled', 'leave_stoper_trigger_type', 'leave_stoper_inactivity_timeout',
		'leave_stoper_open_delay', 'leave_stoper_session_cooldown', 'leave_stoper_disable_on_mobile',
		'leave_stoper_test_mode', 'leave_stoper_reset_on_page_load', 'leave_stoper_media_id',
		'leave_stoper_message_text', 'leave_stoper_alt_text',
	);

	/**
	 * Register hooks.
	 */
	public function register_hooks() {
		add_action( 'admin_menu', array( $this, 'add_submenu' ), 10 );
		add_action( 'admin_init', array( $this, 'register_settings' ) );
		add_action( 'admin_init', array( $this, 'maybe_migrate' ), 5 );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
	}

	/**
	 * Enqueue assets for UI settings page.
	 *
	 * @param string $hook_suffix Current admin page hook.
	 * @return void
	 */
	public function enqueue_admin_assets( $hook_suffix ) {
		if ( 'niwa-ai_page_niwa-ai-ui-settings' !== $hook_suffix ) {
			return;
		}

		wp_enqueue_media();
		$settings_js_path    = NIWA_AI_DIR . 'assets/js/niwa-admin-settings.js';
		$settings_js_version = file_exists( $settings_js_path ) ? (string) filemtime( $settings_js_path ) : NIWA_AI_VERSION;
		wp_enqueue_script(
			'niwa-admin-settings',
			NIWA_AI_URL . 'assets/js/niwa-admin-settings.js',
			array( 'jquery', 'media-editor', 'media-views' ),
			$settings_js_version,
			array( 'in_footer' => true )
		);
		wp_localize_script( 'niwa-admin-settings', 'niwaAdminSettings', array(
			'assetVersion' => $settings_js_version,
		) );

		$repeater_js_path    = NIWA_AI_DIR . 'assets/js/niwa-attention-admin.js';
		$repeater_js_version = file_exists( $repeater_js_path ) ? (string) filemtime( $repeater_js_path ) : NIWA_AI_VERSION;
		wp_enqueue_script(
			'niwa-attention-admin-repeaters',
			NIWA_AI_URL . 'assets/js/niwa-attention-admin.js',
			array(),
			$repeater_js_version,
			array( 'in_footer' => true )
		);
	}

	/**
	 * Add submenu page under Niwa AI.
	 */
	public function add_submenu() {
		add_submenu_page(
			'niwa-ai',
			__( 'UI Settings', 'niwa-ai' ),
			__( 'UI Settings', 'niwa-ai' ),
			'manage_options',
			'niwa-ai-ui-settings',
			array( $this, 'render_page' )
		);
	}

	/**
	 * Register settings.
	 */
	public function register_settings() {
		register_setting( 'niwa_ai_ui_group', self::OPTION_KEY, array( $this, 'sanitize' ) );
	}

	/**
	 * Sanitize UI settings.
	 *
	 * @param array $input Raw input.
	 * @return array Sanitized options.
	 */
	public function sanitize( $input ) {
		$existing = get_option( self::OPTION_KEY, array() );
		$out      = is_array( $existing ) ? $existing : array();

		if ( ! is_array( $input ) ) {
			return $out;
		}

		$form = isset( $input[ self::FORM_FIELD ] ) ? sanitize_key( $input[ self::FORM_FIELD ] ) : '';
		if ( self::FORM_LEAVE_STOPER === $form ) {
			return self::sanitize_leave_stoper_fields( $input, $out );
		}

		// Guard: only the main UI Settings form may rewrite UI fields. Any
		// submit that does not carry `_niwa_ui_form=ui` (e.g. an unrelated
		// admin page or a partial POST that happens to target the same option
		// key) must leave existing values untouched. Without this guard, a
		// missing `enable_flyout` checkbox in the input would silently flip
		// the launcher to disabled.
		if ( self::FORM_UI !== $form ) {
			return $out;
		}

		// Language & Greeting.
		$langs = array( 'en', 'sr', 'hr', 'de', 'es', 'fr' );
		$lang  = isset( $input['ui_language'] ) ? sanitize_text_field( $input['ui_language'] ) : 'en';
		$out['ui_language']   = in_array( $lang, $langs, true ) ? $lang : 'en';
		$out['greeting_text'] = isset( $input['greeting_text'] ) ? wp_kses_post( $input['greeting_text'] ) : '';
		$theme = isset( $input['chat_theme_mode'] ) ? sanitize_text_field( $input['chat_theme_mode'] ) : 'neutral';
		$out['chat_theme_mode'] = in_array( $theme, array( 'neutral', 'auto', 'light', 'dark' ), true ) ? $theme : 'neutral';
		$out['enable_flyout'] = ! empty( $input['enable_flyout'] ) ? 1 : 0;
		$out['hide_niwa_on_blog_pages']   = ! empty( $input['hide_niwa_on_blog_pages'] ) ? 1 : 0;
		$out['hide_niwa_on_custom_pages'] = ! empty( $input['hide_niwa_on_custom_pages'] ) ? 1 : 0;
		$out['hide_niwa_custom_pages']    = $this->sanitize_hide_niwa_custom_pages( $input );
		$out['frontend_avatar_url'] = isset( $input['frontend_avatar_url'] ) ? esc_url_raw( $input['frontend_avatar_url'] ) : '';
		$launcher_label_color_customized      = ! empty( $input['launcher_label_color_customized'] );
		$out['launcher_label']                = isset( $input['launcher_label'] ) ? sanitize_text_field( $input['launcher_label'] ) : '';
		$out['launcher_label_color']          = $launcher_label_color_customized && isset( $input['launcher_label_color'] ) ? sanitize_hex_color( $input['launcher_label_color'] ) : '';
		$out['launcher_use_default_settings'] = ! empty( $input['launcher_use_default_settings'] ) ? 1 : 0;

		// Centerstage.
		$out['centerstage_mode']             = ! empty( $input['centerstage_mode'] ) ? 1 : 0;
		$out['centerstage_pill_text']        = isset( $input['centerstage_pill_text'] ) ? sanitize_text_field( $input['centerstage_pill_text'] ) : 'Ask Niwa';
		$out['centerstage_pill_text_color']  = isset( $input['centerstage_pill_text_color'] ) ? sanitize_hex_color( $input['centerstage_pill_text_color'] ) : '#374151';
		$out['centerstage_pill_arrow_color'] = isset( $input['centerstage_pill_arrow_color'] ) ? sanitize_hex_color( $input['centerstage_pill_arrow_color'] ) : '#374151';

		$centerstage_defaults = array(
			'desktop' => array( 'width' => 224, 'height' => 48 ),
			'tablet'  => array( 'width' => 212, 'height' => 48 ),
			'mobile'  => array( 'width' => 196, 'height' => 48 ),
		);

		foreach ( $centerstage_defaults as $device => $defaults ) {
			$legacy_width  = isset( $input['centerstage_pill_width'] ) ? absint( $input['centerstage_pill_width'] ) : $defaults['width'];
			$legacy_height = isset( $input['centerstage_pill_height'] ) ? absint( $input['centerstage_pill_height'] ) : $defaults['height'];
			$out[ $device . '_centerstage_pill_width' ]  = isset( $input[ $device . '_centerstage_pill_width' ] ) ? max( 140, min( 600, absint( $input[ $device . '_centerstage_pill_width' ] ) ) ) : max( 140, min( 600, $legacy_width ) );
			$out[ $device . '_centerstage_pill_height' ] = isset( $input[ $device . '_centerstage_pill_height' ] ) ? max( 40, min( 100, absint( $input[ $device . '_centerstage_pill_height' ] ) ) ) : max( 40, min( 100, $legacy_height ) );
		}

		// Keep legacy centerstage keys in sync for server-side fallbacks.
		$out['centerstage_pill_width']  = $out['desktop_centerstage_pill_width'];
		$out['centerstage_pill_height'] = $out['desktop_centerstage_pill_height'];

		$centerstage_morph_defaults = array(
			'desktop' => array( 'width' => 494, 'height' => 680 ),
			'tablet'  => array( 'width' => 494, 'height' => 560 ),
		);
		foreach ( $centerstage_morph_defaults as $device => $defaults ) {
			$out[ $device . '_centerstage_morph_width' ]  = isset( $input[ $device . '_centerstage_morph_width' ] ) ? max( 200, min( 2000, absint( $input[ $device . '_centerstage_morph_width' ] ) ) ) : $defaults['width'];
			$out[ $device . '_centerstage_morph_height' ] = isset( $input[ $device . '_centerstage_morph_height' ] ) ? max( 200, min( 2000, absint( $input[ $device . '_centerstage_morph_height' ] ) ) ) : $defaults['height'];
		}

		// Positions - desktop/tablet/mobile.
		$positions = array( 'br', 'bl', 'tr', 'tl' );
		$devices = array(
			'desktop' => array( 'launcher_x' => 24, 'launcher_y' => 24, 'launcher_width' => 148, 'launcher_height' => 48, 'width' => 380, 'height' => 580 ),
			'tablet'  => array( 'launcher_x' => 20, 'launcher_y' => 20, 'launcher_width' => 148, 'launcher_height' => 48, 'width' => 340, 'height' => 560 ),
			'mobile'  => array( 'launcher_x' => 16, 'launcher_y' => 16, 'launcher_width' => 140, 'launcher_height' => 48, 'width' => self::MOBILE_FLYOUT_WIDTH_DEFAULT, 'height' => self::MOBILE_FLYOUT_HEIGHT_DEFAULT ),
		);

		foreach ( $devices as $device => $defaults ) {
			// Launcher position.
			$lp = isset( $input[ $device . '_launcher_position' ] ) ? sanitize_text_field( $input[ $device . '_launcher_position' ] ) : 'br';
			$out[ $device . '_launcher_position' ] = in_array( $lp, $positions, true ) ? $lp : 'br';
			$out[ $device . '_launcher_x' ]      = isset( $input[ $device . '_launcher_x' ] ) ? absint( $input[ $device . '_launcher_x' ] ) : $defaults['launcher_x'];
			$out[ $device . '_launcher_y' ]      = isset( $input[ $device . '_launcher_y' ] ) ? absint( $input[ $device . '_launcher_y' ] ) : $defaults['launcher_y'];
			$out[ $device . '_launcher_width' ]  = isset( $input[ $device . '_launcher_width' ] ) ? max( 120, min( 400, absint( $input[ $device . '_launcher_width' ] ) ) ) : $defaults['launcher_width'];
			$out[ $device . '_launcher_height' ] = isset( $input[ $device . '_launcher_height' ] ) ? max( 40, min( 100, absint( $input[ $device . '_launcher_height' ] ) ) ) : $defaults['launcher_height'];

			// Flyout size.
			if ( 'mobile' === $device ) {
				$mobile_width  = isset( $input['mobile_width_vw'] ) ? absint( $input['mobile_width_vw'] ) : self::MOBILE_FLYOUT_WIDTH_DEFAULT;
				$mobile_height = isset( $input['mobile_height_dvh'] ) ? absint( $input['mobile_height_dvh'] ) : self::MOBILE_FLYOUT_HEIGHT_DEFAULT;
				$out['mobile_width_vw']  = $mobile_width >= 1 && $mobile_width <= 100 ? $mobile_width : self::MOBILE_FLYOUT_WIDTH_DEFAULT;
				$out['mobile_height_dvh'] = $mobile_height >= 1 && $mobile_height <= 100 ? $mobile_height : self::MOBILE_FLYOUT_HEIGHT_DEFAULT;
				unset( $out['mobile_width'], $out['mobile_height'] );
			} else {
				$out[ $device . '_width' ]  = isset( $input[ $device . '_width' ] ) ? max( 0, min( 2000, absint( $input[ $device . '_width' ] ) ) ) : $defaults['width'];
				$out[ $device . '_height' ] = isset( $input[ $device . '_height' ] ) ? max( 0, min( 2000, absint( $input[ $device . '_height' ] ) ) ) : $defaults['height'];
			}
		}

		// Mobile fullscreen.
		$out['mobile_fullscreen'] = ! empty( $input['mobile_fullscreen'] ) ? 1 : 0;

		// Product Cards.
		$out['delivery_days'] = isset( $input['delivery_days'] ) ? max( 1, min( 30, absint( $input['delivery_days'] ) ) ) : 3;
		$action_modes = array( 'store', 'catalog' );
		$footer_modes = array( 'delivery', 'score', 'off' );
		$banner_types = array( 'text', 'percent', 'amount_off', 'off' );
		$action_mode  = isset( $input['product_card_action_mode'] ) ? sanitize_text_field( $input['product_card_action_mode'] ) : 'store';
		$footer_mode  = isset( $input['product_card_footer_mode'] ) ? sanitize_text_field( $input['product_card_footer_mode'] ) : 'delivery';
		$banner_type  = isset( $input['product_card_banner_type'] ) ? sanitize_text_field( $input['product_card_banner_type'] ) : 'percent';
		if ( 'amount' === $banner_type ) {
			$banner_type = 'amount_off';
		}
		$out['product_card_action_mode']   = in_array( $action_mode, $action_modes, true ) ? $action_mode : 'store';
		$out['product_card_footer_mode']   = in_array( $footer_mode, $footer_modes, true ) ? $footer_mode : 'delivery';
		$out['product_card_banner_type']   = in_array( $banner_type, $banner_types, true ) ? $banner_type : 'percent';
		$out['product_card_banner_text']       = isset( $input['product_card_banner_text'] ) ? sanitize_text_field( $input['product_card_banner_text'] ) : '';
		$out['product_card_banner_bg_color']   = isset( $input['product_card_banner_bg_color'] ) ? sanitize_hex_color( $input['product_card_banner_bg_color'] ) : '#000000';
		$out['product_card_banner_bg_opacity'] = isset( $input['product_card_banner_bg_opacity'] ) ? max( 0, min( 100, absint( $input['product_card_banner_bg_opacity'] ) ) ) : 45;

		foreach ( array( 'en', 'sr', 'hr', 'de', 'es', 'fr' ) as $lang_code ) {
			$key       = 'product_card_banner_text_' . $lang_code;
			$out[ $key ] = isset( $input[ $key ] ) ? sanitize_text_field( $input[ $key ] ) : '';
		}

		if ( $this->has_leave_stoper_fields( $input ) ) {
			$out = self::sanitize_leave_stoper_fields( $input, $out );
		}

		return $out;
	}

	/**
	 * Sanitize the Leave Stoper subset of the shared UI option.
	 *
	 * @param array $input Raw input.
	 * @param array $out   Existing output array to merge into.
	 * @return array
	 */
	public static function sanitize_leave_stoper_fields( $input, $out ) {
		$defaults = self::get_leave_stoper_defaults();

		$out['leave_stoper_enabled']             = ! empty( $input['leave_stoper_enabled'] ) ? 1 : 0;
		$trigger = isset( $input['leave_stoper_trigger_type'] ) ? sanitize_text_field( $input['leave_stoper_trigger_type'] ) : $defaults['leave_stoper_trigger_type'];
		$out['leave_stoper_trigger_type']        = in_array( $trigger, array( 'exit_intent', 'inactivity', 'both' ), true ) ? $trigger : $defaults['leave_stoper_trigger_type'];
		$out['leave_stoper_inactivity_timeout']  = isset( $input['leave_stoper_inactivity_timeout'] ) ? max( 10, min( 300, absint( $input['leave_stoper_inactivity_timeout'] ) ) ) : $defaults['leave_stoper_inactivity_timeout'];
		$out['leave_stoper_open_delay']          = isset( $input['leave_stoper_open_delay'] ) ? max( 100, min( 2000, absint( $input['leave_stoper_open_delay'] ) ) ) : $defaults['leave_stoper_open_delay'];
		$out['leave_stoper_session_cooldown']    = isset( $input['leave_stoper_session_cooldown'] ) ? max( 1, min( 120, absint( $input['leave_stoper_session_cooldown'] ) ) ) : $defaults['leave_stoper_session_cooldown'];
		$out['leave_stoper_disable_on_mobile']   = ! empty( $input['leave_stoper_disable_on_mobile'] ) ? 1 : 0;
		$out['leave_stoper_test_mode']           = ! empty( $input['leave_stoper_test_mode'] ) ? 1 : 0;
		$out['leave_stoper_reset_on_page_load']  = ! empty( $input['leave_stoper_reset_on_page_load'] ) ? 1 : 0;
		$out['leave_stoper_media_id']            = isset( $input['leave_stoper_media_id'] ) ? absint( $input['leave_stoper_media_id'] ) : $defaults['leave_stoper_media_id'];
		$out['leave_stoper_message_text']        = isset( $input['leave_stoper_message_text'] ) ? sanitize_textarea_field( $input['leave_stoper_message_text'] ) : $defaults['leave_stoper_message_text'];
		$out['leave_stoper_alt_text']            = isset( $input['leave_stoper_alt_text'] ) ? sanitize_text_field( $input['leave_stoper_alt_text'] ) : $defaults['leave_stoper_alt_text'];

		return $out;
	}

	/**
	 * Sanitize custom page rules where the whole Niwa widget should be hidden.
	 *
	 * @param array $input Raw settings input.
	 * @return array<int, array<string, mixed>>
	 */
	private function sanitize_hide_niwa_custom_pages( $input ) {
		$raw = isset( $input['hide_niwa_custom_pages'] ) && is_array( $input['hide_niwa_custom_pages'] ) ? $input['hide_niwa_custom_pages'] : array();

		$pages = array();
		$id    = 0;
		foreach ( $raw as $page ) {
			if ( ! is_array( $page ) ) {
				continue;
			}

			$url = isset( $page['url'] ) ? trim( sanitize_text_field( $page['url'] ) ) : '';
			if ( '' === $url ) {
				continue;
			}

			$id++;
			$pages[] = array(
				'id'         => $id,
				'enabled'    => ! empty( $page['enabled'] ) ? 1 : 0,
				'url'        => $url,
				'match_type' => $this->sanitize_page_match_type( isset( $page['match_type'] ) ? $page['match_type'] : 'contains' ),
			);
		}

		return $pages;
	}

	/**
	 * Whitelist a URL match type used by custom page rules.
	 *
	 * @param string $value Raw match type.
	 * @return string
	 */
	private function sanitize_page_match_type( $value ) {
		$value = sanitize_key( (string) $value );
		return in_array( $value, array( 'contains', 'equals', 'regex' ), true ) ? $value : 'contains';
	}

	/**
	 * Check whether a submitted option payload includes Leave Stoper fields.
	 *
	 * @param array $input Raw input.
	 * @return bool
	 */
	private function has_leave_stoper_fields( $input ) {
		foreach ( array_keys( self::get_leave_stoper_defaults() ) as $key ) {
			if ( array_key_exists( $key, $input ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Render the UI Settings page.
	 */
	public function render_page() {
		$stored = get_option( self::OPTION_KEY, array() );
		$opts   = wp_parse_args( is_array( $stored ) ? $stored : array(), $this->get_defaults() );
		$mobile_width  = absint( $opts['mobile_width_vw'] );
		$mobile_height = absint( $opts['mobile_height_dvh'] );
		$opts['mobile_width_vw']  = $mobile_width >= 1 && $mobile_width <= 100 ? $mobile_width : self::MOBILE_FLYOUT_WIDTH_DEFAULT;
		$opts['mobile_height_dvh'] = $mobile_height >= 1 && $mobile_height <= 100 ? $mobile_height : self::MOBILE_FLYOUT_HEIGHT_DEFAULT;
		$general_opts = get_option( Niwa_AI_Constants::OPT_SETTINGS, array() );
		if ( empty( $opts['frontend_avatar_url'] ) && is_array( $general_opts ) && ! empty( $general_opts['frontend_avatar_url'] ) ) {
			$opts['frontend_avatar_url'] = (string) $general_opts['frontend_avatar_url'];
		}
		if ( is_array( $general_opts ) && ! array_key_exists( 'chat_theme_mode', is_array( $stored ) ? $stored : array() ) && isset( $general_opts['chat_theme_mode'] ) ) {
			$opts['chat_theme_mode'] = (string) $general_opts['chat_theme_mode'];
		}
		if ( is_array( $general_opts ) && ! array_key_exists( 'enable_flyout', is_array( $stored ) ? $stored : array() ) && array_key_exists( 'enable_flyout', $general_opts ) ) {
			$opts['enable_flyout'] = intval( $general_opts['enable_flyout'] );
		}
		$n    = self::OPTION_KEY;

		$pos_options = array(
			'br'     => 'Bottom Right',
			'bl'     => 'Bottom Left',
			'tr'     => 'Top Right',
			'tl'     => 'Top Left',
		);

		echo '<div class="wrap niwa-wrap">';

		// ── Page Header ──
		echo '<div class="niwa-page-header">';
		echo '<div class="niwa-page-header__icon">';
		echo '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>';
		echo '</div>';
		echo '<div class="niwa-page-header__text">';
		echo '<h1 class="niwa-page-header__title">' . esc_html__( 'UI Settings', 'niwa-ai' ) . '</h1>';
		echo '<p class="niwa-page-header__subtitle">' . esc_html__( 'Chat widget appearance - language, corner modes, centerstage mode, and responsive sizing.', 'niwa-ai' ) . '</p>';
		echo '</div>';
		echo '</div>';

		echo '<form method="post" action="options.php">';
		settings_fields( 'niwa_ai_ui_group' );
		echo '<input type="hidden" name="' . esc_attr( $n . '[' . self::FORM_FIELD . ']' ) . '" value="' . esc_attr( self::FORM_UI ) . '" />';

		// ── Card 1: Language & Greeting (expanded by default) ──
		echo '<div class="niwa-card">';
		echo '<details class="niwa-collapsible" open>';
		echo '<summary>';
		echo '<div class="niwa-card__header">';
		echo '<div class="niwa-card__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></div>';
		echo '<h2 class="niwa-card__title">' . esc_html__( 'Language & Greeting', 'niwa-ai' ) . '</h2>';
		echo '</div>';
		echo '</summary>';
		echo '<div class="niwa-card__body">';

		// UI Language select.
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'UI Language', 'niwa-ai' ) . '</label>';
		echo '<select name="' . esc_attr( $n . '[ui_language]' ) . '">';
		$langs = array(
			'en' => 'English',
			'sr' => 'Serbian',
			'hr' => 'Croatian',
			'de' => 'German',
			'es' => 'Spanish',
			'fr' => 'French',
		);
		foreach ( $langs as $val => $lbl ) {
			echo '<option value="' . esc_attr( $val ) . '"' . selected( $opts['ui_language'], $val, false ) . '>' . esc_html( $lbl ) . '</option>';
		}
		echo '</select>';
		echo '</div>';

		// Greeting Text textarea.
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Greeting Text', 'niwa-ai' ) . '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Customize the initial greeting message. HTML allowed.', 'niwa-ai' ) . '</span>';
		echo '<textarea name="' . esc_attr( $n . '[greeting_text]' ) . '" rows="3" class="niwa-input--wide" style="max-width:100%;">' . esc_textarea( $opts['greeting_text'] ) . '</textarea>';
		echo '</div>';

		echo '<hr style="border:0;border-top:1px solid var(--niwa-edge);margin:20px 0;" />';
		echo '<h3 style="margin:0 0 12px;font-size:14px;color:var(--niwa-ink-1);">' . esc_html__( 'Frontend Chathead', 'niwa-ai' ) . '</h3>';
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Chat Theme', 'niwa-ai' ) . '</label>';
		echo '<select name="' . esc_attr( $n . '[chat_theme_mode]' ) . '">';
		echo '<option value="neutral" ' . selected( isset( $opts['chat_theme_mode'] ) ? $opts['chat_theme_mode'] : 'neutral', 'neutral', false ) . '>' . esc_html__( 'Neutral', 'niwa-ai' ) . '</option>';
		echo '<option value="auto" ' . selected( isset( $opts['chat_theme_mode'] ) ? $opts['chat_theme_mode'] : 'neutral', 'auto', false ) . '>' . esc_html__( 'Auto', 'niwa-ai' ) . '</option>';
		echo '<option value="light" ' . selected( isset( $opts['chat_theme_mode'] ) ? $opts['chat_theme_mode'] : 'neutral', 'light', false ) . '>' . esc_html__( 'Light', 'niwa-ai' ) . '</option>';
		echo '<option value="dark" ' . selected( isset( $opts['chat_theme_mode'] ) ? $opts['chat_theme_mode'] : 'neutral', 'dark', false ) . '>' . esc_html__( 'Dark', 'niwa-ai' ) . '</option>';
		echo '</select>';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-toggle">';
		echo '<input type="checkbox" name="' . esc_attr( $n . '[enable_flyout]' ) . '" value="1" ' . checked( ! isset( $opts['enable_flyout'] ) || intval( $opts['enable_flyout'] ) === 1, true, false ) . ' />';
		echo esc_html__( 'Enable flyout chat', 'niwa-ai' );
		echo '</label>';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-toggle">';
		echo '<input type="checkbox" name="' . esc_attr( $n . '[hide_niwa_on_blog_pages]' ) . '" value="1" ' . checked( ! empty( $opts['hide_niwa_on_blog_pages'] ), true, false ) . ' />';
		echo esc_html__( 'Hide Niwa on blog pages', 'niwa-ai' );
		echo '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Hides the whole Niwa widget on posts, blog index, category, tag, date, and author archive pages.', 'niwa-ai' ) . '</span>';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-toggle">';
		echo '<input type="checkbox" name="' . esc_attr( $n . '[hide_niwa_on_custom_pages]' ) . '" value="1" ' . checked( ! empty( $opts['hide_niwa_on_custom_pages'] ), true, false ) . ' />';
		echo esc_html__( 'Hide Niwa on custom pages', 'niwa-ai' );
		echo '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Uses the same custom page matching style as Attention Capture, but hides the whole Niwa chat widget.', 'niwa-ai' ) . '</span>';
		echo '</div>';

		$hide_pages = ( isset( $opts['hide_niwa_custom_pages'] ) && is_array( $opts['hide_niwa_custom_pages'] ) ) ? $opts['hide_niwa_custom_pages'] : array();
		echo '<div class="niwa-repeater" id="niwa-hide-niwa-pages" data-niwa-repeater="hide-niwa-pages">';
		$hide_page_index = 0;
		foreach ( $hide_pages as $page ) {
			$this->render_hide_niwa_page_card( $hide_page_index, $page, false );
			$hide_page_index++;
		}
		if ( 0 === $hide_page_index ) {
			echo '<p class="niwa-repeater__empty">' . esc_html__( 'No custom hidden pages yet. Click "Add custom page" to create one.', 'niwa-ai' ) . '</p>';
		}
		echo '</div>';

		echo '<button type="button" class="button niwa-btn niwa-btn--secondary niwa-repeater__add" data-niwa-repeater-add="hide-niwa-pages"><span aria-hidden="true">+</span> ' . esc_html__( 'Add custom page', 'niwa-ai' ) . '</button>';
		echo '<template id="niwa-hide-niwa-page-template">';
		$this->render_hide_niwa_page_card( '__INDEX__', array(), true );
		echo '</template>';

		$this->render_avatar_field(
			'frontend_avatar_url',
			esc_html__( 'Chathead Image', 'niwa-ai' ),
			esc_html__( 'Changes the frontend assistant image shown in the floating pill and chat messages.', 'niwa-ai' ),
			isset( $opts['frontend_avatar_url'] ) ? (string) $opts['frontend_avatar_url'] : '',
			defined( 'NIWA_AI_DEFAULT_FRONTEND_AVATAR_URL' )
				? NIWA_AI_DEFAULT_FRONTEND_AVATAR_URL
				: NIWA_AI_URL . 'assets/img/niwa-admin-personal-assistant.png'
		);

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Custom Pill Text', 'niwa-ai' ) . '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Controls the text shown on the floating frontend pill. Leave empty to keep the current language-based default.', 'niwa-ai' ) . '</span>';
		echo '<input type="text" name="' . esc_attr( $n . '[launcher_label]' ) . '" value="' . esc_attr( $opts['launcher_label'] ) . '" class="niwa-input--wide" />';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Pill Text Color', 'niwa-ai' ) . '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Overrides the floating pill text color independently from the active theme. When default settings are enabled below, the theme colors are used instead.', 'niwa-ai' ) . '</span>';
		echo '<input type="hidden" name="' . esc_attr( $n . '[launcher_label_color_customized]' ) . '" value="' . esc_attr( ! empty( $opts['launcher_label_color'] ) ? '1' : '0' ) . '" id="niwa_launcher_label_color_customized" />';
		echo '<input type="color" name="' . esc_attr( $n . '[launcher_label_color]' ) . '" value="' . esc_attr( ! empty( $opts['launcher_label_color'] ) ? $opts['launcher_label_color'] : '#6B7280' ) . '" id="niwa_launcher_label_color" />';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-toggle">';
		echo '<input type="checkbox" name="' . esc_attr( $n . '[launcher_use_default_settings]' ) . '" value="1" ' . checked( ! empty( $opts['launcher_use_default_settings'] ), true, false ) . ' />';
		echo esc_html__( 'Use Default Settings', 'niwa-ai' );
		echo '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'When enabled, the floating frontend pill uses the built-in translated text and theme-based text color.', 'niwa-ai' ) . '</span>';
		echo '</div>';
		echo '<script>
		document.addEventListener("DOMContentLoaded", function() {
			var launcherColor = document.getElementById("niwa_launcher_label_color");
			var launcherColorCustomized = document.getElementById("niwa_launcher_label_color_customized");
			if (!launcherColor || !launcherColorCustomized) {
				return;
			}

			launcherColor.addEventListener("input", function() {
				launcherColorCustomized.value = "1";
			});
		});
		</script>';

		echo '</div>'; // .niwa-card__body
		echo '</details>';
		echo '</div>'; // .niwa-card

		// ── Card 2: Centerstage Pill ──
		echo '<div class="niwa-card">';
		echo '<details class="niwa-collapsible">';
		echo '<summary>';
		echo '<div class="niwa-card__header">';
		echo '<div class="niwa-card__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg></div>';
		echo '<h2 class="niwa-card__title">' . esc_html__( 'Centerstage Pill', 'niwa-ai' ) . '</h2>';
		echo '</div>';
		echo '</summary>';
		echo '<div class="niwa-card__body">';

		// Enable toggle.
		echo '<div class="niwa-field">';
		echo '<label class="niwa-toggle">';
		echo '<input type="checkbox" name="' . esc_attr( $n . '[centerstage_mode]' ) . '" value="1" ' . checked( $opts['centerstage_mode'], 1, false ) . ' />';
		echo esc_html__( 'Enable Centerstage Mode', 'niwa-ai' );
		echo '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Turns on the bottom-center centerstage mode. Leave it off to use the left/right corner launcher positions configured per device below.', 'niwa-ai' ) . '</span>';
		echo '</div>';

		// Pill Text.
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Pill Text', 'niwa-ai' ) . '</label>';
		echo '<input type="text" name="' . esc_attr( $n . '[centerstage_pill_text]' ) . '" value="' . esc_attr( $opts['centerstage_pill_text'] ) . '" class="niwa-input--wide" />';
		echo '</div>';

		// Responsive centerstage pill dimensions.
		echo '<div class="niwa-field">';
		echo '<span class="niwa-field__label">' . esc_html__( 'Responsive Pill Dimensions', 'niwa-ai' ) . '</span>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Set separate centerstage pill sizes for desktop, tablet, and phone.', 'niwa-ai' ) . '</span>';
		foreach ( array( 'desktop' => __( 'Desktop', 'niwa-ai' ), 'tablet' => __( 'Tablet', 'niwa-ai' ), 'mobile' => __( 'Mobile', 'niwa-ai' ) ) as $device => $device_label ) {
			echo '<div class="niwa-field__row" style="align-items:flex-end;margin-top:10px;">';
			echo '<div style="min-width:88px;"><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html( $device_label ) . '</label></div>';
			echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Width (px)', 'niwa-ai' ) . '</label>';
			echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_centerstage_pill_width]' ) . '" value="' . esc_attr( $opts[ $device . '_centerstage_pill_width' ] ) . '" min="140" max="600" style="width:90px;" /></div>';
			echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Height (px)', 'niwa-ai' ) . '</label>';
			echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_centerstage_pill_height]' ) . '" value="' . esc_attr( $opts[ $device . '_centerstage_pill_height' ] ) . '" min="40" max="100" style="width:90px;" /></div>';
			echo '</div>';
		}
		echo '</div>';

		// Responsive expanded centerstage dimensions.
		echo '<div class="niwa-field">';
		echo '<span class="niwa-field__label">' . esc_html__( 'Responsive Expanded Morph Dimensions', 'niwa-ai' ) . '</span>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Set the opened Centerstage size for desktop and wider tablets. Centerstage remains fullscreen at the mobile breakpoint.', 'niwa-ai' ) . '</span>';
		foreach ( array( 'desktop' => __( 'Desktop', 'niwa-ai' ), 'tablet' => __( 'Tablet', 'niwa-ai' ) ) as $device => $device_label ) {
			echo '<div class="niwa-field__row" style="align-items:flex-end;margin-top:10px;">';
			echo '<div style="min-width:88px;"><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html( $device_label ) . '</label></div>';
			echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Width (px)', 'niwa-ai' ) . '</label>';
			echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_centerstage_morph_width]' ) . '" value="' . esc_attr( $opts[ $device . '_centerstage_morph_width' ] ) . '" min="200" max="2000" style="width:90px;" /></div>';
			echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Height (px)', 'niwa-ai' ) . '</label>';
			echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_centerstage_morph_height]' ) . '" value="' . esc_attr( $opts[ $device . '_centerstage_morph_height' ] ) . '" min="200" max="2000" style="width:90px;" /></div>';
			echo '</div>';
		}
		echo '</div>';

		// Pill Text Color & Arrow Color (inline row).
		echo '<div class="niwa-field">';
		echo '<span class="niwa-field__label">' . esc_html__( 'Pill Colors', 'niwa-ai' ) . '</span>';
		echo '<div class="niwa-field__row">';
		echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Text Color', 'niwa-ai' ) . '</label>';
		echo '<input type="color" name="' . esc_attr( $n . '[centerstage_pill_text_color]' ) . '" value="' . esc_attr( $opts['centerstage_pill_text_color'] ) . '" /></div>';
		echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Arrow Color', 'niwa-ai' ) . '</label>';
		echo '<input type="color" name="' . esc_attr( $n . '[centerstage_pill_arrow_color]' ) . '" value="' . esc_attr( $opts['centerstage_pill_arrow_color'] ) . '" /></div>';
		echo '</div>';
		echo '</div>';

		echo '</div>'; // .niwa-card__body
		echo '</details>';
		echo '</div>'; // .niwa-card

		// ── Cards 3-5: Device Positioning ──
		$device_configs = array(
			'desktop' => array(
				'label'      => __( 'Desktop Positioning', 'niwa-ai' ),
				'icon'       => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>',
				'w_label'    => __( 'Width (px)', 'niwa-ai' ),
				'h_label'    => __( 'Height (px)', 'niwa-ai' ),
				'w_max'      => 2000,
			),
			'tablet'  => array(
				'label'      => __( 'Tablet Positioning', 'niwa-ai' ),
				'icon'       => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>',
				'w_label'    => __( 'Width (px)', 'niwa-ai' ),
				'h_label'    => __( 'Height (px)', 'niwa-ai' ),
				'w_max'      => 2000,
			),
			'mobile'  => array(
				'label'      => __( 'Mobile Positioning', 'niwa-ai' ),
				'icon'       => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>',
				'w_label'    => __( 'Width (px)', 'niwa-ai' ),
				'h_label'    => __( 'Height (px)', 'niwa-ai' ),
				'w_max'      => 2000,
			),
		);

		foreach ( $device_configs as $device => $cfg ) {
			echo '<div class="niwa-card">';
			echo '<details class="niwa-collapsible">';
			echo '<summary>';
			echo '<div class="niwa-card__header">';
			echo '<div class="niwa-card__icon">' . $cfg['icon'] . '</div>';
			echo '<h2 class="niwa-card__title">' . esc_html( $cfg['label'] ) . '</h2>';
			echo '</div>';
			echo '</summary>';
			echo '<div class="niwa-card__body">';

			// ── Launcher sub-section ──
			echo '<h3 style="margin:0 0 12px;font-size:14px;font-weight:600;color:var(--niwa-ink);">' . esc_html__( 'Launcher', 'niwa-ai' ) . '</h3>';

			// Launcher Position select.
			echo '<div class="niwa-field">';
			echo '<label class="niwa-field__label">' . esc_html__( 'Position', 'niwa-ai' ) . '</label>';
			echo '<select name="' . esc_attr( $n . '[' . $device . '_launcher_position]' ) . '">';
			foreach ( $pos_options as $val => $lbl ) {
				echo '<option value="' . esc_attr( $val ) . '"' . selected( $opts[ $device . '_launcher_position' ], $val, false ) . '>' . esc_html( $lbl ) . '</option>';
			}
			echo '</select>';
			echo '</div>';

			// Launcher X + Y (inline row).
			echo '<div class="niwa-field">';
			echo '<span class="niwa-field__label">' . esc_html__( 'Position Offsets', 'niwa-ai' ) . '</span>';
			echo '<div class="niwa-field__row">';
			echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'X (px)', 'niwa-ai' ) . '</label>';
			echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_launcher_x]' ) . '" value="' . esc_attr( $opts[ $device . '_launcher_x' ] ) . '" min="0" max="500" style="width:80px;" /></div>';
			echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Y (px)', 'niwa-ai' ) . '</label>';
			echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_launcher_y]' ) . '" value="' . esc_attr( $opts[ $device . '_launcher_y' ] ) . '" min="0" max="500" style="width:80px;" /></div>';
			echo '</div>';
			echo '</div>';

			// ── Flyout sub-section ──
			echo '<div class="niwa-field">';
			echo '<span class="niwa-field__label">' . esc_html__( 'Launcher Pill Size', 'niwa-ai' ) . '</span>';
			echo '<div class="niwa-field__row">';
			echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Width (px)', 'niwa-ai' ) . '</label>';
			echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_launcher_width]' ) . '" value="' . esc_attr( $opts[ $device . '_launcher_width' ] ) . '" min="120" max="400" style="width:90px;" /></div>';
			echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Height (px)', 'niwa-ai' ) . '</label>';
			echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_launcher_height]' ) . '" value="' . esc_attr( $opts[ $device . '_launcher_height' ] ) . '" min="40" max="100" style="width:90px;" /></div>';
			echo '</div>';
			echo '</div>';

			echo '<hr style="border:0;border-top:1px solid var(--niwa-edge);margin:20px 0;" />';
			echo '<h3 style="margin:0 0 12px;font-size:14px;font-weight:600;color:var(--niwa-ink);">' . esc_html__( 'Flyout Size', 'niwa-ai' ) . '</h3>';

			// Width + Height (inline row). Mobile uses viewport-relative sizing.
			echo '<div class="niwa-field">';
			echo '<span class="niwa-field__label">' . esc_html__( 'Expanded Chat Size', 'niwa-ai' ) . '</span>';
			if ( 'mobile' === $device ) {
				echo '<span class="niwa-field__hint">' . esc_html__( 'Set the non-fullscreen size relative to the visible mobile viewport. Default: 95vw x 95dvh.', 'niwa-ai' ) . '</span>';
				echo '<div class="niwa-field__row">';
				echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Width (vw)', 'niwa-ai' ) . '</label>';
				echo '<input type="number" name="' . esc_attr( $n . '[mobile_width_vw]' ) . '" value="' . esc_attr( $opts['mobile_width_vw'] ) . '" min="1" max="100" style="width:80px;" /></div>';
				echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Height (dvh)', 'niwa-ai' ) . '</label>';
				echo '<input type="number" name="' . esc_attr( $n . '[mobile_height_dvh]' ) . '" value="' . esc_attr( $opts['mobile_height_dvh'] ) . '" min="1" max="100" style="width:90px;" /></div>';
				echo '</div>';
			} else {
				echo '<span class="niwa-field__hint">' . esc_html__( 'Set the expanded chat window size in pixels.', 'niwa-ai' ) . '</span>';
				echo '<div class="niwa-field__row">';
				echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html( $cfg['w_label'] ) . '</label>';
				echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_width]' ) . '" value="' . esc_attr( $opts[ $device . '_width' ] ) . '" min="0" max="' . esc_attr( $cfg['w_max'] ) . '" style="width:80px;" /></div>';
				echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html( $cfg['h_label'] ) . '</label>';
				echo '<input type="number" name="' . esc_attr( $n . '[' . $device . '_height]' ) . '" value="' . esc_attr( $opts[ $device . '_height' ] ) . '" min="0" max="2000" style="width:90px;" /></div>';
				echo '</div>';
			}
			echo '</div>';

			// Fullscreen toggle — only show for mobile device.
			if ( 'mobile' === $device ) {
				echo '<div class="niwa-field" style="margin-top:12px;">';
				echo '<label class="niwa-toggle">';
				echo '<input type="checkbox" name="' . esc_attr( $n . '[mobile_fullscreen]' ) . '" value="1" ' . checked( $opts['mobile_fullscreen'] ?? 0, 1, false ) . ' />';
				echo esc_html__( 'Fullscreen on mobile', 'niwa-ai' );
				echo '</label>';
				echo '<span class="niwa-field__hint">' . esc_html__( 'When enabled, chat uses the complete mobile viewport. When disabled, the vw and dvh values above are used and the remaining space is split evenly around the chat.', 'niwa-ai' ) . '</span>';
				echo '</div>';
			}

			echo '</div>'; // .niwa-card__body
			echo '</details>';
			echo '</div>'; // .niwa-card
		}

		// ── Card 6: Product Cards ──
		echo '<div class="niwa-card">';
		echo '<details class="niwa-collapsible">';
		echo '<summary>';
		echo '<div class="niwa-card__header">';
		echo '<div class="niwa-card__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 0 1-8 0"/></svg></div>';
		echo '<h2 class="niwa-card__title">' . esc_html__( 'Product Cards', 'niwa-ai' ) . '</h2>';
		echo '</div>';
		echo '</summary>';
		echo '<div class="niwa-card__body">';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Delivery Days', 'niwa-ai' ) . '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Number of days shown when footer mode is set to Delivery.', 'niwa-ai' ) . '</span>';
		echo '<input type="number" name="' . esc_attr( $n . '[delivery_days]' ) . '" value="' . esc_attr( $opts['delivery_days'] ) . '" min="1" max="30" />';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<input type="hidden" name="' . esc_attr( $n . '[product_card_action_mode]' ) . '" value="store" />';
		echo '<label class="niwa-toggle">';
		echo '<input type="checkbox" name="' . esc_attr( $n . '[product_card_action_mode]' ) . '" value="catalog" ' . checked( $opts['product_card_action_mode'], 'catalog', false ) . ' />';
		echo esc_html__( 'Catalog mode - show View Details only', 'niwa-ai' );
		echo '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Use this when the WooCommerce site is a product catalog, not a shop. Product cards will link to product detail pages instead of showing Buy Now or Add to Cart actions.', 'niwa-ai' ) . '</span>';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Footer Info Mode', 'niwa-ai' ) . '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Choose what is shown on the left side of the product card footer: delivery info, score/reviews, or nothing.', 'niwa-ai' ) . '</span>';
		echo '<select name="' . esc_attr( $n . '[product_card_footer_mode]' ) . '">';
		foreach ( array(
			'delivery' => __( 'Delivery', 'niwa-ai' ),
			'score'    => __( 'Score', 'niwa-ai' ),
			'off'      => __( 'Off', 'niwa-ai' ),
		) as $value => $label ) {
			echo '<option value="' . esc_attr( $value ) . '"' . selected( $opts['product_card_footer_mode'], $value, false ) . '>' . esc_html( $label ) . '</option>';
		}
		echo '</select>';
		echo '</div>';

		echo '<hr style="border:0;border-top:1px solid var(--niwa-edge);margin:20px 0;" />';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Banner Content Mode', 'niwa-ai' ) . '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Choose whether the top banner shows custom text, WooCommerce sale percentage, or the exact amount off for each product.', 'niwa-ai' ) . '</span>';
		echo '<select name="' . esc_attr( $n . '[product_card_banner_type]' ) . '">';
		foreach ( array(
			'text'    => __( 'Text', 'niwa-ai' ),
			'percent' => __( 'Percent Off', 'niwa-ai' ),
			'amount_off'  => __( 'Amount Off', 'niwa-ai' ),
			'off'     => __( 'Off', 'niwa-ai' ),
		) as $value => $label ) {
			$current_banner_type = 'amount' === $opts['product_card_banner_type'] ? 'amount_off' : $opts['product_card_banner_type'];
			echo '<option value="' . esc_attr( $value ) . '"' . selected( $current_banner_type, $value, false ) . '>' . esc_html( $label ) . '</option>';
		}
		echo '</select>';
		echo '</div>';

		$banner_text_value = isset( $opts['product_card_banner_text'] ) ? (string) $opts['product_card_banner_text'] : '';
		if ( '' === $banner_text_value ) {
			foreach ( array( 'en', 'sr', 'hr', 'de', 'es', 'fr' ) as $lang_code ) {
				$legacy_key = 'product_card_banner_text_' . $lang_code;
				if ( ! empty( $opts[ $legacy_key ] ) ) {
					$banner_text_value = (string) $opts[ $legacy_key ];
					break;
				}
			}
		}
		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Custom Banner Text', 'niwa-ai' ) . '</label>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Used when Banner Content Mode is set to Text. This is a custom free-text value, so clients can enter it directly in their own language.', 'niwa-ai' ) . '</span>';
		echo '<input type="text" name="' . esc_attr( $n . '[product_card_banner_text]' ) . '" value="' . esc_attr( $banner_text_value ) . '" class="niwa-input--wide" />';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<span class="niwa-field__label">' . esc_html__( 'Banner Background', 'niwa-ai' ) . '</span>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Controls the color and opacity of the top banner background on product cards.', 'niwa-ai' ) . '</span>';
		echo '<div class="niwa-field__row">';
		echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Color', 'niwa-ai' ) . '</label>';
		echo '<input type="color" name="' . esc_attr( $n . '[product_card_banner_bg_color]' ) . '" value="' . esc_attr( $opts['product_card_banner_bg_color'] ) . '" /></div>';
		echo '<div><label style="font-size:12px;color:var(--niwa-ink-3);display:block;margin-bottom:3px;">' . esc_html__( 'Opacity (%)', 'niwa-ai' ) . '</label>';
		echo '<input type="number" name="' . esc_attr( $n . '[product_card_banner_bg_opacity]' ) . '" value="' . esc_attr( $opts['product_card_banner_bg_opacity'] ) . '" min="0" max="100" style="width:90px;" /></div>';
		echo '</div>';
		echo '</div>';

		echo '</div>'; // .niwa-card__body
		echo '</details>';
		echo '</div>'; // .niwa-card

		// ── Form Footer ──
		echo '<div style="margin-top:16px;">';
		submit_button( __( 'Save UI Settings', 'niwa-ai' ), 'primary niwa-btn niwa-btn--primary', 'submit', false );
		echo '</div>';

		echo '</form>';
		echo '</div>'; // .wrap .niwa-wrap
	}

	private function render_avatar_field( $field_key, $label, $hint, $value, $default_url ) {
		$field_key   = sanitize_key( $field_key );
		$input_id    = 'niwa_' . $field_key;
		$preview_id  = $input_id . '_preview';
		$upload_id   = $input_id . '_upload';
		$remove_id   = $input_id . '_remove';
		$image_url   = '' !== trim( (string) $value ) ? (string) $value : (string) $default_url;
		$remove_css  = '' !== trim( (string) $value ) ? '' : ' style="display:none;"';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label" for="' . esc_attr( $input_id ) . '">' . esc_html( $label ) . '</label>';
		echo '<span class="niwa-field__hint">' . esc_html( $hint ) . '</span>';
		echo '<input type="hidden" id="' . esc_attr( $input_id ) . '" name="' . esc_attr( self::OPTION_KEY . '[' . $field_key . ']' ) . '" value="' . esc_attr( $value ) . '" />';
		echo '<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px;">';
		echo '<button type="button" class="button" id="' . esc_attr( $upload_id ) . '" data-niwa-media-target="' . esc_attr( $input_id ) . '" data-niwa-preview-target="' . esc_attr( $preview_id ) . '">' . esc_html__( 'Choose Image', 'niwa-ai' ) . '</button>';
		echo '<button type="button" class="button" id="' . esc_attr( $remove_id ) . '" data-niwa-remove-target="' . esc_attr( $input_id ) . '" data-niwa-preview-target="' . esc_attr( $preview_id ) . '" data-niwa-default-preview="' . esc_attr( $default_url ) . '"' . $remove_css . '>' . esc_html__( 'Remove', 'niwa-ai' ) . '</button>';
		echo '</div>';
		echo '<div id="' . esc_attr( $preview_id ) . '" style="margin-top:10px;">';
		echo '<img src="' . esc_url( $image_url ) . '" alt="" style="width:64px;height:64px;object-fit:cover;border-radius:999px;border:1px solid rgba(15,23,42,0.08);background:#fff;display:block;" />';
		echo '</div>';
		echo '</div>';
	}

	/**
	 * Render one custom page hide rule.
	 *
	 * @param int|string $index Numeric index, or __INDEX__ for the template.
	 * @param array      $page Page rule.
	 * @param bool       $is_template Whether this is the hidden clone source.
	 */
	private function render_hide_niwa_page_card( $index, $page, $is_template = false ) {
		$base = self::OPTION_KEY . '[hide_niwa_custom_pages][' . $index . ']';

		$page = wp_parse_args(
			is_array( $page ) ? $page : array(),
			array(
				'enabled'    => 1,
				'url'        => '',
				'match_type' => 'contains',
			)
		);

		$enabled_checked = ! empty( $page['enabled'] ) ? 'checked' : '';
		$match_type      = in_array( $page['match_type'], array( 'contains', 'equals', 'regex' ), true ) ? $page['match_type'] : 'contains';
		$match_opts      = array(
			'contains' => __( 'Contains - page address contains this text', 'niwa-ai' ),
			'equals'   => __( 'Equals - address or path must match exactly', 'niwa-ai' ),
			'regex'    => __( 'Regex - advanced pattern', 'niwa-ai' ),
		);

		echo '<div class="niwa-rule niwa-hide-niwa-page"' . ( $is_template ? ' data-niwa-rule-template="1"' : '' ) . '>';

		echo '<div class="niwa-rule__header">';
		echo '<span class="niwa-rule__title">' . esc_html__( 'Custom page', 'niwa-ai' ) . '</span>';
		echo '<button type="button" class="niwa-rule__remove" aria-label="' . esc_attr__( 'Remove custom page', 'niwa-ai' ) . '">';
		echo '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
		echo '<span>' . esc_html__( 'Remove', 'niwa-ai' ) . '</span>';
		echo '</button>';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-toggle">';
		echo '<input type="checkbox" name="' . esc_attr( $base . '[enabled]' ) . '" value="1" ' . $enabled_checked . ' />';
		echo esc_html__( 'Enable this custom page', 'niwa-ai' );
		echo '</label>';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Page address', 'niwa-ai' ) . '</label>';
		echo '<input type="text" name="' . esc_attr( $base . '[url]' ) . '" value="' . esc_attr( $page['url'] ) . '" class="niwa-input--wide" placeholder="/blog/my-post or https://..." />';
		echo '<span class="niwa-field__hint">' . esc_html__( 'Paste the page link or part of it. When this rule matches, the whole Niwa widget is not rendered.', 'niwa-ai' ) . '</span>';
		echo '</div>';

		echo '<div class="niwa-field">';
		echo '<label class="niwa-field__label">' . esc_html__( 'Match type', 'niwa-ai' ) . '</label>';
		echo '<select name="' . esc_attr( $base . '[match_type]' ) . '">';
		foreach ( $match_opts as $value => $label ) {
			echo '<option value="' . esc_attr( $value ) . '" ' . selected( $match_type, $value, false ) . '>' . esc_html( $label ) . '</option>';
		}
		echo '</select>';
		echo '<span class="niwa-field__hint">' . esc_html__( 'How the page address above is compared to the visitor current page address.', 'niwa-ai' ) . '</span>';
		echo '</div>';

		echo '</div>';
	}

	/**
	 * One-time migration: extract UI keys from the shared niwa_ai_settings
	 * option into the dedicated niwa_ai_ui_settings option.
	 */
	public function maybe_migrate() {
		if ( false !== get_option( self::OPTION_KEY, false ) ) {
			return;
		}

		$shared = get_option( Niwa_AI_Constants::OPT_SETTINGS, array() );
		if ( ! is_array( $shared ) || empty( $shared ) ) {
			return;
		}

		$ui_data  = array();
		$cleaned  = $shared;
		$changed  = false;

		foreach ( self::$ui_keys as $key ) {
			if ( array_key_exists( $key, $shared ) ) {
				$ui_data[ $key ] = $shared[ $key ];
				unset( $cleaned[ $key ] );
				$changed = true;
			}
		}

		if ( $changed ) {
			update_option( self::OPTION_KEY, $ui_data, true );
			update_option( Niwa_AI_Constants::OPT_SETTINGS, $cleaned, true );
		}
	}

	/**
	 * Get defaults for Leave Stoper fields.
	 *
	 * @return array
	 */
	public static function get_leave_stoper_defaults() {
		return array(
			'leave_stoper_enabled'             => 0,
			'leave_stoper_trigger_type'        => 'exit_intent',
			'leave_stoper_inactivity_timeout'  => 45,
			'leave_stoper_open_delay'          => 300,
			'leave_stoper_session_cooldown'    => 30,
			'leave_stoper_disable_on_mobile'   => 0,
			'leave_stoper_test_mode'           => 0,
			'leave_stoper_reset_on_page_load'  => 0,
			'leave_stoper_media_id'            => 0,
			'leave_stoper_message_text'        => '',
			'leave_stoper_alt_text'            => '',
		);
	}

	/**
	 * Get defaults for all UI fields.
	 *
	 * @return array
	 */
	private function get_defaults() {
		return array(
			'ui_language'                      => 'en',
			'greeting_text'                    => '',
			'chat_theme_mode'                  => 'neutral',
			'enable_flyout'                    => 1,
			'hide_niwa_on_blog_pages'          => 0,
			'hide_niwa_on_custom_pages'        => 0,
			'hide_niwa_custom_pages'           => array(),
			'frontend_avatar_url'              => '',
			'launcher_label'                   => '',
			'launcher_label_color'             => '',
			'launcher_use_default_settings'    => 1,
			'centerstage_mode'                 => 0,
			'centerstage_pill_width'           => 224,
			'centerstage_pill_height'          => 48,
			'centerstage_pill_text'            => 'Ask Niwa',
			'centerstage_pill_text_color'      => '#374151',
			'centerstage_pill_arrow_color'     => '#374151',
			'desktop_centerstage_pill_width'   => 224,
			'desktop_centerstage_pill_height'  => 48,
			'tablet_centerstage_pill_width'    => 212,
			'tablet_centerstage_pill_height'   => 48,
			'mobile_centerstage_pill_width'    => 196,
			'mobile_centerstage_pill_height'   => 48,
			'desktop_centerstage_morph_width'  => 494,
			'desktop_centerstage_morph_height' => 680,
			'tablet_centerstage_morph_width'   => 494,
			'tablet_centerstage_morph_height'  => 560,
			'desktop_launcher_position'        => 'br',
			'desktop_launcher_x'               => 24,
			'desktop_launcher_y'               => 24,
			'desktop_launcher_width'           => 148,
			'desktop_launcher_height'          => 48,
			'tablet_launcher_position'         => 'br',
			'tablet_launcher_x'                => 20,
			'tablet_launcher_y'                => 20,
			'tablet_launcher_width'            => 148,
			'tablet_launcher_height'           => 48,
			'mobile_launcher_position'         => 'br',
			'mobile_launcher_x'                => 16,
			'mobile_launcher_y'                => 16,
			'mobile_launcher_width'            => 140,
			'mobile_launcher_height'           => 48,
			'desktop_width'                    => 380,
			'desktop_height'                   => 680,
			'tablet_width'                     => 340,
			'tablet_height'                    => 560,
			'mobile_width_vw'                  => self::MOBILE_FLYOUT_WIDTH_DEFAULT,
			'mobile_height_dvh'                => self::MOBILE_FLYOUT_HEIGHT_DEFAULT,
			'mobile_fullscreen'                => 1,
			'delivery_days'                    => 3,
			'product_card_action_mode'         => 'store',
			'product_card_footer_mode'         => 'delivery',
			'product_card_banner_type'         => 'percent',
			'product_card_banner_text'         => 'Banner text',
			'product_card_banner_text_en'      => 'Banner text',
			'product_card_banner_text_sr'      => 'Baner tekst',
			'product_card_banner_text_hr'      => 'Banner tekst',
			'product_card_banner_text_de'      => 'Bannertext',
			'product_card_banner_text_es'      => 'Texto del banner',
			'product_card_banner_text_fr'      => 'Texte du bandeau',
			'product_card_banner_bg_color'     => '#000000',
			'product_card_banner_bg_opacity'   => 45,
			'leave_stoper_enabled'             => 0,
			'leave_stoper_trigger_type'        => 'exit_intent',
			'leave_stoper_inactivity_timeout'  => 45,
			'leave_stoper_open_delay'          => 300,
			'leave_stoper_session_cooldown'    => 30,
			'leave_stoper_disable_on_mobile'   => 0,
			'leave_stoper_test_mode'           => 0,
			'leave_stoper_reset_on_page_load'  => 0,
			'leave_stoper_media_id'            => 0,
			'leave_stoper_message_text'        => '',
			'leave_stoper_alt_text'            => '',
		);
	}

}
