<?php
namespace MatthiasWeb\RealMediaLibrary\lite\folder;

use Exception;
use MatthiasWeb\RealMediaLibrary\Core;
use MatthiasWeb\RealMediaLibrary\exception\FolderAlreadyExistsException;
use MatthiasWeb\RealMediaLibrary\Util;

// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- This trait throws internal exceptions and returns internal messages.

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

trait Creatable {
    // Documented in IFolderActions
    public function resetSubfolderOrder() {
        delete_media_folder_meta($this->id, 'lastSubOrderBy');
        $this->debug("Deleted subfolder order of the folder $this->id", __METHOD__);
        return true;
    }

    // Documented in IFolderActions
    public function orderSubfolders($orderby, $writeMetadata = true) {
        $orders = self::getAvailableSubfolderOrders();
        $core = Core::getInstance();
        $core->debug("Try to order the subfolders of $this->id by $orderby...", __METHOD__);
        if (in_array($orderby, array_keys($orders), true)) {
            global $wpdb;

            // Get order
            $split = explode('_', $orderby);
            $order = $orders[$orderby];
            $direction = $split[1];
            $table_name = $core->getTableName();

            // Run SQL
            // phpcs:disable WordPress.DB.PreparedSQL
            $sql = $wpdb->prepare(
                "UPDATE $table_name AS rmlo2
                LEFT JOIN (
                    SELECT @rownum := @rownum + 1 AS ord, rmlo.id
                    FROM $table_name AS rmlo
                    JOIN (SELECT @rownum := 0) AS r
                    WHERE rmlo.parent = %d
                    ORDER BY " .
                    $order['sqlOrder'] .
                    " $direction
                ) AS rmlonew ON rmlo2.id = rmlonew.id
                SET rmlo2.ord = rmlonew.ord
                WHERE rmlo2.parent = %d",
                $this->id,
                $this->id
            );
            $wpdb->query($sql);
            // phpcs:enable WordPress.DB.PreparedSQL

            // Save in the metadata
            if ($writeMetadata) {
                update_media_folder_meta($this->id, 'lastSubOrderBy', $orderby);
            }
            $core->debug('Successfully ordered folder', __METHOD__);
            return true;
        } else {
            $core->debug("'$orderby' is not a valid order...", __METHOD__);
            return false;
        }
    }

    /**
     * Check if the current folders parent is automatically ordered by a criteria so
     * the order can be applied. This should be called when the hierarchy of the
     * folder is changed or when a new folder is added to that parent.
     *
     * @return boolean
     */
    protected function applySubfolderOrderBy() {
        $parent = wp_rml_get_object_by_id($this->getParent());
        if (!is_rml_folder($parent)) {
            return false;
        }

        $orderAutomatically = (bool) $parent->getRowData('subOrderAutomatically');

        if ($orderAutomatically) {
            $order = $parent->getRowData('lastSubOrderBy');

            if (!empty($order)) {
                $this->debug(
                    'New subfolder ' .
                        $this->getId() .
                        ' in folder ' .
                        $parent->getId() .
                        ", automatically order by $order ...",
                    __METHOD__
                );
                return $parent->orderSubfolders($order, false);
            }
        }
        return false;
    }

    // Documented in IOverrideCreatable
    public function persistCheckParent() {
        // Check, if the parent exists
        $parentObj = wp_rml_get_object_by_id($this->parent);
        if (!is_rml_folder($parentObj)) {
            // translators:
            throw new Exception(sprintf(__('The parent %d does not exist.', 'real-media-library-lite'), $this->parent));
        }
        return $parentObj;
    }

    // Documented in IFolderActions
    public function reindexChildrens($resetData = false) {
        global $wpdb;
        $table_name = $this->getTableName();
        // phpcs:disable WordPress.DB.PreparedSQL
        $sql = "UPDATE $table_name AS rml2
            LEFT JOIN (
                SELECT @rownum := @rownum + 1 AS nr, rml.id
                FROM $table_name AS rml
                JOIN (SELECT @rownum := 0) AS r
                WHERE rml.parent = $this->id
                ORDER BY rml.ord
            ) AS rmlnew
                ON rml2.id = rmlnew.id
            SET rml2.ord = rmlnew.nr
            WHERE rml2.parent = $this->id";

        $wpdb->query($sql);
        // phpcs:enable WordPress.DB.PreparedSQL
        $this->debug("Reindexed the childrens order of $this->id", __METHOD__);
        if ($resetData) {
            wp_rml_structure_reset(null, false);
        }
        return true;
    }

