<?php
/**
 * Multi-category links for system / library blocks.
 *
 * @package JOCBM
 */

namespace JOCBM\Services;

use JOCBM\Database\CategoryTaxonomy;
use JOCBM\Database\Schema;

defined( 'ABSPATH' ) || exit;

/**
 * Class BlockCategories
 */
final class BlockCategories {

	/**
	 * Normalize primary + optional list into unique resolved leaf slugs (primary first).
	 *
	 * @param string               $primary    Primary category slug.
	 * @param array<int, mixed>|null $categories Extra category slugs (may include primary).
	 * @return array{0: string, 1: array<int, string>} Primary + full list.
	 */
	public static function normalize( string $primary, $categories = null ): array {
		$primary = CategoryTaxonomy::resolve( $primary );
		$slugs   = array();

		if ( is_array( $categories ) ) {
			foreach ( $categories as $slug ) {
				$resolved = CategoryTaxonomy::resolve( (string) $slug );
				if ( '' !== $resolved ) {
					$slugs[] = $resolved;
				}
			}
		}

		if ( '' !== $primary ) {
			array_unshift( $slugs, $primary );
		}

		$unique = array();
		foreach ( $slugs as $slug ) {
			if ( '' === $slug || isset( $unique[ $slug ] ) ) {
				continue;
			}
			$unique[ $slug ] = true;
		}

		$list = array_keys( $unique );
		if ( ! $list ) {
			return array( '', array() );
		}

		if ( '' === $primary || ! isset( $unique[ $primary ] ) ) {
			$primary = $list[0];
		} else {
			// Keep primary first.
			$list = array_values(
				array_merge(
					array( $primary ),
					array_values( array_filter( $list, static fn( $s ) => $s !== $primary ) )
				)
			);
		}

		return array( $primary, $list );
	}

	/**
	 * Resolve categories from a template / pack meta array.
	 *
	 * @param array<string, mixed> $meta block.json / pack meta.
	 * @return array{0: string, 1: array<int, string>}
	 */
	public static function from_meta( array $meta ): array {
		$raw_list = $meta['categories'] ?? null;
		if ( ! is_array( $raw_list ) && isset( $meta['categorySlugs'] ) && is_array( $meta['categorySlugs'] ) ) {
			$raw_list = $meta['categorySlugs'];
		}

		return self::normalize(
			(string) ( $meta['category'] ?? '' ),
			is_array( $raw_list ) ? $raw_list : null
		);
	}

