<?php

namespace Livewire;

use WeakMap;
use Livewire\Drawer\Utils;

class ComponentHookRegistry
{
    protected static $components;

    protected static $componentHooks = [];

    static function register($hook)
    {
        if (method_exists($hook, 'provide')) $hook::provide();

        if (in_array($hook, static::$componentHooks)) return;

        static::$componentHooks[] = $hook;
    }

    static function getHook($component, $hook)
    {
        if (! isset(static::$components[$component])) return;

        $componentHooks = static::$components[$component];

        foreach ($componentHooks as $componentHook) {
            if ($componentHook instanceof $hook) return $componentHook;
        }
    }

    static function boot()
    {
        static::$components = new WeakMap;

        foreach (static::$componentHooks as $hook) {
            on('mount', function ($component, $params, $key, $parent, $attributes) use ($hook) {
                if (! $hook = static::initializeHook($hook, $component)) {
                    return;
                }

                $hook->callBoot();
                $hook->callMount($params, $parent, $attributes);
            });

            on('hydrate', function ($component, $memo) use ($hook) {
                if (! $hook = static::initializeHook($hook, $component)) {
                    return;
                }

                $hook->callBoot();
                $hook->callHydrate($memo);
            });
        }

        on('update', function ($component, $fullPath, $newValue) {
            $propertyName = Utils::beforeFirstDot($fullPath);

            return static::proxyCallToHooks($component, 'callUpdate')($propertyName, $fullPath, $newValue);
        });

        on('call', function ($component, $method, $params, $componentContext, $earlyReturn, $metadata) {
            return static::proxyCallToHooks($component, 'callCall')($method, $params, $earlyReturn, $metadata, $componentContext);
        });

        on('render', function ($component, $view, $data) {
            return static::proxyCallToHooks($component, 'callRender')($view, $data);
        });

        on('renderIsland', function ($component, $name, $view, $data) {
            return static::proxyCallToHooks($component, 'callRenderIsland')($name, $view, $data);
        });

        on('dehydrate', function ($component, $context) {
            static::proxyCallToHooks($component, 'callDehydrate')($context);
        });

        on('destroy', function ($component, $context) {
            static::proxyCallToHooks($component, 'callDestroy')($context);
        });

        on('exception', function ($target, $e, $stopPropagation) {
            if ($target instanceof \Livewire\Features\SupportFormObjects\Form) {
                $target = $target->getComponent();
            }

            if ($target instanceof \Livewire\Component) {
                static::proxyCallToHooks($target, 'callException')($e, $stopPropagation);
            }
        });
    }

    static public function initializeHook($hook, $target)
    {
        if (! isset(static::$components[$target])) static::$components[$target] = [];

        $hook = new $hook;

        $hook->setComponent($target);

        // If no `skip` method has been implemented, then boot the hook anyway
        if (method_exists($hook, 'skip') && $hook->skip()) {
            return;
        }

        static::$components[$target][] = $hook;

        return $hook;
    }

    static function proxyCallToHooks($target, $method) {
        return function (...$params) use ($target, $method) {
            $forwardCallbacks = [];

            foreach (static::$components[$target] ?? [] as $hook) {
                if ($callback = $hook->{$method}(...$params)) {
                    $forwardCallbacks[] = $callback;
                }
            }

            return function (...$forwards) use ($forwardCallbacks) {
                foreach ($forwardCallbacks as $callback) {
                    $callback(...$forwards);
                }
            };
        };
    }
}
