<?php
namespace Prodigy\Includes\Frontend\Mappers;

use Prodigy\Includes\Content\Prodigy_Catalog_Filters_Parser;
use Prodigy\Includes\Content\Prodigy_Request_Maker;
use Prodigy\Includes\Helpers\Prodigy_Page;
use Prodigy\Includes\Helpers\Prodigy_Url_Helper;
use Prodigy\Includes\Helpers\Prodigy_Widget_Options;
use Prodigy\includes\models\Prodigy_Product_Attributes;
use Prodigy\Includes\Prodigy;

defined( 'ABSPATH' ) || exit;

/**
 * Prodigy categories filter data mapper
 *
 * @version    3.1.2
 * @package    Prodigy
 * @subpackage Prodigy/includes
 */
class Prodigy_Categories_Filter_Data_Mapper extends Prodigy_Main_Data_Mapper {

	private $cat_current_id;
	private $request_attrs;
	private $show_level_depth = 0;

	const LEVEL = 1;

	/**
	 * @param array $atts
	 *
	 * @return array
	 */
	public static array $default_options = array(
		'show_product_count' => '',
		'show_hierarchy'     => 'true',
		'hide_empty'         => 'true',
		'max_depth'          => '10',
		'sorting'            => 'ID',
	);


	/**
	 * @param array $atts
	 *
	 * @return array
	 */
	public function set_default_options( array $atts ): array {
		$merged_options = $this->merge_with_default_options( $atts );

		return shortcode_atts( $merged_options, $merged_options, 'prodigy_categories_filter' );
	}

	/**
	 * @param array $atts
	 *
	 * @return array
	 */
	private function merge_with_default_options( array $atts ): array {
		foreach ( self::$default_options as $key => $default ) {
			if ( empty( $atts[ $key ] ) ) {
				$atts[ $key ] = $default;
			}
		}

		return $atts;
	}


	/**
	 * Retrieves default parameters for the Prodigy categories filter.
	 *
	 * @param array $options
	 *
	 * @return array
	 */
	public function get_default_parameters( array $options ): array {
		$categoriesResponse = $this->fetch_categories_response();
		$currentUriPath     = $this->get_current_uri_path();
		$activeCategory     = $this->get_active_category( $currentUriPath );
		$categories         = Prodigy_Catalog_Filters_Parser::get_categories( $categoriesResponse );

		$defaultOptions      = $this->set_default_options( $options );
		$shortcodeAttributes = shortcode_atts( $defaultOptions, $defaultOptions, 'prodigy_categories_filter' );

		if ( empty( $shortcodeAttributes ) || empty( $categories ) ) {
			return $this->get_fallback_parameters( $shortcodeAttributes );
		}

		$this->request_attrs     = $shortcodeAttributes;
		$this->cat_current_id    = $this->get_category_current_id( $activeCategory );
		$hierarchicalViewEnabled = Prodigy_Widget_Options::$bool_mapper[ $shortcodeAttributes['show_hierarchy'] ] ?? false;

		if ( $hierarchicalViewEnabled ) {
			return $this->build_hierarchical_parameters( $categories, $activeCategory );
		}

		return $this->build_flat_parameters( $categories, $activeCategory );
	}

	/**
	 * Fetches categories response from the API.
	 *
	 * @return array
	 */
	private function fetch_categories_response(): array {
		$query        = '';
		$requestMaker = Prodigy_Request_Maker::get_instance();
		return Prodigy_Page::prodigy_is_frontend()
			? $requestMaker->do_catalog_filters_request( $query )
			: $requestMaker->do_admin_catalog_filters_request( $query );
	}

	/**
	 * Parses the active category from the current URI.
	 *
	 * @param string $uriPath
	 *
	 * @return string
	 */
	private function get_active_category( string $uriPath = '' ): string {
		if ( ! $uriPath ) {
			return '';
		}

		$pathSegments = explode( '/', $uriPath );
		if ( isset( $pathSegments[2] ) ) {
			$term = get_term_by( 'slug', $pathSegments[2], Prodigy::get_prodigy_category_type() );

			return $term->name ?? '';
		}

		return '';
	}

	/**
	 * Retrieves the current URI path.
	 *
	 * @return string
	 */
	private function get_current_uri_path(): string {
		$uri = Prodigy_Url_Helper::prodigy_parse_request_uri();

		return $uri['path'] ?? '';
	}