    // Documented in IFolder
    public function isValidChildrenType($type) {
        $allowed = $this->getAllowedChildrenTypes();
        $this->debug(
            "Check if children type '$type' of $this->id... is allowed here: " .
                ($allowed === true ? 'All is allowed here' : 'Only ' . json_encode($allowed) . ' is allowed here'),
            __METHOD__
        );
        return $allowed === true ? true : in_array($type, $allowed, true);
    }

    // Documented in IFolderActions
    public function setParent($id, $ord = -1, $force = false) {
        // Get the parent id
        $this->debug("Try to set parent of $this->id from $this->parent to $id...", __METHOD__);

        // Get the parent object
        $parent = wp_rml_get_object_by_id($id);
        if ($id === $this->parent) {
            $this->debug('The parent is the same, probably only the order is changed...', __METHOD__);
        } else {
            // Check if parent folder is given
            if ($parent === null) {
                throw new Exception(
                    __('The given parent does not exist to set the parent for this folder.', 'real-media-library-lite')
                );
            }

            // Check if allowed to change the parent
            if (!$force && $this->isRestrictFor('par')) {
                throw new Exception(
                    __('You are not allowed to change the parent for this folder.', 'real-media-library-lite')
                );
            }

            // Check, if the folder type is allowed here
            if (!$force && !$parent->isValidChildrenType($this->getType())) {
                throw new Exception(__('The given parent does not allow the folder type.', 'real-media-library-lite'));
            }

            // Check, if the parent has already the given folder name
            if ($parent->hasChildren($this->name)) {
                throw new FolderAlreadyExistsException($id, $this->name);
            }
        }

        $newOrder = $ord > -1 ? $ord : $parent->getMaxOrder() + 1;
        $isRelocate = $id === $this->parent;

        /**
         * (Pro only) This action is called when a folder was moved in the folder tree. That
         * means the parent and order was changed.
         *
         * @param {IFolder} $folder The folder object
         * @param {int} $id The new parent folder id
         * @param {int} $order The (new) order number
         * @param {boolean} $force If true the relocating was forced
         * @hook RML/Folder/Move
         * @since 4.0.7
         * @since 4.12.1 This hook is only available in PRO-Version
         */
        do_action($isRelocate ? 'RML/Folder/Relocate' : 'RML/Folder/Move', $this, $id, $newOrder, $force);

        $oldData = $this->getRowData();
        $beforeId = $this->parent;
        $this->parent = $id;
        $this->order = $newOrder;
        $this->debug("Use $this->order (passed $ord as parameter) as new order value", __METHOD__);

        // Save in database
        if ($this->id > -1) {
            global $wpdb;

            // Update childrens
            if ($beforeId !== $this->parent) {
                $this->updateThisAndChildrensAbsolutePath();
            }

            // Update order
            // phpcs:disable WordPress.DB.PreparedSQL
            $table_name = $this->getTableName();
            $wpdb->query(
                $wpdb->prepare("UPDATE $table_name SET parent=%d, ord=%d WHERE id = %d", $id, $this->order, $this->id)
            );
            // phpcs:enable WordPress.DB.PreparedSQL

            /**
             * (Pro only) This action is called when a folder was moved in the folder tree. That
             * means the parent and order was changed.
             *
             * @param {IFolder} $folder The folder object
             * @param {int} $id The new parent folder id
             * @param {int} $order The (new) order number
             * @param {boolean} $force If true the relocating was forced
             * @param {object} $oldData The old SQL row data (raw) of the folder
             * @hook RML/Folder/Moved
             * @since 4.12.1 This hook is only available in PRO-Version
             */
            do_action(
                $isRelocate ? 'RML/Folder/Relocated' : 'RML/Folder/Moved',
                $this,
                $id,
                $this->order,
                $force,
                $oldData
            );
            $this->debug('Successfully moved and saved in database', __METHOD__);

            Util::getInstance()->doActionAnyParentHas($this, 'Folder/RelocatedOrMoved', [
                $this,
                $id,
                $this->order,
                $force,
                $oldData,
            ]);
            $this->applySubfolderOrderBy();
        } else {
            $this->debug('Successfully setted the new parent', __METHOD__);
            $this->getAbsolutePath(true, true);
        }

        return true;
    }

