<?php
/**
 * Class VK Blocks PostList
 *
 * @package vk_blocks
 */

use VektorInc\VK_Component\VK_Component_Posts;

/**
 * VK Blocks PostList
 */
class Vk_Blocks_PostList {

	/**
	 * Return html to display latest post list.
	 *
	 * @param array  $attributes attributes.
	 * @param object $wp_query wp_query.
	 * @param array  $options_loop options loop.
	 *
	 * @return string
	 */
	public static function render_post_list( $attributes, $wp_query, $options_loop ) {

		if ( ! empty( $attributes['className'] ) ) {
			$options_loop['class_loop_outer'] .= ' ' . esc_attr( $attributes['className'] );
		}

		if ( ! isset( $wp_query ) || false === $wp_query || 'false' === $wp_query || empty( $wp_query->posts ) ) {
			return wp_kses_post( self::get_render_no_post( $wp_query, $attributes ) );
		}
		$options = array(
			'layout'                     => esc_html( $attributes['layout'] ),
			'slug'                       => '',
			'display_image'              => esc_html( $attributes['display_image'] ),
			'display_image_overlay_term' => esc_html( $attributes['display_image_overlay_term'] ),
			'display_excerpt'            => esc_html( $attributes['display_excerpt'] ),
			'display_author'             => esc_html( $attributes['display_author'] ),
			'display_date'               => esc_html( $attributes['display_date'] ),
			'display_modified'           => esc_html( $attributes['display_modified'] ),
			'display_new'                => esc_html( $attributes['display_new'] ),
			'display_taxonomies'         => esc_html( $attributes['display_taxonomies'] ),
			'display_btn'                => esc_html( $attributes['display_btn'] ),
			'image_default_url'          => esc_url( VK_BLOCKS_URL . 'images/no-image.png' ),
			'overlay'                    => false,
			'new_text'                   => esc_html( $attributes['new_text'] ),
			'new_date'                   => esc_html( $attributes['new_date'] ),
			'btn_text'                   => esc_html( $attributes['btn_text'] ),
			'btn_align'                  => esc_html( $attributes['btn_align'] ),
			'col_xs'                     => esc_html( $attributes['col_xs'] ),
			'col_sm'                     => esc_html( $attributes['col_sm'] ),
			'col_md'                     => esc_html( $attributes['col_md'] ),
			'col_lg'                     => esc_html( $attributes['col_lg'] ),
			'col_xl'                     => esc_html( $attributes['col_xl'] ),
			'col_xxl'                    => esc_html( $attributes['col_xxl'] ),
			'class_outer'                => '',
			'class_title'                => '',
			'body_prepend'               => '',
			'body_append'                => '',
			'vkb_hidden'                 => isset( $attributes['vkb_hidden'] ) ? esc_attr( $attributes['vkb_hidden'] ) : '',
			'vkb_hidden_xxl'             => isset( $attributes['vkb_hidden_xxl'] ) ? esc_attr( $attributes['vkb_hidden_xxl'] ) : '',
			'vkb_hidden_xl'              => isset( $attributes['vkb_hidden_xl'] ) ? esc_attr( $attributes['vkb_hidden_xl'] ) : '',
			'vkb_hidden_xl_v2'           => isset( $attributes['vkb_hidden_xl_v2'] ) ? esc_attr( $attributes['vkb_hidden_xl_v2'] ) : '',
			'vkb_hidden_lg'              => isset( $attributes['vkb_hidden_lg'] ) ? esc_attr( $attributes['vkb_hidden_lg'] ) : '',
			'vkb_hidden_md'              => isset( $attributes['vkb_hidden_md'] ) ? esc_attr( $attributes['vkb_hidden_md'] ) : '',
			'vkb_hidden_sm'              => isset( $attributes['vkb_hidden_sm'] ) ? esc_attr( $attributes['vkb_hidden_sm'] ) : '',
			'vkb_hidden_xs'              => isset( $attributes['vkb_hidden_xs'] ) ? esc_attr( $attributes['vkb_hidden_xs'] ) : '',
			'marginTop'                  => isset( $attributes['marginTop'] ) ? esc_attr( $attributes['marginTop'] ) : '',
			'marginBottom'               => isset( $attributes['marginBottom'] ) ? esc_attr( $attributes['marginBottom'] ) : '',
		);

		$elm = VK_Component_Posts::get_loop( $wp_query, $options, $options_loop );

		wp_reset_postdata();

		return $elm;
	}

	/**
	 * Is Array Exist
	 *
	 * @param array $arr array.
	 *
	 * @return bool
	 */
	private function is_array_exist( $arr ) {
		if ( ! $arr ) {
			return false;
		}
		return true;
	}

