<?php

use App\Modules\ModuleContract;
use App\Modules\ModuleRepository;
use Illuminate\Support\Collection;

/**
 * Return modular repo
 */
function modular(): ModuleRepository
{
    return app(ModuleRepository::class);
}

/**
 * Modules directory path
 */
function modules_path(string $path = ''): string
{
    return modular()->path().'/'.$path;
}

/**
 * Get module
 */
function module(string $moduleName): ModuleContract
{
    return modular()->module($moduleName);
}

/**
 * Modules
 *
 * @return Collection<array-key, App\Modules\BaseModule>
 */
function modules(bool $withDisabled = false): Collection
{
    return $withDisabled ? modular()->allModules() : modular()->modules();
}

/**
 * Empty module
 */
function emptyModule(): ModuleContract
{
    return modular()->emptyModule();
}

/**
 * Module exists
 */
function module_exists(string $moduleName): bool
{
    return modular()->moduleExists($moduleName);
}

/**
 * Cast to string
 */
function string(mixed $value): string
{
    if (! is_string($value)) {
        throw new Exception('!!!Make it string!!!');
    }

    return $value;
}

/**
 * Create an anonymous dynamic object with both properties and callable methods.
 *
 * You can define any mix of:
 *  - scalar or object properties
 *  - closures (which become methods bound to $this)
 *
 * Example:
 * ```php
 * $user = emptyClass(
 *     name: 'Ali',
 *     greet: fn(): string => "Hello, {$this->name}",
 *     setName: fn(string $n): void => $this->name = $n,
 * );
 *
 * echo $user->greet();   // Hello, Ali
 * $user->setName('Ahmet');
 * echo $user->greet();   // Hello, Ahmet
 * ```
 *
 *
 * @param  mixed  ...$arguments  Key-value pairs (property name => value or Closure)
 */
function emptyClass(mixed ...$arguments): object
{
    /**
     * @var array<string, mixed> $arguments
     */
    return new class($arguments)
    {
        /**
         * Internal data store.
         *
         * @var array<string, mixed>
         */
        private array $data = [];

        /**
         * Constructor that sets up dynamic properties and binds closures.
         *
         * @param  array<string, mixed>  $props
         */
        public function __construct(array $props)
        {
            foreach ($props as $key => $value) {
                if ($value instanceof Closure) {
                    /** @var Closure $value */
                    $value = $value->bindTo($this, self::class);
                }

                $this->data[$key] = $value;
            }
        }

        /**
         * Magic getter.
         *
         * @return mixed|null
         */
        public function __get(string $key): mixed
        {
            return $this->data[$key] ?? null;
        }

        /**
         * Magic setter.
         */
        public function __set(string $key, mixed $value): void
        {
            $this->data[$key] = $value;
        }

        /**
         * Magic isset.
         */
        public function __isset(string $key): bool
        {
            return isset($this->data[$key]);
        }

        /**
         * Magic method caller — invokes stored closures as methods.
         *
         * @param  array<int, mixed>  $args
         *
         * @throws BadMethodCallException if the method does not exist or is not callable.
         */
        public function __call(string $method, array $args): mixed
        {
            $callable = $this->data[$method] ?? null;

            if ($callable instanceof Closure) {
                return $callable(...$args);
            }

            throw new BadMethodCallException(sprintf(
                'Method %s() does not exist on %s.',
                $method,
                self::class
            ));
        }
    };
}
