<?php
namespace Tiamr\Admin;

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

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

final class Nav_Menu_Roles_Importer {
    public const NMR_ROLE_META = '_nav_menu_role';
    public const NMR_DISPLAY_MODE_META = '_nav_menu_role_display_mode';

    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']++;

            $rule = $this->map_nav_menu_roles_rule($menu_item_id);

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

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

            $stats['importable']++;

            if ($this->has_tiamr_rule($menu_item_id)) {
                $stats['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']++;

            $rule = $this->map_nav_menu_roles_rule($menu_item_id);

            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_free_rule($menu_item_id, $rule);
                    $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::NMR_ROLE_META,
                    'compare' => 'EXISTS',
                ],
            ],
        ]);

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

    /**
     * @return array{action:string,audience:string,location:string,roles:array,pages:array,posts:array}|null
     */
    private function map_nav_menu_roles_rule(int $menu_item_id): ?array {
        $target = get_post_meta($menu_item_id, self::NMR_ROLE_META, true);

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

        $display_mode = (string) get_post_meta($menu_item_id, self::NMR_DISPLAY_MODE_META, true);
        $action       = 'hide' === $display_mode ? Meta::ACTION_HIDE : Meta::ACTION_SHOW;

        $audience = Meta::AUDIENCE_EVERYONE;
        $roles    = [];

        if (is_array($target)) {
            $roles = Sanitizer::sanitize_roles($target);

            if (empty($roles)) {
                return null;
            }

            $audience = Meta::AUDIENCE_ROLE_IS;
        } elseif ('in' === $target) {
            $audience = Meta::AUDIENCE_LOGGED_IN;
        } elseif ('out' === $target) {
            $audience = Meta::AUDIENCE_LOGGED_OUT;
        } else {
            return null;
        }

        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;
    }

    /**
     * Store a Pro condition group that matches the imported Free-compatible rule.
     * This lets users choose to make the imported Nav Menu Roles rule the active
     * rule even when Titanium Nav Menu Roles & Visibility Rules is active later.
     *
     * @param array{action:string,audience:string,location:string,roles:array,pages:array,posts:array} $rule
     */
    private function update_pro_rule_from_free_rule(int $menu_item_id, array $rule): void {
        $action     = Meta::ACTION_HIDE === ($rule['action'] ?? '') ? Meta::ACTION_HIDE : Meta::ACTION_SHOW;
        $audience   = (string) ($rule['audience'] ?? Meta::AUDIENCE_EVERYONE);
        $conditions = [];

        if (Meta::AUDIENCE_ROLE_IS === $audience || Meta::AUDIENCE_ROLE_IS_NOT === $audience) {
            $roles = Sanitizer::sanitize_roles($rule['roles'] ?? []);

            if (!empty($roles)) {
                $conditions[] = [
                    'action'   => $action,
                    'type'     => 'role',
                    'operator' => Meta::AUDIENCE_ROLE_IS_NOT === $audience ? 'is_not' : 'is',
                    'values'   => $roles,
                    'labels'   => $this->get_role_labels($roles),
                ];
            }
        } elseif (Meta::AUDIENCE_LOGGED_IN === $audience || Meta::AUDIENCE_LOGGED_OUT === $audience) {
            $status = Meta::AUDIENCE_LOGGED_OUT === $audience ? 'logged_out' : 'logged_in';

            $conditions[] = [
                'action'   => $action,
                'type'     => 'login_status',
                'operator' => 'is',
                'values'   => [$status],
                'labels'   => [
                    $status => Meta::AUDIENCE_LOGGED_OUT === $audience
                        ? __('Logged Out', 'titanium-advanced-menu-rules')
                        : __('Logged In', 'titanium-advanced-menu-rules'),
                ],
            ];
        } elseif (Meta::AUDIENCE_EVERYONE === $audience) {
            $conditions[] = [
                'action'   => $action,
                'type'     => 'everyone',
                'operator' => 'is',
                'values'   => ['1'],
                'labels'   => [
                    '1' => __('Everyone', 'titanium-advanced-menu-rules'),
                ],
            ];
        }

        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'     => $action,
                'conditions' => $conditions,
            ],
        ]);

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

    /**
     * @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;
    }
}
