<?php
namespace DevOwl\RealCategoryLibrary;

use DevOwl\RealCategoryLibrary\base\UtilsProvider;
use MatthiasWeb\WpdbBatch\Update;
use WP_Error;
use WP_Term;

defined('ABSPATH') or die('No script kiddies please!'); // Avoid direct file request

/**
 * Adds a custom order to the taxonomies.
 */
class TaxOrder {
    use UtilsProvider;

    const IGNORE_TAXONOMIES = [
        /**
         * WP File Download. They provide an own interface for drag & drop functionality.
         */
        'wpfd-category',
    ];

    /**
     * This function is called when a new term is created.
     *
     * @param int $term_id
     * @param int $tt_id
     * @param string $taxonomy
     */
    public function created_term($term_id, $tt_id, $taxonomy) {
        if (is_taxonomy_hierarchical($taxonomy)) {
            $nextOrder = wp_count_terms($taxonomy, [
                'hide_empty' => 0,
            ]);
            $this->update($nextOrder, $term_id, $taxonomy);
        }
    }

    /**
     * Updates the order for our categories. It also respects WooCommerce attribute taxonomies.
     *
     * You should also check with is_taxonomy_hierarchical before
     * using this function. WooCommerce counts the $ord parameter one
     * up because the index starts with 1.
     *
     * @param int $ord
     * @param int $cid
     * @param string $taxonomy
     */
    public function update($ord, $cid, $taxonomy) {
        global $wpdb;

        if ($this->getCore()->getWooCommerce()->isWooCommerceTaxonomy($taxonomy)) {
            wc_set_term_order($cid, $ord + 1, $taxonomy);
        } else {
            $wpdb->query($wpdb->prepare("UPDATE $wpdb->terms SET term_order = %d WHERE term_id =%d", $ord, $cid));
            $wpdb->query(
                $wpdb->prepare(
                    "UPDATE $wpdb->term_relationships SET term_order = %d WHERE term_taxonomy_id =%d",
                    $ord,
                    $cid
                )
            );
        }
    }

    /**
     * Update hierarchy with the batch method.
     *
     * @param array $hierarchy
     * @param string $taxonomy
     */
    public function batchUpdate($hierarchy, $taxonomy) {
        global $wpdb;

        if ($this->getCore()->getWooCommerce()->isWooCommerceTaxonomy($taxonomy)) {
            // Meta name
            if (taxonomy_is_product_attribute($taxonomy)) {
                $meta_name = 'order_' . esc_attr($taxonomy);
            } else {
                $meta_name = 'order';
            }

            // Update wp_termmeta, get all meta ids...
            $keyValueOrder = [];
            for ($i = 0; $i < count($hierarchy); $i++) {
                $nodeId = intval($hierarchy[$i]->term_id);
                $keyValueOrder[$nodeId] = $i;
            }

            if (count($keyValueOrder) > 0) {
                // phpcs:disable WordPress.DB.PreparedSQL
                $metas = $wpdb->get_results(
                    'SELECT meta_id, term_id FROM ' .
                        $wpdb->termmeta .
                        ' WHERE meta_key = "' .
                        $meta_name .
                        '" AND term_id IN (' .
                        implode(',', array_keys($keyValueOrder)) .
                        ')'
                );
                // phpcs:enable WordPress.DB.PreparedSQL

                // Create batch update if metas exists
                if (count($metas) > 0) {
                    $wpbu = new Update($wpdb->termmeta, 'meta_id');
                    foreach ($metas as $meta) {
                        $wpbu->add($meta->meta_id, [
                            'meta_value' => $keyValueOrder[$meta->term_id],
                        ]);
                    }

                    $wpbu->execute(500);
                }
            }
        } else {
            $wpbu_terms = new Update($wpdb->terms, 'term_id', [
                'term_id' => '%d',
                'term_order' => '%d',
            ]);

            $wpbu_rels = new Update($wpdb->term_relationships, 'term_taxonomy_id', [
                'term_taxonomy_id' => '%d',
                'term_order' => '%d',
            ]);

            for ($i = 0; $i < count($hierarchy); $i++) {
                $id = $hierarchy[$i]->term_id;
                $arr = [
                    'term_order' => $i,
                ];

                $wpbu_terms->add($id, $arr);
                $wpbu_rels->add($id, $arr);
            }

            // Execute
            $wpbu_terms->execute(500);
            $wpbu_rels->execute(500);
        }
    }

