<?php

use MintyPHP\Http\Input\FormErrors;
use MintyPHP\Http\Input\RequestInput;
use MintyPHP\Http\Input\RequestInputFactory;
use MintyPHP\Http\Request;
use MintyPHP\Router;
use MintyPHP\Service\Access\AuthorizationDecision;
use MintyPHP\Session;
use MintyPHP\Support\Flash;

function requestInput(): RequestInput
{
    static $requestInput = null;
    if ($requestInput instanceof RequestInput) {
        return $requestInput;
    }

    $factory = app(RequestInputFactory::class);
    $requestInput = $factory->create();
    return $requestInput;
}

function formErrors(): FormErrors
{
    return new FormErrors();
}

function formErrorsFrom(array|FormErrors $errors): FormErrors
{
    return formErrors()->merge($errors);
}

function flashFormErrors(FormErrors $errors, ?string $scope = null, string $keyPrefix = 'form_error'): void
{
    $index = 0;
    foreach ($errors->toFlatList() as $message) {
        $key = $keyPrefix . '_' . $index;
        Flash::error((string) $message, $scope, $key);
        $index++;
    }
}

/**
 * Enforce POST request method for mutation actions.
 *
 * Browser requests are redirected to $redirect.
 * Optional JSON mode returns 405 with a JSON error payload.
 */
function actionRequirePost(
    string $redirect,
    bool $jsonAware = false,
    string $jsonError = 'method_not_allowed',
    int $jsonStatusCode = 405,
    array $jsonPayload = [],
    bool $forceJson = false
): bool {
    if (requestInput()->isMethod('POST')) {
        return true;
    }

    if ($jsonAware && ($forceJson || Request::wantsJson())) {
        header('Allow: POST');
        http_response_code($jsonStatusCode);
        $payload = $jsonPayload !== []
            ? $jsonPayload
            : ['error' => trim($jsonError) !== '' ? trim($jsonError) : 'method_not_allowed'];
        Router::json($payload);
        return false;
    }

    Router::redirect($redirect);
    return false;
}

/**
 * Enforce CSRF protection for POST actions.
 *
 * Returns false after responding/redirecting when token validation fails.
 */
function actionRequireCsrf(
    string $redirect,
    ?string $flashScope = null,
    string $flashKeyPrefix = 'csrf_expired',
    bool $jsonAware = false,
    string $jsonError = 'csrf',
    bool $flashOnFailure = true,
    bool $redirectOnFailure = true,
    int $jsonStatusCode = 400,
    array $jsonPayload = [],
    bool $forceJson = false
): bool {
    if (Session::checkCsrfToken()) {
        return true;
    }

    if ($jsonAware && ($forceJson || Request::wantsJson())) {
        http_response_code($jsonStatusCode);
        $payload = $jsonPayload !== []
            ? $jsonPayload
            : ['error' => trim($jsonError) !== '' ? trim($jsonError) : 'csrf'];
        Router::json($payload);
        return false;
    }

    if ($flashOnFailure) {
        $errorBag = formErrors();
        $errorBag->addGlobal(t('Form expired, please try again'));
        flashFormErrors($errorBag, $flashScope ?? $redirect, $flashKeyPrefix);
    }

    if ($redirectOnFailure) {
        Router::redirect($redirect);
    }
    return false;
}

/**
 * Handle authorization decisions consistently for JSON and browser requests.
 */
function actionAllowOrDeny(
    AuthorizationDecision $decision,
    string $redirect = 'error/forbidden',
    bool $jsonAware = true
): bool {
    if ($decision->isAllowed()) {
        return true;
    }

    if ($jsonAware && Request::wantsJson()) {
        http_response_code($decision->status());
        Router::json(['error' => $decision->error()]);
        return false;
    }

    if ($redirect === 'error/forbidden') {
        Router::redirect('error/forbidden?url=' . urlencode(Request::pathWithQuery()));
        return false;
    }

    Router::redirect($redirect);
    return false;
}

/**
 * Consume and remove flash messages matching any provided scope.
 *
 * @param array<int, string> $scopes
 * @return array<int, array<string, mixed>>
 */
function flashConsumeForScopes(array $scopes): array
{
    return Flash::consumeForScopes($scopes);
}

