<?php

/**
 * Helper functions for syncing WordPress data with Hub.
 *
 * @package northmill-online-shared
 */
namespace Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Helpers;

use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Abstract_Shared_Base;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Abstracts\Hub_API\Abstract_API_Base;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use WP_Post;
defined('ABSPATH') || exit;
/**
 * WP_Data_Sync_Helpers class
 *
 * @used Northmill Booking
 */
class WP_Data_Sync_Helpers
{
    /**
     * Updates meta value if it's not the same as before
     *
     * @param string      $meta_key   Key of meta to update.
     * @param string|null $meta_value New value.
     * @param bool|null   $autoload   Whether to autoload the option. Default is null, which means it will use the default value for the autoload parameter.
     *
     * @used Northmill Booking
     */
    public static function update_option(string $meta_key, ?string $meta_value, bool $autoload = null): void
    {
        $current = get_option($meta_key, '');
        $meta_value = (string) $meta_value;
        if ($meta_value === $current) {
            return;
        }
        update_option($meta_key, $meta_value, $autoload);
        Abstract_Shared_Base::base_instance()->logger->log('sync', 'WP option set', array('meta' => $meta_key, 'val' => $meta_value));
    }
    /**
     * Updates meta value if it's not the same as before
     *
     * @param int    $post_id    Post ID.
     * @param string $meta_key   Key of meta to update.
     * @param mixed  $meta_value Array or scalar value.
     *
     * @used Northmill Booking
     */
    public static function update_meta(int $post_id, string $meta_key, mixed $meta_value): void
    {
        $current = get_post_meta($post_id, $meta_key, \true);
        if (!is_array($meta_value) && $meta_value === $current) {
            return;
        }
        update_post_meta($post_id, $meta_key, $meta_value);
        Abstract_Shared_Base::base_instance()->logger->log('sync', 'Set meta', array('post' => $post_id, 'meta' => $meta_key, 'val' => $meta_value));
    }
    /**
     * Get all posts of a certain post type.
     *
     * @param string $post_type Post type slug.
     *
     * @return WP_Post[]
     *
     * @used Northmill Express Order
     */
    public static function get_posts(string $post_type): array
    {
        return get_posts(array('post_type' => $post_type, 'status' => 'publish', 'posts_per_page' => -1, 'ignore_sticky_posts' => \true));
    }
    /**
     * Gets post id that has unique mf id.
     *
     * @param string $mf_id     Unique mf id.
     * @param string $post_type Post Type.
     *
     * @return int|bool Post id on success, else false
     */
    public static function get_post_id(string $mf_id, string $post_type): bool|int
    {
        $posts = get_posts(array('post_type' => $post_type, 'status' => 'all', 'posts_per_page' => 1, 'ignore_sticky_posts' => \true, 'meta_query' => array(array('key' => 'mf_id', 'value' => $mf_id))));
        if (!empty($posts)) {
            return $posts[0]->ID;
        }
        return \false;
    }
    /**
     * Creates or updates post that has unique mf id
     *
     * @param string $mf_id     Unique mf id for this post.
     * @param string $name      Title of the post.
     * @param string $post_type Post type.
     * @param string $content   Post Content.
     *
     * @return int|bool Post ID on success, else false
     *
     * @used Northmill Express Order
     * @used Northmill Booking
     */
    public static function update_post(string $mf_id, string $name, string $post_type, string $content = ''): bool|int
    {
        $post_id = self::get_post_id($mf_id, $post_type);
        if ($post_id) {
            $post = get_post($post_id);
            $updates = array();
            if ($name !== $post->post_title) {
                $updates['post_title'] = $name;
            }
            if ($content !== $post->post_content) {
                $updates['post_content'] = $content;
            }
            if (!empty($updates)) {
                $updates['ID'] = $post_id;
                wp_update_post($updates);
                Abstract_Shared_Base::base_instance()->logger->log('sync', 'Updated post in WP.', array('hub_id' => $mf_id, 'post_type' => $post_type));
            }
        } else {
            $post_id = wp_insert_post(array('post_type' => $post_type, 'post_title' => $name, 'post_status' => 'publish', 'post_content' => $content));
            if (is_wp_error($post_id)) {
                Abstract_Shared_Base::base_instance()->logger->log('sync', 'Error creating new post in WP.', array('hub_id' => $mf_id, 'error' => $post_id), Log_Level_Enum::ERROR);
                return \false;
            }
            if (!$post_id) {
                Abstract_Shared_Base::base_instance()->logger->log('sync', 'Error creating new post in WP. wp_insert_post returned 0.', array('hub_id' => $mf_id), Log_Level_Enum::ERROR);
                return \false;
            }
            self::update_meta($post_id, 'mf_id', $mf_id);
            Abstract_Shared_Base::base_instance()->logger->log('sync', 'New post created in WP', array('hub_id' => $mf_id, 'post_id' => $post_id, 'name' => $name));
        }
        return $post_id;
    }
    /**
     * Removes all old imports of $post_type, that doesn't have the latest import guid.
     *
     * @param string $latest_import_guid Latest import guid.
     * @param string $post_type          Post type.
     *
     * @used Northmill Express Order
     * @used Northmill Booking
     */
    public static function delete_posts_from_old_import(string $latest_import_guid, string $post_type): void
    {
        $posts = get_posts(array('post_type' => $post_type, 'nopaging' => \true, 'fields' => 'ids', 'ignore_sticky_posts' => \true, 'post_status' => 'all', 'meta_query' => array(array('key' => 'mf_import_guid', 'compare' => '!=', 'value' => $latest_import_guid))));
        if (empty($posts)) {
            return;
        }
        foreach ($posts as $post) {
            $mf_id = get_post_meta($post, 'mf_id', \true);
            if (!$mf_id) {
                continue;
            }
            self::delete_post($mf_id, $post_type);
        }
    }
    /**
     * Deletes a post that has unique mf id. If it's not found then it does nothing
     *
     * @param string $mf_id     Unique mf id.
     * @param string $post_type Post type-.
     *
     * @return bool True on deleted, false on not found
     *
     * @used Northmill Booking
     */
    public static function delete_post(string $mf_id, string $post_type): bool
    {
        $post_id = self::get_post_id($mf_id, $post_type);
        if ($post_id) {
            $title = get_the_title($post_id);
            wp_delete_post($post_id);
            Abstract_Shared_Base::base_instance()->logger->log('sync', 'Post deleted in WP', array('hub_id' => $mf_id, 'post_type' => $post_type, 'title' => $title));
            return \true;
        }
        return \false;
    }
    /**
     * Gets term that has unique mf id
     *
     * @param string $mf_id    Unique mf id for this term.
     * @param string $taxonomy Taxonomy slug.
     *
     * @return int|bool Term ID on success, else false
     *
     * @used Northmill Express Order
     * @used Northmill Booking
     */
    public static function get_term(string $mf_id, string $taxonomy): bool|int
    {
        $terms = get_terms(array('taxonomy' => $taxonomy, 'hide_empty' => \false, 'meta_query' => array(array('key' => 'mf_id', 'value' => $mf_id)), 'number' => 1));
        return empty($terms) ? \false : $terms[0]->term_id;
    }
    /**
     * Creates or updates term that has unique mf id
     *
     * @param string $mf_id    Unique mf id for this term.
     * @param string $name     Title of the term.
     * @param string $taxonomy Taxonomy slug.
     *
     * @return int|bool Term ID on success, else false
     *
     * @used Northmill Booking
     */
    public static function update_term(string $mf_id, string $name, string $taxonomy): bool|int
    {
        $term_id = self::get_term($mf_id, $taxonomy);
        if (!$term_id) {
            $inserted_term = wp_insert_term($name, $taxonomy);
            if (is_wp_error($inserted_term)) {
                Abstract_Shared_Base::base_instance()->logger->log('sync', 'Error creating new term in WP.', array('hub_id' => $mf_id, 'error' => $inserted_term), Log_Level_Enum::ERROR);
                return \false;
            }
            $term_id = $inserted_term['term_id'];
            update_term_meta($term_id, 'mf_id', $mf_id);
            Abstract_Shared_Base::base_instance()->logger->log('sync', 'New term created in WP', array('hub_id' => $mf_id, 'term' => $taxonomy, 'name' => $name));
        } else {
            $term = get_term($term_id, $taxonomy);
            if ($term->name !== $name) {
                $slug = wp_unique_term_slug(sanitize_title($name), $term);
                wp_update_term($term_id, $taxonomy, array('name' => $name, 'slug' => $slug));
                Abstract_Shared_Base::base_instance()->logger->log('sync', 'Updated term in WP', array('hub_id' => $mf_id, 'term' => $taxonomy, 'name' => $name, 'slug' => $slug));
            }
        }
        return $term_id;
    }
    /**
     * Updates term meta value if it's not the same as before
     *
     * @param int    $term_id    Term id.
     * @param string $meta_key   Key of meta to update.
     * @param mixed  $meta_value New value.
     *
     * @used Northmill Booking
     */
    public static function update_term_meta(int $term_id, string $meta_key, mixed $meta_value): void
    {
        $current = get_term_meta($term_id, $meta_key, \true);
        if (!is_array($meta_value) && $meta_value === $current) {
            return;
        }
        update_term_meta($term_id, $meta_key, $meta_value);
        Abstract_Shared_Base::base_instance()->logger->log('sync', 'Set term meta', array('term' => $term_id, 'meta' => $meta_key, 'val' => $meta_value));
    }
    /**
     * Adds a page as a nav item to a menu.
     *
     * @param int $menu_id Menu WP ID.
     * @param int $page_id Page WP ID.
     *
     * @used Northmill Booking
     */
    public static function add_nav_menu_item(int $menu_id, int $page_id): void
    {
        $menu_items = wp_get_nav_menu_items($menu_id);
        if (\false === $menu_items) {
            return;
        }
        if (self::has_menu_item($menu_items, $page_id)) {
            return;
        }
        wp_update_nav_menu_item($menu_id, 0, array('menu-item-title' => get_the_title($page_id), 'menu-item-object-id' => $page_id, 'menu-item-object' => 'page', 'menu-item-status' => 'publish', 'menu-item-type' => 'post_type'));
    }
    /**
     * Removes a page from a menu
     *
     * @param int $menu_id Menu WP ID.
     * @param int $page_id Page WP ID.
     *
     * @used Northmill Booking
     */
    public static function remove_nav_menu_item(int $menu_id, int $page_id): void
    {
        $menu_items = wp_get_nav_menu_items($menu_id);
        if (\false === $menu_items) {
            return;
        }
        foreach ($menu_items as $menu_item) {
            if ($menu_item->object_id !== $page_id) {
                continue;
            }
            wp_delete_post($menu_item->ID);
        }
    }
    /**
     * Checks if a page nav item exists in a menu
     *
     * @param array $menu_items The menu items.
     * @param int   $page_id    The page ID.
     *
     * @return bool
     */
    public static function has_menu_item(array $menu_items, int $page_id): bool
    {
        if (empty($menu_items)) {
            return \false;
        }
        foreach ($menu_items as $menu_item) {
            if (is_a($menu_item, 'WP_Post') && $menu_item->object_id === $page_id) {
                return \true;
            }
        }
        return \false;
    }
    /**
     * Imports image to WordPress if it doesn't exist
     *
     * @param string            $mf_id    Unique mf id for this attachment.
     * @param string            $filename Filename of the image.
     * @param string            $url      URL to the image.
     * @param Abstract_API_Base $api      API instance.
     *
     * @return int|false Attachment Id, false on failure
     *
     * @used Northmill Express Order
     * @used Northmill Booking
     */
    public static function import_image(string $mf_id, string $filename, string $url, Abstract_API_Base $api): false|int
    {
        $attachment_id = self::get_post_id($mf_id, 'attachment');
        if ($attachment_id) {
            return $attachment_id;
        }
        // Import new image in WP.
        if (!in_array(strtolower(pathinfo($url, \PATHINFO_EXTENSION)), array('jpg', 'jpeg', 'png'), \true)) {
            Abstract_Shared_Base::base_instance()->logger->log('sync', 'Cannot import image of unknown file extension', array('url' => $url), Log_Level_Enum::ERROR);
            return \false;
        }
        Abstract_Shared_Base::base_instance()->logger->log('sync', 'Start import of new image', array('url' => $url));
        $image_data = $api->get_image_file($url);
        if (!$image_data) {
            Abstract_Shared_Base::base_instance()->logger->log('sync', 'Error during import of image. Could not be fetched.', array('url' => $url), Log_Level_Enum::ERROR);
            return \false;
        }
        // Make sure we have access to the internal functions.
        require_once \ABSPATH . 'wp-admin/includes/media.php';
        require_once \ABSPATH . 'wp-admin/includes/file.php';
        require_once \ABSPATH . 'wp-admin/includes/image.php';
        $file_array = array('tmp_name' => get_temp_dir() . $filename, 'name' => $filename);
        $stored = File_System_Helper::put_contents($file_array['tmp_name'], $image_data);
        if (!$stored) {
            Abstract_Shared_Base::base_instance()->logger->log('sync', 'Error during import of image. Image could not be written to temp folder.', array('url' => $url), Log_Level_Enum::ERROR);
            return \false;
        }
        $attachment_id = media_handle_sideload($file_array);
        if (is_wp_error($attachment_id)) {
            Abstract_Shared_Base::base_instance()->logger->log('sync', 'Error during import of image.', array('error' => $attachment_id), Log_Level_Enum::ERROR);
            return \false;
        }
        self::update_meta($attachment_id, 'mf_id', $mf_id);
        Abstract_Shared_Base::base_instance()->logger->log('sync', 'Successfully downloaded image.', array('hub_id' => $mf_id, 'filename' => $filename, 'url' => $url));
        return $attachment_id;
    }
}