	/**
	 * Gets the current category ID based on the active category.
	 *
	 * @param string $activeCategory
	 *
	 * @return int|false
	 */
	private function get_category_current_id( string $activeCategory ) {
		if ( ! $activeCategory ) {
			return false;
		}

		$localCategory = get_term_by( 'slug', $activeCategory, Prodigy::get_prodigy_category_type() );
		if ( isset( $localCategory->term_id ) ) {
			return (int) get_term_meta( (int) $localCategory->term_id, Prodigy::PRODIGY_HOSTED_CATEGORY_RELATION, true );
		}

		return false;
	}

	/**
	 * Builds parameters for hierarchical view of categories.
	 *
	 * @param array  $categories
	 * @param string $activeCategory
	 *
	 * @return array
	 */
	private function build_hierarchical_parameters( array $categories, string $activeCategory ): array {
		$parentCategories = $this->get_parent_categories( $categories );
		$parentCategories = $this->apply_widget_settings( $parentCategories );
		$subcategories    = $this->get_subcategories_by_parent_categories(
			$this->apply_widget_settings( $categories, true )
		);

		return array(
			'categories'      => $parentCategories ?? null,
			'subcategories'   => $subcategories ?? null,
			'request_count'   => $this->get_product_count_flag(),
			'active_category' => $activeCategory,
			'active_id'       => $this->cat_current_id,
			'parent_id'       => $this->get_root_category( $categories, (int) $this->cat_current_id ),
			'attr_shortcode'  => $this->request_attrs,
		);
	}

	/**
	 * Builds parameters for flat view of categories.
	 *
	 * @param array  $categories
	 * @param string $activeCategory
	 *
	 * @return array
	 */
	private function build_flat_parameters( array $categories, string $activeCategory ): array {
		$mergedCategories = $this->merge_categories( $categories );
		$mergedCategories = $this->apply_widget_settings( $mergedCategories );

		return array(
			'cat_parent_id'   => false,
			'categories'      => $mergedCategories ?? null,
			'request_count'   => $this->get_product_count_flag(),
			'active_category' => $activeCategory,
			'attr_shortcode'  => $this->request_attrs,
		);
	}

	/**
	 * Returns fallback parameters if no valid categories or attributes are found.
	 *
	 * @param array $attributes
	 *
	 * @return array
	 */
	private function get_fallback_parameters( array $attributes ): array {
		return array(
			'cat_parent_id'  => false,
			'categories'     => array(),
			'subcategories'  => array(),
			'request_count'  => Prodigy_Widget_Options::$bool_mapper[ $attributes['show_product_count'] ] ?? 'false',
			'attr_shortcode' => $attributes,
		);
	}

	/**
	 *
	 * @return string
	 */
	private function get_product_count_flag(): string {
		return Prodigy_Widget_Options::$bool_mapper[ $this->request_attrs['show_product_count'] ] ?? 'false';
	}


	/**
	 * @param array $categories
	 *
	 * @return array
	 */
	public function sort_by_position( array $categories ): array {
		usort(
			$categories,
			function ( $a, $b ) {
				return $a['position'] - $b['position'];
			}
		);

		return $categories;
	}

	/**
	 * @param array $categories
	 * @param int   $parent_id_category
	 * @param bool  $sub_menu_button
	 * @param int   $sub_level
	 *
	 * @return string
	 */
	protected function get_list_sub_categories( array $categories, int $parent_id_category, bool $sub_menu_button = false, int $sub_level = self::LEVEL ): string {
		++$this->show_level_depth;

		// Return early if we exceed the allowed depth
		if ( ! empty( $sub_level ) && ! empty( $this->request_attrs['max_depth'] ) && $this->request_attrs['max_depth'] < $this->show_level_depth ) {
			return '';
		}

		$sub_categories = $this->sort_by_position( $this->get_subcategory( $categories, $parent_id_category ) );
		if ( empty( $sub_categories ) ) {
			return '';
		}

		$collapse_class = '';
		$aria_expanded  = 'false';
		$id_collapse    = '';
		if ( $sub_menu_button ) {
			$is_current_category = $parent_id_category === $this->cat_current_id;
			$collapse_class      = 'collapse ' . ( $is_current_category ? '' : 'show' );
			$aria_expanded       = $is_current_category ? 'false' : 'true';
			$id_collapse         = 'id="collapse_' . $parent_id_category . '_1"';
		}

		$html_output = '';
		if ( $sub_menu_button ) {
			$html_output .= $this->generate_collapse_button( $parent_id_category, $sub_level, $aria_expanded );
		}

		$html_output .= '<ul class="prodigy-filter__card-list ' . $collapse_class . '" ' . $id_collapse . '>';
		foreach ( $sub_categories as $sub_category ) {
			$html_output .= $this->generate_category_list_item( $categories, $sub_category );
		}
		$html_output .= '</ul>';

		return $html_output;
	}

