<?php

declare(strict_types=1);

/**
 * CNPJ Alphanumeric validator (RFB 2.229/2024).
 *
 * Rules:
 * - 14 chars total.
 * - First 12: [A-Z0-9]; last 2: digits (DVs).
 * - DV: modulo 11 with weights 2..9 (right-to-left, cycling), using (ASCII - 48) per char.
 */
final class Agro_Validate_CnpjAlpha
{
    /** Unmasked, uppercase: 12 alphanumerics + 2 digits */
    private const REGEX_NORMALIZED = '/^[A-Z0-9]{12}\d{2}$/';

    /** Mask helpers: AA.AAA.AAA/AAAA-DD */
    private const MASK_POS = [2, 5, 8, 12];
    private const MASK_SYMS = ['.', '.', '/', '-'];

    /**
     * Normalize input: strip non-alphanumerics and uppercase letters.
     */
    public static function normalize(string $value): string
    {
        $v = preg_replace('/[^A-Za-z0-9]/', '', $value) ?? '';
        return strtoupper($v);
    }

    /**
     * Validate alphanumeric CNPJ (accepts masked or unmasked; case-insensitive).
     * Also validates legacy numeric CNPJ seamlessly (digits are mapped identically).
     */
    public static function isValid(string $value): bool
    {
        $norm = self::normalize($value);

        if (!preg_match(self::REGEX_NORMALIZED, $norm)) {
            return false;
        }

        $base12 = substr($norm, 0, 12);
        $dvInformed = substr($norm, 12, 2);
        $dvCalc = self::computeCheckDigits($base12);

        return $dvInformed === $dvCalc;
    }

    /**
     * Compute both check digits from the first 12 characters.
     */
    public static function computeCheckDigits(string $first12): string
    {
        $base12 = self::normalize($first12);
        if (!preg_match('/^[A-Z0-9]{12}$/', $base12)) {
            throw new \InvalidArgumentException('CNPJ base must have 12 alphanumeric chars (normalized).');
        }

        $dv1 = self::computeSingleDV($base12);
        $dv2 = self::computeSingleDV($base12 . (string)$dv1);

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

    /**
     * Format normalized 14-character CNPJ as AA.AAA.AAA/AAAA-DD.
     */
    public static function format(string $normalized14): string
    {
        $norm = self::normalize($normalized14);
        if (!preg_match(self::REGEX_NORMALIZED, $norm)) {
            throw new \InvalidArgumentException('Normalized CNPJ must be 14 chars: [A-Z0-9]{12}[0-9]{2}.');
        }

        $parts = str_split($norm);
        $out = '';
        $maskIndex = 0;

        foreach ($parts as $i => $ch) {
            $out .= $ch;
            if ($maskIndex < count(self::MASK_POS) && $i + 1 === self::MASK_POS[$maskIndex]) {
                $out .= self::MASK_SYMS[$maskIndex];
                $maskIndex++;
            }
        }
        return $out;
    }

    /**
     * Compute a single DV using modulo 11 with weights 2..9 (right-to-left, cycling).
     * Each char contributes (ord(char) - 48).
     */
    private static function computeSingleDV(string $input): int
    {
        $chars = str_split($input);
        $sum = 0;
        $weight = 2;

        for ($i = count($chars) - 1; $i >= 0; $i--) {
            $val = ord($chars[$i]) - 48;
            if ($val < 0) {

                throw new \RuntimeException('Invalid character mapping.');
            }
            $sum += $val * $weight;
            $weight = ($weight === 9) ? 2 : $weight + 1;
        }

        $rest = $sum % 11;
        return ($rest === 0 || $rest === 1) ? 0 : (11 - $rest);
    }
}