	/**
	 * Format Terms
	 *
	 * @param array  $tax_query_relation : AND or OR.
	 * @param array  $is_checked_terms : checked terms. チェックされたタームidの配列.
	 * @param string $post_type Post type.
	 *
	 * @return array $return : tax_query
	 */
	private static function format_terms( $tax_query_relation, $is_checked_terms, $post_type ) {
		$return = array(
			'relation' => $tax_query_relation,
		);

		foreach ( $is_checked_terms as $term_id ) {
			$term = get_term( $term_id );
			if ( ! $term || is_wp_error( $term ) ) {
				continue; // Skip invalid or non-existent terms
			}
			$post_type_taxonomies = get_object_taxonomies( $post_type );

			if ( in_array( $term->taxonomy, $post_type_taxonomies, true ) ) {
				$new_array = array(
					'taxonomy' => $term->taxonomy,
					'field'    => 'term_id',
					'terms'    => $term_id,
				);
				$return[]  = $new_array;
			}
		}
		return $return;
	}

	/**
	 * Format Exclude Terms
	 *
	 * 除外するタームを tax_query の NOT IN 句に変換する。
	 * 同一タクソノミーのタームはまとめて 1 つの NOT IN 句にし、各句は AND で連結する。
	 * Convert terms to be excluded into NOT IN clauses of tax_query.
	 * Terms belonging to the same taxonomy are grouped into a single NOT IN clause, and each clause is joined with AND.
	 *
	 * @param array        $exclusion_terms : term ids to exclude. 除外するタームidの配列.
	 * @param array|string $post_type Post type(s).
	 *
	 * @return array $return : NOT IN clauses for tax_query.
	 */
	private static function format_exclude_terms( $exclusion_terms, $post_type ) {
		// タクソノミーごとに除外タームをまとめる / Group exclusion terms by taxonomy.
		$grouped_terms = array();

		// 投稿タイプのタクソノミー一覧はループ内で不変なので一度だけ取得する。
		// The post type's taxonomies are invariant within the loop, so fetch them once.
		$post_type_taxonomies = get_object_taxonomies( $post_type );

		foreach ( $exclusion_terms as $term_id ) {
			$term = get_term( $term_id );
			if ( ! $term || is_wp_error( $term ) ) {
				continue; // 不正なタームや存在しないタームはスキップ / Skip invalid or non-existent terms.
			}

			// 対象投稿タイプに属するタクソノミーのタームのみ対象 / Only terms whose taxonomy belongs to the target post type.
			if ( in_array( $term->taxonomy, $post_type_taxonomies, true ) ) {
				$grouped_terms[ $term->taxonomy ][] = $term_id;
			}
		}

		// タクソノミーごとに NOT IN 句を生成 / Build a NOT IN clause per taxonomy.
		$return = array();
		foreach ( $grouped_terms as $taxonomy => $term_ids ) {
			$return[] = array(
				'taxonomy' => $taxonomy,
				'field'    => 'term_id',
				'terms'    => $term_ids,
				'operator' => 'NOT IN',
			);
		}
		return $return;
	}

	/**
	 * Build Tax Query
	 *
	 * 絞り込み（include）と除外（exclude）の tax_query を組み立てて返す。
	 * 両方指定された場合は AND で連結する。
	 * Build the tax_query from the include and exclude conditions.
	 * When both are specified, they are joined with AND.
	 *
	 * @param string       $tax_query_relation : AND or OR ( relation for include terms ).
	 * @param array        $is_checked_terms : term ids to include. 絞り込むタームidの配列.
	 * @param array        $exclusion_terms : term ids to exclude. 除外するタームidの配列.
	 * @param array|string $post_type Post type(s).
	 *
	 * @return array $tax_query.
	 */
	private static function build_tax_query( $tax_query_relation, $is_checked_terms, $exclusion_terms, $post_type ) {
		// 絞り込み句を生成（relation のみで実句が無い場合は空扱い） / Build include clauses ( treated as empty when only relation exists ).
		$include_query   = ! empty( $is_checked_terms ) ? self::format_terms( $tax_query_relation, $is_checked_terms, $post_type ) : array();
		$include_clauses = array();
		foreach ( $include_query as $key => $value ) {
			if ( 'relation' !== $key ) {
				$include_clauses[] = $value;
			}
		}

		// 除外句を生成 / Build exclude clauses.
		$exclude_clauses = ! empty( $exclusion_terms ) ? self::format_exclude_terms( $exclusion_terms, $post_type ) : array();

		$has_include = ! empty( $include_clauses );
		$has_exclude = ! empty( $exclude_clauses );

		// どちらも無い場合は空の tax_query / Empty tax_query when neither is set.
		if ( ! $has_include && ! $has_exclude ) {
			return array();
		}

		// 絞り込みのみの場合は従来通りの構造を返す / Return the conventional structure when only include is set.
		if ( $has_include && ! $has_exclude ) {
			return $include_query;
		}

		// 除外のみの場合は除外句を AND で連結 / Join exclude clauses with AND when only exclude is set.
		if ( ! $has_include && $has_exclude ) {
			return array_merge( array( 'relation' => 'AND' ), $exclude_clauses );
		}

		// 絞り込み・除外の両方がある場合は、絞り込みサブクエリと除外句を AND で連結 / Join the include sub-query and exclude clauses with AND when both are set.
		return array_merge(
			array(
				'relation' => 'AND',
				$include_query,
			),
			$exclude_clauses
		);
	}

