<?php
/**
 * Inline Search module. Renders an elegant search field that expands from a
 * search icon, scoped to a configurable container selector (default the site
 * header). Exposed three ways: the [bw_dev_inline_search] shortcode, a widget,
 * and automatic replacement of the theme's own search toggle inside the
 * container (notably Kadence's header search drawer).
 *
 * Ported from the standalone `bw-inline-search` plugin. Re-prefixed throughout:
 *   - Option:    bw_search_container_selector  → bw_dev_settings[inline_search][container_selector]
 *   - JS config: bwInlineSearchSettings         → bwDevInlineSearchConfig
 *   - CSS:       .bw-inline-search-*             → .bw-dev-inline-search-*
 *                .bw-search-icon-wrapper         → .bw-dev-search-icon-wrapper
 *                .bw-theme-dark/.bw-active/...   → .bw-dev-theme-dark/.bw-dev-active/...
 *   - Shortcode: [bw_inline_search]              → [bw_dev_inline_search]
 *                (the legacy [bw_inline_search] is kept as a back-compat alias
 *                only when no other plugin already registers it)
 *
 * Sites that hand-wrote CSS against the old `.bw-inline-search-*` classes must
 * update to `.bw-dev-inline-search-*` after migration (mirrors the Sticky
 * module's `.bw-is-sticky` → `.bw-dev-is-sticky` rename).
 *
 * @package BW_Dev
 */

defined( 'ABSPATH' ) || exit;

class BW_Dev_Module_Inline_Search implements BW_Dev_Module_Interface {

	public function slug(): string {
		return 'inline_search';
	}

	public function label(): string {
		return __( 'Inline Search', 'bw-dev' );
	}

	public function group(): string {
		return 'frontend';
	}

	public function default_settings(): array {
		return array( 'container_selector' => '.site-header' );
	}

	/**
	 * The container selector is interpolated directly into a <style> block on
	 * the frontend, so it must never contain characters that could break out of
	 * a selector context. We strip CSS structural characters ( ; { } \ < > " ' )
	 * and collapse whitespace, then fall back to the default if nothing usable
	 * remains.
	 */
	public function sanitize( array $data ): array {
		$raw       = isset( $data['container_selector'] ) ? wp_unslash( (string) $data['container_selector'] ) : '';
		$selector  = self::clean_selector( $raw );
		if ( '' === $selector ) {
			$selector = '.site-header';
		}
		return array( 'container_selector' => $selector );
	}

	/**
	 * Strip anything that could escape a CSS selector context. Shared by
	 * sanitize() (on save) and the frontend output (defence in depth).
	 */
	private static function clean_selector( string $selector ): string {
		$selector = preg_replace( '/[;{}\\\\<>"\']/', '', $selector );
		$selector = preg_replace( '/\s+/', ' ', (string) $selector );
		return trim( (string) $selector );
	}

	private function container_selector(): string {
		$value    = (string) bw_dev()->settings()->get( $this->slug(), 'container_selector', '.site-header' );
		$selector = self::clean_selector( $value );
		return '' === $selector ? '.site-header' : $selector;
	}

	public function register(): void {
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
		add_action( 'wp_head', array( $this, 'output_scoped_css' ) );
		add_action( 'wp_footer', array( $this, 'output_template' ) );
		add_action( 'widgets_init', array( $this, 'register_widget' ) );

		add_shortcode( 'bw_dev_inline_search', array( $this, 'shortcode_output' ) );
		// Back-compat alias for content created under the standalone plugin.
		// Only register it when nothing else has claimed the old tag, so the
		// legacy plugin (if still active) keeps ownership during a phased rollout.
		if ( ! shortcode_exists( 'bw_inline_search' ) ) {
			add_shortcode( 'bw_inline_search', array( $this, 'shortcode_output' ) );
		}
	}

	public function enqueue_assets(): void {
		wp_enqueue_script(
			'bw-dev-inline-search',
			BW_DEV_URL . 'assets/js/inline-search.js',
			array(),
			BW_DEV_VERSION,
			true
		);
		wp_localize_script(
			'bw-dev-inline-search',
			'bwDevInlineSearchConfig',
			array( 'containerSelector' => $this->container_selector() )
		);
	}

