<?php

namespace GriffinForms\Frontend\Security\FieldValidations;

abstract class Format extends \GriffinForms\Frontend\Security\Format
{
    protected $field;
    protected $is_single = true;
    protected $validations = [];
    protected $errors = [];

    public function setField($field) {
        $this->field = $field;
        $this->validations = $this->field->getProp('validations') ?? [];
    }

    protected function addError($message, $iteration_index = null)
    {
        $field_label = $this->field->getProp('heading') ?? __('Field', 'griffinforms-form-builder');
        $max_length = 30;
        if (strlen($field_label) > $max_length) {
            $field_label = substr($field_label, 0, $max_length - 3) . '...';
        }
    
        // Ensure iteration info is added when valid
        if (is_numeric($iteration_index) && $iteration_index >= 0) {
            /* translators: %d is the 1-based index of the current entry in a repeating/iteration field */
            $iteration_info = sprintf(__(' (Entry #%d)', 'griffinforms-form-builder'), $iteration_index + 1);
            $field_label .= $iteration_info;
        }
    
        /* translators: %1$s is the field label and %2$s is the error message associated with the field */
        $formatted_message = sprintf(__('Error in "%1$s": %2$s', 'griffinforms-form-builder'), $field_label, $message);
    
        // Prevent duplicate errors
        if (!in_array($formatted_message, $this->errors, true)) {
            $this->errors[] = $formatted_message;
        }
    }

    // Get validation errors
    public function getErrors()
    {
        return $this->errors;
    }

    // Common validation: Required field
    protected function validateRequired($value, $iteration_index)
    {    
        if ($value === null || $value === '' || (is_array($value) && empty($value))) {
            $this->addError(__('This field is required.', 'griffinforms-form-builder'));
        }
    }

    // Main validate function that calls specific validation rules
    public function validateAllIterations(array $all_iterations)
    {
        $iteration_count = count($all_iterations);
        $this->is_single = $iteration_count === 1;
    
        $this->clearErrors();
        $sanitized_values = [];
    
        // First, sanitize the values before checking uniqueness
        foreach ($all_iterations as $iteration_index => $value) {
            $sanitized_value = $this->sanitizeValue($value);
            $sanitized_values[$iteration_index] = $sanitized_value;
        }
    
        // Validate iteration count for min/max iteration limits
        $this->validateIterationCount($all_iterations);
    
        // Check for unique values if enabled in the database
        $is_unique = $this->field->getProp('iterations')['unique'] ?? 0;
        if ($is_unique === 1) {
            // After sanitization, validate unique values
            $this->validateUniqueValues($sanitized_values);
        }
    
        foreach ($sanitized_values as $iteration_index => $sanitized_value) {
            $is_required = $this->field->getProp('is_required');
            if (!empty($is_required) && $is_required !== '0') {
                $this->validateRequired($sanitized_value, $iteration_index);
    
                // Skip further validation for this iteration if required validation fails
                if (!empty($this->errors)) {
                    continue;
                }
            }
    
            $this->validateDbValue($sanitized_value, $iteration_index);
            $this->customValidate($sanitized_value, $iteration_index);
        }
    
        if (empty($this->errors) && method_exists($this, 'transformSanitizedValues')) {
            $sanitized_values = $this->transformSanitizedValues($sanitized_values);
        }

        return [
            'errors' => empty($this->errors) ? true : $this->errors,
            'sanitized_values' => $sanitized_values,
        ];
    }

