<?php


class Brizy_Admin_Rules_Api extends Brizy_Admin_AbstractApi
{

    const nonce = Brizy_Editor_API::nonce;
    const CREATE_RULES_ACTION = '_add_rules';
    const CREATE_RULE_ACTION = '_add_rule';
    const UPDATE_RULES_ACTION = '_update_rules';
    const DELETE_RULE_ACTION = '_delete_rule';
    const LIST_RULE_ACTION = '_list_rules';
    const VALIDATE_RULE = '_validate_rule';
    const RULE_GROUP_LIST = '_rule_group_list';
    const RULE_POSTS_GROUP_LIST = '_rule_posts_group_list';
    const RULE_ARCHIVE_GROUP_LIST = '_rule_archive_group_list';
    const RULE_TEMPLATE_GROUP_LIST = '_rule_template_group_list';


    /**
     * @var Brizy_Admin_Rules_Manager
     */
    private $manager;


    /**
     * Brizy_Admin_Rules_Api constructor.
     *
     * @param Brizy_Admin_Rules_Manager $manager
     */
    public function __construct($manager)
    {
        $this->manager = $manager;

        parent::__construct();
    }

    /**
     * @return Brizy_Admin_Rules_Api
     */
    public static function _init()
    {
        static $instance;

        if (!$instance) {
            $instance = new self(new Brizy_Admin_Rules_Manager());
        }

        return $instance;
    }

    protected function getRequestNonce()
    {
        return $this->param('hash');
    }

    protected function initializeApiActions()
    {
        $pref = 'wp_ajax_'.Brizy_Editor::prefix();

        add_action($pref.self::CREATE_RULE_ACTION, array($this, 'actionCreateRule'));
        add_action($pref.self::CREATE_RULES_ACTION, array($this, 'actionCreateRules'));
        add_action($pref.self::UPDATE_RULES_ACTION, array($this, 'actionUpdateRules'));
        add_action($pref.self::DELETE_RULE_ACTION, array($this, 'actionDeleteRule'));
        add_action($pref.self::LIST_RULE_ACTION, array($this, 'actionGetRuleList'));
        add_action($pref.self::VALIDATE_RULE, array($this, 'actionValidateRule'));
        add_action($pref.self::RULE_GROUP_LIST, array($this, 'getGroupList'));
        add_action($pref.self::RULE_POSTS_GROUP_LIST, array($this, 'getPostsGroupsList'));
        add_action($pref.self::RULE_ARCHIVE_GROUP_LIST, array($this, 'getArchiveGroupsList'));
        add_action($pref.self::RULE_TEMPLATE_GROUP_LIST, array($this, 'getTemplateGroupsList'));
    }

    /**
     * @return null|void
     */
    public function actionGetRuleList()
    {

        $this->verifyNonce(self::nonce);

        $postId = (int)$this->param('post');

        if (!$postId) {
            wp_send_json_error((object)array('message' => 'Invalid template'), 400);
        }

        try {
            $rules = $this->manager->getRules($postId);

            $this->success($rules);
        } catch (Exception $e) {
            Brizy_Logger::instance()->error($e->getMessage(), [$e]);
            $this->error(400, $e->getMessage());
        }

        return null;
    }

    public function actionValidateRule()
    {
        $this->verifyNonce(self::nonce);

        $postId = (int)$this->param('post');

        $postType = get_post_type($postId);

        if (!$postId) {
            $this->error(400, "Validation".'Invalid post');
        }

        $ruleData = file_get_contents("php://input");

        try {
            $rule = $this->manager->createRuleFromJson($ruleData, $postType);
            $ruleValidator = Brizy_Admin_Rules_ValidatorFactory::getValidator($postId);

            if (!$ruleValidator) {
                $this->error(400, 'Unable to get the rule validator for this post type');
            }

            $ruleValidator->validateRuleForPostId($rule, $postId);

            wp_send_json_success($rule, 200);
        } catch (Brizy_Admin_Rules_ValidationException $e) {
            wp_send_json_error(array('message' => $e->getMessage(), 'rule' => $e->getRuleId()), 400);
        } catch (Exception $e) {
            $this->error(400, "Validation".$e->getMessage());
        }
    }

