<?php

namespace GriffinForms\Frontend\Security\FieldValidations;

/**
 * Class InputUrl
 * Handles validation for URL input fields in GriffinForms.
 */
class InputUrl extends Format
{
    /**
     * Custom validation for input URL fields.
     *
     * @param string $value The user-inputted URL.
     * @param int|null $iteration_index The index for iterable fields (if applicable).
     */
    protected function customValidate($value, $iteration_index = null)
    {
        // Fetch validation rules once to optimize performance
        $validations = $this->field->getProp('validations');
        if (!$validations) return;
    
        // Skip validation for empty fields unless required validation is handled in validateAllIterations()
        if (empty($value)) {
            return;
        }
    
        // URL format validation
        if (!$this->validateUrlFormat($value)) {
            return; // If format is invalid, skip further validation
        }
    
        // Apply additional validations
        if (!empty($validations['protocols'])) {
            $this->validateProtocols($value, $validations['protocols']);
        }
        if (!empty($validations['restricted_tld'])) {
            $this->validateRestrictedTlds($value, $validations['restricted_tld']);
        }
        if (!empty($validations['excluded_tld'])) {
            $this->validateExcludedTlds($value, $validations['excluded_tld']);
        }
        if (!empty($validations['pattern'])) {
            $this->validatePattern($value, $validations['pattern']);
        }
    }

    /**
     * Validates that the input is a properly formatted URL.
     *
     * @param string $value The URL to validate.
     * @return bool Returns true if valid, false otherwise.
     */
    private function validateUrlFormat($value)
    {
        // Retrieve protocol validations
        $validations = $this->field->getProp('validations');
        $enforce_protocol = !empty($validations['protocols']);

        // If protocol validation is active, check if the input starts with one of the allowed protocols
        if ($enforce_protocol && is_array($validations['protocols'])) {
            // Escape each protocol to safely build a regex
            $escaped_protocols = array_map('preg_quote', $validations['protocols']);
            $pattern = '/^(' . implode('|', $escaped_protocols) . '):\/\//i';

            // If the value doesn't start with a permitted protocol, trigger an error
            if (!preg_match($pattern, $value)) {
                $this->addError(sprintf(
                    __('The URL must start with one of the following protocols: %s', 'griffinforms-form-builder'),
                    implode(', ', $validations['protocols'])
                ));
                return false;
            }
        }
    
        // Now apply filter_var validation
        if (!filter_var($value, FILTER_VALIDATE_URL)) {
            $this->addError(__('Please enter a valid URL.', 'griffinforms-form-builder'));
            return false;
        }
    
        return true;
    }

    /**
     * Validates that the URL uses an allowed protocol (scheme).
     *
     * @param string $value The URL to check.
     * @param array $protocols Allowed protocols (e.g., ['http', 'https']).
     */
    private function validateProtocols($value, $protocols)
    {
        $parsed_url = wp_parse_url($value);
        $protocol = $parsed_url['scheme'] ?? '';

        if (empty($protocol) || !in_array($protocol, $protocols)) {
            /* translators: %s is a comma-separated list of allowed protocols like http, https */
            $this->addError(sprintf(__('The URL must start with one of the following protocols: %s', 'griffinforms-form-builder'), implode(', ', $protocols)));
        }
    }

    /**
     * Extracts the top-level domain (TLD) from a given URL.
     *
     * @param string $url The URL to extract the TLD from.
     * @param array $tlds List of TLDs to check against.
     * @return string The extracted TLD or an empty string if not found.
     */
    private function extractTld($url, array $tlds)
    {
        $url_host = wp_parse_url($url, PHP_URL_HOST);
        if (!$url_host || empty($tlds)) return '';

        $host = strtolower($url_host);
        $tlds = array_map('strtolower', $tlds);

        foreach ($tlds as $tld) {
            if (str_ends_with($host, '.' . $tld)) {
                return $tld;
            }
        }

        return '';
    }

    /**
     * Validates that the URL ends with a restricted (allowed) TLD.
     *
     * @param string $value The URL to validate.
     * @param array $tlds Allowed TLDs (e.g., ['com', 'org', 'net']).
     */
    private function validateRestrictedTlds($value, $tlds)
    {    
        // Remove empty values from the array
        $tlds = array_filter($tlds, function($tld) {
            return !empty(trim($tld));
        });
        
        // If the array is empty after filtering, skip validation
        if (empty($tlds)) {
            return;
        }
    
        $tld = $this->extractTld($value, $tlds);
        if (!in_array($tld, $tlds)) {
            /* translators: %s is a comma-separated list of allowed top-level domains (TLDs) like com, org, net */
            $this->addError(sprintf(__('The URL must end with one of the following TLDs: %s', 'griffinforms-form-builder'), implode(', ', $tlds)));
        }
    }

    /**
     * Validates that the URL does not end with a forbidden (excluded) TLD.
     *
     * @param string $value The URL to validate.
     * @param array $tlds Forbidden TLDs (e.g., ['xyz', 'ru', 'biz']).
     */
    private function validateExcludedTlds($value, $tlds)
    {
        $tld = $this->extractTld($value, $tlds);
        if (in_array($tld, $tlds)) {
            /* translators: %s is a comma-separated list of disallowed top-level domains (TLDs) like xyz, ru, biz */
            $this->addError(sprintf(__('The URL cannot end with any of the following TLDs: %s', 'griffinforms-form-builder'), implode(', ', $tlds)));
        }
    }
}