function requestExtractNestedReturnValue(mixed $value): string
{
    if (is_string($value)) {
        return trim($value);
    }
    if (!is_array($value)) {
        return '';
    }

    for ($i = count($value) - 1; $i >= 0; $i--) {
        $item = $value[$i] ?? null;
        if (is_string($item) && trim($item) !== '') {
            return trim($item);
        }
    }

    return '';
}

function requestLooksLikeDetailPath(string $path): bool
{
    $path = trim($path, '/');
    if ($path === '') {
        return false;
    }

    $segments = array_values(array_filter(explode('/', $path), static fn (string $segment): bool => $segment !== ''));
    return in_array('edit', $segments, true) || in_array('create', $segments, true);
}

function requestNormalizeSafeInternalTarget(string $target, int $maxDepth = 5): string
{
    $target = trim($target);
    if ($target === '') {
        return '';
    }

    $parts = parse_url($target);
    if (
        !is_array($parts)
        || ($parts['scheme'] ?? '') !== ''
        || ($parts['host'] ?? '') !== ''
    ) {
        return '';
    }

    $safeTarget = Request::safeReturnTarget($target);
    if ($safeTarget === '') {
        return '';
    }

    $safeParts = parse_url($safeTarget);
    if (!is_array($safeParts)) {
        return '';
    }

    $path = Request::stripLocale(Request::stripBasePath((string) ($safeParts['path'] ?? '')));
    if ($path === '') {
        return '';
    }

    $queryParams = [];
    parse_str((string) ($safeParts['query'] ?? ''), $queryParams);
    $nestedReturnTarget = requestExtractNestedReturnValue($queryParams['return'] ?? null);
    unset($queryParams['return']);
    $query = http_build_query($queryParams);
    $normalizedOuterTarget = $path . ($query !== '' ? '?' . $query : '');

    if ($maxDepth > 0 && $nestedReturnTarget !== '' && requestLooksLikeDetailPath($path)) {
        $resolvedNestedTarget = requestNormalizeSafeInternalTarget($nestedReturnTarget, $maxDepth - 1);
        if ($resolvedNestedTarget !== '') {
            return $resolvedNestedTarget;
        }
    }

    return $normalizedOuterTarget;
}

function requestResolveReturnTargetFromInput(RequestInput $input, string $fallback = ''): string
{
    $normalizedFallback = requestNormalizeSafeInternalTarget($fallback);

    $candidate = $input->body('return', '');
    if (!is_string($candidate)) {
        $candidate = '';
    }
    $candidate = trim($candidate);

    if ($candidate === '') {
        $candidate = $input->query('return', '');
        if (!is_string($candidate)) {
            $candidate = '';
        }
        $candidate = trim($candidate);
    }

    if ($candidate !== '') {
        $resolved = requestNormalizeSafeInternalTarget($candidate);
        if ($resolved !== '') {
            if ($normalizedFallback === '') {
                return $resolved;
            }
            $resolvedPath = (string) parse_url($resolved, PHP_URL_PATH);
            if ($resolvedPath !== '' && !requestLooksLikeDetailPath($resolvedPath)) {
                return $resolved;
            }
        }
    }

    return $normalizedFallback;
}

function requestResolveReturnTarget(string $fallback = ''): string
{
    return requestResolveReturnTargetFromInput(requestInput(), $fallback);
}

function requestPathWithReturnTarget(string $path, string $returnTarget = ''): string
{
    $resolvedPath = requestNormalizeSafeInternalTarget($path);
    if ($resolvedPath === '') {
        return '';
    }

    $returnTarget = trim($returnTarget);
    if ($returnTarget === '') {
        return $resolvedPath;
    }

    $normalizedReturnTarget = requestNormalizeSafeInternalTarget($returnTarget);
    if ($normalizedReturnTarget === '') {
        return $resolvedPath;
    }

    $parts = parse_url($resolvedPath);
    if (!is_array($parts)) {
        return '';
    }
    $pathValue = Request::stripLocale(Request::stripBasePath((string) ($parts['path'] ?? '')));
    if ($pathValue === '') {
        return '';
    }
    $queryParams = [];
    parse_str((string) ($parts['query'] ?? ''), $queryParams);
    unset($queryParams['return']);
    $queryParams['return'] = $normalizedReturnTarget;
    $query = http_build_query($queryParams);

    return $pathValue . ($query !== '' ? '?' . $query : '');
}