    public function actionCreateRule()
    {

        $this->verifyNonce(self::nonce);

        $postId = (int)$this->param('post');
        $ignoreDataVersion = (int)$this->param('ignoreDataVersion');
        $dataVersion = (int)$this->param('dataVersion');

        $postType = get_post_type($postId);

        if (!$postId) {
            $this->error(400, "Validation".'Invalid post');
        }

        if (!$dataVersion && $ignoreDataVersion === 0) {
            $this->error(400, "Validation".'Invalid data version');
        }

        $ruleData = file_get_contents("php://input");

        try {
            $rule = $this->manager->createRuleFromJson($ruleData, $postType);
        } catch (Exception $e) {
            $this->error(400, "Validation".$e->getMessage());
        }

        try {
            // validate rule
            $ruleValidator = Brizy_Admin_Rules_ValidatorFactory::getValidator($postId);

            if (!$ruleValidator) {
                $this->error(400, 'Unable to get the rule validator for this post type');
            }

            $ruleValidator->validateRuleForPostId($rule, $postId);

            if (!$ignoreDataVersion) {
                $post = Brizy_Editor_Entity::get($postId);
                $post->setDataVersion($dataVersion);
                $post->save(0);
            }

            $this->manager->addRule($postId, $rule);

        } catch (Brizy_Editor_Exceptions_DataVersionMismatch $e) {
            $this->error(400, 'Invalid data version');
        } catch (Brizy_Admin_Rules_ValidationException $e) {
            wp_send_json_error(array('message' => $e->getMessage(), 'rule' => $e->getRuleId()), 400);
        } catch (Exception $e) {
            $this->error(400, $e->getMessage());
        }

        wp_send_json_success($rule, 200);
    }

    public function actionCreateRules()
    {
        $this->verifyNonce(self::nonce);

        $postId = (int)$this->param('post');
        $ignoreDataVersion = (int)$this->param('ignoreDataVersion');
        $dataVersion = (int)$this->param('dataVersion');
        $postType = get_post_type($postId);

        if (!$postId) {
            $this->error(400, 'Invalid post');
        }

        if (!$dataVersion && $ignoreDataVersion === 0) {
            $this->error(400, "Validation".'Invalid data version');
        }

        $rulesData = file_get_contents("php://input");

        try {
            $rules = $this->manager->createRulesFromJson($rulesData, $postType);

            if (count($rules) == 0) {
                $this->error(400, "No rules found.");
            }

        } catch (Exception $e) {
            $this->error(400, $e->getMessage());
        }

        // validate rule
        $validator = Brizy_Admin_Rules_ValidatorFactory::getValidator($postId);

        if (!$validator) {
            $this->error(400, 'Unable to get the rule validator for this post type');
        }

        try {
            $validator->validateRulesForPostId($rules, $postId);

            if (!$ignoreDataVersion) {
                $post = Brizy_Editor_Entity::get($postId);
                $post->setDataVersion($dataVersion);
                $post->save(0);
            }

            foreach ($rules as $newRule) {
                $this->manager->addRule($postId, $newRule);
            }

        } catch (Brizy_Editor_Exceptions_DataVersionMismatch $e) {
            $this->error(400, 'Invalid data version');
        } catch (Brizy_Admin_Rules_ValidationException $e) {
            wp_send_json_error(array(
                'rule' => $e->getRuleId(),
                'message' => $e->getMessage(),
            ), 400);
        }

        $this->success($rules);

        return null;
    }


