<?php

namespace WeDevs\Wpuf\Pro\Fields;

/**
 * Date Field Class
 *
 * @since 3.1.0
 **/
class Field_Date extends Pro_Field_Contract {

    /**
     * Default year range for datepicker
     * Format: '-100:+20' means 100 years in the past, 20 years in the future
     *
     * @var string
     */
    const DEFAULT_YEAR_RANGE = '-100:+20';

    /**
     * Days per week for week-to-day conversion
     *
     * @var int
     */
    const DAYS_PER_WEEK = 7;

    public function __construct() {
        $this->name       = __( 'Date / Time', 'wpuf-pro' );
        $this->input_type = 'date_field';
        $this->icon       = 'clock';

        $this->enqueue_scripts();
    }

    /**
     * Render the Date field
     *
     * @param  array  $field_settings
     *
     * @param  integer  $form_id
     *
     * @param  string  $type
     *
     * @param  integer  $post_id
     *
     * @return void
     */
    public function render( $field_settings, $form_id, $type = 'post', $post_id = null ) {
        if ( isset( $post_id ) && (int) $post_id !== 0 ) {
            if ( $this->is_meta( $field_settings ) ) {
                $value = $this->get_meta( $post_id, $field_settings['name'], $type );
            }
        } else {
            if ( isset( $field_settings['default'] ) && ! empty( $field_settings['default'] ) ) {
                $value = $field_settings['default'];
            } else {
                $value = '';
            }
        }

        $this->field_print_label( $field_settings, $form_id );
        // if date field is assigned as publish date
        if ( isset( $field_settings['is_publish_time'] ) && $field_settings['is_publish_time'] === 'yes' ) {
            ?>
            <input type="hidden" name="wpuf_is_publish_time" value="<?php echo $field_settings['name']; ?>" />
            <?php
        }
        ?>

        <div class="wpuf-fields">
            <input id="wpuf-date-<?php echo $field_settings['name']; ?>" type="text" autocomplete="off" class="datepicker wpuf-date-field <?php echo ' wpuf_' . $field_settings['name'] . '_' . $form_id; ?>" data-required="<?php echo $field_settings['required']; ?>" data-type="text" name="<?php echo esc_attr( $field_settings['name'] ); ?>" placeholder="<?php echo esc_attr( $field_settings['format'] ); ?>" value="<?php echo esc_attr( $value ); ?>" size="30"
                data-format="<?php echo esc_attr( $field_settings['format'] ); ?>"
                data-time="<?php echo isset( $field_settings['time'] ) && $field_settings['time'] === 'yes' ? 'yes' : 'no'; ?>"
                data-mintime="<?php echo ! empty( $field_settings['mintime'] ) ? esc_attr( $field_settings['mintime'] ) : ''; ?>"
                data-maxtime="<?php echo ! empty( $field_settings['maxtime'] ) ? esc_attr( $field_settings['maxtime'] ) : ''; ?>"
            />
            <?php $this->help_text( $field_settings ); ?>
        </div>

        <script type="text/javascript">
            jQuery(document).ready(function($) {
                var $dateField = $("#wpuf-date-<?php echo $field_settings['name']; ?>");

                // Ensure the element exists in the DOM before initializing
                if ( $dateField.length === 0 ) {
                    return;
                }

                var date_default = {
                    dateFormat: '<?php echo esc_js( $field_settings['format'] ); ?>',
                    changeMonth: true,
                    changeYear: true,
                    yearRange: '<?php echo esc_js( self::DEFAULT_YEAR_RANGE ); ?>'
                };

                /**
                 * Parse relative or absolute date string for datepicker
                 * Handles formats: plain numbers, +/- numbers, units (d/m/w/y), and absolute dates
                 *
                 * @param {string} dateStr - Date string to parse (e.g., "-10d", "+1m", "05-11-2025")
                 * @return {number|Date|string} Parsed date value for datepicker
                 */
                function parseDateValue(dateStr) {
                    if ( !dateStr || typeof dateStr !== 'string' ) {
                        return null;
                    }

                    dateStr = dateStr.trim();

                    if ( !dateStr ) {
                        return null;
                    }

                    // Regex: Matches relative dates with optional +/- sign, digits, and optional unit (d/m/w/y)
                    // Examples: "-10", "+7", "-10d", "+1m", "30", "10w", "2y"
                    var relativeDatePattern = /^[+-]?\d+[dmwy]?$/i;
                    var isRelativeDate = relativeDatePattern.test(dateStr);

                    if ( isRelativeDate ) {
                        // Plain number (days) - e.g., "0", "10", "30"
                        // Note: "0" means today, which jQuery UI datepicker accepts
                        var plainNumberPattern = /^\d+$/;
                        if ( plainNumberPattern.test(dateStr) ) {
                            return parseInt(dateStr, 10);
                        }

                        // Number with +/- prefix but no suffix (days) - e.g., "-10", "+7"
                        var signedNumberPattern = /^[+-]\d+$/;
                        if ( signedNumberPattern.test(dateStr) ) {
                            return parseInt(dateStr, 10);
                        }

                        // Has suffix (d/m/w/y) - e.g., "-10d", "+1m", "30w"
                        // Regex: Captures optional sign, digits, and unit
                        var unitMatch = dateStr.match(/^([+-]?)(\d+)([dmwy])$/i);

                        if ( unitMatch ) {
                            var sign = unitMatch[1] || '+';
                            var value = parseInt(unitMatch[2], 10);
                            var unit = unitMatch[3].toLowerCase();

                            var today = new Date();
                            var targetDate = new Date(today);

                            if ( unit === 'd' ) {
                                // Days - use number for better compatibility
                                return (sign === '-' ? -1 : 1) * value;
                            } else if ( unit === 'w' ) {
                                // Weeks - convert to days
                                return (sign === '-' ? -1 : 1) * (value * <?php echo self::DAYS_PER_WEEK; ?>);
                            } else if ( unit === 'm' ) {
                                // Months - calculate actual date
                                if ( sign === '-' ) {
                                    targetDate.setMonth(today.getMonth() - value);
                                } else {
                                    targetDate.setMonth(today.getMonth() + value);
                                }
                                return targetDate;
                            } else if ( unit === 'y' ) {
                                // Years - calculate actual date
                                if ( sign === '-' ) {
                                    targetDate.setFullYear(today.getFullYear() - value);
                                } else {
                                    targetDate.setFullYear(today.getFullYear() + value);
                                }
                                return targetDate;
                            }
                        }

                        // Fallback: return as string
                        return dateStr;
                    } else {
                        // Try to parse as full date (DD-MM-YYYY or DD/MM/YYYY)
                        var dateParts = dateStr.split(/[-/]/);

                        if ( dateParts.length === 3 ) {
                            var year = parseInt(dateParts[2], 10);
                            var month = parseInt(dateParts[1], 10) - 1; // JavaScript months are 0-indexed
                            var day = parseInt(dateParts[0], 10);
                            return new Date(year, month, day);
                        }
                    }

                    return null;
                }

                <?php
                // Process min/max dates if they exist
                // Note: Use isset() and strlen() instead of empty() to handle "0" correctly
                // PHP's empty() treats "0" as empty, but "0" is a valid date value (today)
                $maxtime = '';
                $mintime = '';

                $has_mintime = isset( $field_settings['mintime'] ) && strlen( trim( $field_settings['mintime'] ) ) > 0;
                $has_maxtime = isset( $field_settings['maxtime'] ) && strlen( trim( $field_settings['maxtime'] ) ) > 0;

                if ( $has_mintime || $has_maxtime ) {
                    if ( $has_mintime ) {
                        $mintime = str_replace( '/', '-', trim( $field_settings['mintime'] ) );
                    }
                    if ( $has_maxtime ) {
                        $maxtime = str_replace( '/', '-', trim( $field_settings['maxtime'] ) );
                    }
                ?>
                var dateObj = jQuery.extend({}, date_default);

                <?php if ( $has_mintime ) { ?>
                var parsedMinDate = parseDateValue("<?php echo esc_js( $mintime ); ?>");

                if ( parsedMinDate !== null ) {
                    dateObj.minDate = parsedMinDate;
                }
                <?php } ?>

                <?php if ( $has_maxtime ) { ?>
                var parsedMaxDate = parseDateValue("<?php echo esc_js( $maxtime ); ?>");

                if ( parsedMaxDate !== null ) {
                    dateObj.maxDate = parsedMaxDate;
                }
                <?php } ?>

                <?php
                if ( isset( $field_settings['time'] ) && $field_settings['time'] === 'yes' ) {
                ?>
                // Check if datepicker already exists and destroy it first
                if ( $dateField.hasClass('hasDatepicker') ) {
                    $dateField.datepicker('destroy');
                }

                $dateField.datetimepicker(dateObj);

                // Explicitly set minDate and maxDate after initialization to ensure they stick
                if ( dateObj.hasOwnProperty('minDate') ) {
                    try {
                        $dateField.datetimepicker('option', 'minDate', dateObj.minDate);
                    } catch (e) {
                        // Silent fail
                    }
                }

                if ( dateObj.hasOwnProperty('maxDate') ) {
                    try {
                        $dateField.datetimepicker('option', 'maxDate', dateObj.maxDate);
                    } catch (e) {
                        // Silent fail
                    }
                }
                <?php } else { ?>
                // Check if datepicker already exists and destroy it first
                if ( $dateField.hasClass('hasDatepicker') ) {
                    $dateField.datepicker('destroy');
                }

                $dateField.datepicker(dateObj);

                // Explicitly set minDate and maxDate after initialization to ensure they stick
                if ( dateObj.hasOwnProperty('minDate') ) {
                    try {
                        $dateField.datepicker('option', 'minDate', dateObj.minDate);
                    } catch (e) {
                        // Silent fail
                    }
                }

                if ( dateObj.hasOwnProperty('maxDate') ) {
                    try {
                        $dateField.datepicker('option', 'maxDate', dateObj.maxDate);
                    } catch (e) {
                        // Silent fail
                    }
                }
                <?php
                }
                ?>
                // Mark as initialized to prevent field-initialization.js from reinitializing
                $dateField.addClass('wpuf-initialized');

                <?php
                } else {
                ?>
                var dateObj = jQuery.extend({}, date_default);

                <?php
                if ( isset( $field_settings['time'] ) && $field_settings['time'] === 'yes' ) {
                ?>
                $dateField.datetimepicker(date_default);
                <?php } else { ?>
                $dateField.datepicker(date_default);
                <?php
                }
                ?>
                // Mark as initialized to prevent field-initialization.js from reinitializing
                $dateField.addClass('wpuf-initialized');
                <?php
                }
                ?>
            });
        </script>

        <?php
        $this->after_field_print_label();
    }