	/**
	 * Generate collapse button HTML.
	 *
	 * @param int    $parent_id_category
	 * @param int    $sub_level
	 * @param string $aria_expanded
	 *
	 * @return string
	 */
	private function generate_collapse_button( int $parent_id_category, int $sub_level, string $aria_expanded ): string {
		return '
        <button class="prodigy-filter__card-btn prodigy-icon-btn" 
           data-toggle="collapse"
           data-target="#collapse_' . $parent_id_category . '_' . $sub_level . '"
           aria-expanded="' . $aria_expanded . '"
           aria-controls="collapse_' . $parent_id_category . '_' . $sub_level . '"
		   aria-label="Expand">
           <i class="icon-arrow-down"></i>
        </button>
    ';
	}

	/**
	 * Generate a single category list item with subcategories if available.
	 *
	 * @param array $categories
	 * @param array $sub_category
	 *
	 * @return string
	 */
	private function generate_category_list_item( array $categories, array $sub_category ): string {
		$line_sub_menu      = '';
		$sub_sub_categories = $this->get_subcategory( $categories, $sub_category['id'] );

		if ( ! empty( $sub_sub_categories ) ) {
			$line_sub_menu = $this->get_list_sub_categories( $categories, $sub_category['id'], true, self::LEVEL );
		}

		$category_term = Prodigy_Product_Attributes::get_term_id_by_meta_key( Prodigy::PRODIGY_HOSTED_CATEGORY_RELATION, (int) $sub_category['id'] );
		$url           = get_category_link( $category_term->term_id ?? '' );

		$category_html  = '<li class="prodigy-filter__card-list-item" id="sub_cat_' . esc_attr( $sub_category['id'] ) . '">';
		$category_html .= '<a class="filter-category-id-js" href="' . $url . '" data-category="' . $sub_category['id'] . '">';
		$category_html .= esc_attr( $sub_category['name'] ) . '</a>';

		if ( ! empty( $this->request_attrs['show_product_count'] ) && Prodigy_Widget_Options::$bool_mapper[ $this->request_attrs['show_product_count'] ] ) {
			$category_html .= ' <span class="filter__card-count">(' . esc_attr( $sub_category['count'] ) . ')</span>';
		}

		if ( ! empty( $line_sub_menu ) ) {
			$category_html .= $line_sub_menu;
		}
		$category_html .= '</li>';

		return $category_html;
	}


	/**
	 * @param array  $categories
	 * @param int    $category_id
	 * @param string $type
	 *
	 * @return array|int
	 */
	public function get_subcategory( array $categories, int $category_id, string $type = 'children' ) {
		$subcategory = array();

		$category = $this->find_category_by_id( $categories, $category_id );
		if ( ! $category || empty( $category['relationships'][ $type ]['data'] ) ) {
			return $subcategory;
		}

		$subcategories = $category['relationships'][ $type ]['data'];

		foreach ( $subcategories as $key => $subcategoryData ) {
			if ( $type === 'children' ) {
				$childCategory = $this->find_category_by_id( $categories, (int) $subcategoryData['id'] );
				if ( $childCategory ) {
					$attributes          = (array) $childCategory['attributes'];
					$subcategory[ $key ] = array(
						'id'       => $childCategory['id'],
						'name'     => $attributes['name'] ?? '',
						'count'    => $attributes['products-count'] ?? 0,
						'position' => $attributes['position'] ?? 0,
					);
				}
			} else {
				return (int) $subcategories['id'];
			}
		}

		return $subcategory;
	}

