<?php
/**
 * Universal Validator Class v4
 * Backend Validation (synced with JS)
 * Supports: required, email, phone (country-aware), min, max, numeric, range, postalCode, pattern, conditional state
 */

class Validator
{
    /* ───────────────────────────────
     * 1️⃣ Country-wise Postal Patterns
     * ─────────────────────────────── */
    private static $postalPatterns = [
        'AD' => '/^AD\d{3}$/', 'AR' => '/^\d{4}$/', 'AS' => '/^96799$/',
        'AT' => '/^\d{4}$/', 'AU' => '/^\d{4}$/', 'BD' => '/^\d{4}$/',
        'BE' => '/^\d{4}$/', 'BG' => '/^\d{4}$/', 'BR' => '/^\d{5}-?\d{3}$/',
        'BY' => '/^\d{6}$/', 'CA' => '/^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/',
        'CH' => '/^\d{4}$/', 'CL' => '/^\d{7}$/', 'CN' => '/^\d{6}$/',
        'CO' => '/^\d{6}$/', 'CR' => '/^\d{4,5}$/', 'CY' => '/^\d{4}$/',
        'CZ' => '/^\d{3}\s?\d{2}$/', 'DE' => '/^\d{5}$/', 'DK' => '/^\d{4}$/',
        'DO' => '/^\d{5}$/', 'DZ' => '/^\d{5}$/', 'EC' => '/^\d{6}$/',
        'EE' => '/^\d{5}$/', 'ES' => '/^\d{5}$/', 'FI' => '/^\d{5}$/',
        'FR' => '/^\d{5}$/', 'GB' => '/^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/',
        'GE' => '/^\d{4}$/', 'GR' => '/^\d{5}$/', 'GT' => '/^\d{5}$/',
        'HK' => '/^[A-Za-z0-9]{3,10}$/', 'HR' => '/^\d{5}$/', 'HU' => '/^\d{4}$/',
        'ID' => '/^\d{5}$/', 'IE' => '/^[A-Za-z0-9]{3}\s?[A-Za-z0-9]{4}$/',
        'IL' => '/^\d{7}$/', 'IN' => '/^[1-9][0-9]{5}$/', 'IS' => '/^\d{3}$/',
        'IT' => '/^\d{5}$/', 'JP' => '/^\d{3}-?\d{4}$/', 'KR' => '/^\d{5}$/',
        'KW' => '/^\d{5}$/', 'KZ' => '/^\d{6}$/', 'LT' => '/^\d{5}$/',
        'LU' => '/^\d{4}$/', 'LV' => '/^LV-\d{4}$/', 'MA' => '/^\d{5}$/',
        'MD' => '/^\d{4}$/', 'ME' => '/^\d{5}$/', 'MK' => '/^\d{4}$/',
        'MM' => '/^\d{5}$/', 'MT' => '/^[A-Z]{3}\s?\d{2,4}$/', 'MX' => '/^\d{5}$/',
        'MY' => '/^\d{5}$/', 'NG' => '/^\d{6}$/', 'NL' => '/^\d{4}\s?[A-Za-z]{2}$/',
        'NO' => '/^\d{4}$/', 'NP' => '/^\d{5}$/', 'NZ' => '/^\d{4}$/',
        'PH' => '/^\d{4}$/', 'PK' => '/^\d{5}$/', 'PL' => '/^\d{2}[- ]?\d{3}$/',
        'PT' => '/^\d{4}-?\d{3}$/', 'RO' => '/^\d{6}$/', 'RS' => '/^\d{5}$/',
        'RU' => '/^\d{6}$/', 'SA' => '/^\d{5}$/', 'SE' => '/^\d{3}\s?\d{2}$/',
        'SG' => '/^\d{6}$/', 'SI' => '/^\d{4}$/', 'SK' => '/^\d{3}\s?\d{2}$/',
        'TH' => '/^\d{5}$/', 'TR' => '/^\d{5}$/', 'TW' => '/^\d{3}(\d{2})?$/',
        'UA' => '/^\d{5}$/', 'US' => '/^\d{5}(-\d{4})?$/', 'UY' => '/^\d{5}$/',
        'VN' => '/^\d{6}$/', 'ZA' => '/^\d{4}$/', 'ZW' => '/^[A-Za-z0-9]{3,10}$/',
        'default' => '/^[A-Za-z0-9\s\-]{3,10}$/'
    ];

