<?php
namespace Tiamr\Admin;

use Tiamr\Meta;
use Tiamr\Utils\Sanitizer;

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

final class User_Menus_Importer {
    public const USER_MENUS_META = '_jp_nav_item_options';

    public const TIAMR_PRO_GROUPS_META = '_tiamr_pro_condition_groups';
    public const TIAMR_PRO_ACTION_META = '_tiamr_pro_action';
    public const TIAMR_PRO_ENABLED_META = '_tiamr_pro_enabled';

    /**
     * @return array{found:int,importable:int,existing:int,unsupported:int,pro_existing:int}
     */
    public function get_stats(): array {
        $stats = [
            'found'        => 0,
            'importable'   => 0,
            'existing'     => 0,
            'unsupported'  => 0,
            'pro_existing' => 0,
        ];

        foreach ($this->get_candidate_menu_item_ids() as $menu_item_id) {
            $stats['found']++;

            $options = $this->get_options($menu_item_id);
            $rule    = $this->map_user_menus_rule($options);

            if (null === $rule) {
                $stats['unsupported']++;
                continue;
            }

            $stats['importable']++;

            if ($this->has_tiamr_rule($menu_item_id)) {
                $stats['existing']++;
            }

            if ($this->has_pro_rule($menu_item_id)) {
                $stats['pro_existing']++;
            }
        }

        return $stats;
    }

    /**
     * @return array{found:int,imported:int,skipped_existing:int,skipped_unsupported:int,replaced_pro:int,kept_pro:int}
     */
    public function import(bool $overwrite_existing = false, bool $overwrite_pro = false): array {
        $result = [
            'found'               => 0,
            'imported'            => 0,
            'skipped_existing'    => 0,
            'skipped_unsupported' => 0,
            'replaced_pro'        => 0,
            'kept_pro'            => 0,
        ];

        foreach ($this->get_candidate_menu_item_ids() as $menu_item_id) {
            $result['found']++;

            $options = $this->get_options($menu_item_id);
            $rule    = $this->map_user_menus_rule($options);

            if (null === $rule) {
                $result['skipped_unsupported']++;
                continue;
            }

            if (!$overwrite_existing && $this->has_tiamr_rule($menu_item_id)) {
                $result['skipped_existing']++;
                continue;
            }

            Meta::update_rule($menu_item_id, $rule);
            $result['imported']++;

            if ($this->has_pro_rule($menu_item_id)) {
                if ($overwrite_pro) {
                    $this->update_pro_rule_from_user_menus_options($menu_item_id, $options);
                    $result['replaced_pro']++;
                } else {
                    $result['kept_pro']++;
                }
            }
        }

        return $result;
    }

    /**
     * @return int[]
     */
    private function get_candidate_menu_item_ids(): array {
        $ids = get_posts([
            'post_type'      => 'nav_menu_item',
            'post_status'    => 'any',
            'posts_per_page' => -1,
            'fields'         => 'ids',
            'orderby'        => 'ID',
            'order'          => 'ASC',
            'meta_query'     => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
                [
                    'key'     => self::USER_MENUS_META,
                    'compare' => 'EXISTS',
                ],
            ],
        ]);

