<?php

namespace WordpressModels\Page;

/**
 * Extension of the Page class to provide client-side routing with the WordPress admin menu.
 *
 * Make sure your route exists in your client application.
 *
 * For example, when using react and react-router:
 *
 * Your 'main' page: 'admin.php?page=your-page' (aliased as 'admin.php?page=your-page#/')
 *
 * Somewhere in your plugin:
 * ```php
 *   ...
 *  $mainPage = new YourMainPage(...);
 *  $mainPage->addRoute(new YourSubPage(pageId: 'your-sub-page', ...));
 * ```
 *
 * Your 'sub' page will now be: 'admin.php?page=your-page&path=your-sub-page'.
 *
 * This class supports a single level of nesting.
 *
 * ---
 *
 * This class can also be used using Dependency Injection. When using Dependency Injection, inject the PageStack as
 * the $parent parameter of the constructor of the sub page, this will automatically set the subpage as a route
 * of the parent page.
 *
 * There are a couple of different ways to use this class:
 * - Adding a page stack as a toplevel page:
 *   You can do this by setting the {@see AbstractPage::$parent} property to 'toplevel' (default).
 * - Adding a page stack as a subpage:
 *   You can do this by setting the {@see AbstractPage::$parent} property to an existing menu slug or AbstractPage instance.
 * - Adding a page as a route for the stack, but a displayed as a toplevel page:
 *   You can do this by setting the {@see AbstractPage::$parent} to 'toplevel' and adding the page using the {@see AbstractPageStack::addRoute()} method.
 * - Adding a page as a route for the stack, but displayed as a subpage:
 *   You can do this by setting the {@see AbstractPage::$parent} to an existing menu slug or AbstractPage instance and
 *   adding the page using the {@see AbstractPageStack::addRoute()} method.
 * - Adding the page stack, but hiding the main page:
 *   You can do this by setting the {@see AbstractPage::$parent} to an empty string.
 * - Adding the page as a route for the stack, but hiding it in the menu:
 *  You can do this by setting the {@see AbstractPage::$parent} to an empty string and adding the page using the
 *  {@see AbstractPageStack::addRoute()} method, setting the second argument to `false` ($isSubPage).
 */
abstract class AbstractPageStack extends AbstractPage
{

    /**
     * @var AbstractPage[]
     */
    protected array $pages = [];

    public function getRouteUrl(string $route)
    {
        return get_admin_url(path: "admin.php?page=$this->menuSlug&path=/$route");
    }

    /**
     * @param AbstractPage $page
     * @param bool $isSubPage
     * @return void
     */
    public function addRoute(AbstractPage $page, bool $isSubPage = true): void
    {
        if ($page instanceof AbstractPageStack) {
            throw new \InvalidArgumentException('Cannot add a PageStack as a route of a PageStack');
        }

        $this->pages[] = $page;
        // set the menu slug to the route url
        $page->menuSlug = $this->getRouteUrl($page->pageId);

        // add all additional scripts to the stack
        $this->additionalScripts = [
            ...$this->additionalScripts,
            ...$page->additionalScripts
        ];

        // set the parent to the main page if it is a sub page
        if ($isSubPage) {
            $page->setParent($this->menuSlug);
        }

        // clear the render callback, such that wordpress sets the url of the admin item to the route url
        $page->renderCallback = '';
        $page->setRenderCallback('');
    }

    public function doEnqueue(): void
    {
        wp_localize_script($this->pageId, 'pages', array_reduce(
            $this->pages,
            function (array $carry, AbstractPage $page) {
                if (current_user_can($page->capability)) {
                    $carry[] = $page->pageId;
                }
                return $carry;
            }, []));
    }

    public function getUrl(): string
    {
        return $this->getRouteUrl('');
    }

    /**
     * @return AbstractPage[]
     */
    public function getPages(): array
    {
        return $this->pages;
    }

}