    /* ───────────────────────────────
     * 2️⃣ Postal Length Rules
     * ─────────────────────────────── */
    private static $postalLength = [
        'IN' => [6, 6], 'US' => [5, 10], 'CA' => [6, 7],
        'GB' => [5, 8], 'SG' => [6, 6], 'default' => [3, 10],
    ];

    /* ───────────────────────────────
     * 3️⃣ Country-wise Phone Patterns
     * ─────────────────────────────── */
        private static $phonePatterns = [
            'IN' => '/^\+91\.[6-9]\d{9}$/',              // +91.9876543210
            'US' => '/^\+1\.[2-9]\d{2}\d{7}$/',          // +1.2025550123
            'GB' => '/^\+44\.7\d{9}$/',                  // +44.7123456789
            'AE' => '/^\+971\.5\d{8}$/',                 // +971.501234567
            'CA' => '/^\+1\.[2-9]\d{2}\d{7}$/',          // +1.6045551234
            'AU' => '/^\+61\.4\d{8}$/',                  // +61.412345678
            'SG' => '/^\+65\.\d{8}$/',                   // +65.91234567
            'DE' => '/^\+49\.\d{10,14}$/',               // +49.15123456789
            'FR' => '/^\+33\.\d{9}$/',                   // +33.612345678
            'PK' => '/^\+92\.3\d{9}$/',                  // +92.345678901
            'BD' => '/^\+880\.1[3-9]\d{8}$/',            // +880.1712345678
            'SO' => '/^\+252\.(6[1-9]|9\d)\d{6,7}$/',    // +252.612345678
            'NG' => '/^\+234\.[789]\d{9}$/',             // +234.8031234567
            'ZA' => '/^\+27\.[6-8]\d{8}$/',              // +27.682345678
            'default' => '/^\+\d{1,3}\.\d{6,14}$/'       // +CC.XXXXXXXXXX (generic fallback)
        ];


    /* ───────────────────────────────
     * 4️⃣ Custom Pattern Types (GST, PAN, etc.)
     * ─────────────────────────────── */
    private static $customPatterns = [
        'GST'     => '/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/',
        'PAN'     => '/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/',
        'AADHAAR' => '/^[2-9]{1}[0-9]{11}$/',
        'PHONE'   => '/^(\+91[\-\s]?)?[6-9]\d{9}$/',
        'IFSC'    => '/^[A-Z]{4}0[A-Z0-9]{6}$/',
        'CIN'     => '/^[A-Z]\d{5}[A-Z]{2}\d{4}[A-Z]{3}\d{6}$/',
        'PIN'     => '/^[1-9][0-9]{5}$/',
    ];

    /* ───────────────────────────────
     * 4️⃣ Basic Validation Rules
     * ─────────────────────────────── */
    public static function required($field, $value) {
        return trim($value) !== '' ? null : "$field is required.";
    }

    public static function email($field, $value) {
        return filter_var($value, FILTER_VALIDATE_EMAIL)
            ? null : "$field must be a valid email address.";
    }

    public static function min($field, $value, $min) {
        return strlen(trim($value)) < $min
            ? "$field must be at least $min characters." : null;
    }

    public static function max($field, $value, $max) {
        return strlen(trim($value)) > $max
            ? "$field must be no more than $max characters." : null;
    }

    public static function numeric($field, $value) {
        return is_numeric($value) ? null : "$field must be numeric.";
    }

    /* ───────────────────────────────
     * 5️⃣ Phone Validation (Dot format)
     * ─────────────────────────────── */
    public static function phone($field, $value, $cc = null) {
        if (!$value) return "$field is required.";
        $val = trim($value);
        $pattern = self::$phonePatterns[$cc] ?? self::$phonePatterns['default'];

        if (!preg_match($pattern, $val)) {
            if ($cc) {
                return "Invalid phone pattern for {$cc}: pattern {$pattern}";
            } else {
                return "Invalid phone pattern: pattern {$pattern}";
            }
        }
        return null;
    }