    protected function sanitizeValue($value)
    {
        $field_type = $this->field->getProp('field_type');

        switch ($field_type) {
            case 'inputtext':
                return (isset($value) && is_string($value)) ? sanitize_textarea_field($value) : '';

            case 'textarea':
                return (isset($value) && is_string($value)) ? wp_kses_post($value) : '';

            case 'inputnumber':
                if ($value === '' || $value === null) {
                    return '';
                }

                if (!is_numeric($value)) {
                    return 'NaN';
                }

                return floatval($value);

            case 'inputemail':
                return is_email($value) ? $value : '';

            case 'inputphone':
                return sanitize_text_field($value);

            case 'inputurl':
                $value = esc_url_raw($value);
                return $value ?: '';

            case 'inputdate':
                if (!empty($value)) {
                    $normalized_date = $this->normalizeDateValue($value);
                    return $normalized_date !== null ? $normalized_date : '';
                }
                return '';

            case 'inputtime':
                if (!empty($value)) {
                    $timestamp = strtotime($value);
                    return $timestamp ? gmdate('H:i', $timestamp) : '';
                }
                return '';

            case 'inputcheckbox':
                if (is_string($value)) {
                    $decoded_value = json_decode($value, true);
                    if (is_array($decoded_value)) {
                        return array_map('sanitize_text_field', $decoded_value);
                    }
                }
                return is_array($value) ? array_map('sanitize_text_field', $value) : [];

            case 'imageselection':
                if (is_string($value)) {
                    $decoded_value = json_decode($value, true);
                    if (is_array($decoded_value)) {
                        return array_values(array_filter(array_map('sanitize_key', $decoded_value), static function ($item) {
                            return $item !== '';
                        }));
                    }
                }
                if (!is_array($value)) {
                    return [];
                }
                return array_values(array_filter(array_map('sanitize_key', $value), static function ($item) {
                    return $item !== '';
                }));

            case 'inputradio':
                return sanitize_text_field($value);

            case 'selectfield':
                return sanitize_text_field($value);

            case 'inputaddress':
                $decoded = $this->normalizeAddressPayload($value);

                $components = array('line1', 'line2', 'city', 'state', 'postal', 'country');
                $sanitized = array();
                foreach ($components as $part) {
                    if (isset($decoded[$part])) {
                        $sanitized[$part] = sanitize_text_field((string) $decoded[$part]);
                    } else {
                        $sanitized[$part] = '';
                    }
                }

                $all_empty = true;
                foreach ($sanitized as $part_value) {
                    if ($part_value !== '') {
                        $all_empty = false;
                        break;
                    }
                }

                if ($all_empty) {
                    return array();
                }

                $is_required_field = $this->field->getProp('is_required');
                if (!empty($is_required_field) && ($sanitized['line1'] === '' || $sanitized['country'] === '')) {
                    return array();
                }

                return $sanitized;

            case 'termsconditions':
                if ($value === '1' || $value === 1 || $value === 'accepted') {
                    return '1';
                }
                return '';

            case 'payment':
                $decoded = array();
                if (is_string($value)) {
                    $decoded = $value !== '' ? json_decode($value, true) : array();
                    if (!is_array($decoded)) {
                        $decoded = array();
                    }
                } elseif (is_array($value)) {
                    $decoded = $value;
                }

                $gateway_value = '';
                if (isset($decoded['items']) && is_array($decoded['items'])) {
                    $gateway_value = sanitize_key($decoded['gateway'] ?? '');
                    $decoded = $decoded['items'];
                }

                if (isset($decoded['product_id']) || isset($decoded['selected'])) {
                    $decoded = array($decoded);
                }

                $sanitized_items = array();
                if (is_array($decoded)) {
                    foreach ($decoded as $entry) {
                        if (!is_array($entry) || empty($entry['selected'])) {
                            continue;
                        }
                        $product_id = sanitize_text_field($entry['product_id'] ?? '');
                        if ($product_id === '') {
                            continue;
                        }
                        $sanitized_items[] = array(
                            'selected'     => true,
                            'product_id'   => $product_id,
                            'product_name' => sanitize_text_field($entry['product_name'] ?? ''),
                            'quantity'     => absint($entry['quantity'] ?? 0),
                            'unit_price'   => isset($entry['unit_price']) ? (float) $entry['unit_price'] : 0,
                            'currency'     => sanitize_text_field($entry['currency'] ?? ''),
                            'total'        => isset($entry['total']) ? (float) $entry['total'] : 0,
                        );
                    }
                }

                return array(
                    'items'   => $sanitized_items,
                    'gateway' => $gateway_value,
                );

            default:
                return sanitize_text_field($value);
        }
    }

    private function normalizeAddressPayload($value): array
    {
        if (is_string($value) && $value !== '') {
            $decoded = json_decode($value, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return $this->normalizeAddressPayload($decoded);
            }
            return array();
        }

        if (!is_array($value)) {
            return array();
        }

        if (array_keys($value) === range(0, count($value) - 1) && count($value) === 1 && isset($value[0])) {
            return $this->normalizeAddressPayload($value[0]);
        }

        return $value;
    }

    protected function normalizeDateValue($value): ?string
    {
        if (!is_scalar($value)) {
            return null;
        }

        $raw = trim((string) $value);
        if ($raw === '') {
            return null;
        }

        $field_settings = $this->field->getProp('field_settings');
        $js_format = is_array($field_settings) && !empty($field_settings['date_format'])
            ? (string) $field_settings['date_format']
            : 'mm-dd-yy';

        $php_format_map = [
            'mm-dd-yy' => 'm-d-Y',
            'dd-mm-yy' => 'd-m-Y',
            'yy-mm-dd' => 'Y-m-d',
        ];

        if (isset($php_format_map[$js_format])) {
            $date = \DateTimeImmutable::createFromFormat('!' . $php_format_map[$js_format], $raw);
            $errors = \DateTimeImmutable::getLastErrors();
            $has_errors = is_array($errors) && (($errors['warning_count'] ?? 0) > 0 || ($errors['error_count'] ?? 0) > 0);
            if ($date instanceof \DateTimeImmutable && !$has_errors) {
                return $date->format('Y-m-d');
            }
        }

        $timestamp = strtotime($raw);
        if ($timestamp === false) {
            return null;
        }

        return gmdate('Y-m-d', $timestamp);
    }

