<?php
/**
 * Validates the minimum legal fields of a withdrawal declaration.
 *
 * @package SkyServers\Retragere
 */

declare(strict_types=1);

namespace SkyServers\Retragere\Core;

/**
 * Pure validator. Knows nothing about WordPress: it cleans and checks,
 * escaping for output is the adapter's job.
 */
final class RequestValidator
{
    public const MAX_NAME   = 120;
    public const MAX_EMAIL  = 190;
    public const MAX_REF    = 100;
    public const MAX_REASON = 2000;

    public const ERR_REQUIRED = 'required';
    public const ERR_TOO_LONG = 'too_long';
    public const ERR_INVALID  = 'invalid';
    public const ERR_SPAM     = 'spam';

    /**
     * @param array<string,mixed> $input
     *
     * @return array{valid:bool,errors:array<string,string>,clean:array<string,string>}
     */
    public function validate(array $input): array
    {
        $errors = [];

        $name   = $this->clean((string) ($input['name'] ?? ''));
        $email  = strtolower($this->clean((string) ($input['email'] ?? '')));
        $ref    = $this->clean((string) ($input['contract_ref'] ?? ''));
        $reason = $this->clean((string) ($input['reason'] ?? ''));
        $hp     = trim((string) ($input['hp'] ?? ''));

        if ('' !== $hp) {
            $errors['hp'] = self::ERR_SPAM;
        }

        if ('' === $name) {
            $errors['name'] = self::ERR_REQUIRED;
        } elseif ($this->length($name) > self::MAX_NAME) {
            $errors['name'] = self::ERR_TOO_LONG;
        }

        if ('' === $email) {
            $errors['email'] = self::ERR_REQUIRED;
        } elseif ($this->length($email) > self::MAX_EMAIL) {
            $errors['email'] = self::ERR_TOO_LONG;
        } elseif (false === filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = self::ERR_INVALID;
        }

        if ('' === $ref) {
            $errors['contract_ref'] = self::ERR_REQUIRED;
        } elseif ($this->length($ref) > self::MAX_REF) {
            $errors['contract_ref'] = self::ERR_TOO_LONG;
        }

        if ($this->length($reason) > self::MAX_REASON) {
            $errors['reason'] = self::ERR_TOO_LONG;
        }

        return [
            'valid'  => [] === $errors,
            'errors' => $errors,
            'clean'  => [
                'name'         => $name,
                'email'        => $email,
                'contract_ref' => $ref,
                'reason'       => $reason,
            ],
        ];
    }

    /**
     * Removes markup, collapses whitespace and trims.
     */
    private function clean(string $value): string
    {
        // wp_strip_all_tags() would be the WordPress way, and this class is
        // deliberately outside WordPress: it is the layer the test suite runs
        // without a WordPress install.
        // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- Core carries no WordPress dependency.
        $value = strip_tags($value);
        $value = preg_replace('/\s+/u', ' ', $value);

        return trim((string) $value);
    }

    private function length(string $value): int
    {
        return function_exists('mb_strlen') ? mb_strlen($value, 'UTF-8') : strlen($value);
    }
}