	/**
	 * Replace junction rows for a system block.
	 *
	 * @param int                  $system_block_id System block id.
	 * @param array<int, string>   $slugs           Category leaf slugs.
	 * @param string               $primary_slug    Primary slug (must be in $slugs when non-empty).
	 */
	public static function sync( int $system_block_id, array $slugs, string $primary_slug = '' ): void {
		global $wpdb;

		$system_block_id = (int) $system_block_id;
		if ( $system_block_id <= 0 ) {
			return;
		}

		$junction = Schema::table( 'system_block_categories' );
		$cats     = Schema::table( 'categories' );
		if ( '' === $junction || '' === $cats ) {
			return;
		}

		list( $primary_slug, $slugs ) = self::normalize( $primary_slug, $slugs );

		// Avoid printing DB errors into REST/HTML output (breaks JSON when WP_DEBUG_DISPLAY).
		$prev_suppress = $wpdb->suppress_errors( true );
		$prev_show     = $wpdb->show_errors( false );

		if ( ! $slugs ) {
			$wpdb->query(
				$wpdb->prepare(
					'DELETE FROM %i WHERE system_block_id = %d',
					$junction,
					$system_block_id
				)
			);
			$wpdb->show_errors( $prev_show );
			$wpdb->suppress_errors( $prev_suppress );
			return;
		}

		$by_slug = array();
		$rows    = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT id, slug FROM %i WHERE slug IN (' . implode( ',', array_fill( 0, count( $slugs ), '%s' ) ) . ')',
				$cats,
				...$slugs
			),
			ARRAY_A
		);
		foreach ( $rows ?: array() as $row ) {
			$by_slug[ (string) $row['slug'] ] = (int) $row['id'];
		}

		// Auto-create missing leaves so Pro / unknown slugs still link.
		foreach ( $slugs as $slug ) {
			if ( isset( $by_slug[ $slug ] ) ) {
				continue;
			}
			$wpdb->insert(
				$cats,
				array(
					'slug'        => $slug,
					'name'        => ucwords( str_replace( '-', ' ', $slug ) ),
					'description' => '',
					'parent_slug' => '',
					'is_pro'      => 0,
					'sort_order'  => 100,
				),
				array( '%s', '%s', '%s', '%s', '%d', '%d' )
			);
			$id = (int) $wpdb->insert_id;
			if ( $id ) {
				$by_slug[ $slug ] = $id;
			}
		}

		// Resolve slug → category_id (dedupe by id; primary wins is_primary).
		$wanted = array(); // category_id => is_primary
		foreach ( $slugs as $slug ) {
			$cat_id = $by_slug[ $slug ] ?? 0;
			if ( ! $cat_id ) {
				continue;
			}
			$is_primary = ( $slug === $primary_slug ) ? 1 : 0;
			if ( ! isset( $wanted[ $cat_id ] ) || $is_primary ) {
				$wanted[ $cat_id ] = $is_primary;
			}
		}

		$wpdb->query(
			$wpdb->prepare(
				'DELETE FROM %i WHERE system_block_id = %d',
				$junction,
				$system_block_id
			)
		);

		foreach ( $wanted as $cat_id => $is_primary ) {
			// REPLACE avoids duplicate-key fatals under concurrent seed/sync races.
			$wpdb->replace(
				$junction,
				array(
					'system_block_id' => $system_block_id,
					'category_id'     => (int) $cat_id,
					'is_primary'      => (int) $is_primary,
				),
				array( '%d', '%d', '%d' )
			);
		}

		$wpdb->show_errors( $prev_show );
		$wpdb->suppress_errors( $prev_suppress );
	}

	/**
	 * Category leaf slugs for one system block (primary first).
	 *
	 * @param int $system_block_id System block id.
	 * @return array<int, string>
	 */
	public static function slugs_for_block( int $system_block_id ): array {
		$map = self::slugs_for_blocks( array( $system_block_id ) );
		return $map[ $system_block_id ] ?? array();
	}

	/**
	 * Batch-load category leaf slugs keyed by system block id (primary first).
	 *
	 * @param array<int, int> $system_block_ids Ids.
	 * @return array<int, array<int, string>>
	 */
	public static function slugs_for_blocks( array $system_block_ids ): array {
		global $wpdb;

		$ids = array_values(
			array_unique(
				array_filter(
					array_map( 'intval', $system_block_ids )
				)
			)
		);
		if ( ! $ids ) {
			return array();
		}

		$junction = Schema::table( 'system_block_categories' );
		$cats     = Schema::table( 'categories' );
		if ( '' === $junction || '' === $cats ) {
			return array();
		}

		$rows = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT j.system_block_id, c.slug, j.is_primary
				FROM %i j
				INNER JOIN %i c ON c.id = j.category_id
				WHERE j.system_block_id IN (' . implode( ',', array_fill( 0, count( $ids ), '%d' ) ) . ')
				ORDER BY j.is_primary DESC, c.sort_order ASC, c.name ASC',
				$junction,
				$cats,
				...$ids
			),
			ARRAY_A
		);

		$out = array();
		foreach ( $ids as $id ) {
			$out[ $id ] = array();
		}
		foreach ( $rows ?: array() as $row ) {
			$id   = (int) $row['system_block_id'];
			$slug = sanitize_key( (string) ( $row['slug'] ?? '' ) );
			if ( '' === $slug || ! isset( $out[ $id ] ) ) {
				continue;
			}
			if ( in_array( $slug, $out[ $id ], true ) ) {
				continue;
			}
			$out[ $id ][] = $slug;
		}

		return $out;
	}

	/**
	 * Whether a block matches a library filter slug (leaf or parent).
	 *
	 * @param array<string, mixed> $block Formatted block.
	 * @param string               $filter Active category filter (not "all"/"featured").
	 * @param array<string, string> $parent_by_slug Optional slug → parent_slug map.
	 */
	public static function block_matches_filter( array $block, string $filter, array $parent_by_slug = array() ): bool {
		$filter = sanitize_key( $filter );
		if ( '' === $filter ) {
			return true;
		}

		$slugs = array();
		if ( ! empty( $block['categorySlugs'] ) && is_array( $block['categorySlugs'] ) ) {
			foreach ( $block['categorySlugs'] as $slug ) {
				$slug = sanitize_key( (string) $slug );
				if ( '' !== $slug ) {
					$slugs[] = $slug;
				}
			}
		}
		if ( ! $slugs ) {
			$primary = sanitize_key( (string) ( $block['categorySlug'] ?? $block['category'] ?? '' ) );
			if ( '' !== $primary ) {
				$slugs[] = $primary;
			}
		}

		foreach ( $slugs as $slug ) {
			if ( $slug === $filter ) {
				return true;
			}
			$parent = $parent_by_slug[ $slug ] ?? '';
			if ( '' === $parent && isset( $block['categoryParentSlug'] ) && $slug === sanitize_key( (string) ( $block['categorySlug'] ?? '' ) ) ) {
				$parent = sanitize_key( (string) $block['categoryParentSlug'] );
			}
			if ( $parent && $parent === $filter ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Parent slug map for all categories (slug → parent_slug).
	 *
	 * @return array<string, string>
	 */
	public static function parent_map(): array {
		global $wpdb;

		$cats = Schema::table( 'categories' );
		if ( '' === $cats ) {
			return array();
		}

		$rows = $wpdb->get_results(
			$wpdb->prepare( 'SELECT slug, parent_slug FROM %i', $cats ),
			ARRAY_A
		);
		$map  = array();
		foreach ( $rows ?: array() as $row ) {
			$map[ (string) $row['slug'] ] = (string) ( $row['parent_slug'] ?? '' );
		}
		return $map;
	}

	/**
	 * Delete junction rows for a system block.
	 *
	 * @param int $system_block_id Id.
	 */
	public static function delete_for_block( int $system_block_id ): void {
		global $wpdb;

		$junction = Schema::table( 'system_block_categories' );
		if ( '' === $junction || $system_block_id <= 0 ) {
			return;
		}

		$wpdb->delete(
			$junction,
			array( 'system_block_id' => $system_block_id ),
			array( '%d' )
		);
	}
}