    /* ───────────────────────────────
     * 6️⃣ Postal Code Validation
     * ─────────────────────────────── */
    public static function postalCode($field, $value, $cc) {
        if (!$value) return "$field is required.";
        $pattern = self::$postalPatterns[$cc] ?? self::$postalPatterns['default'];

        if (!preg_match($pattern, trim($value))) {
            return "$field is invalid for selected country ($cc).";
        }
        return null;
    }

    /* ───────────────────────────────
     * 7️⃣ Custom Pattern Validation (e.g. GST, PAN)
     * ─────────────────────────────── */
    public static function pattern($field, $value, $type) {
        if (!$value) return "$field is required.";
        $regex = self::$customPatterns[$type] ?? null;
        if (!$regex) return "Unknown validation type for $field.";

        if (!preg_match($regex, trim($value))) {
            return "{$field} format is invalid ({$type}): pattern {$regex}";
        }
        return null;
    }

    /* ───────────────────────────────
     * 8️⃣ Conditional State Validation
     * ─────────────────────────────── */
    public static function conditionalState($data) {
        $stateId = isset($data['state_id']) ? trim($data['state_id']) : '';
        $fullState = isset($data['full_state']) ? trim($data['full_state']) : '';

        if ($stateId === '' || $stateId === '0' || $stateId === 0) {
            if ($fullState === '') {
                return "Full State is required when State is not selected or set to 'Other'.";
            }
        }
        return null;
    }

    /* ───────────────────────────────
     * 9️⃣ Master Validate Method
     * ─────────────────────────────── */
    public static function validate($rules, $data, $checkConditionalState = true) {
        foreach ($rules as $field => $ruleSet) {
            $value = $data[$field] ?? '';
            $label = self::formatFieldName($field);

            foreach ($ruleSet as $rule) {
                $method = $rule[0];
                $params = array_slice($rule, 1);

                if (!method_exists(self::class, $method)) continue;

                $error = self::$method($label, $value, ...$params);
                if ($error) return ['success' => false, 'error' => $error];
            }
        }

        if ($checkConditionalState) {
            $condError = self::conditionalState($data);
            if ($condError) return ['success' => false, 'error' => $condError];
        }

        return ['success' => true, 'error' => null];
    }

    /* ───────────────────────────────
     * 🔠 Helper
     * ─────────────────────────────── */
    private static function formatFieldName($name) {
        return ucwords(str_replace('_', ' ', $name));
    }

// ------------------------------------------ normal function ---------------------------------------------
    //   Check if order_id is present and valid.
    public static function isValidOrderId(array $input): bool
    {
        return isset($input['order_id']) && Validator::isValidInteger($input['order_id']);
    }
    
    // Sanitize any input string
    public static function sanitizeInput($input) {
        return trim(htmlspecialchars(strtolower($input), ENT_QUOTES, 'UTF-8'));
    }

    // Validate email address
    public static function isValidEmail($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }


    public static function isValidInteger($val): bool {
        return is_numeric($val) && (int)$val == $val && $val > 0;
    }
    // Validate if value is a valid float
    // public static function isValidFloat($value) {
    //     return filter_var($value, FILTER_VALIDATE_FLOAT) !== false;
    // }

    // Validate if a string only contains letters (no numbers or special chars)
    // public static function isAlpha($string) {
    //     return preg_match('/^[a-zA-Z]+$/', $string);
    // }

    // Validate if a string only contains letters and numbers
    // public static function isAlphanumeric($string) {
    //     return preg_match('/^[a-zA-Z0-9]+$/', $string);
    // }
    
    function validateDSRecord($ds) {
        // key_tag check
        if (!is_int($ds['key_tag']) || $ds['key_tag'] < 0 || $ds['key_tag'] > 65535) return false;
        
        // algorithm check (example allowed list)
        $allowed_algorithms = [1, 8, 13];
        if (!in_array($ds['algorithm'], $allowed_algorithms)) return false;
        
        // digest_type check
        $allowed_digest_types = [1, 2, 4];
        if (!in_array($ds['digest_type'], $allowed_digest_types)) return false;
        
        // digest length check
        $digest = strtolower($ds['digest']);
        if (!ctype_xdigit($digest)) return false;
        
        $expected_lengths = [1 => 40, 2 => 64, 4 => 96];
        if (strlen($digest) !== $expected_lengths[$ds['digest_type']]) return false;
        
        return true;
    }
    