    public function actionUpdateRules()
    {
        $this->verifyNonce(self::nonce);

        $postId = (int)$this->param('post');
        $ignoreDataVersion = (int)$this->param('ignoreDataVersion');
        $dataVersion = (int)$this->param('dataVersion');
        $postType = get_post_type($postId);

        if (!$postId || !in_array($postType, [
                Brizy_Admin_Templates::CP_TEMPLATE,
                Brizy_Admin_Popups_Main::CP_POPUP,
            ])) {
            wp_send_json_error((object)array('message' => 'Invalid template'), 400);
        }

        if (!$dataVersion && $ignoreDataVersion === 0) {
            $this->error(400, "Validation".'Invalid data version');
        }

        $rulesData = file_get_contents("php://input");

        try {
            $rules = $this->manager->createRulesFromJson($rulesData, $postType);
        } catch (Exception $e) {
            Brizy_Logger::instance()->error($e->getMessage(), [$e]);
            $this->error(400, $e->getMessage());
        }

//		$validator = Brizy_Admin_Rules_ValidatorFactory::getValidator( $postId );
//		if ( ! $validator ) {
//			$this->error( 400, 'Unable to get the rule validator for this post type' );
//		}

        try {
            if (!$ignoreDataVersion) {
                $post = Brizy_Editor_Entity::get($postId);
                $post->setDataVersion($dataVersion);
                $post->save(0);
            }

            $this->manager->saveRules($postId, $rules);
        } catch (Brizy_Editor_Exceptions_DataVersionMismatch $e) {
            $this->error(400, 'Invalid data version');
        } catch (Exception $e) {
            $this->error(400, 'Unable to save rules');
        }

        wp_send_json_success($rules, 200);

        return null;
    }

    public function actionDeleteRule()
    {

        $this->verifyNonce(self::nonce);

        $postId = (int)$this->param('post');
        $ignoreDataVersion = (int)$this->param('ignoreDataVersion');
        $dataVersion = (int)$this->param('dataVersion');
        $ruleId = $this->param('rule');

        if (!$postId || !$ruleId) {
            $this->error(400, 'Invalid request');
        }

        if (!$dataVersion && $ignoreDataVersion === 0) {
            $this->error(400, "Validation".'Invalid data version');
        }

        try {

            if (!$ignoreDataVersion) {
                $post = Brizy_Editor_Entity::get($postId);
                $post->setDataVersion($dataVersion);
                $post->save(0);
            }

            $this->manager->deleteRule($postId, $ruleId);
        } catch (Brizy_Editor_Exceptions_DataVersionMismatch $e) {
            $this->error(400, 'Invalid data version');
        } catch (Exception $e) {
            $this->error(400, 'Unable to delete rules');
        }

        $this->success(null);
    }

    public function getGroupList()
    {

        $context = $this->param('context');
        $templateType = $this->param('templateType');

        $closure = function ($v) {
            return array(
                'title' => $v->label,
                'value' => $v->name,
                'groupValue' => $v->groupValue,
            );
        };

        $groups = [];

        if ($templateType == 'single' || $templateType == 'single_product' || $context == 'popup-rules' || $context == 'global-block-rules') {
            $groups[] = array(
                'title' => 'Main Content',
                'value' => Brizy_Admin_Rule::POSTS,
                'items' => array_map($closure, $this->getCustomPostsList(Brizy_Admin_Rule::POSTS, $templateType, $context)),
            );
        }

        if ($templateType == 'product_archive') {
            $wooPageItems = $this->getWooPageList(Brizy_Admin_Rule::ARCHIVE, $templateType);
            $groups[] =
                $templateType == 'product_archive' && count($wooPageItems) && Brizy_Editor_Editor_Editor::get_woocomerce_plugin_info(
                ) ? array(
                    'title' => 'Pages',
                    'value' => Brizy_Admin_Rule::WOO_SHOP_PAGE,
                    'items' => $wooPageItems,
                ) : null;
        }

        if ($templateType == 'archive' || $templateType == 'product_archive' || $context == 'popup-rules' || $context == 'global-block-rules') {

            $archiveItems = array_map($closure, $this->getArchivesList(Brizy_Admin_Rule::ARCHIVE, $templateType));
            $taxonomyItems = array_map($closure, $this->getTaxonomyList(Brizy_Admin_Rule::TAXONOMY, $templateType));

            if ($templateType === 'product_archive') {
                $archiveItems[] = array(
                    'title' => 'Search page',
                    'value' => 'search',
                    'groupValue' => Brizy_Admin_Rule::TEMPLATE,
                );
            }

            $groups[] =
                count($taxonomyItems) ? array(
                    'title' => 'Categories',
                    'value' => Brizy_Admin_Rule::TAXONOMY,
                    'items' => $taxonomyItems,
                ) : null;
            $groups[] =
                count($archiveItems) ? array(
                    'title' => 'Archives',
                    'value' => Brizy_Admin_Rule::ARCHIVE,
                    'items' => $archiveItems,
                ) : null;
        }

        if ($items = $this->geTemplateList($context, $templateType)) {
            $groups[] = array(
                'title' => 'Others',
                'value' => Brizy_Admin_Rule::TEMPLATE,
                'items' => $items,
            );
        }

        $groups = array_values(array_filter($groups, function ($o) {
            return !is_null($o);
        }));
        wp_send_json_success($groups, 200);
    }