	/**
	 * Find category by ID.
	 *
	 * @param array $categories
	 * @param int   $id
	 *
	 * @return array
	 */
	private function find_category_by_id( array $categories, int $id ): array {
		foreach ( $categories as $category ) {
			if ( (int) $category['id'] === $id ) {
				return $category;
			}
		}

		return array();
	}

	/**
	 * @param array $categories
	 *
	 * @return array
	 */
	public function get_subcategories_by_parent_categories( array $categories ): array {
		$list_sub_categories = array();
		if ( ! empty( $categories ) ) {
			foreach ( $categories as $category ) {
				$list_sub_categories[ $category['id'] ] = $this->get_list_sub_categories( $categories, $category['id'] );
			}
		}

		return $list_sub_categories;
	}

	/**
	 * @param array $categories
	 * @param bool  $isSubcategory
	 *
	 * @return array
	 */
	public function apply_widget_settings( array $categories, bool $isSubcategory = false ): array {
		if ( ! $this->should_display_categories() ) {
			return $categories;
		}

		$categories = $this->filter_categories( $categories, $isSubcategory );

		if ( ! $isSubcategory ) {
			$this->sort_categories( $categories );
		}

		return $categories;
	}

	/**
	 * @return bool
	 */
	private function should_display_categories(): bool {
		return Prodigy_Widget_Options::$bool_mapper[ $this->request_attrs['hide_empty'] ] ?? false;
	}

	/**
	 * @param array $categories
	 * @param bool  $isSubcategory
	 *
	 * @return array
	 */
	private function filter_categories( array $categories, bool $isSubcategory ): array {
		foreach ( $categories as $key => $category ) {
			$productCount = $isSubcategory ? ( $category['attributes']['products-count'] ?? null ) : ( $category['count'] ?? null );

			if ( empty( $productCount ) ) {
				unset( $categories[ $key ] );
			}
		}

		return array_values( $categories );
	}

	/**
	 * @param array $categories
	 *
	 * @return void
	 */
	private function sort_categories( array &$categories ) {
		if ( $this->request_attrs['sorting'] === 'ID' ) {
			usort( $categories, array( $this, 'compare_by_id' ) );
		} else {
			usort( $categories, array( $this, 'compare_by_name' ) );
		}
	}

	/**
	 * @param array $a
	 * @param array $b
	 *
	 * @return int
	 */
	private function compare_by_id( array $a, array $b ): int {
		return $a['id'] <=> $b['id'];
	}

	/**
	 * @param array $a
	 * @param array $b
	 *
	 * @return int
	 */
	private function compare_by_name( array $a, array $b ): int {
		return strcmp( $a['name'], $b['name'] );
	}

	/**
	 * @param array $categories
	 *
	 * @return array
	 */
	public function get_parent_categories( array $categories ): array {
		$parent_categories = array();
		if ( ! empty( $categories ) ) {
			foreach ( $categories as $key => $category ) {
				if ( empty( $category['relationships']['parent']['data'] ) ) {
					$parent_categories[ $key ]['id']       = $category['id'];
					$attributes                            = (array) $category['attributes'];
					$parent_categories[ $key ]['name']     = $attributes['name'];
					$parent_categories[ $key ]['count']    = $attributes['products-count'];
					$parent_categories[ $key ]['position'] = $attributes['position'];
				}
			}
		}

		return $parent_categories;
	}

	/**
	 * @param array $categories
	 *
	 * @return array
	 */
	public function merge_categories( array $categories ): array {
		$new_categories = array();
		if ( ! empty( $categories ) ) {
			foreach ( $categories as $key => $category ) {
				$new_categories[ $key ]['id']       = $category['id'];
				$attributes                         = (array) $category['attributes'];
				$new_categories[ $key ]['name']     = $attributes['name'];
				$new_categories[ $key ]['count']    = $attributes['products-count'];
				$new_categories[ $key ]['position'] = $attributes['position'];
			}
		}

		return $new_categories;
	}

	/**
	 * @param array $categories
	 * @param int   $child_category
	 *
	 * @return int
	 */
	public function get_root_category( array $categories, int $child_category ): int {
		$root_id = 0;
		if ( ! empty( $categories ) ) {
			foreach ( $categories as $category ) {
				if ( (int) $category['id'] === $child_category ) {
					$attributes = (array) $category['attributes'];
					$root_id    = $attributes['root-id'];
				}
			}
		}

		return (int) $root_id;
	}
}
