<?php
/**
 * Taxonomy Filter module for PublishPress Checklists Pro
 *
 * @package     PublishPress\ChecklistsPro\TaxonomyFilter
 * @author      PublishPress <help@publishpress.com>
 * @copyright   Copyright (c) 2024 PublishPress. All rights reserved.
 * @license     GPLv2 or later
 * @since       1.0.0
 */

namespace PublishPress\ChecklistsPro\TaxonomyFilter;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Class TaxonomyFilter
 *
 * Adds the ability to exclude checklist requirements based on taxonomy terms
 */
class TaxonomyFilter
{
    /**
     * Option name for storing taxonomy exclusions
     *
     * @var string
     */
    const OPTION_NAME = 'ppc_taxonomies_per_requirement';

    /**
     * Nonce action for AJAX requests
     *
     * @var string
     */
    const NONCE_ACTION = 'ppc_taxonomy_filter_nonce';

    /**
     * Settings option group name
     *
     * @var string
     */
    const SETTINGS_OPTION_NAME = 'publishpress_checklists_settings_options';

    /**
     * Cached exclusion options
     *
     * @var array|null
     */
    private $cachedOptions = null;

    /**
     * Constructor
     */
    public function __construct()
    {
        // Add column header and cell to global checklist table
        add_action('publishpress_checklists_tasks_list_th', [$this, 'addTaxonomiesColumnHeader']);
        add_action('publishpress_checklists_tasks_list_td', [$this, 'addTaxonomiesColumnCell'], 10, 2);

        // Filter requirements for post edit screen
        add_filter('publishpress_checklists_requirement_list', [$this, 'filterRequirementsByTaxonomy'], 25, 2);

        // AJAX for saving taxonomies
        add_action('wp_ajax_ppc_save_checklist_taxonomies', [$this, 'ajaxSaveTaxonomies']);

        // Enqueue assets
        add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']);
    }

    /**
     * Get enabled taxonomies from settings
     *
     * @return array Array of enabled taxonomy slugs
     */
    private function getEnabledTaxonomies()
    {
        $options = (array) get_option(self::SETTINGS_OPTION_NAME, []);
        $enabledTaxonomies = isset($options['taxonomy_filter_taxonomies']) ? $options['taxonomy_filter_taxonomies'] : [];
        
        // If no taxonomies selected, return empty array (don't show column)
        if (empty($enabledTaxonomies)) {
            return [];
        }
        
        return (array) $enabledTaxonomies;
    }

    /**
     * Get all public taxonomies
     *
     * @return array Array of taxonomy objects keyed by slug
     */
    private function getAllTaxonomies()
    {
        $taxonomies = get_taxonomies(['public' => true], 'objects');
        
        // Filter out post_format as it's usually not user-facing
        unset($taxonomies['post_format']);
        
        return $taxonomies;
    }

    /**
     * Get all terms from enabled taxonomies for a specific post type
     *
     * @param string $post_type The post type
     * @return array Array of terms grouped by taxonomy
     */
    private function getTermsForPostType($post_type)
    {
        $enabledTaxonomies = $this->getEnabledTaxonomies();
        $postTypeTaxonomies = get_object_taxonomies($post_type, 'objects');
        $terms = [];

        foreach ($postTypeTaxonomies as $taxonomy_slug => $taxonomy_obj) {
            // Only include if taxonomy is enabled in settings
            if (!in_array($taxonomy_slug, $enabledTaxonomies)) {
                continue;
            }

            $taxonomy_terms = get_terms([
                'taxonomy' => $taxonomy_slug,
                'hide_empty' => false,
            ]);

            if (!is_wp_error($taxonomy_terms) && !empty($taxonomy_terms)) {
                $terms[$taxonomy_slug] = [
                    'label' => $taxonomy_obj->label,
                    'terms' => $taxonomy_terms,
                ];
            }
        }

        return $terms;
    }

    /**
     * Add the column header for Taxonomies
     */
    public function addTaxonomiesColumnHeader()
    {
        echo '<th>' . esc_html__('Exclude Taxonomies', 'publishpress-checklists-pro') . '</th>';
    }