	/**
	 * Output the scoped stylesheet. Every rule is prefixed with the configured
	 * container selector so the styles + behavior stay confined to that one
	 * region of the page (the site header by default).
	 */
	public function output_scoped_css(): void {
		$c   = $this->container_selector();
		$css = "
{$c} .bw-dev-inline-search-container { position: relative; display: inline-flex; align-items: center; height: 36px; }
{$c} .bw-dev-search-icon-wrapper { cursor: pointer; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 4px; transition: background-color 0.2s ease, opacity 0.2s ease; position: relative; z-index: 1001; }
{$c} .bw-dev-search-icon-wrapper:hover { background-color: rgba(255, 255, 255, 0.1); }
{$c} .bw-dev-search-icon-wrapper.bw-dev-hidden { opacity: 0; pointer-events: none; position: absolute; }
{$c} .bw-dev-inline-search-wrapper { position: absolute; right: 0; top: 50%; transform: translateY(-50%); display: flex; align-items: center; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.2s ease, visibility 0.2s ease; z-index: 1000; }
{$c} .bw-dev-inline-search-wrapper.bw-dev-active { opacity: 1; visibility: visible; pointer-events: auto; }
{$c} .bw-dev-inline-search-form { display: flex; align-items: center; background: #545b64; border: 1px solid #8c929a; border-radius: 4px; padding: 0 4px 0 10px; margin: 0; overflow: hidden; box-sizing: border-box; height: 36px; width: 180px; transition: border-color 0.2s ease; }
{$c} .bw-dev-inline-search-form:focus-within { border-color: #4bcac0; }
{$c} .bw-dev-inline-search-input { border: none !important; outline: none !important; background: transparent !important; font-size: 14px !important; line-height: 34px !important; height: 34px !important; padding: 0 !important; margin: 0 !important; width: 100%; min-width: 0; color: #ffffff !important; box-shadow: none !important; box-sizing: border-box !important; }
{$c} .bw-dev-inline-search-input::placeholder { color: #b0b0b0 !important; opacity: 1; }
{$c} .bw-dev-inline-search-input:focus { outline: none !important; box-shadow: none !important; }
{$c} .bw-dev-inline-search-submit { background: #4bcac0; border: none; border-radius: 3px; padding: 0; width: 28px; height: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: #ffffff; transition: background-color 0.2s ease; flex-shrink: 0; }
{$c} .bw-dev-inline-search-submit:hover { background: #3aa8a0; }
{$c} .bw-dev-inline-search-submit svg { display: block; width: 14px; height: 14px; }
{$c}.bw-dev-inline-search-active .search-toggle-open-container { display: none !important; }
.bw-dev-inline-search-active #search-drawer { display: none !important; }
@media (max-width: 1024px) { {$c} .bw-dev-inline-search-form { width: 160px; } }
@media (max-width: 768px) { {$c} .bw-dev-inline-search-wrapper { position: fixed; right: 15px; top: 12px; transform: none; } {$c} .bw-dev-inline-search-form { width: 200px; } }
{$c} .bw-dev-inline-search-wrapper.bw-dev-theme-dark .bw-dev-inline-search-form { background: #545b64; border-color: #8c929a; }
{$c} .bw-dev-inline-search-wrapper.bw-dev-theme-dark .bw-dev-inline-search-input { color: #ffffff !important; }
{$c} .bw-dev-inline-search-wrapper.bw-dev-theme-dark .bw-dev-inline-search-input::placeholder { color: #b0b0b0 !important; }
{$c} .bw-dev-inline-search-wrapper:not(.bw-dev-theme-dark) .bw-dev-inline-search-form { background: #ffffff; border-color: #d0d0d0; }
{$c} .bw-dev-inline-search-wrapper:not(.bw-dev-theme-dark) .bw-dev-inline-search-input { color: #333333 !important; }
{$c} .bw-dev-inline-search-wrapper:not(.bw-dev-theme-dark) .bw-dev-inline-search-input::placeholder { color: #999999 !important; }";

		// The selector is sanitized by clean_selector() (structural CSS chars
		// stripped); the remainder of the string is static plugin markup.
		echo '<style id="bw-dev-inline-search-css">' . $css . '</style>' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * The search SVG used by both the icon trigger and the submit button.
	 */
	private function search_svg( int $size = 20 ): string {
		return '<svg xmlns="http://www.w3.org/2000/svg" width="' . $size . '" height="' . $size . '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';
	}

	/**
	 * Shortcode: [bw_dev_inline_search placeholder="" icon_color="" theme="" align=""]
	 */
	public function shortcode_output( $atts ): string {
		$atts = shortcode_atts(
			array(
				'placeholder' => __( 'Search...', 'bw-dev' ),
				'icon_color'  => 'currentColor',
				'theme'       => 'light',
				'align'       => 'left',
			),
			$atts,
			'bw_dev_inline_search'
		);

		$theme_class = ( 'dark' === $atts['theme'] ) ? 'bw-dev-theme-dark' : '';
		$align       = in_array( $atts['align'], array( 'left', 'center', 'right' ), true ) ? $atts['align'] : 'left';

		ob_start();
		?>
		<div class="bw-dev-inline-search-container bw-dev-shortcode" style="text-align: <?php echo esc_attr( $align ); ?>;">
			<div class="bw-dev-search-icon-wrapper" style="color: <?php echo esc_attr( $atts['icon_color'] ); ?>;" data-bw-dev-search-toggle>
				<?php echo $this->search_svg( 20 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static SVG markup. ?>
			</div>
			<div class="bw-dev-inline-search-wrapper <?php echo esc_attr( $theme_class ); ?>">
				<form role="search" method="get" class="bw-dev-inline-search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
					<input type="search" class="bw-dev-inline-search-input" placeholder="<?php echo esc_attr( $atts['placeholder'] ); ?>" value="" name="s" autocomplete="off" />
					<button type="submit" class="bw-dev-inline-search-submit" aria-label="<?php esc_attr_e( 'Submit search', 'bw-dev' ); ?>">
						<?php echo $this->search_svg( 18 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static SVG markup. ?>
					</button>
				</form>
			</div>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	public function register_widget(): void {
		register_widget( 'BW_Dev_Inline_Search_Widget' );
	}

	/**
	 * Output a <template> the frontend JS clones when it auto-replaces a theme
	 * search toggle inside the container.
	 */
	public function output_template(): void {
		?>
		<template id="bw-dev-inline-search-template">
			<div class="bw-dev-inline-search-wrapper">
				<form role="search" method="get" class="bw-dev-inline-search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
					<input type="search" class="bw-dev-inline-search-input" placeholder="<?php esc_attr_e( 'Search...', 'bw-dev' ); ?>" value="" name="s" autocomplete="off" />
					<button type="submit" class="bw-dev-inline-search-submit" aria-label="<?php esc_attr_e( 'Submit search', 'bw-dev' ); ?>">
						<?php echo $this->search_svg( 18 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static SVG markup. ?>
					</button>
				</form>
			</div>
		</template>
		<?php
	}

	public function render_tab(): void {
		$selector = $this->container_selector();
		$field_id = 'bw-dev-inline-search-selector';
		$field_nm = BW_Dev_Settings::OPTION . '[' . $this->slug() . '][container_selector]';
		?>
		<p class="description">
			<?php esc_html_e( 'An elegant search field that expands from a search icon. The styles and auto-replacement behavior are scoped to one container — the site header by default — so it never leaks onto the rest of the page.', 'bw-dev' ); ?>
		</p>

		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row">
						<label for="<?php echo esc_attr( $field_id ); ?>"><?php esc_html_e( 'Container CSS selector', 'bw-dev' ); ?></label>
					</th>
					<td>
						<input type="text" id="<?php echo esc_attr( $field_id ); ?>" name="<?php echo esc_attr( $field_nm ); ?>" value="<?php echo esc_attr( $selector ); ?>" class="regular-text" placeholder=".site-header" />
						<p class="description">
							<?php esc_html_e( 'The element the inline search lives inside. Inside this container, the theme\'s own search toggle (e.g. Kadence\'s header search) is automatically replaced. Default: .site-header (Kadence).', 'bw-dev' ); ?><br />
							<?php esc_html_e( 'Examples: #masthead, .main-header, .site-header, #site-header', 'bw-dev' ); ?>
						</p>
					</td>
				</tr>
			</tbody>
		</table>

		<h3><?php esc_html_e( 'Other ways to place it', 'bw-dev' ); ?></h3>
		<ul style="list-style:disc;margin-left:24px;max-width:720px;">
			<li>
				<?php
				printf(
					/* translators: %s: shortcode example */
					esc_html__( 'Shortcode: %s (attributes: placeholder, icon_color, theme="light|dark", align="left|center|right").', 'bw-dev' ),
					'<code>[bw_dev_inline_search]</code>'
				);
				?>
			</li>
			<li><?php esc_html_e( 'Widget: add the "BW Inline Search" widget to any widget area.', 'bw-dev' ); ?></li>
		</ul>
		<?php
	}

	public function uninstall(): void {
		// No-op. Root option drop handles persisted state.
	}
}

/**
 * Widget wrapper around the [bw_dev_inline_search] shortcode.
 *
 * Ported from `BW_Inline_Search_Widget`; widget base id re-prefixed to
 * `bw_dev_inline_search_widget`.
 */
class BW_Dev_Inline_Search_Widget extends WP_Widget {

	public function __construct() {
		parent::__construct(
			'bw_dev_inline_search_widget',
			__( 'BW Inline Search', 'bw-dev' ),
			array(
				'description' => __( 'An elegant inline search that expands from an icon.', 'bw-dev' ),
				'classname'   => 'bw-dev-inline-search-widget',
			)
		);
	}

	public function widget( $args, $instance ) {
		$placeholder = ! empty( $instance['placeholder'] ) ? $instance['placeholder'] : __( 'Search...', 'bw-dev' );
		$theme       = ! empty( $instance['theme'] ) ? $instance['theme'] : 'light';
		$icon_color  = ! empty( $instance['icon_color'] ) ? $instance['icon_color'] : 'currentColor';

		echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- theme-provided wrapper markup.

		$shortcode = sprintf(
			'[bw_dev_inline_search placeholder="%s" theme="%s" icon_color="%s"]',
			esc_attr( $placeholder ),
			esc_attr( $theme ),
			esc_attr( $icon_color )
		);
		echo do_shortcode( $shortcode ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- shortcode output is escaped at source.

		echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- theme-provided wrapper markup.
	}

	public function form( $instance ) {
		$placeholder = ! empty( $instance['placeholder'] ) ? $instance['placeholder'] : __( 'Search...', 'bw-dev' );
		$theme       = ! empty( $instance['theme'] ) ? $instance['theme'] : 'light';
		$icon_color  = ! empty( $instance['icon_color'] ) ? $instance['icon_color'] : '';
		?>
		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'placeholder' ) ); ?>"><?php esc_html_e( 'Placeholder Text:', 'bw-dev' ); ?></label>
			<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'placeholder' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'placeholder' ) ); ?>" type="text" value="<?php echo esc_attr( $placeholder ); ?>">
		</p>
		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'theme' ) ); ?>"><?php esc_html_e( 'Theme:', 'bw-dev' ); ?></label>
			<select class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'theme' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'theme' ) ); ?>">
				<option value="light" <?php selected( $theme, 'light' ); ?>><?php esc_html_e( 'Light', 'bw-dev' ); ?></option>
				<option value="dark" <?php selected( $theme, 'dark' ); ?>><?php esc_html_e( 'Dark', 'bw-dev' ); ?></option>
			</select>
		</p>
		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'icon_color' ) ); ?>"><?php esc_html_e( 'Icon Color (CSS color):', 'bw-dev' ); ?></label>
			<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'icon_color' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'icon_color' ) ); ?>" type="text" value="<?php echo esc_attr( $icon_color ); ?>" placeholder="e.g. #ffffff or currentColor">
		</p>
		<?php
	}

	public function update( $new_instance, $old_instance ) {
		$instance                = array();
		$instance['placeholder'] = ! empty( $new_instance['placeholder'] ) ? sanitize_text_field( $new_instance['placeholder'] ) : '';
		$instance['theme']       = ! empty( $new_instance['theme'] ) ? sanitize_text_field( $new_instance['theme'] ) : 'light';
		$instance['icon_color']  = ! empty( $new_instance['icon_color'] ) ? sanitize_text_field( $new_instance['icon_color'] ) : '';
		return $instance;
	}
}