    // Validate iteration count
    private function validateIterationCount(array $all_iterations)
    {
        // Retrieve the iteration settings from the 'iterations' column (already unserialized)
        $iteration_settings = $this->field->getProp('iterations') ?? [];
    
        // Retrieve the required setting for the field
        $is_required = $this->field->getProp('is_required') ?? 0; // Default to not required
    
        // Check if 'min' and 'max' are available in the iterations data
        $min_iterations = $iteration_settings['min'] ?? 1;  // Default to 1 if not set
        $max_iterations = $iteration_settings['max'] ?? PHP_INT_MAX;  // Default to PHP_INT_MAX if not set
    
        // Calculate iteration count
        $iteration_count = count($all_iterations);
    
        // Always check for max iterations
        if ($iteration_count > $max_iterations) {
            /* translators: %d is the maximum number of entries allowed for this field */
            $this->addError(sprintf(__('You can submit up to %d entries.', 'griffinforms-form-builder'), $max_iterations));
        }
    
        // If the field is required, check for min iterations
        if ($is_required !== '0') { // If the field is required
            if ($iteration_count < $min_iterations) {
                /* translators: %d is the minimum number of entries required for this field */
                $this->addError(sprintf(__('You must provide at least %d entries', 'griffinforms-form-builder'), $min_iterations));
            }
        }
    }

    // Validate unique values across all iterations
    protected function validateUniqueValues(array $all_iterations)
    {
        $values = [];
    
        // First, we check if we are dealing with an array of arrays (checkbox field with multiple iterations)
        if (isset($all_iterations[0]) && is_array($all_iterations[0])) {
            // Handle array of arrays (checkbox with multiple iterations)
            foreach ($all_iterations as $iteration_values) {
                // Ensure that each iteration's values are unique within that iteration
                $iteration_values = array_map('trim', $iteration_values);  // Trim each value before checking
                $values[] = json_encode($iteration_values); // Store the serialized (json) version of each iteration's values
            }
    
            // Now, we check if all the serialized values (from each iteration) are unique
            if (count($values) !== count(array_unique($values))) {
                $this->addError(__('Please ensure all values are unique..', 'griffinforms-form-builder'));
            }
        } else {
            // Handle array of strings (for non-checkbox fields)
            foreach ($all_iterations as $value) {
                $values[] = trim($value);  // Trim and add each value to the values array
            }
    
            // Remove empty values from the array
            $values = array_filter($values, function ($value) {
                return !empty($value); // Only keep non-empty values
            });
    
            // Check for duplicates across all values
            if (count($values) !== count(array_unique($values))) {
                $this->addError(__('Duplicate values among entries are not allowed.', 'griffinforms-form-builder'));
            }
        }
    }

    // Validate database value (in_column/not_in_column)
    protected function validateDbValue($value, $iteration_index = null)
    {
        $validations = $this->field->getProp('validations');
        if (empty($validations['type']) || $validations['type'] !== 'db-validate') {
            return; // Skip if not a db-validate rule
        }
    
        $table = $validations['table'] ?? '';
        $column = $validations['column'] ?? '';
        $match_type = $validations['match_type'] ?? 'in_column';
    
        if (!$table || !$column) {
            return; // Skip if table or column is not defined
        }
    
        $exists = $this->dbCheck($table, $column, $value);
    
        if ($match_type === 'in_column' && !$exists) {
            $this->addError(__('The entered value is invalid or not recognized. Please check again.', 'griffinforms-form-builder'), $iteration_index);
        }
        
        if ($match_type === 'not_in_column' && $exists) {
            $this->addError(__('The value you entered already exists. Please enter a different one.', 'griffinforms-form-builder'), $iteration_index);
        }
    }

    // Check if value exists in the database
    private function dbCheck($table, $column, $value)
    {
        global $wpdb;
    
        if (empty($table) || empty($column) || empty($value)) {
            return false;
        }
    
        // Ensure table name does not already contain prefix
        if (strpos($table, $wpdb->prefix) === 0) {
            $table_name = $table; // Already prefixed
        } else {
            $table_name = $wpdb->prefix . $table; // Add prefix if missing
        }
    
        return $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM %i WHERE %i = %s",
            $table_name, $column, $value)) > 0;
    }

    protected function validatePattern($value, $pattern, $iteration_index = null)
    {
        // Ensure pattern has delimiters before using it
        if ($pattern[0] !== '/' || substr($pattern, -1) !== '/') {
            $pattern = '/' . trim($pattern, '/') . '/';
        }
    
        if (@preg_match($pattern, '') === false) {
            $this->addError(__('The format of the entered value is incorrect. Please follow the required pattern.', 'griffinforms-form-builder'), $iteration_index);
            return;
        }
    
        if (!preg_match($pattern, $value)) {
            $this->addError(__('Please ensure the value matches the required format.', 'griffinforms-form-builder'), $iteration_index);
        }
    }

    // Clear errors for the next iteration
    protected function clearErrors()
    {
        $this->errors = [];
    }

    abstract protected function customValidate($value, $iteration_index);
}
