<?php

declare(strict_types=1);

namespace Inpsyde\MultilingualPress\NavMenu;

use Inpsyde\MultilingualPress\Cache\NavMenuItemsSerializer;
use Inpsyde\MultilingualPress\Core\Admin\Settings\Cache\CacheSettingsRepository;
use Inpsyde\MultilingualPress\Core\Admin\SiteSettingsRepository;
use Inpsyde\MultilingualPress\Framework\Api\SiteRelations;
use Inpsyde\MultilingualPress\Framework\Api\Translations;
use Inpsyde\MultilingualPress\Framework\Cache\Server\Facade;
use Inpsyde\MultilingualPress\Framework\Cache\Server\ItemLogic;
use Inpsyde\MultilingualPress\Framework\Cache\Server\Server;
use Inpsyde\MultilingualPress\Framework\Factory\NonceFactory;
use Inpsyde\MultilingualPress\Framework\Http\ServerRequest;
use Inpsyde\MultilingualPress\Framework\Module\ModuleManager;
use Inpsyde\MultilingualPress\Framework\Service\BootstrappableServiceProvider;
use Inpsyde\MultilingualPress\Framework\Service\Container;
use Inpsyde\MultilingualPress\Framework\Service\IntegrationServiceProvider;
use Inpsyde\MultilingualPress\Framework\WordpressContext;
use Inpsyde\MultilingualPress\Module\Blocks\Context\ContextFactoryInterface;
use Inpsyde\MultilingualPress\NavMenu\BlockTypes\LanguageMenuContextFactory;
use Inpsyde\MultilingualPress\NavMenu\CopyNavMenu\Ajax\CopyNavMenuSettingsView;
use Inpsyde\MultilingualPress\NavMenu\CopyNavMenu\CopyNavMenu;
use Inpsyde\MultilingualPress\Module\Blocks\ServiceProvider as BlocksModule;

use function Inpsyde\MultilingualPress\siteNameWithLanguage;
use function Inpsyde\MultilingualPress\wpHookProxy;

/**
 * @psalm-type relatedSites = array{id: int, name: string}
 */
final class ServiceProvider implements BootstrappableServiceProvider, IntegrationServiceProvider
{
    protected const NONCE_ACTION = 'add_languages_to_nav_menu';
    public const NONCE_COPY_NAV_MENU_ACTION = 'copy_nav_menu';

    /**
     * @inheritdoc
     * phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
     */
    public function register(Container $container)
    {
        // phpcs:enable

        $moduleDirPath = __DIR__ ;

        /**
         * Configuration to the path of module directory.
         */
        $container->share(
            'multilingualpress.NavMenu.ModuleDirPath',
            static function () use ($moduleDirPath): string {
                return $moduleDirPath;
            }
        );

        /**
         * Configuration to the path of module block type templates.
         */
        $container->share(
            'multilingualpress.NavMenu.BlockTypesTemplatePath',
            static function () use ($moduleDirPath): string {
                return $moduleDirPath . '/tpl/';
            }
        );

        $container->addService(
            AjaxHandler::class,
            static function (Container $container): AjaxHandler {
                return new AjaxHandler(
                    $container[NonceFactory::class]->create([self::NONCE_ACTION]),
                    $container[ItemRepository::class],
                    $container[ServerRequest::class]
                );
            }
        );

        $container->addService(
            CopyNavMenuSettingsView::class,
            static function (Container $container): CopyNavMenuSettingsView {
                return new CopyNavMenuSettingsView(
                    $container[NonceFactory::class]->create([self::NONCE_COPY_NAV_MENU_ACTION])
                );
            }
        );

        $container->addService(
            ItemDeletor::class,
            static function (Container $container): ItemDeletor {
                return new ItemDeletor($container[\wpdb::class]);
            }
        );

        $container->addService(
            ItemFilter::class,
            static function (Container $container): ItemFilter {
                return new ItemFilter(
                    $container->get(Translations::class),
                    $container->get(ItemRepository::class),
                    new Facade($container->get(Server::class), ItemFilter::class),
                    $container->get(CacheSettingsRepository::class),
                    $container->get(SiteSettingsRepository::class)
                );
            }
        );

        $container->addService(
            LanguagesMetaboxView::class,
            static function (Container $container): LanguagesMetaboxView {
                $nonce = $container[NonceFactory::class]->create([self::NONCE_ACTION]);
                return new LanguagesMetaboxView($nonce);
            }
        );

        $container->addService(
            CopyNavMenu::class,
            static function (Container $container): CopyNavMenu {
                return new CopyNavMenu(
                    $container[NonceFactory::class]->create([self::NONCE_COPY_NAV_MENU_ACTION]),
                    $container[ServerRequest::class]
                );
            }
        );

        $container->share(
            ItemRepository::class,
            static function (): ItemRepository {
                return new ItemRepository();
            }
        );

        /**
         * Configuration for related sites.
         *
         * @return array The list of related sites info, which is a map of a site ID to site name.
         * @psalm-return array<relatedSites>
         */
        $container->share(
            'multilingualpress.NavMenu.RelatedSites',
            static function (Container $container): array {
                $siteRelations = $container->get(SiteRelations::class);
                $relatedSiteIds = $siteRelations->relatedSiteIds(get_current_blog_id(), true);
                $relatedSitesConfig = [];

                foreach ($relatedSiteIds as $key => $siteId) {
                    $relatedSitesConfig[$key]['id'] = $siteId;
                    $relatedSitesConfig[$key]['name'] = siteNameWithLanguage($siteId);
                }

                return $relatedSitesConfig;
            }
        );

        $container->share(
            LanguageMenuContextFactory::class,
            static function (Container $container): ContextFactoryInterface {
                return new LanguageMenuContextFactory(
                    $container->get(Translations::class),
                    $container->get('multilingualpress.siteFlags.flagFactory')
                );
            }
        );

        $moduleManager = $container->get(ModuleManager::class);
        if ($moduleManager->isModuleActive(BlocksModule::MODULE_ID)) {
            /**
             * Configuration for block types.
             */
            $container->extend(
                'multilingualpress.Blocks.BlockTypes',
                require $moduleDirPath . '/BlockTypes/block-types.php'
            );
        }
    }

