<?php

declare(strict_types=1);

/**
 * CPF validator (Brazilian individual taxpayer id).
 *
 * Rules:
 * - 11 digits total (numeric only).
 * - Check digits (2 last digits) are computed with modulo 11:
 *   - 1st DV: weights 10..2 over the first 9 digits.
 *   - 2nd DV: weights 11..2 over the first 9 digits + 1st DV.
 *   - Remainder rule: if (sum % 11) < 2 => DV = 0; else DV = 11 - (sum % 11).
 */
final class Agro_Validate_Cpf
{
    /** 11 digits only */
    private const REGEX_NORMALIZED = '/^\d{11}$/';

    /**
     * Keep only digits.
     */
    public static function normalize(string $value): string
    {
        return preg_replace('/\D+/', '', $value) ?? '';
    }

    /**
     * Validate CPF (accepts masked or unmasked).
     * - Normalize to digits only;
     * - Must be 11 digits;
     * - Reject repeated sequences (e.g., "00000000000");
     * - Check both DVs.
     */
    public static function isValid(string $value): bool
    {
        $norm = self::normalize($value);
        if (!preg_match(self::REGEX_NORMALIZED, $norm)) {
            return false;
        }

        // Reject trivial repeated sequences like 00000000000, 11111111111...
        if (preg_match('/^(\d)\1{10}$/', $norm)) {
            return false;
        }

        $base9 = substr($norm, 0, 9);
        $dvInformed = substr($norm, 9, 2);
        $dvCalc = self::computeCheckDigits($base9);

        return $dvCalc === $dvInformed;
    }

    /**
     * Compute the two DVs from the first 9 digits.
     *
     * @throws \InvalidArgumentException if base is not exactly 9 digits.
     */
    public static function computeCheckDigits(string $first9): string
    {
        $base9 = self::normalize($first9);
        if (!preg_match('/^\d{9}$/', $base9)) {
            throw new \InvalidArgumentException('CPF base must have exactly 9 digits (normalized).');
        }

        $dv1 = self::computeSingleDV($base9, /* start weight */ 10);
        $dv2 = self::computeSingleDV($base9 . $dv1, /* start weight */ 11);

        return (string)$dv1 . (string)$dv2;
    }

    /**
     * Format normalized 11-digit CPF as 000.000.000-00.
     *
     * @throws \InvalidArgumentException if not 11 digits.
     */
    public static function format(string $normalized11): string
    {
        $norm = self::normalize($normalized11);
        if (!preg_match(self::REGEX_NORMALIZED, $norm)) {
            throw new \InvalidArgumentException('Normalized CPF must be exactly 11 digits.');
        }

        return sprintf('%s.%s.%s-%s',
            substr($norm, 0, 3),
            substr($norm, 3, 3),
            substr($norm, 6, 3),
            substr($norm, 9, 2)
        );
    }

    /**
     * DV (mod 11) with descending weights, remainder rule:
     * if remainder < 2 => DV=0; else DV=11 - remainder.
     */
    private static function computeSingleDV(string $digits, int $startWeight): int
    {
        $sum = 0;
        $w = $startWeight;

        for ($i = 0; $i < strlen($digits); $i++, $w--) {
            $sum += ((int)$digits[$i]) * $w;
        }

        $rest = $sum % 11;
        return ($rest < 2) ? 0 : (11 - $rest);
    }
}