    /**
     * Get field options setting
     *
     * @return array
     */
    public function get_options_settings() {
        $default_options = $this->get_default_option_settings();

        $settings = array(
            array(
                'name'      => 'format',
                'title'     => __( 'Date Format', 'wpuf-pro' ),
                'type'      => 'text',
                'section'   => 'advanced',
                'priority'  => 23,
                'help_text' => __( 'The date format', 'wpuf-pro' ),
            ),

            array(
                'name'          => 'time',
                'title'         => '',
                'type'          => 'checkbox',
                'is_single_opt' => true,
                'options'       => array(
                    'yes'   => __( 'Enable time input', 'wpuf-pro' ),
                ),
                'section'       => 'advanced',
                'priority'      => 24,
                'help_text'     => '',
            ),

            array(
                'name'          => 'mintime',
                'title'         => __( 'Enter minimum date', 'wpuf-pro' ),
                'type'          => 'text',
                'section'       => 'advanced',
                'priority'      => 24,
                'help_text'     => __( 'Enter min range for user date input', 'wpuf-pro' ),
                'placeholder'   => __( 'DD/MM/YY', 'wpuf-pro' ),
            ),

            array(
                'name'          => 'maxtime',
                'title'         => __( 'Enter maximum date', 'wpuf-pro' ),
                'type'          => 'text',
                'section'       => 'advanced',
                'priority'      => 24,
                'help_text'     => __( 'Enter max range for user date input', 'wpuf-pro' ),
                'placeholder'   => __( 'DD/MM/YY', 'wpuf-pro' ),
            ),

            array(
                'name'          => 'is_publish_time',
                'title'         => '',
                'type'          => 'checkbox',
                'is_single_opt' => true,
                'options'       => array(
                    'yes'   => __( 'Set this as publish time input', 'wpuf-pro' ),
                ),
                'section'       => 'advanced',
                'priority'      => 24,
                'help_text'     => '',
            ),
        );

        return array_merge( $default_options, $settings );
    }