    /**
     * Add the column cell with Select2 for taxonomy terms
     *
     * @param object $requirement The requirement object
     * @param string $post_type   The post type
     */
    public function addTaxonomiesColumnCell($requirement, $post_type)
    {
        // Get requirement ID primarily from object's name property
        $requirement_id = '';
        if (is_object($requirement) && isset($requirement->name)) {
            $requirement_id = esc_attr($requirement->name);
        }

        $termsGrouped = $this->getTermsForPostType($post_type);
        $excluded = $this->getExcludedTerms($requirement_id, $post_type);

        echo '<td>';
        echo '<select class="ppc-taxonomies-select" name="ppc_taxonomies[' . esc_attr($requirement_id) . '][]" multiple="multiple" data-requirement="' . esc_attr($requirement_id) . '" data-post_type="' . esc_attr($post_type) . '" data-placeholder="' . esc_attr__('Select terms to exclude', 'publishpress-checklists-pro') . '">';

        foreach ($termsGrouped as $taxonomy_slug => $taxonomy_data) {
            echo '<optgroup label="' . esc_attr($taxonomy_data['label']) . '">';
            
            foreach ($taxonomy_data['terms'] as $term) {
                $term_value = $taxonomy_slug . ':' . $term->term_id;
                $is_selected = in_array($term_value, $excluded) ? 'selected' : '';
                echo '<option value="' . esc_attr($term_value) . '" ' . $is_selected . '>' . esc_html($term->name) . '</option>';
            }
            
            echo '</optgroup>';
        }

        echo '</select>';
        echo '</td>';
    }

    /**
     * Get the cached exclusion options
     *
     * @return array
     */
    private function getOptions()
    {
        if ($this->cachedOptions === null) {
            $this->cachedOptions = get_option(self::OPTION_NAME, []);
        }
        return $this->cachedOptions;
    }

    /**
     * Get excluded terms for a requirement
     *
     * @param string $requirement_id The requirement ID
     * @param string $post_type      The post type
     *
     * @return array Array of excluded term values (taxonomy:term_id format)
     */
    private function getExcludedTerms($requirement_id, $post_type)
    {
        $option = $this->getOptions();
        return $option[$post_type][$requirement_id] ?? [];
    }