    /**
     * Relocate a category within the tree.
     *
     * @param string $type The post type
     * @param string $taxonomy The taxonomy
     * @param int $id The term id we want to move
     * @param int $parent The parent where the movement should take place
     * @param int $nextId The next id relative to id
     * @param boolean $persist If true the new order gets persisted to database
     * @return WP_Error|array If successful you will get an array with old and new hierarchy
     * @since 3.1.0
     */
    public function relocate($type, $taxonomy, $id, $parent, $nextId, $persist = true) {
        if (!taxonomy_exists($taxonomy) || !post_type_exists($type)) {
            return new WP_Error(
                'rcl_build_hierarchy',
                sprintf(
                    // translators:
                    __(
                        'No categories found for this post type (%1$s) with the taxonomy (%2$s).',
                        'real-category-library-lite'
                    ),
                    $type,
                    $taxonomy
                ),
                ['status' => 500]
            );
        }

        if (!apply_filters('RCL/Sorting', true, $taxonomy)) {
            return new WP_Error(
                'rcl_forbidden_sort',
                __('You are not allowed to sort this tree.', 'real-category-library-lite'),
                [
                    'status' => 403,
                ]
            );
        }

        // Receive tree
        $taxTree = new TaxTree($type, $taxonomy, null, true, $parent);
        $tree = $taxTree->getCats();
        $cnt = count($tree);

        $hierarchy = [];
        $current = null;

        // Get current
        foreach ($tree as $node) {
            if ($node->term_id === $id) {
                $current = $node;
                break;
            }
        }

        // The moved item comes from another hierarchical level
        if ($current === null) {
            $cats = get_categories([
                // Get single categorie with multi-function
                'hide_empty' => 0,
                'type' => $type,
                'taxonomy' => $taxonomy,
                'include' => $id,
            ]);
            $taxTree->enrichTerm($current);

            // Update parent
            if (isset($cats[0])) {
                $current = $cats[0];
                $current->parent = $parent;
                $current->category_parent = $parent;

                if ($persist) {
                    wp_update_term($id, $taxonomy, [
                        'parent' => $parent,
                    ]);
                }
            }
        }

        if ($current === null) {
            return new WP_Error('rcl_build_hierarchy', __('No item found.', 'real-category-library-lite'), [
                'status' => 500,
            ]);
        }

        // Create order
        for ($i = 0; $i < count($tree); $i++) {
            $node = $tree[$i];
            $next = isset($tree[$i + 1]) ? $tree[$i + 1] : null;

            // Next is at first position
            if ($i === 0 && $node->term_id === $nextId) {
                if (!in_array($current, $hierarchy, true)) {
                    $hierarchy[] = $current;
                }
                if (!in_array($node, $hierarchy, true)) {
                    $hierarchy[] = $node;
                }
            }

            // Next, but not first
            if ($next !== null && $next->term_id === $nextId) {
                if (!in_array($node, $hierarchy, true)) {
                    $hierarchy[] = $node;
                }
                if (!in_array($current, $hierarchy, true)) {
                    $hierarchy[] = $current;
                }
            }

            // Skip current
            if ($node->term_id === $id) {
                continue;
            }

            if (!in_array($node, $hierarchy, true)) {
                $hierarchy[] = $node;
            }
        }

        // Add the moved node if it should be at the end
        if ($cnt !== count($hierarchy) && $current !== null && !in_array($current, $hierarchy, true)) {
            $hierarchy[] = $current;
        }

        // Persist
        if ($persist) {
            /*for ($i = 0; $i < count($hierarchy); $i++) {
                $this->update($i, $hierarchy[$i]->term_id, $taxonomy);
            }*/
            $this->batchUpdate($hierarchy, $taxonomy);
        }

        return [
            'old' => $tree,
            'new' => $hierarchy,
        ];
    }

    /**
     * Change the WordPress filter to get the orderby
     * SQL attribute for terms. Now use it in different filters.
     *
     * @param WP_Term[] $terms
     */
    public function wp_get_object_terms($terms) {
        /* Filter documentated below */
        if (is_array($terms) && count($terms) > 0) {
            // Get the first object and taxonomy name
            $first = reset($terms);
            if (is_object($first) && isset($first->taxonomy) && !isset($_GET['orderby'])) {
                $taxonomy = $first->taxonomy;
                if (
                    apply_filters('RCL/Sorting', true, $taxonomy) &&
                    is_taxonomy_hierarchical($taxonomy) &&
                    !$this->getCore()->getWooCommerce()->isWooCommerceTaxonomy($taxonomy)
                ) {
                    // only for hierarchical taxonomies
                    usort($terms, [TaxOrder::class, 'compare']);
                }
            }
        }

        return $terms;
    }

    /**
     * Change the WordPress filter to get the orderby
     * SQL attribute for terms.
     *
     * @param string $orderby
     * @param array $args
     */
    public function get_terms_orderby($orderby, $args) {
        $taxonomy = isset($args['taxonomy']) ? $args['taxonomy'] : 'category';

        /**
         * This filter allows you to disable the custom order functionality. This
         * can be useful if you have another plugin which handles your sorting.
         *
         * @param {boolean} $enabled=true - If true the custom order of RCL is available
         * @param {string} $taxonomy - Since 3.4.9: Disable sorting functionality by taxonomy
         * @return {boolean}
         * @hook RCL/Sorting
         */
        if (!apply_filters('RCL/Sorting', true, $taxonomy)) {
            return $orderby;
        }

        if (
            is_array($taxonomy) ||
            !is_taxonomy_hierarchical($taxonomy) ||
            $this->getCore()->getWooCommerce()->isWooCommerceTaxonomy($taxonomy)
        ) {
            return $orderby;
        }

        if ($args['orderby'] === 'term_order') {
            return 't.term_order';
        } elseif ($args['orderby'] === 'name') {
            return 't.name';
        } elseif (!isset($_GET['orderby'])) {
            return 't.term_order';
        } else {
            return $orderby;
        }
    }

    /**
     * Disable RCL's sorting functionality by a given taxonomy.
     *
     * @param boolean $sorting
     * @param string $taxonomy
     */
    public function disable_by_taxonomy($sorting, $taxonomy) {
        if (in_array($taxonomy, self::IGNORE_TAXONOMIES, true)) {
            return false;
        }
        return $sorting;
    }

    /**
     * usort the terms.
     *
     * @param WP_Term $a
     * @param WP_Term $b
     */
    public static function compare($a, $b) {
        if (intval($a->term_order) === intval($b->term_order)) {
            return 0;
        } elseif (intval($a->term_order) < intval($b->term_order)) {
            return -1;
        } else {
            return 1;
        }
    }
}