    /**
     * Get the field props
     *
     * @return array
     */
    public function get_field_props() {
        $defaults = $this->default_attributes();

        $props = array(
            'input_type'        => 'date',
            'format'            => 'dd/mm/yy',
            'is_meta'           => 'yes',
            'width'             => 'large',
            'id'                => 0,
            'is_new'            => true,
            'show_in_post'      => 'yes',
            'hide_field_label'  => 'no',
        );

        return array_merge( $defaults, $props );
    }

    /**
     * Prepare entry
     *
     * @param $field
     *
     * @return mixed
     */
    public function prepare_entry( $field ) {
        return ! empty( $_REQUEST[ $field['name'] ] ) ? strip_shortcodes( trim( sanitize_text_field( wp_unslash( $_REQUEST[ $field['name'] ] ) ) ) ) : '';
    }

    /**
     * Enqueue scripts
     *
     * @since 3.4.7
     */
    public function enqueue_scripts() {
        wp_enqueue_script( 'jquery-ui-timepicker', WPUF_PRO_ASSET_URI . '/js/jquery-ui-timepicker-addon.js', [ 'jquery-ui-datepicker' ], '1.2' );
    }

    /**
     * Validate date fields during form submission
     *
     * Checks all date fields in the form against their minDate/maxDate constraints.
     * This method is called via the 'wpuf_add_post_validate' and 'wpuf_update_post_validate' filters.
     *
     * @param string $error Existing error message from previous validation hooks
     * @return string Error message if validation fails, or empty string if valid
     * @since 4.2.3
     *
     * @example
     * // Hook registration (already done in Fields_Manager):
     * add_filter( 'wpuf_add_post_validate', [ 'WeDevs\Wpuf\Pro\Fields\Field_Date', 'validate_date_fields' ], 20 );
     */
    public static function validate_date_fields( $error ) {
        // If there's already an error from a previous hook, preserve it
        if ( ! empty( $error ) ) {
            return $error;
        }

        $form_id = isset( $_POST['form_id'] ) ? intval( wp_unslash( $_POST['form_id'] ) ) : 0;
        if ( ! $form_id ) {
            return $error;
        }

        $form = new \WeDevs\Wpuf\Admin\Forms\Form( $form_id );
        $form_fields = $form->get_fields();

        foreach ( $form_fields as $field ) {
            if ( 'date_field' !== $field['template'] && 'date_field' !== $field['input_type'] ) {
                continue;
            }

            $field_name = isset( $field['name'] ) ? $field['name'] : '';

            // Validate field name to prevent arbitrary POST data access
            if ( empty( $field_name ) || ! preg_match( '/^[a-zA-Z0-9_-]+$/', $field_name ) ) {
                continue;
            }

            $submitted_value = isset( $_POST[ $field_name ] ) ? sanitize_text_field( wp_unslash( $_POST[ $field_name ] ) ) : '';

            if ( empty( $submitted_value ) ) {
                continue;
            }

            $validation_error = self::validate_date_value( $submitted_value, $field );

            if ( ! empty( $validation_error ) ) {
                // Return error string - Frontend_Form_Ajax will handle sending it
                return $validation_error;
            }
        }

        return $error;
    }

