<?php

namespace apexl\Io\includes;

use apexl\Io\enums\HttpMethod;
use apexl\Io\interfaces\IoModuleInterface;
use apexl\Io\services\InstallChecker;
use function apexl\Io\config;

abstract class Module implements IoModuleInterface
{
    protected string $baseRoute = '';
    protected array $defaultEntityPatterns;
    protected bool $patternsOverridden = false;

    protected array $entityPatterns = [];
    protected array $entityControllers = [];

    public function __construct(protected readonly InstallChecker $installChecker) {}

    public function requiresInstall(): bool
    {
        return !empty($this->schema());
    }

    /**
     * @return mixed
     */
    public function schema(): object
    {
        return (object) [];
    }

    public function initialise(): void
    {
        $this->setBaseRoute(strtolower($this->getName()));
    }

    public function setBaseRoute($route): static
    {
        $this->baseRoute = $route;

        return $this;
    }

    public function getName(): string
    {
        return $this->getNameFromNamespace(get_class($this));
    }

    public function getNameFromNamespace($namespace): string
    {
        $moduleSpaces = explode('\\', $namespace);

        return str_replace('Module', '', array_pop($moduleSpaces));
    }

    public function install(): void {}

    public function uninstall(): void {}

    public function apiVersion(): int
    {
        return config('app.api_version', 1);
    }

    protected function setEntityPatterns(Entity $entity, array $patterns = []): static
    {
        $this->defaultEntityPatterns = [
            'get' => '[/{' . $entity->primaryKey . '}]',
            'put' => '[/{' . $entity->primaryKey . '}]',
            'post' => '', //no pattern here as we should POST to the base entity path (by default)
            'delete' => '[/{' . $entity->primaryKey . '}]',
        ];
        $this->patternsOverridden = $patterns !== [];
        $this->entityPatterns[$entity->getEntityName()] = array_merge($this->defaultEntityPatterns, $patterns);

        return $this;
    }

    /**
     * Allows us to set the callables for each entity declared
     */
    protected function setEntityCallable(Entity $entity, string $verb, string $callable): static
    {
        $this->entityControllers[$entity->getEntityName()][$verb] = $callable;

        return $this;
    }

    /**
     * Normal structure is /ModuleName/EntityName/{pattern}
     * Build any default entity routes.
     */
    protected function buildEntityRoutes(RouteManager $routeManager, Entity $entity, bool $rootRoutes = false): void
    {
        $method = $rootRoutes ? 'rootRoute' : 'addRoute';
        foreach ($this->entityPatterns[$entity->getEntityName()] as $verb => $pattern) {
            $this->$method(
                $routeManager,
                HttpMethod::get(strtoupper($verb)),
                sprintf('%s.%s', $entity->getEntityName(), strtolower((string) $verb)),
                sprintf(
                    '%s%s',
                    str_replace(
                        'entity',
                        '',
                        sprintf(
                            'entities/%s',
                            strtolower($entity->getEntityName())
                        )
                    ),
                    $pattern
                ),
                $this->entityControllers[$entity->getEntityName()][$verb]
            );
        }
    }

    protected function rootRoute(RouteManager $routeManager, HttpMethod $verb, string $name, string $pattern, mixed $callable): static
    {
        $routeManager->addRoute(
            $verb,
            $name,
            sprintf(
                '/%s',
                ltrim($pattern, '/')
            ),
            $callable
        );

        $this->addModuleNameToRoute($routeManager, $name);

        return $this;
    }

    protected function addRoute(RouteManager $routeManager, $verb, string $name, string $pattern, mixed $callable): static
    {
        if (is_string($verb)) {
            trigger_error(
                'Passing a string to `Module::addRoute()` is deprecated, please use `HttpMethod` enum instead',
                E_USER_DEPRECATED
            );
            $verb = HttpMethod::from(strtoupper($verb));
        }

        $routeManager->addRoute(
            $verb,
            $name,
            sprintf(
                '%s/%s',
                rtrim($this->baseRoute, '/'),
                ltrim($pattern, '/')
            ),
            $callable
        );

        $this->addModuleNameToRoute($routeManager, $name);

        return $this;
    }

    protected function addModuleNameToRoute(RouteManager $routeManager, $name): Module
    {
        //add the declared module name as a route arg for use later.
        $routeManager->addRouteArg($name, 'moduleName', strtolower($this->getName()));

        return $this;
    }
}