    public function getPostsGroupsList()
    {

        global $wp_post_types;

        if (!($post_type = $this->param('postType'))) {
            wp_send_json_error('Invalid post type', 400);
        }

        if (!isset($wp_post_types[$post_type])) {
            wp_send_json_error('Post type not found', 400);
        }

        $postTypeName = $wp_post_types[$post_type]->labels->name;
        $taxonomies = get_object_taxonomies($post_type, 'objects');

        if (isset($taxonomies['product_visibility'])) {
            unset($taxonomies['product_visibility']);
        }

        $groups = array();

        $closureFromTerm = function ($v) {
            return array(
                'title' => $v->name,
                'value' => "in|".$v->taxonomy."|".$v->term_id,
                'groupValue' => $v->taxonomy,
            );
        };
        $closureChildTerm = function ($v) {
            return array(
                'title' => $v->name,
                'value' => "child|".$v->taxonomy."|".$v->term_id,
                'groupValue' => $v->taxonomy,
            );
        };

        $closureAuthor = function ($v) use ($postTypeName) {
            return array(
                'title' => ucfirst($v->data->user_nicename),
                'value' => 'author|'.$v->ID,
                'groupValue' => 'author',
            );
        };

        $closurePost = function ($v) {
            return array(
                'title' => $v->post_title,
                'value' => $v->ID,
                'groupValue' => $v->post_type,
                'status' => $v->post_status,
            );
        };


        // exclude woocomerce hidden tags
        $exclude = ['simple', 'variable', 'grouped', 'external'];

        foreach ($taxonomies as $tax) {
            $groups[] = array(
                'title' => __("From", 'brizy')." ".$tax->labels->singular_name,
                'value' => Brizy_Admin_Rule::POSTS,
                'items' => array_merge([
                    [
                        'title' => 'All '.$tax->labels->name,
                        'value' => "in|".$tax->name,
                        'groupValue' => $tax->name,
                    ],
                ],
                    array_map(
                        $closureFromTerm,
                        array_filter(get_terms([
                            'taxonomy' => $tax->name,
                            'hide_empty' => false,
                        ]), function ($term) use ($exclude) {
                            return in_array($term->slug, $exclude) ? false : true;
                        })
                    )),
            );

            if ($tax->hierarchical) {
                $groups[] = array(
                    'title' => __("From any child of", 'brizy')." ".$tax->labels->singular_name,
                    'value' => Brizy_Admin_Rule::POSTS,
                    'items' => array_map($closureChildTerm, get_terms([
                        'taxonomy' => $tax->name,
                        'hide_empty' => false,
                    ])),
                );
            }
        }

        unset($taxonomies);

        $groups[] = array(
            'title' => 'Specific '.$postTypeName,
            'value' => Brizy_Admin_Rule::POSTS,
            'items' => array_map($closurePost, Brizy_Editor_Post::getPostList(null, $post_type, null, 0, 100000)),
        );

        $groups[] = array(
            'title' => 'Specific Author',
            'value' => Brizy_Admin_Rule::POSTS,
            'items' => array_merge([
                [
                    'title' => __('All Authors', 'brizy'),
                    'value' => 'author|',
                    'groupValue' => 'author',
                ],
            ], array_map($closureAuthor, get_users())),
        );

        $groups = array_values(array_filter($groups, function ($o) {
            return !is_null($o);
        }));

        wp_send_json_success($groups, 200);
    }