    /**
     * Validate a single date value against field settings
     *
     * Validates date format, minDate, and maxDate constraints.
     *
     * @param string $date_value Submitted date value (e.g., "05/11/2025")
     * @param array  $field_settings Field configuration including 'format', 'mintime', 'maxtime', 'label'
     * @return string Error message if validation fails, or empty string if valid
     * @since 4.2.3
     *
     * @example
     * $error = self::validate_date_value( "05/11/2025", [
     *     'format' => 'dd/mm/yy',
     *     'mintime' => '01-01-2025',
     *     'maxtime' => '31-12-2025',
     *     'label' => 'Event Date'
     * ] );
     */
    private static function validate_date_value( $date_value, $field_settings ) {
        $format = isset( $field_settings['format'] ) ? $field_settings['format'] : 'dd/mm/yy';
        $field_label = isset( $field_settings['label'] ) ? $field_settings['label'] : __( 'Date field', 'wpuf-pro' );
        $time_enabled = isset( $field_settings['time'] ) && $field_settings['time'] === 'yes';

        // Convert jQuery date format to PHP format
        $php_format = str_replace( [ 'dd', 'mm', 'yy', 'yyyy' ], [ 'd', 'm', 'y', 'Y' ], $format );

        // If time is enabled, append time format (jQuery UI timepicker uses HH:mm format)
        $php_format_with_time = $php_format . ' H:i';

        // Parse submitted date - try date+time format first if time is enabled
        $date = false;
        if ( $time_enabled ) {
            // Try with date+time format (2-digit year)
            $date = \DateTime::createFromFormat( $php_format_with_time, $date_value );

            // If parsing fails and format uses 2-digit year (y), try with 4-digit year (Y)
            if ( false === $date && strpos( $php_format, 'y' ) !== false && strpos( $php_format, 'Y' ) === false ) {
                $php_format_4digit_with_time = str_replace( 'y', 'Y', $php_format ) . ' H:i';
                $date = \DateTime::createFromFormat( $php_format_4digit_with_time, $date_value );
            }
        }

        // If date+time parsing failed (or time not enabled), try date-only format
        if ( false === $date ) {
            $date = \DateTime::createFromFormat( $php_format, $date_value );

            // If parsing fails and format uses 2-digit year (y), try with 4-digit year (Y)
            if ( false === $date && strpos( $php_format, 'y' ) !== false && strpos( $php_format, 'Y' ) === false ) {
                $php_format_4digit = str_replace( 'y', 'Y', $php_format );
                $date = \DateTime::createFromFormat( $php_format_4digit, $date_value );
            }
        }

        // If still failing, try common date formats (with and without time)
        if ( false === $date ) {
            $common_formats = [ 'd/m/Y', 'd-m-Y', 'Y-m-d', 'Y/m/d' ];
            foreach ( $common_formats as $common_format ) {
                // Try date-only format
                $date = \DateTime::createFromFormat( $common_format, $date_value );
                if ( false !== $date ) {
                    break;
                }

                // If time is enabled, also try with time appended
                if ( $time_enabled ) {
                    $date = \DateTime::createFromFormat( $common_format . ' H:i', $date_value );
                    if ( false !== $date ) {
                        break;
                    }
                }
            }
        }

        if ( false === $date ) {
            $expected_format = $time_enabled ? $format . ' HH:mm' : $format;
            return sprintf(
                /* translators: 1: Field label, 2: Expected date format */
                __( 'Invalid date format for %1$s. Expected format: %2$s', 'wpuf-pro' ),
                $field_label,
                $expected_format
            );
        }

        // Set time to midnight for date-only comparison
        $date->setTime( 0, 0, 0 );

        // Validate minDate
        // Note: Use isset() and strlen() instead of empty() to handle "0" correctly
        $has_mintime = isset( $field_settings['mintime'] ) && strlen( trim( $field_settings['mintime'] ) ) > 0;

        if ( $has_mintime ) {
            $min_date = self::parse_min_max_date( $field_settings['mintime'] );

            if ( false !== $min_date ) {
                // Set time to midnight for date-only comparison
                $min_date->setTime( 0, 0, 0 );

                if ( $date < $min_date ) {
                    return sprintf(
                        /* translators: 1: Field label, 2: Minimum date */
                        __( 'The date for %1$s must be on or after %2$s', 'wpuf-pro' ),
                        $field_label,
                        $min_date->format( $php_format )
                    );
                }
            }
        }

        // Validate maxDate
        // Note: Use isset() and strlen() instead of empty() to handle "0" correctly
        $has_maxtime = isset( $field_settings['maxtime'] ) && strlen( trim( $field_settings['maxtime'] ) ) > 0;

        if ( $has_maxtime ) {
            $max_date = self::parse_min_max_date( $field_settings['maxtime'] );

            if ( false !== $max_date ) {
                // Set time to midnight for date-only comparison
                $max_date->setTime( 0, 0, 0 );

                if ( $date > $max_date ) {
                    return sprintf(
                        /* translators: 1: Field label, 2: Maximum date */
                        __( 'The date for %1$s must be on or before %2$s', 'wpuf-pro' ),
                        $field_label,
                        $max_date->format( $php_format )
                    );
                }
            }
        }

        return '';
    }