    /**
     * AJAX handler to save selected taxonomy terms
     */
    public function ajaxSaveTaxonomies()
    {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], self::NONCE_ACTION)) {
            wp_send_json_error('Invalid nonce');
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error('Unauthorized');
        }

        $requirement_id = sanitize_text_field($_POST['requirement_id'] ?? '');
        $terms = isset($_POST['terms']) && is_array($_POST['terms'])
            ? array_map('sanitize_text_field', $_POST['terms'])
            : [];
        $post_type = sanitize_text_field($_POST['post_type'] ?? 'post');

        $option = get_option(self::OPTION_NAME, []);
        $option[$post_type][$requirement_id] = $terms;
        update_option(self::OPTION_NAME, $option);

        // Clear cache
        $this->cachedOptions = null;

        wp_send_json_success();
    }

    /**
     * Filter requirements by taxonomy terms (for post edit screen)
     *
     * @param array        $requirements List of requirements
     * @param int|WP_Post  $post         Post ID or post object
     *
     * @return array Filtered requirements
     */
    public function filterRequirementsByTaxonomy($requirements, $post)
    {
        
        $current_screen = function_exists('get_current_screen') ? get_current_screen() : null;
        if ($current_screen && method_exists($current_screen, 'is_block_editor') && $current_screen->is_block_editor()) {
            return $requirements;
        }

        $post_obj = null;

        if (is_numeric($post)) {
            $post_obj = get_post($post);
        } elseif (is_object($post) && isset($post->ID)) {
            $post_obj = $post;
        }

        if (!$post_obj) {
            return $requirements;
        }

        $current_post_type = $post_obj->post_type;
        $option = $this->getOptions();
        $filtered_requirements = [];

        // Get all terms assigned to this post
        $post_terms = $this->getPostTerms($post_obj->ID, $current_post_type);

        // Process each requirement
        foreach ($requirements as $req_id => $req_data) {
            // If requirement ID is empty, keep it in the list
            if (empty($req_id)) {
                $filtered_requirements[$req_id] = $req_data;
                continue;
            }

            // Get excluded terms for this requirement
            $excluded_terms = [];
            if (isset($option[$current_post_type][$req_id])) {
                $excluded_terms = $option[$current_post_type][$req_id];
            }

            // Check if any of the post's terms are in the excluded list
            $is_excluded = $this->hasExcludedTerm($post_terms, $excluded_terms);

            // Add to filtered list if not excluded
            if (!$is_excluded) {
                $filtered_requirements[$req_id] = $req_data;
            }
        }

        return $filtered_requirements;
    }

    /**
     * Get all terms assigned to a post
     *
     * @param int    $post_id   The post ID
     * @param string $post_type The post type
     *
     * @return array Array of term values (taxonomy:term_id format)
     */
    private function getPostTerms($post_id, $post_type)
    {
        $enabledTaxonomies = $this->getEnabledTaxonomies();
        $postTypeTaxonomies = get_object_taxonomies($post_type);
        $post_terms = [];

        foreach ($postTypeTaxonomies as $taxonomy_slug) {
            // Only check enabled taxonomies
            if (!in_array($taxonomy_slug, $enabledTaxonomies)) {
                continue;
            }

            $terms = wp_get_post_terms($post_id, $taxonomy_slug, ['fields' => 'ids']);
            
            if (!is_wp_error($terms)) {
                foreach ($terms as $term_id) {
                    $post_terms[] = $taxonomy_slug . ':' . $term_id;
                }
            }
        }

        return $post_terms;
    }

    /**
     * Check if any post term is in the excluded list
     *
     * @param array $post_terms     Terms assigned to the post
     * @param array $excluded_terms Terms that should exclude this requirement
     *
     * @return bool True if any term matches (should exclude)
     */
    private function hasExcludedTerm($post_terms, $excluded_terms)
    {
        if (empty($excluded_terms)) {
            return false;
        }

        foreach ($post_terms as $term) {
            if (in_array($term, $excluded_terms, true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Enqueue JS and CSS assets for the taxonomies column
     *
     * @param string $hook The current admin page
     */
    public function enqueueAssets($hook)
    {
        $base_url = plugin_dir_url(__FILE__);

        if ($hook === 'toplevel_page_ppch-checklists') {
            wp_enqueue_script(
                'ppc-taxonomies-column',
                $base_url . 'assets/js/taxonomies-column.js',
                ['jquery'],
                PPCHPRO_VERSION,
                true
            );

            wp_localize_script(
                'ppc-taxonomies-column',
                'ppcTaxonomyFilterData',
                [
                    'nonce' => wp_create_nonce(self::NONCE_ACTION)
                ]
            );
        }

        // Gutenberg specific script
        $current_screen = get_current_screen();
        if ($current_screen && $current_screen->is_block_editor()) {
            wp_enqueue_script(
                'ppc-gutenberg-taxonomy-visibility',
                $base_url . 'assets/js/gutenberg-taxonomy-visibility.js',
                ['wp-data', 'wp-dom-ready', 'jquery', 'wp-edit-post'],
                PPCHPRO_VERSION,
                true
            );

            $post_type = $current_screen->post_type;
            $excluded_option = $this->getOptions();

            // Get all requirements for the current post type
            $all_req_objects = [];
            $all_req_ids = [];

            if (class_exists('\PublishPress\Checklists\Core\Requirements')) {
                $requirements_handler = \PublishPress\Checklists\Core\Requirements::instance();
                if (method_exists($requirements_handler, 'get_requirements_for_post_type')) {
                    $all_req_objects = $requirements_handler->get_requirements_for_post_type($post_type, 'edit');
                }
            }

            // Extract requirement IDs
            if (is_array($all_req_objects)) {
                $all_req_ids = array_keys($all_req_objects);
            }

            // Get enabled taxonomies for this post type
            $enabledTaxonomies = $this->getEnabledTaxonomies();
            $postTypeTaxonomies = get_object_taxonomies($post_type);
            $activeTaxonomies = array_intersect($enabledTaxonomies, $postTypeTaxonomies);

            // Build taxonomy REST base mapping for JavaScript
            // WordPress Gutenberg uses REST base names (e.g., 'categories' instead of 'category')
            $taxonomyRestBases = [];
            foreach ($activeTaxonomies as $taxonomy_slug) {
                $taxonomy_obj = get_taxonomy($taxonomy_slug);
                if ($taxonomy_obj && !empty($taxonomy_obj->rest_base)) {
                    $taxonomyRestBases[$taxonomy_slug] = $taxonomy_obj->rest_base;
                } else {
                    // Default REST base mappings
                    if ($taxonomy_slug === 'category') {
                        $taxonomyRestBases[$taxonomy_slug] = 'categories';
                    } elseif ($taxonomy_slug === 'post_tag') {
                        $taxonomyRestBases[$taxonomy_slug] = 'tags';
                    } else {
                        $taxonomyRestBases[$taxonomy_slug] = $taxonomy_slug;
                    }
                }
            }

            // Data to pass to JavaScript
            $data_to_localize = [
                'exclusionOptions' => isset($excluded_option[$post_type]) ? $excluded_option[$post_type] : [],
                'enabledTaxonomies' => array_values($activeTaxonomies),
                'taxonomyRestBases' => $taxonomyRestBases,
                'postType' => $post_type,
            ];

            wp_localize_script(
                'ppc-gutenberg-taxonomy-visibility',
                'ppcTaxonomyFilterGutenbergData',
                $data_to_localize
            );
        }
    }
}