    public function getArchiveGroupsList()
    {

        if (!($taxonomy = $this->param('taxonomy'))) {
            wp_send_json_error('Invalid taxonomy', 400);
        }

        $groups = [];

        $taxonomies = get_taxonomies(array('public' => true, 'show_ui' => true, 'name' => $taxonomy), 'objects');

        $closureSingleTerm = function ($v) {
            return array(
                'title' => $v->name,
                'value' => $v->term_id,
                'groupValue' => $v->taxonomy,
            );
        };

        $closureTerm = function ($v) {
            return array(
                'title' => $v->name,
                'value' => "child|".$v->taxonomy."|".$v->term_id,
                'groupValue' => $v->taxonomy,
            );
        };

        foreach ($taxonomies as $tax) {
            $groups[] = array(
                'title' => __("Specific", 'brizy')." ".$tax->labels->singular_name,
                'value' => Brizy_Admin_Rule::TAXONOMY,
                'items' => array_merge([
                    [
                        'title' => 'All '.$tax->labels->name,
                        'value' => "in|".$tax->name,
                        'groupValue' => $tax->name,
                    ],
                ],
                    array_map($closureSingleTerm, get_terms([
                        'taxonomy' => $tax->name,
                        'hide_empty' => false,
                    ]))),
            );

            if ($tax->hierarchical) {
                $groups[] = array(
                    'title' => __("Any child of", 'brizy')." ".$tax->labels->singular_name,
                    'value' => Brizy_Admin_Rule::TAXONOMY,
                    'items' => array_map($closureTerm, get_terms([
                        'taxonomy' => $tax->name,
                        'hide_empty' => false,
                    ])),
                );
            }
        }

        $groups = array_values(array_filter($groups, function ($o) {
            return !is_null($o);
        }));
        wp_send_json_success($groups, 200);

    }

    public function getTemplateGroupsList()
    {
        $searchQuery = $this->param('searchQuery');
        $page = (int)$this->param('page')?:1;

        $groups = [];

        $closureAuthor = function ($v) {
            return array(
                'title' => $v->user_nicename,
                'value' => $v->ID,
                'groupValue' => 'author',
            );
        };
	    $get_users = get_users( [ 'fields'         => [ 'ID', 'user_nicename' ],
	                              'search'         => $searchQuery?"*$searchQuery*":"",
	                              'search_columns' => array( 'user_nicename', 'user_email','display_name' ),
	                              'offset'         => ( $page - 1 ) * 30,
	                              'number'         => 30
	    ] );
	    $groups[]  = array(
            'title' => 'Specific Author',
            'value' => Brizy_Admin_Rule::TEMPLATE,
            'items' => array_map($closureAuthor, $get_users ),
        );

        $groups = array_values(array_filter($groups, function ($o) {
            return !is_null($o);
        }));
        wp_send_json_success($groups, 200);
    }

    private function getCustomPostsList($groupValue, $templateType, $context)
    {
        $postTypes = get_post_types(['public' => true, 'show_ui' => true], 'objects');
        $postTypes = array_diff_key($postTypes, array_flip([
            'attachment',
            'elementor_library',
            Brizy_Admin_Stories_Main::CP_STORY,
        ]));

        $postTypes = array_filter($postTypes, function ($type) use ($groupValue, $templateType, $context) {

            if ($context == 'template-rules') {
                if ($type->name == 'product') {
                    return $templateType == 'single_product';
                } else {
                    return $templateType == 'single';
                }
            }

            return $type->public && $type->show_ui;
        });

        return array_map(function ($t) use ($groupValue) {
            $t->groupValue = $groupValue;

            return $t;
        }, array_values($postTypes));
    }

