<?php

use Illuminate\Support\Traits\ReflectsClosures;

if (! function_exists('lazy')) {
    /**
     * Create a lazy instance.
     *
     * @template TValue of object
     *
     * @param  class-string<TValue>|(\Closure(TValue): mixed)  $class
     * @param  (\Closure(TValue): mixed)|int  $callback
     * @param  int  $options
     * @param  array<string, mixed>  $eager
     * @return TValue
     *
     * @throws \ReflectionException
     */
    function lazy($class, $callback = 0, $options = 0, $eager = [])
    {
        static $closureReflector;

        $closureReflector ??= new class
        {
            use ReflectsClosures;

            public function typeFromParameter($callback)
            {
                return $this->firstClosureParameterType($callback);
            }
        };

        [$class, $callback, $options] = is_string($class)
            ? [$class, $callback, $options]
            : [$closureReflector->typeFromParameter($class), $class, $callback ?: $options];

        $reflectionClass = new ReflectionClass($class);

        $instance = $reflectionClass->newLazyGhost(function ($instance) use ($callback) {
            $result = $callback($instance);

            if (is_array($result)) {
                $instance->__construct(...$result);
            }
        }, $options);

        foreach ($eager as $property => $value) {
            $reflectionClass->getProperty($property)->setRawValueWithoutLazyInitialization($instance, $value);
        }

        return $instance;
    }
}

if (! function_exists('proxy')) {
    /**
     * Create a lazy proxy instance.
     *
     * @template TValue of object
     *
     * @param  class-string<TValue>|(\Closure(TValue): TValue)  $class
     * @param  (\Closure(TValue): TValue)|int  $callback
     * @param  int  $options
     * @param  array<string, mixed>  $eager
     * @return TValue
     *
     * @throws \ReflectionException
     */
    function proxy($class, $callback = 0, $options = 0, $eager = [])
    {
        static $closureReflector;

        $closureReflector ??= new class
        {
            use ReflectsClosures;

            public function get($callback)
            {
                return $this->closureReturnTypes($callback)[0] ?? $this->firstClosureParameterType($callback);
            }
        };

        [$class, $callback, $options] = is_string($class)
            ? [$class, $callback, $options]
            : [$closureReflector->get($class), $class, $callback ?: $options];

        $reflectionClass = new ReflectionClass($class);

        $proxy = $reflectionClass->newLazyProxy(function () use ($callback, $eager, &$proxy) {
            $instance = $callback($proxy, $eager);

            return $instance;
        }, $options);

        foreach ($eager as $property => $value) {
            $reflectionClass->getProperty($property)->setRawValueWithoutLazyInitialization($proxy, $value);
        }

        return $proxy;
    }
}