    /**
     * @inheritdoc
     */
    public function integrate(Container $container)
    {
        if (!is_admin()) {
            $this->integrateCache($container);
        }
    }

    /**
     * @inheritdoc
     */
    public function bootstrap(Container $container)
    {
        $this->handleDeleteSiteAction($container[ItemDeletor::class]);

        if (is_admin()) {
            $this->bootstrapAdmin($container);

            return;
        }

        $itemFilter = $container[ItemFilter::class];
        add_filter('wp_nav_menu_objects', wpHookProxy([$itemFilter, 'filterItems']), PHP_INT_MAX);
    }

    /**
     * @param Container $container
     */
    private function bootstrapAdmin(Container $container)
    {
        $metaboxView = $container[LanguagesMetaboxView::class];
        $wordpressContext = $container[WordpressContext::class];
        $copyNavMenu = $container[CopyNavMenu::class];

        add_action(
            'admin_init',
            static function () use ($metaboxView, $wordpressContext, $copyNavMenu) {
                if ($wordpressContext->isType(WordPressContext::TYPE_CUSTOMIZER)) {
                    return;
                }

                add_meta_box(
                    'mlp-languages',
                    esc_html__('Languages', 'multilingualpress'),
                    [$metaboxView, 'render'],
                    'nav-menus',
                    'side',
                    'low'
                );

                $copyNavMenu->handleCopyNavMenu();
            }
        );

        add_action(
            'wp_ajax_' . AjaxHandler::ACTION,
            [$container[AjaxHandler::class], 'handle']
        );

        add_action(
            'wp_ajax_' . CopyNavMenuSettingsView::ACTION,
            [$container[CopyNavMenuSettingsView::class], 'handle']
        );

        $itemRepository = $container[ItemRepository::class];

        add_filter(
            'wp_setup_nav_menu_item',
            static function ($item) use ($itemRepository) {
                if ($itemRepository->siteIdOfMenuItem((int)($item->ID ?? 0))) {
                    $item->type_label = esc_html__(
                        'Language',
                        'multilingualpress'
                    );
                }

                return $item;
            }
        );
    }

    /**
     * @param Container $container
     */
    private function integrateCache(Container $container)
    {
        $itemFilter = $container[ItemFilter::class];
        $itemFilterCacheLogic =
            (new ItemLogic(ItemFilter::class, ItemFilter::ITEMS_FILTER_CACHE_KEY))
                ->generateKeyWith(
                    static function (string $key, array $postArrays): string {
                        $url = trim((string) parse_url(add_query_arg([]), PHP_URL_PATH), '/');
                        if ($_GET) { // phpcs:ignore
                            $url = add_query_arg($_GET, $url); // phpcs:ignore
                        }
                        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
                        $key .= substr(md5(serialize(array_keys($postArrays)) . $url), -12, 10);
                        return $key;
                    }
                )
                ->updateWith(
                    static function (array $postArrays) use ($itemFilter): array {

                        $postArrays = array_values($postArrays);
                        $serializer = NavMenuItemsSerializer::fromSerialized(...$postArrays);
                        $filtered = $itemFilter->filterItems($serializer->unserialize());

                        return NavMenuItemsSerializer::fromWpPostItems(...$filtered)->serialize();
                    }
                );

        $container[Server::class]->register($itemFilterCacheLogic);
    }

    /**
     * @param ItemDeletor $itemDeletor
     * @return void
     * @throws \Throwable
     */
    private function handleDeleteSiteAction(ItemDeletor $itemDeletor)
    {
        global $wp_version;
        if (version_compare($wp_version, '5.1', '<')) {
            add_action('delete_blog', wpHookProxy(static function (int $siteId) use ($itemDeletor) {
                $site = get_site($siteId);
                $site and $itemDeletor->deleteItemsForDeletedSite($site);
            }));
            return;
        }

        add_action('wp_uninitialize_site', wpHookProxy([$itemDeletor, 'deleteItemsForDeletedSite']));
    }
}
