<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\services;

use apexl\Config\ConfigurationInterface;
use apexl\Io\modules\display\Controllers\DisplayController;

/**
 * Where PermissionMiddleware sends a signed-in user who is denied a display
 * route. Pure — no container, no DB — so it is unit-testable on its own.
 */
final class AccessDeniedRedirect
{
    /** Config key. Unset/blank keeps the historic login redirect for everyone. */
    public const string CONFIG_KEY = 'auth.access_denied.redirect';

    /**
     * The configured target verbatim, or null when unset/blank. Null means "no
     * opt-in": the middleware then behaves exactly as it did before this existed.
     */
    public static function configured(ConfigurationInterface $config): ?string
    {
        if (!$config->has(self::CONFIG_KEY)) {
            return null;
        }

        $value = trim((string) ($config->get(self::CONFIG_KEY) ?? ''));

        return $value === '' ? null : $value;
    }

    /**
     * Redirect target for a denied display route. With no access-denied path
     * configured this is always the login path — i.e. the historic behaviour.
     */
    public static function target(?string $deniedPath, bool $isLoggedIn, ?string $loginPath): ?string
    {
        return $isLoggedIn && $deniedPath !== null ? $deniedPath : $loginPath;
    }

    /** A leading slash marks a literal path; anything else is a registered route name. */
    public static function isPath(string $target): bool
    {
        return str_starts_with($target, '/');
    }

    /**
     * Strict display-route detection. Routes are commonly registered as
     * "Fqcn::method" strings, which is_subclass_of() on the whole string never matches.
     */
    public static function isDisplayRoute(mixed $callable): bool
    {
        if (is_string($callable)) {
            $class = str_contains($callable, '::') ? explode('::', $callable, 2)[0] : $callable;

            return is_a($class, DisplayController::class, true);
        }

        if (is_array($callable) && isset($callable[0])) {
            $class = is_object($callable[0]) ? $callable[0]::class : (string) $callable[0];

            return is_a($class, DisplayController::class, true);
        }

        return $callable instanceof DisplayController;
    }
}