        return array_map('absint', is_array($ids) ? $ids : []);
    }

    /**
     * @return array{avatar_size:int,redirect_type:string,redirect_url:string,which_users:string,can_see:string,roles:array}
     */
    private function get_options(int $menu_item_id): array {
        $options = get_post_meta($menu_item_id, self::USER_MENUS_META, true);

        if (is_string($options)) {
            $maybe_unserialized = maybe_unserialize($options);
            if (is_array($maybe_unserialized)) {
                $options = $maybe_unserialized;
            }
        }

        if (!is_array($options)) {
            $options = [];
        }

        return wp_parse_args($options, [
            'avatar_size'   => 24,
            'redirect_type' => 'current',
            'redirect_url'  => '',
            'which_users'   => '',
            'can_see'       => 'yes',
            'roles'         => [],
        ]);
    }

    /**
     * @param array{which_users:string,can_see:string,roles:array} $options
     * @return array{action:string,audience:string,location:string,roles:array,pages:array,posts:array}|null
     */
    private function map_user_menus_rule(array $options): ?array {
        $which_users = sanitize_key((string) ($options['which_users'] ?? ''));
        $can_see     = sanitize_key((string) ($options['can_see'] ?? 'yes'));
        $roles       = Sanitizer::sanitize_roles($options['roles'] ?? []);

        if ('logged_out' === $which_users) {
            return $this->free_rule(Meta::ACTION_SHOW, Meta::AUDIENCE_LOGGED_OUT, []);
        }

        if ('logged_in' !== $which_users) {
            return null;
        }

        if (empty($roles)) {
            return $this->free_rule(Meta::ACTION_SHOW, Meta::AUDIENCE_LOGGED_IN, []);
        }

        if ('no' === $can_see) {
            return $this->free_rule(Meta::ACTION_HIDE, Meta::AUDIENCE_ROLE_IS, $roles);
        }

        return $this->free_rule(Meta::ACTION_SHOW, Meta::AUDIENCE_ROLE_IS, $roles);
    }

    /**
     * @param string[] $roles
     * @return array{action:string,audience:string,location:string,roles:array,pages:array,posts:array}
     */
    private function free_rule(string $action, string $audience, array $roles): array {
        return [
            'action'   => $action,
            'audience' => $audience,
            'location' => Meta::LOCATION_EVERYWHERE,
            'roles'    => $roles,
            'pages'    => [],
            'posts'    => [],
        ];
    }

    private function has_tiamr_rule(int $menu_item_id): bool {
        $keys = [
            Meta::CONDITION,
            Meta::ACTION,
            Meta::AUDIENCE,
            Meta::LOCATION,
            Meta::ROLES,
            Meta::PAGES,
            Meta::POSTS,
        ];

        foreach ($keys as $key) {
            if (metadata_exists('post', $menu_item_id, $key)) {
                return true;
            }
        }

        return false;
    }

    private function has_pro_rule(int $menu_item_id): bool {
        $groups = get_post_meta($menu_item_id, self::TIAMR_PRO_GROUPS_META, true);

        if (is_array($groups) && !empty($groups)) {
            return true;
        }

        if (is_string($groups) && '' !== trim($groups)) {
            return true;
        }

        return false;
    }

    /**
     * @param array{which_users:string,can_see:string,roles:array} $options
     */
    private function update_pro_rule_from_user_menus_options(int $menu_item_id, array $options): void {
        $which_users = sanitize_key((string) ($options['which_users'] ?? ''));
        $can_see     = sanitize_key((string) ($options['can_see'] ?? 'yes'));
        $roles       = Sanitizer::sanitize_roles($options['roles'] ?? []);

        $conditions = [];

        if ('logged_out' === $which_users) {
            $conditions[] = $this->pro_login_condition(Meta::ACTION_SHOW, 'logged_out');
        } elseif ('logged_in' === $which_users) {
            if (empty($roles)) {
                $conditions[] = $this->pro_login_condition(Meta::ACTION_SHOW, 'logged_in');
            } elseif ('no' === $can_see) {
                // Exact Pro equivalent of User Menus:
                // logged-in users can see it, except the selected roles.
                $conditions[] = $this->pro_login_condition(Meta::ACTION_SHOW, 'logged_in');
                $conditions[] = $this->pro_role_condition(Meta::ACTION_HIDE, 'is', $roles);
            } else {
                $conditions[] = $this->pro_role_condition(Meta::ACTION_SHOW, 'is', $roles);
            }
        }

        if (empty($conditions)) {
            delete_post_meta($menu_item_id, self::TIAMR_PRO_GROUPS_META);
            delete_post_meta($menu_item_id, self::TIAMR_PRO_ACTION_META);
            delete_post_meta($menu_item_id, self::TIAMR_PRO_ENABLED_META);
            return;
        }

        update_post_meta($menu_item_id, self::TIAMR_PRO_GROUPS_META, [
            [
                'action'     => Meta::ACTION_SHOW,
                'conditions' => $conditions,
            ],
        ]);

        delete_post_meta($menu_item_id, self::TIAMR_PRO_ACTION_META);
        delete_post_meta($menu_item_id, self::TIAMR_PRO_ENABLED_META);
    }

    /**
     * @return array{action:string,type:string,operator:string,values:array,labels:array}
     */
    private function pro_login_condition(string $action, string $status): array {
        return [
            'action'   => $action,
            'type'     => 'login_status',
            'operator' => 'is',
            'values'   => [$status],
            'labels'   => [
                $status => 'logged_out' === $status
                    ? __('Logged Out', 'titanium-advanced-menu-rules')
                    : __('Logged In', 'titanium-advanced-menu-rules'),
            ],
        ];
    }

    /**
     * @param string[] $roles
     * @return array{action:string,type:string,operator:string,values:array,labels:array}
     */
    private function pro_role_condition(string $action, string $operator, array $roles): array {
        return [
            'action'   => $action,
            'type'     => 'role',
            'operator' => $operator,
            'values'   => $roles,
            'labels'   => $this->get_role_labels($roles),
        ];
    }

    /**
     * @param string[] $roles
     * @return array<string,string>
     */
    private function get_role_labels(array $roles): array {
        $labels   = [];
        $wp_roles = wp_roles();

        foreach ($roles as $role) {
            $labels[$role] = isset($wp_roles->roles[$role]['name'])
                ? translate_user_role($wp_roles->roles[$role]['name'])
                : $role;
        }

        return $labels;
    }
}