    /**
     * Parse min/max date string (relative or absolute)
     *
     * Supports relative dates: "-10d", "+1m", "30", "+2w", "-1y"
     * Supports absolute dates: "01-01-2024", "01/01/2024"
     *
     * @param string $date_string Date string (e.g., "-10d", "+1m", "01/01/2024")
     * @return \DateTime|false DateTime object or false on failure
     * @since 4.2.3
     */
    private static function parse_min_max_date( $date_string ) {
        $date_string = trim( $date_string );

        // Regex pattern breakdown:
        // ^([+-]?) - Optional sign at start (captures +, -, or empty)
        // (\d+)    - One or more digits (captures the numeric value)
        // ([dmwy])? - Optional unit suffix (d=days, m=months, w=weeks, y=years)
        // $        - End of string
        // Examples: "-10d", "+1m", "30", "+2w", "-1y", "0"
        if ( preg_match( '/^([+-]?)(\d+)([dmwy])?$/i', $date_string, $matches ) ) {
            $today = new \DateTime();
            $sign = $matches[1] ?: '+';
            $value = intval( $matches[2] );
            $unit = isset( $matches[3] ) ? strtolower( $matches[3] ) : 'd';

            if ( $sign === '-' ) {
                $value = -$value;
            }

            switch ( $unit ) {
                case 'd':
                    $today->modify( $value . ' days' );
                    break;
                case 'w':
                    $today->modify( ( $value * 7 ) . ' days' );
                    break;
                case 'm':
                    $today->modify( $value . ' months' );
                    break;
                case 'y':
                    $today->modify( $value . ' years' );
                    break;
            }

            $today->setTime( 0, 0, 0 );
            return $today;
        }

        // Try to parse as absolute date - handle both single and double digit days/months
        $normalized_string = str_replace( '/', '-', $date_string );
        $parts = explode( '-', $normalized_string );

        if ( count( $parts ) === 3 ) {
            // Try j-n-Y format (handles single digit day/month)
            $parsed = \DateTime::createFromFormat( 'j-n-Y', $normalized_string );
            if ( false === $parsed ) {
                // Fallback to d-m-Y format (double digit day/month)
                $parsed = \DateTime::createFromFormat( 'd-m-Y', $normalized_string );
            }

            // Validate that parsed date matches input to prevent overflow (e.g., 31-02-2024 becomes 03-03-2024)
            if ( false !== $parsed ) {
                // Check if the formatted date matches the input (prevents invalid dates like 31-02)
                $formatted_date = $parsed->format( 'j-n-Y' );
                $formatted_date_padded = $parsed->format( 'd-m-Y' );

                // Normalize input for comparison (remove leading zeros)
                $normalized_input = preg_replace( '/\b0+(\d)/', '$1', $normalized_string );

                // Only return if the date is valid (not overflowed)
                if ( $formatted_date === $normalized_input || $formatted_date_padded === $normalized_string ) {
                    $parsed->setTime( 0, 0, 0 );
                    return $parsed;
                }
            }
        }

        return false;
    }

}