	/**
	 * Get Loop Query
	 *
	 * @param array $attributes attributes.
	 *
	 * @return WP_Query|bool WP_Query object or false.
	 */
	public static function get_loop_query( $attributes ) {

		$is_checked_post_type = json_decode( $attributes['isCheckedPostType'], true );
		$is_checked_terms     = isset( $attributes['isCheckedTerms'] ) ? json_decode( $attributes['isCheckedTerms'], true ) : array();
		$tax_query_relation   = isset( $attributes['taxQueryRelation'] ) ? $attributes['taxQueryRelation'] : 'OR';

		// 除外設定（タクソノミー・投稿ID）を取得 / Get exclusion settings ( taxonomy / post id ).
		$exclusion_terms = isset( $attributes['exclusionTerms'] ) ? json_decode( $attributes['exclusionTerms'], true ) : array();
		$exclusion_posts = isset( $attributes['exclusionPosts'] ) ? json_decode( $attributes['exclusionPosts'], true ) : array();
		// json_decode が配列以外（null 等）を返した場合に備えて配列へ正規化 / Normalize to array in case json_decode returns a non-array ( e.g. null ).
		$exclusion_terms = is_array( $exclusion_terms ) ? $exclusion_terms : array();
		$exclusion_posts = is_array( $exclusion_posts ) ? $exclusion_posts : array();

		if ( empty( $is_checked_post_type ) ) {
			return false;
		}

		$post__not_in = array();
		if ( ! empty( $attributes['selfIgnore'] ) ) {
			$post__not_in = array( get_the_ID() );
		}

		// 除外する投稿IDを post__not_in にマージ / Merge excluded post ids into post__not_in.
		if ( ! empty( $exclusion_posts ) ) {
			$post__not_in = array_merge( $post__not_in, array_map( 'intval', $exclusion_posts ) );
		}

		$offset         = isset( $attributes['offset'] ) ? intval( $attributes['offset'] ) : 0;
		$posts_per_page = isset( $attributes['numberPosts'] ) ? intval( $attributes['numberPosts'] ) : 0;

		$date_query = array();
		if ( ! empty( $attributes['targetPeriod'] ) ) {
			switch ( $attributes['targetPeriod'] ) {
				case 'from-today':
					$date_query = array(
						array(
							'column'    => 'post_date_gmt',
							'after'     => gmdate( 'Y-m-d' ),
							'inclusive' => true,
						),
					);
					break;
				case 'from-now':
					$date_query = array(
						array(
							'column'    => 'post_date_gmt',
							'after'     => gmdate( 'Y-m-d H:i:s' ),
							'inclusive' => true,
						),
					);
					break;
				case 'from-tomorrow':
					$date_query = array(
						array(
							'column'    => 'post_date_gmt',
							'after'     => gmdate( 'Y-m-d', strtotime( '+1 day' ) ),
							'inclusive' => true,
						),
					);
					break;
				case 'within-year':
					$date_query = array(
						array(
							'column'    => 'post_date_gmt',
							'after'     => gmdate( 'Y-m-d', strtotime( '-1 year' ) ),
							'inclusive' => true,
						),
					);
					break;
			}
		}

		global $wp_query;

		// とりあえず１を入れつつ2ページ目の情報があったら上書き
		$paged = 1;
		if ( ! empty( $attributes['pagedlock'] ) ) {
			$paged = 1;
		} elseif ( is_singular() && isset( $wp_query->query_vars['page'] ) ) {
			$paged  = $wp_query->query_vars['page'] ? $wp_query->query_vars['page'] : 1;
			$offset = $offset + $posts_per_page * ( $paged - 1 );
		} elseif ( isset( $wp_query->query_vars['paged'] ) ) {
			$paged  = $wp_query->query_vars['paged'] ? $wp_query->query_vars['paged'] : 1;
			$offset = $offset + $posts_per_page * ( $paged - 1 );
		}

		$args = array(
			'post_type'              => $is_checked_post_type,
			// 絞り込み・除外を合わせた tax_query を構築 / Build the tax_query combining include and exclude conditions.
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			'tax_query'              => self::build_tax_query( $tax_query_relation, $is_checked_terms, $exclusion_terms, $is_checked_post_type ),
			'paged'                  => $paged,
			// 0で全件取得
			'posts_per_page'         => $posts_per_page,
			'order'                  => $attributes['order'],
			'orderby'                => $attributes['orderby'],
			'post__not_in'           => array_map( 'intval', $post__not_in ),
			'date_query'             => $date_query,
			'update_post_meta_cache' => false,
			'no_found_rows'          => true,
		);

		if ( ! is_null( $offset ) ) {
			$args['offset'] = $offset;
		}

		if ( ! empty( $date_query ) ) {
			$args['date_query'] = $date_query;
		}

		$sticky_posts_mode = isset( $attributes['stickyPosts'] ) ? $attributes['stickyPosts'] : 'include';

		switch ( $sticky_posts_mode ) {
			case 'include':
				$args['ignore_sticky_posts'] = false;
				break;

			case 'exclude':
				$args['post__not_in'] = array_merge( isset( $args['post__not_in'] ) ? $args['post__not_in'] : array(), get_option( 'sticky_posts' ) );
				break;

			case 'only':
				$sticky_posts           = get_option( 'sticky_posts' );
				$args['post__in']       = ! empty( $sticky_posts ) ? $sticky_posts : array( 0 );
				$args['posts_per_page'] = count( $args['post__in'] );
				$args['orderby']        = $attributes['orderby'];
				break;
		}

		$args = apply_filters( 'vk_blocks_post_list_query_args', $args, $attributes );

		return new WP_Query( $args );
	}