    private function getWooPageList($groupValue, $templateType)
    {
        return [
            [
                'title' => 'Shop Page',
                'value' => 'shop_page',
                'groupValue' => Brizy_Admin_Rule::WOO_SHOP_PAGE,
            ],
        ];
    }

    private function getArchivesList($groupValue, $templateType)
    {
        global $wp_post_types;

        return array_values(array_filter($wp_post_types, function ($type) use ($groupValue, $templateType) {
            $type->groupValue = $groupValue;
            $is_product = $type->name == 'product';

            if ($templateType == 'product_archive') {
                return $is_product && $type->public && $type->show_ui && $type->has_archive;
            } else {
                return !$is_product && $type->public && $type->show_ui && $type->has_archive;
            }
        }));
    }

    private function getTaxonomyList($groupValue, $templateType)
    {
        $terms = get_taxonomies(array('public' => true, 'show_ui' => true), 'objects');

        return array_values(array_filter($terms, function ($term) use ($groupValue, $templateType) {
            $term->groupValue = $groupValue;
            $is_product_term = $term->name == 'product_cat' || $term->name == 'product_tag';

            if ($templateType == 'product_archive') {
                return $is_product_term;
            } else {
                return !$is_product_term;
            }
        }));
    }

    public function geTemplateList($context, $templateType)
    {

        $isPopupOrBlockRules = $context == 'popup-rules' || $context == 'global-block-rules';
        $list = array(
            $templateType === 'archive' || $isPopupOrBlockRules ? array(
                'title' => 'Author page',
                'value' => 'author',
                'groupValue' => Brizy_Admin_Rule::TEMPLATE,
            ) : null,
            $templateType === 'archive' || $isPopupOrBlockRules ? array(
                'title' => 'Search page',
                'value' => 'search',
                'groupValue' => Brizy_Admin_Rule::TEMPLATE,
            ) : null,
            $templateType === 'single' || $isPopupOrBlockRules ? array(
                'title' => 'Front page',
                'value' => 'front_page',
                'groupValue' => Brizy_Admin_Rule::TEMPLATE,
            ) : null,
            $templateType === 'archive' || $isPopupOrBlockRules ? array(
                'title' => 'Blog / Posts page',
                'value' => 'home_page',
                'groupValue' => Brizy_Admin_Rule::TEMPLATE,
            ) : null,
            $templateType === 'single' || $isPopupOrBlockRules ? array(
                'title' => '404 page',
                'value' => '404',
                'groupValue' => Brizy_Admin_Rule::TEMPLATE,
            ) : null,

            $templateType === 'archive' || $isPopupOrBlockRules ? array(
                'title' => 'Day Archive page',
                'value' => '',
                'groupValue' => Brizy_Admin_Rule::DAY_ARCHIVE,
            ) : null,
            $templateType === 'archive' || $isPopupOrBlockRules ? array(
                'title' => 'Month Archive page',
                'value' => '',
                'groupValue' => Brizy_Admin_Rule::MONTH_ARCHIVE,
            ) : null,
            $templateType === 'archive' || $isPopupOrBlockRules ? array(
                'title' => 'Year Archive page',
                'value' => '',
                'groupValue' => Brizy_Admin_Rule::YEAR_ARCHIVE,
            ) : null,
            $templateType === 'archive' || $isPopupOrBlockRules ? array(
                'title' => 'Date Archive page',
                'value' => '',
                'groupValue' => Brizy_Admin_Rule::DATE_ARCHIVE,
            ) : null,
            $templateType === 'archive' || $isPopupOrBlockRules ? array(
                'title' => 'Archive page',
                'value' => '',
                'groupValue' => Brizy_Admin_Rule::ARCHIVE,
            ) : null,
        );

        if (($context !== 'template-rules' && $templateType === 'single') || $context == 'global-block-rules') {

            $list[] = array(
                'title' => 'Brizy Templates',
                'value' => 'editor-template',
                'groupValue' => Brizy_Admin_Rule::BRIZY_TEMPLATE,
            );
        }

        return array_values(array_filter($list, function ($o) {
            return !is_null($o);
        }));
    }


}