 /*   public static function NameServers($new_ns_string, $current_ns_string, $min_nameserver, $max_nameserver)
    {
      
        // Convert to arrays and sanitize
        $new_ns = array_filter(array_map('trim', explode(',', strtolower($new_ns_string))));
        $current_ns = array_filter(array_map('trim', explode(',', strtolower($current_ns_string))));
        
        // Fill data: Old and New NS with keys
        $data = [];
        $data['NoOfNewNS'] = count($new_ns);
    
        // Old NS
        foreach (array_values($current_ns) as $idx => $ns) {
            $key = 'OldNS' . ($idx + 1);
            $data[$key] = $ns;
        }
    
        // New NS
        foreach (array_values($new_ns) as $idx => $ns) {
            $key = 'NewNS' . ($idx + 1);
            $data[$key] = $ns;
        }

        // Put data in response early
        $response['data'] = $data;

    
        // Check same list
        $sorted_new = $new_ns;
        $sorted_current = $current_ns;
        sort($sorted_new);
        sort($sorted_current);
    
        if ($sorted_new === $sorted_current) {
            return AppError::errorResponse("New nameservers are same as current nameservers.", 409);
        }
    
        // Check count
        $count = count($new_ns);
        if ($count < $min_nameserver) {
            return AppError::errorResponse("Minimum {$min_nameserver} nameservers required.", 422);
        }
    
        if ($count > $max_nameserver) {
            return AppError::errorResponse("Maximum {$max_nameserver} nameservers allowed.", 422);
        }
        
        // Check for duplicates, and show which ones are duplicate
        $duplicates = array();
        $counts = array_count_values($new_ns);
        foreach ($counts as $ns => $c) {
            if ($c > 1) {
                $duplicates[] = $ns;
            }
        }
        if (!empty($duplicates)) {
            return AppError::errorResponse("Duplicate nameservers are not allowed: " . implode(', ', $duplicates), 409);
        }    
        // Validate format
        $pattern = '/^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$/';
        foreach ($new_ns as $ns) {
            if (!preg_match($pattern, $ns)) {
                return AppError::errorResponse("Invalid nameserver format: {$ns}", 422);
            }
        }
        
        $blockPatterns = [
            '/(^|\.)ghs\.google\.com$/',
            '/(^|\.)googlehosted\.com$/',
            '/(^|\.)cdn\.cloudflare\.net$/',
            '/(^|\.)cf\.cloudflare\.com$/',
            '/(^|\.)cloudflare\.net$/',
            '/(^|\.)cloudflare-resolve-to\./',
            '/(^|\.)myshopify\.com$/',
            '/(^|\.)shops\.myshopify\.com$/',
            '/(^|\.)wixdns\.net$/',
            '/(^|\.)wixsite\.com$/',
            '/(^|\.)ext-cust\.squarespace\.com$/',
            '/(^|\.)verify\.squarespace\.com$/',
            '/(^|\.)zmverify\.zoho\.com$/',
            '/(^|\.)outlook\.com$/',
            '/(^|\.)protection\.outlook\.com$/',
            '/(^|\.)herokudns\.com$/',
            '/(^|\.)herokussl\.com$/',
            '/(^|\.)wordpress\.com$/',
            '/(^|\.)wpengine\.com$/',
            '/(^|\.)amazonaws\.com$/',
            '/(^|\.)mailgun\.org$/',
            '/(^|\.)mandrillapp\.com$/'
        ];
        foreach ($new_ns as $ns) {
            foreach ($blockPatterns as $pattern) {
                if (preg_match($pattern, $ns)) {
                    return AppError::errorResponse("Nameserver field cannot contain third-party CNAME endpoints: {$ns}", 422);
                }
            }
        }

        $response['success'] = true;
        $response['code'] = 200;
        
        return $response;
    }*/

}