	/**
	 * Get Loop Query Child
	 *
	 * @param array $attributes attributes.
	 *
	 * @return WP_Query|bool WP_Query object or false.
	 */
	public static function get_loop_query_child( $attributes ) {

		// ParentIdを指定.
		if ( isset( $attributes['selectId'] ) && 'false' !== $attributes['selectId'] ) {
			$select_id = ( $attributes['selectId'] > 0 ) ? $attributes['selectId'] : get_the_ID();

			$post__not_in = array();
			if ( ! empty( $attributes['selfIgnore'] ) ) {
				$post__not_in = array( get_the_ID() );
			}

			$offset = '';
			if ( ! empty( $attributes['offset'] ) ) {
				$offset = intval( $attributes['offset'] );
			}

			$args = array(
				'post_type'      => 'page',
				'paged'          => 0,
				// 0で全件取得
				'posts_per_page' => -1,
				'order'          => 'ASC',
				'orderby'        => 'menu_order',
				'post_parent'    => intval( $select_id ),
				'offset'         => $offset,
				'post__not_in'   => $post__not_in,
			);
			return new WP_Query( $args );
		} else {
			return false;
		}
	}

	/**
	 * Render No Posts
	 *
	 * @param object $wp_query @since 1.27.0.
	 * @param array  $attributes attributes.
	 * @return string
	 */
	public static function get_render_no_post( $wp_query = null, $attributes = array() ) {
		$name = '';
		if ( ! empty( $wp_query->query['post_type'] ) ) {
			if ( is_array( $wp_query->query['post_type'] ) ) {
				$post_type = $wp_query->query['post_type'][0];
			} else {
				$post_type = $wp_query->query['post_type'];
			}
			$post_type_object = get_post_type_object( $post_type );
			if ( ! empty( $post_type_object->label ) ) {
				$name = $post_type_object->label;
			}
		}

		if ( ! $name ) {
			$name = __( 'Post', 'vk-blocks' );
		}

		$class_name = '';
		if ( ! empty( $attributes['className'] ) ) {
			$class_name = ' ' . esc_attr( $attributes['className'] );
		}

		/* translators: %s: 投稿タイプ名 */
		$html = '<div class="alert alert-warning text-center' . $class_name . '">' . sprintf( __( 'There are no %s.', 'vk-blocks' ), strtolower( $name ) ) . '</div>';
		return apply_filters( 'vk_blocks_post_list_render_no_post', $html, $wp_query );
	}
}
