<?php

namespace WordpressModels\DependencyInjection\Metabox;

use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Metabox service registry.
 */
class MetaboxRegistry
{

    /**
     * @var array{
     *     id: string,
     *     title: string,
     *     callback: callable,
     *     screen: string,
     *     context: string,
     *     priority: string,
     *     callback_args: array
     * }
     */
    private array $metaboxes = [];

    /**
     * @var array<string, bool>
     */
    private array $shouldRenderMetabox;

    public function __construct(private ContainerInterface $container)
    {
    }

    /**
     * Registers the metaboxes for the current action to WordPress.
     *
     * @param array $args
     * @return void
     */
    public function doAddMetabox(...$args)
    {
        $metaboxes = $this->metaboxes[current_action()];

        foreach ($metaboxes as $metabox) {
            $this->shouldRenderMetabox[$metabox['id']] = apply_filters("wpm_should_render_metabox_{$metabox['id']}", true, ...$args);
            if ($this->shouldRenderMetabox[$metabox['id']]) {
                add_meta_box($metabox['id'],
                    $metabox['title'],
                    $metabox['callback'],
                    $metabox['screen'],
                    $metabox['context'],
                    $metabox['priority'],
                    $metabox['callback_args']
                );
            }
        }
    }

    /**
     * Schedule actions for metaboxes.
     *
     * Iterates over the metaboxes and schedules the actions for each.
     *
     * @return void
     */
    public function registerMetaboxes()
    {
        foreach ($this->metaboxes as $hook => $metaboxes) {
            // add the action to add the metabox
            add_action($hook, [$this, 'doAddMetabox']);

            // add additional hooks for each metabox
            foreach ($metaboxes as $metabox) {
                $id = $metabox['id'];
                $screen = $metabox['screen'];
                $serviceId = self::getMetaboxServiceId($id, $screen);
                $service = $this->container->get($serviceId);

                if (is_a($service, AbstractMetabox::class)) {
                    // add shouldRender hook
                    add_filter("wpm_should_render_metabox_$id", [$service, 'shouldRender'], 10, 2);

                    // add script enqueue hooks
                    add_action("admin_enqueue_scripts", fn() => $this->scheduleScriptEnqueue($service, $metabox));
                }
            }
        }
    }

    /**
     * Runs the script enqueue for a metabox.
     *
     * @param AbstractMetabox $service
     * @param array $metabox
     * @return void
     */
    public function scheduleScriptEnqueue(AbstractMetabox $service, array $metabox): void
    {
        // dont enqueue if the metabox should not render
        if ($this->shouldRenderMetabox[$metabox['id']] ?? false) {
            $service->enqueueScript();
        }
    }

    /**
     * Add a metabox to the registry.
     * Runs for all tagged 'metabox' services at container initialization.
     *
     * @param string $id
     * @param string $title
     * @param callable $callback
     * @param string $context
     * @param string $priority
     * @param string|array|null $screen
     * @param array $args
     * @return void
     */
    public function addMetabox(string            $id,
                               string            $title,
                               callable          $callback,
                               string            $context = 'advanced',
                               string            $priority = 'default',
                               string|array|null $screen = null,
                               array             $args = [])
    {
        if (is_string($screen)) {
            $screen = [$screen];
        } elseif (is_null($screen)) {
            $screen = [null];
        }

        foreach ($screen as $type) {
            $hook = !$type ? 'add_meta_boxes' : "add_meta_boxes_$type";

            $this->metaboxes[$hook][] = [
                'id' => $id,
                'title' => $title,
                'callback' => $callback,
                'screen' => $type,
                'context' => $context,
                'priority' => $priority,
                'callback_args' => $args
            ];
        }
    }

    /**
     * @return array
     */
    public function getMetaboxes(): array
    {
        return $this->metaboxes;
    }

    public static function getMetaboxServiceId(string $id, ?string $screen = null)
    {
        return !$screen ? "metabox.$id" : "metabox.$screen.$id";
    }

}