    // Documented in IFolderActions
    public function relocate($parentId, $nextFolderId = false) {
        global $wpdb;

        // Collect data
        $table_name = $this->getTableName();
        $this->debug(
            $parentId === $this->id
                ? "Start to relocate folder $this->id inside parent..."
                : "Start to relocate folder $this->id to parent $parentId...",
            __METHOD__
        );
        $this->debug(
            $nextFolderId === false
                ? 'The folder should take place at the end of the list...'
                : "The folder should take place before folder id $nextFolderId...",
            __METHOD__
        );
        $parent = $parentId === $this->id ? $this : wp_rml_get_object_by_id($parentId);
        $next = $nextFolderId === false ? null : wp_rml_get_object_by_id($nextFolderId);

        // At end of the list
        try {
            if ($next === null && is_rml_folder($parent)) {
                // Only set the parent
                $this->setParent($parent->id);
            } elseif (is_rml_folder($next) && is_rml_folder($parent)) {
                // Reindex and reget
                $parent->reindexChildrens();
                $_this = wp_rml_structure_reset(null, false, $this->id);
                $next = wp_rml_get_object_by_id($next->id);

                // Get the order of the next folder
                $newOrder = $next->order;

                // Count up the next ids
                // phpcs:disable WordPress.DB.PreparedSQL
                $sql = "UPDATE $table_name SET ord = ord + 1 WHERE parent = $parent->id AND ord >= $newOrder";
                $wpdb->query($sql);
                // phpcs:enable WordPress.DB.PreparedSQL

                // Set the new parent
                $_this->setParent($parent->id, $newOrder);
            } else {
                // There is nothing given
                throw new Exception(__('Something went wrong.', 'real-media-library-lite'));
            }

            $this->debug('Successfully relocated', __METHOD__);
            return true;
        } catch (Exception $e) {
            $this->debug('Error: ' . $e->getMessage(), __METHOD__);
            return [$e->getMessage()];
        }
    }

    /**
     * Delete the subOrderAutomatically metadata when deleting the subfolder
     * order and also reset the subfolder order. It also handles the content order.
     *
     * @param int[] $meta_ids
     * @param int $object_id
     * @param string $meta_key
     */
    public static function deleted_realmedialibrary_meta($meta_ids, $object_id, $meta_key) {
        global $wpdb;

        if (empty($object_id)) {
            return;
        }

        if ($meta_key === 'lastSubOrderBy') {
            // The default is to order by ID ascending
            $folder = wp_rml_get_object_by_id($object_id);
            if (is_rml_folder($folder)) {
                $folder->orderSubfolders('id_asc', false);
            }

            // phpcs:disable WordPress.DB.PreparedSQL
            $wpdb->query(
                $wpdb->prepare(
                    'UPDATE ' . Core::getInstance()->getTableName() . ' SET ord=NULL, oldCustomOrder=NULL WHERE id=%d',
                    $object_id
                )
            );
            // phpcs:enable WordPress.DB.PreparedSQL
            delete_media_folder_meta($object_id, 'subOrderAutomatically');
        }

        if ($meta_key === 'orderby') {
            // phpcs:disable WordPress.DB.PreparedSQL
            $wpdb->query(
                $wpdb->prepare(
                    'UPDATE ' .
                        Core::getInstance()->getTableName('posts') .
                        ' SET nr=NULL, oldCustomNr=NULL WHERE fid=%d',
                    $object_id
                )
            );
            $wpdb->query(
                $wpdb->prepare(
                    'UPDATE ' . Core::getInstance()->getTableName() . ' SET contentCustomOrder=0 WHERE id=%d',
                    $object_id
                )
            );
            // phpcs:enable WordPress.DB.PreparedSQL
            delete_media_folder_meta($object_id, 'orderAutomatically');
        }
    }
}
