<?php
/**
 * WPFAQBlock Shortcode
 *
 * Usage: [wpfaqblock category="orders" limit="5"]
 *
 * Attributes:
 *   category  – faq_group term slug (optional; omit for all groups)
 *   limit     – max FAQs to show per group (default: -1 = all)
 *   order     – ASC|DESC (default: ASC, by menu_order)
 *
 * @package WPFAQBlock
 * @since   2.0.0
 */

// Prevent direct file access.
if (!defined('ABSPATH')) {
	exit;
}

/**
 * Class WPFAQBlock_Shortcode
 */
class WPFAQBlock_Shortcode
{

	/**
	 * Collect rendered FAQs for optional JSON-LD schema output.
	 *
	 * @var array
	 */
	public static $rendered_faqs = array();

	/**
	 * Register the shortcode and frontend assets.
	 *
	 * Hooked into: init (via loader)
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function register(): void
	{
		add_shortcode('wpfaqblock', array($this, 'render'));
		add_action('wp_enqueue_scripts', array($this, 'enqueue_assets'));

		if ($this->is_schema_enabled()) {
			add_action('wp_footer', array($this, 'output_faq_schema'));
		}
	}

	/**
	 * Enqueue frontend CSS and JS.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function enqueue_assets(): void
	{
		// Enqueue new assets
		wp_enqueue_style(
			'wpfaqblock-frontend',
			WPFAQBLOCK_URL . 'assets/css/frontend.css',
			array(),
			WPFAQBLOCK_VERSION
		);

		wp_enqueue_script(
			'wpfaqblock-frontend',
			WPFAQBLOCK_URL . 'assets/js/frontend.js',
			array(),
			WPFAQBLOCK_VERSION,
			true
		);

		// Enqueue old assets for backward compatibility with old templates
		wp_enqueue_style(
			'wpfaqblock-stylesheet',
			WPFAQBLOCK_URL . 'assets/css/wpfaqblock-stylesheet.css',
			array(),
			WPFAQBLOCK_VERSION
		);
		wp_enqueue_style('load-fa', WPFAQBLOCK_URL . 'assets/css/all.css', array(), '5.5.0');

		wp_enqueue_script(
			'wpfaqblock-script',
			WPFAQBLOCK_URL . 'assets/js/wpfaqblock-script.js',
			array('jquery'),
			WPFAQBLOCK_VERSION,
			true
		);
	}

	/**
	 * Render the shortcode output.
	 *
	 * @since 2.0.0
	 *
	 * @param array|string $atts Shortcode attributes.
	 * @return string HTML output.
	 */
	public function render($atts): string
	{
		$args = shortcode_atts(
			array(
				// New attributes
				'category' => '',
				'ids' => '',
				'limit' => -1,
				'order' => 'ASC',
				// Old backward compatibility attributes
				'title' => '',
				'placeholder' => '',
				'search' => '',
				'orderby' => 'date',
				'open' => '',
				'open_first' => '',
				'accordion' => '',
				'class' => '',
				'include_category' => '',
				'exclude_category' => '',
				'category_title' => '1',
				'faqtheme' => '',
				'faqicon' => 'plus',
				'closed_icon' => '',
				'open_icon' => '',
				'template' => '',
				'feedback' => false,
			),
			$atts,
			'wpfaqblock'
		);

		$selected_ids = $this->parse_selected_ids($args['ids']);

		// Use old rendering ONLY when a legacy flat-list shortcode attribute is explicitly set.
		// faqtheme alone does NOT trigger old flat rendering — it is resolved to a template file
		// and used per-group so both groups AND themes work simultaneously.
		$is_old_template = !empty($args['template']) || !empty($args['include_category']) || !empty($args['exclude_category']) || !empty($args['search']);

		if ($is_old_template) {
			// Ensure defaults for old template variables.
			if (empty($args['faqtheme'])) {
				$args['faqtheme'] = 'wpfaqblock-theme-1';
			}

			$queryargs = array(
				'post_type' => 'wpfaqblock',
				'post_status' => 'publish',
				'posts_per_page' => -1,
				'orderby' => $args['orderby'],
				'order' => $args['order'],
			);

			if (!empty($selected_ids)) {
				$queryargs['post__in'] = $selected_ids;
				$queryargs['orderby'] = 'post__in';
				$queryargs['posts_per_page'] = count($selected_ids);
			}

			if (isset($_GET['wpfaqblocksearch']) && $args['search']) {
				// Verify nonce for security (recommended by Plugin Check).
				if (isset($_GET['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'wpfaqblock_search')) {
					$queryargs['s'] = trim(sanitize_text_field(wp_unslash($_GET['wpfaqblocksearch'])));
				}
			}

			$tax_query = array();

			if (!empty($args['category'])) {
				$tax_query[] = array(
					'taxonomy' => WPFAQBlock_Taxonomy::SLUG,
					'field' => 'slug',
					'terms' => sanitize_title($args['category']),
					'operator' => 'IN',
				);
			}

			if (!empty($args['include_category'])) {
				$tax_query[] = array(
					'taxonomy' => WPFAQBlock_Taxonomy::SLUG,
					'field' => 'slug',
					'terms' => array_filter(array_map('sanitize_title', explode(',', $args['include_category']))),
					'operator' => 'IN',
				);
			}

			if (!empty($args['exclude_category'])) {
				$tax_query[] = array(
					'taxonomy' => WPFAQBlock_Taxonomy::SLUG,
					'field' => 'slug',
					'terms' => array_filter(array_map('sanitize_title', explode(',', $args['exclude_category']))),
					'operator' => 'NOT IN',
				);
			}
			if (!empty($tax_query)) {
				// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
				$queryargs['tax_query'] = $tax_query;
			}
			$query = new WP_Query($queryargs);
			$this->collect_schema_faqs($query->posts);

			$wpfaqblock_file = WPFAQBLOCK_DIR . 'includes/templates/template-1.php';
			if (!empty($args['faqtheme']) && preg_match('/^wpfaqblock-theme-[1-7]$/', $args['faqtheme'])) {
				$theme_number = str_replace('wpfaqblock-theme-', '', $args['faqtheme']);
				$t_path = WPFAQBLOCK_DIR . 'includes/templates/template-' . $theme_number . '.php';
				if (file_exists($t_path)) {
					$wpfaqblock_file = $t_path;
				}
			}

			if (!empty($args['template'])) {
				$template_name = str_replace('.php', '', $args['template']);
				$template_name = sanitize_file_name($template_name);

				if (preg_match('/^template-\d+$/', $template_name)) {
					$wpfaqblock_path = get_template_directory() . '/wpfaqblock/' . $template_name . '.php';
					$wpfaqblock_plugin_path = WPFAQBLOCK_DIR . 'includes/templates/' . $template_name . '.php';

					if (file_exists($wpfaqblock_path)) {
						$wpfaqblock_file = $wpfaqblock_path;
					} elseif (file_exists($wpfaqblock_plugin_path)) {
						$wpfaqblock_file = $wpfaqblock_plugin_path;
					}
				}
			}

			ob_start();
			include $wpfaqblock_file;
			return $this->append_powered_by_html(ob_get_clean(), $args);
		}

		// ── New Grouped Rendering Logic ───────────────────────────────────────

		$limit = (int) $args['limit'];
		$order = strtoupper($args['order']) === 'DESC' ? 'DESC' : 'ASC';

		// Fetch all groups with their FAQs.
		$data = new WPFAQBlock_Data();

		if (!empty($selected_ids)) {
			$faqs = $data->get_faqs_by_ids($selected_ids);

			if (empty($faqs)) {
				return '<p class="wpfaqblock-no-faqs">' . esc_html__('No FAQs found.', 'wpfaqblock') . '</p>';
			}

			$wpfaqblock_file = $this->resolve_template_file($args);

			return $this->render_selected_faqs($faqs, $args, $wpfaqblock_file);
		}

		$grouped_faqs = $data->get_grouped_faqs();

		// Filter by category slug if requested.
		if (!empty($args['category'])) {
			$grouped_faqs = array_filter(
				$grouped_faqs,
				function ($group) use ($args) {
					if (!$group['term'] instanceof WP_Term) {
						return false;
					}
					return $group['term']->slug === sanitize_title($args['category']);
				}
			);
		}

		if (empty($grouped_faqs)) {
			return '<p class="wpfaqblock-no-faqs">' . esc_html__('No FAQs found.', 'wpfaqblock') . '</p>';
		}

		$wpfaqblock_file = $this->resolve_template_file($args);

		// Outer wrapper class.
		$theme_class = !empty($args['faqtheme']) ? ' ' . sanitize_html_class($args['faqtheme']) : '';

		ob_start();
		echo $this->render_display_title($args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo '<div class="wpfaqblock-grouped-wrap' . esc_attr($theme_class) . '">';

		foreach ($grouped_faqs as $group) {

			// Only published FAQs on frontend.
			$faqs = array_filter(
				$group['faqs'],
				function (WP_Post $faq) {
					return 'publish' === $faq->post_status;
				}
			);

			// Apply order.
			if ('DESC' === $order) {
				$faqs = array_reverse(array_values($faqs));
			}

			// Apply limit.
			if ($limit > 0) {
				$faqs = array_slice($faqs, 0, $limit);
			}

			if (empty($faqs)) {
				continue;
			}

			// Build ordered post ID list (preserves drag-drop order from builder).
			$post_ids = array_map(
				function (WP_Post $faq) {
					return $faq->ID;
				},
				array_values($faqs)
			);

			// Build a WP_Query so existing template files work without modification.
			$query = new WP_Query( // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
				array(
					'post_type' => WPFAQBlock_CPT::SLUG,
					'post_status' => 'publish',
					'post__in' => $post_ids,
					'orderby' => 'post__in',
					'posts_per_page' => -1,
					'no_found_rows' => true,
				)
			);

			$this->collect_schema_faqs($faqs);

			$term_name = $group['term'] instanceof WP_Term ? $group['term']->name : '';

			// Suppress the template's own <h2> title; we render the group <h3> separately.
			$args['title'] = '';

			echo '<div class="wpfaqblock-group">';
			if ($term_name && '0' !== (string) $args['category_title']) {
				echo '<h3 class="wpfaqblock-group__title">' . esc_html($term_name) . '</h3>';
			}

			// Include the selected theme template — it reads $query and $args.
			include $wpfaqblock_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable

			echo '</div><!-- /.wpfaqblock-group -->';
		}

		echo '</div><!-- /.wpfaqblock-grouped-wrap -->';

		return $this->append_powered_by_html(ob_get_clean(), $args);
	}

	/**
	 * Parse selected FAQ IDs from a shortcode/block attribute.
	 *
	 * @since 2.0.0
	 *
	 * @param mixed $ids Attribute value. Accepts comma-separated string or array.
	 * @return int[] Ordered list of unique FAQ IDs.
	 */
	private function parse_selected_ids($ids): array
	{
		if (is_array($ids)) {
			$parts = $ids;
		} else {
			$parts = preg_split('/[\s,]+/', (string) $ids);
		}

		$result = array();
		foreach ((array) $parts as $id) {
			$id = absint($id);
			if ($id > 0 && !in_array($id, $result, true)) {
				$result[] = $id;
			}
		}

		return $result;
	}

	/**
	 * Resolve the template file based on the selected FAQ theme.
	 *
	 * @since 2.0.0
	 *
	 * @param array $args Shortcode args.
	 * @return string Template file path.
	 */
	private function resolve_template_file(array $args): string
	{
		$wpfaqblock_file = WPFAQBLOCK_DIR . 'includes/templates/template-1.php';

		if (!empty($args['faqtheme']) && preg_match('/^wpfaqblock-theme-([1-8])$/', $args['faqtheme'], $theme_matches)) {
			$t_path = WPFAQBLOCK_DIR . 'includes/templates/template-' . $theme_matches[1] . '.php';
			if (file_exists($t_path)) {
				$wpfaqblock_file = $t_path;
			}
		}

		return $wpfaqblock_file;
	}

	/**
	 * Render the main FAQ display title for the current grouped output.
	 *
	 * @since 2.0.0
	 *
	 * @param array $args Shortcode args.
	 * @return string
	 */
	private function render_display_title(array $args): string
	{
		$title = isset($args['title']) ? trim(wp_strip_all_tags((string) $args['title'])) : '';

		if ('' === $title) {
			return '';
		}

		return '<h2 class="wpfaqblock-title wpfaqblock-display-title">' . esc_html($title) . '</h2>';
	}

	/**
	 * Append optional powered by attribution HTML after a FAQ display.
	 *
	 * The filter returns an empty string by default, so no attribution is shown
	 * unless a site developer explicitly opts in.
	 *
	 * @since 2.0.0
	 *
	 * @param string $output Rendered FAQ output.
	 * @param array  $args   Shortcode/block display args.
	 * @return string Rendered FAQ output with optional attribution.
	 */
	private function append_powered_by_html(string $output, array $args): string
	{
		/**
		 * Filter optional powered by attribution HTML.
		 *
		 * Example:
		 * add_filter(
		 *     'wpfaqblock_powered_by_html',
		 *     function () {
		 *         return '<p class="wpfaqblock-powered-by">Powered by <a href="https://wpfaqblock.com" target="_blank" rel="noopener">WPFAQBlock.com</a></p>';
		 *     }
		 * );
		 *
		 * @since 2.0.0
		 *
		 * @param string $html   Attribution HTML. Default empty.
		 * @param array  $args   Shortcode/block display args.
		 * @param string $output Rendered FAQ output before attribution.
		 */
		$html = apply_filters('wpfaqblock_powered_by_html', '', $args, $output);
		$html = is_string($html) ? trim($html) : '';

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

		return $output . wp_kses_post($html);
	}

	/**
	 * Render a manually selected list of FAQs as one display instance.
	 *
	 * @since 2.0.0
	 *
	 * @param WP_Post[] $faqs             Selected FAQ posts.
	 * @param array     $args             Shortcode args.
	 * @param string    $wpfaqblock_file  Template file path.
	 * @return string Rendered HTML.
	 */
	private function render_selected_faqs(array $faqs, array $args, string $wpfaqblock_file): string
	{
		$post_ids = array_map(
			function (WP_Post $faq) {
				return $faq->ID;
			},
			$faqs
		);

		$query = new WP_Query( // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
			array(
				'post_type' => WPFAQBlock_CPT::SLUG,
				'post_status' => 'publish',
				'post__in' => $post_ids,
				'orderby' => 'post__in',
				'posts_per_page' => count($post_ids),
				'no_found_rows' => true,
			)
		);

		$this->collect_schema_faqs($faqs);

		$theme_class = !empty($args['faqtheme']) ? ' ' . sanitize_html_class($args['faqtheme']) : '';

		ob_start();
		echo $this->render_display_title($args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo '<div class="wpfaqblock-grouped-wrap wpfaqblock-selected-wrap' . esc_attr($theme_class) . '">';
		echo '<div class="wpfaqblock-group wpfaqblock-group--selected">';

		$args['title'] = '';

		include $wpfaqblock_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable

		echo '</div><!-- /.wpfaqblock-group -->';
		echo '</div><!-- /.wpfaqblock-grouped-wrap -->';

		return $this->append_powered_by_html(ob_get_clean(), $args);
	}

	/**
	 * Render custom accordion icon images if both states are configured.
	 *
	 * @since 2.0.0
	 *
	 * @param array $args Shortcode args.
	 * @return string Custom icon HTML, or an empty string when defaults should be used.
	 */
	public function render_custom_icons(array $args): string
	{
		$closed_icon = !empty($args['closed_icon']) ? esc_url($args['closed_icon']) : '';
		$open_icon = !empty($args['open_icon']) ? esc_url($args['open_icon']) : '';

		if (empty($closed_icon) || empty($open_icon)) {
			return '';
		}

		return sprintf(
			'<span class="wpfaqblock-custom-icons" aria-hidden="true"><img class="wpfaqblock-custom-icon wpfaqblock-custom-icon--closed" src="%1$s" alt=""><img class="wpfaqblock-custom-icon wpfaqblock-custom-icon--open" src="%2$s" alt=""></span>',
			$closed_icon,
			$open_icon
		);
	}

	/**
	 * Determine whether the current FAQ item should render opened.
	 *
	 * @since 2.0.0
	 *
	 * @param array    $args  Shortcode args.
	 * @param WP_Query $query Query currently used by the template.
	 * @return bool
	 */
	public function is_item_open(array &$args, WP_Query $query): bool
	{
		$open = isset($args['open']) ? $args['open'] : '';
		$open_first = isset($args['open_first']) ? $args['open_first'] : '';

		if (1 === (int) $open) {
			return true;
		}

		if (1 !== (int) $open_first) {
			return false;
		}

		if (!empty($args['_wpfaqblock_first_opened'])) {
			return false;
		}

		if (0 !== (int) $query->current_post) {
			return false;
		}

		$args['_wpfaqblock_first_opened'] = true;

		return true;
	}

	/**
	 * Check whether optional FAQ schema output is enabled.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	private function is_schema_enabled(): bool
	{
		return class_exists('WPFAQBlock_Settings') && WPFAQBlock_Settings::is_schema_enabled();
	}

	/**
	 * Collect rendered FAQs for optional JSON-LD output.
	 *
	 * @since 2.0.0
	 *
	 * @param array $faqs FAQ posts that are about to be rendered.
	 * @return void
	 */
	private function collect_schema_faqs(array $faqs): void
	{
		if (!$this->is_schema_enabled()) {
			return;
		}

		foreach ($faqs as $faq) {
			if (!$faq instanceof WP_Post || 'publish' !== $faq->post_status) {
				continue;
			}

			self::$rendered_faqs[] = array(
				'id' => $faq->ID,
				'question' => $faq->post_title,
				'answer' => $faq->post_content,
			);
		}
	}

	/**
	 * Output JSON-LD FAQ Schema in footer.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function output_faq_schema(): void
	{
		if (!$this->is_schema_enabled() || empty(self::$rendered_faqs)) {
			return;
		}

		$schema = array(
			'@context' => 'https://schema.org',
			'@type' => 'FAQPage',
			'mainEntity' => array(),
		);

		$seen = array();

		foreach (self::$rendered_faqs as $faq) {
			$question = isset($faq['question']) ? trim(wp_strip_all_tags((string) $faq['question'])) : '';
			$answer = isset($faq['answer']) ? trim(wp_strip_all_tags((string) $faq['answer'])) : '';

			if ('' === $question || '' === $answer) {
				continue;
			}

			$dedupe_key = isset($faq['id']) ? 'post-' . absint($faq['id']) : md5($question . "\n" . $answer);
			if (isset($seen[$dedupe_key])) {
				continue;
			}

			$seen[$dedupe_key] = true;

			$schema['mainEntity'][] = array(
				'@type' => 'Question',
				'name' => $question,
				'acceptedAnswer' => array(
					'@type' => 'Answer',
					'text' => $answer,
				),
			);
		}

		if (empty($schema['mainEntity'])) {
			return;
		}

		$schema_json = wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
		if (!$schema_json) {
			return;
		}

		echo "\n<!-- WPFAQBlock FAQ Schema -->\n";
		echo '<script type="application/ld+json">' . $schema_json . "</script>\n";
	}
}
