<?php

namespace WPFormsSurveys\Reporting;

/**
 * Field related survey reporting methods.
 *
 * @since 1.0.0
 */
class Fields {

	/**
	 * Build and return the survey data for a given field.
	 *
	 * @since 1.0.0
	 *
	 * @param array $field       Field settings.
	 * @param int   $form_id     Form ID.
	 * @param int   $entry_count Total number of entries.
	 * @param array $form_data   Form data and settings.
	 *
	 * @return array
	 */
	public static function get_survey_field_data( $field, $form_id, $entry_count = 0, $form_data = [] ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded

		// Get answers for this field.
		$answers = wpforms()->get( 'entry_fields' )->get_fields(
			[
				'number'   => -1,
				'form_id'  => $form_id,
				'field_id' => $field['id'],
				'order'    => 'ASC',
			]
		);

		$ignored_entry_ids = self::get_ignored_entry_ids( $form_id );

		$answers = array_filter(
			$answers,
			static function ( $answer ) use ( $ignored_entry_ids ) {

				return ! in_array( $answer->entry_id, $ignored_entry_ids, true );
			}
		);

		// Setup and define basic default data.
		$data = [
			'id'       => $field['id'],
			'type'     => $field['type'],
			'badge'    => self::get_field_badge_markup( $field['type'] ),
			'question' => $field['label'],
			'total'    => $entry_count,
			'answered' => count( $answers ),
			'skipped'  => ! empty( $answers ) && ! empty( $entry_count ) ? $entry_count - count( $answers ) : 0,
			'answers'  => [],
			'chart'    => [
				'supports' => [ 'bar', 'bar-h', 'pie', 'line' ],
				'default'  => 'bar',
				'labels'   => [],
				'totals'   => [],
				'data'     => [],
			],
		];

		// If there are no answers, bail to prevent calculations.
		if ( empty( $answers ) ) {
			return $data;
		}

		// Get ready to process and calculate the data.
		switch ( $field['type'] ) {
			// Radio, Select, and Checkbox fields all share the same calculations.
			case 'radio':
			case 'select':
			case 'checkbox':
			case 'rating':
				// Loop through each answer for process.
				foreach ( $answers as $answer ) {

					if ( self::is_multiple( $field ) ) {

						// Checkbox and Multiple Select values are slightly different because
						// there can be multiple values in a single answer.
						// This requires adjusted logic.
						$checks = explode( "\n", $answer->value );

						// Checkbox and Multiple Select reporting does not support the pie graph
						// so remove it from the defaults graph types supported.
						$data['chart']['supports'] = [ 'bar', 'bar-h', 'line' ];

						// Process values.
						foreach ( $checks as $check_key => $check ) {

							$exists = false;

							if ( ! empty( $data['answers'] ) ) {
								foreach ( $data['answers'] as $key => $item ) {
									if ( $check === $item['value'] ) {
										$data['answers'][ $key ]['count']++;

										$exists = true;

										break;
									}
								}
							}

							if ( ! $exists ) {
								$data['answers'][ $answer->id . '_' . $check_key ] = [
									'value' => $check,
									'count' => 1,
								];
							}
						}
					} else {

						$answer_value = $answer->value;
						$exists       = false;

						// For the rating field we adjust the values to make
						// them more human readable.
						if ( $field['type'] === 'rating' ) {

							switch ( $field['icon'] ) {
								case 'star':
									/* translators: %s - number of stars. */
									$answer_value = esc_html( sprintf( _n( '%s star', '%s stars', $answer->value, 'wpforms-surveys-polls' ), number_format_i18n( $answer->value ) ) );
									break;

								case 'heart':
									/* translators: %s - number of hearts. */
									$answer_value = esc_html( sprintf( _n( '%s heart', '%s hearts', $answer->value, 'wpforms-surveys-polls' ), number_format_i18n( $answer->value ) ) );
									break;

								case 'thumb':
									/* translators: %s - number of thumbs. */
									$answer_value = esc_html( sprintf( _n( '%s thumb', '%s thumbs', $answer->value, 'wpforms-surveys-polls' ), number_format_i18n( $answer->value ) ) );
									break;

								case 'smiley':
									/* translators: %s - number of smileys. */
									$answer_value = esc_html( sprintf( _n( '%s smiley', '%s smileys', $answer->value, 'wpforms-surveys-polls' ), number_format_i18n( $answer->value ) ) );
									break;
							}

							// Add the scale at the end for reference.
							$answer_value .= " ({$answer->value}/{$field['scale']})";
						}

						// Process values.
						if ( ! empty( $data['answers'] ) ) {
							foreach ( $data['answers'] as $key => $item ) {
								if ( $answer_value === $item['value'] ) {
									$data['answers'][ $key ]['count']++;

									$exists = true;

									break;
								}
							}
						}

						if ( ! $exists ) {
							$data['answers'][ $answer->id ] = [
								'value' => $answer_value,
								'count' => 1,
							];

							if ( $field['type'] === 'rating' ) {
								$data['answers'][ $answer->id ]['value_raw'] = $answer->value;
							}
						}
					}
				}

				// Rating field specific actions.
				if ( $field['type'] === 'rating' && ! empty( $data['answers'] ) ) {
					// Reorder answers by numeric value.
					usort(
						$data['answers'],
						function( $a, $b ) {
							return $a['value_raw'] - $b['value_raw'];
						}
					);

					// Calculate average rating.
					$total = 0;

					foreach ( $answers as $answer ) {
						$total += absint( $answer->value );
					}

					$data['average'] = round( $total / count( $answers ), 1 );
				}

				// For Radio, Checkboxes, and Select fields, reorder the answers
				// to match the order they are displayed in the survey.
				if ( in_array( $field['type'], [ 'radio', 'select', 'checkbox' ], true ) && ! empty( $data['answers'] ) ) {

					$answers_originals = $data['answers'];
					$answers_ordered   = [];

					foreach ( $field['choices'] as $choice_key => $choice ) {
						foreach ( $answers_originals as $key => $answers_original ) {
							if ( sanitize_text_field( $choice['label'] ) === $answers_original['value'] ) {
								$answers_ordered[ $key ]              = $answers_original;
								$answers_ordered[ $key ]['choice_id'] = $choice_key;

								unset( $answers_originals[ $key ] );
								break;
							}
						}
					}

					// If there are any answers remaining that means they are
					// choices that were available at some point, but have been
					// removed and no longer exist. In this case we add them to
					// the end.
					if ( ! empty( $answers_originals ) ) {
						$answers_ordered = array_replace_recursive( $answers_ordered, $answers_originals );
					}

					$data['answers'] = $answers_ordered;
				}

				// Loop through each answer and compile/format values needed for Chart JS.
				if ( ! empty( $data['answers'] ) ) {
					foreach ( $data['answers'] as $key => $item ) {
						$percent                            = round( ( $item['count'] / count( $answers ) ) * 100 );
						$data['answers'][ $key ]['percent'] = $percent;
						$data['chart']['labels'][]          = $item['value'];
						$data['chart']['totals'][]          = $item['count'];
						$data['chart']['data'][]            = $percent;
					}
				}
				break;

			case 'text':
			case 'textarea':
				// Text input and text area results.
				// Text based fields don't support charts so we disable.
				$data['chart']['supports'] = [];
				$data['chart']['default']  = false;

				// Loop through each answer.
				foreach ( $answers as $answer ) {

					$data['answers'][ $answer->id ] = [
						'value'     => $answer->value,
						'date'      => wpforms_datetime_format( $answer->date, '', true ),
						'date_unix' => strtotime( $answer->date ),
						'entry_id'  => absint( $answer->entry_id ),
					];
				}
				break;

			// Likert Scale results.
			case 'likert_scale':
				// Get the form data to have access to field rows and columns.
				if ( empty( $form_data ) ) {
					$form_data = wpforms()->get( 'form' )->get(
						$form_id,
						[
							'content_only' => true,
						]
					);
				}

				// Likert fields don't support charts so we disable.
				$data['chart']['supports'] = [];
				$data['chart']['default']  = false;

				// Basic details for rendering the results table.
				$data['table']            = [];
				$data['table']['columns'] = [];
				$data['table']['rows']    = [];
				$data['table']['single']  = ! empty( $field['single_row'] ) ? true : false;
				$data['table']['width']   = $data['table']['single'] ? round( 100 / count( $form_data['fields'][ $field['id'] ]['columns'] ), 4 ) : round( 80 / count( $form_data['fields'][ $field['id'] ]['columns'] ), 4 );

				// Prefix the field rows and column keys to preserve the order
				// when looping in our javascript template.
				foreach ( $form_data['fields'][ $field['id'] ]['rows'] as $k => $v ) {
					$data['table']['rows'][ "r{$k}" ] = $v;
				}
				foreach ( $form_data['fields'][ $field['id'] ]['columns'] as $k => $v ) {
					$data['table']['columns'][ "c{$k}" ] = $v;
				}

				// Get the row and column IDs to use to verify data.
				$row_ids    = array_map( 'absint', array_keys( $form_data['fields'][ $field['id'] ]['rows'] ) );
				$column_ids = array_map( 'absint', array_keys( $form_data['fields'][ $field['id'] ]['columns'] ) );

				// Set all the initial counts to zero.
				$counts = [];

				foreach ( $row_ids as $r ) {
					foreach ( $column_ids as $c ) {
						$counts[ $r ][ $c ] = 0;
					}
				}

				// Loop through each answer to process counts.
				foreach ( $answers as $answer ) {

					// Fetch and decode the raw values (arrays).
					$values = json_decode( $answer->value, true );
					$values = ! empty( $values['value_raw'] ) ? $values['value_raw'] : false;

					if ( ! is_array( $values ) ) {
						continue;
					}

					foreach ( $values as $row_key => $column_keys ) {

						// If this row key is not found, that means the admin
						// has likely removed that row from the field settings,
						// so we discard it.
						if ( ! in_array( absint( $row_key ), $row_ids, true ) ) {
							continue;
						}

						$column_keys = (array) $column_keys;

						foreach ( $column_keys as $column_key ) {

							// If this column key is not found, that means the
							// admin has likely removed that column from the
							// field settings, so we discard it.
							if ( ! in_array( absint( $column_key ), $column_ids, true ) ) {
								continue;
							}

							// Increment the count.
							$counts[ $row_key ][ $column_key ]++;
						}
					}
				}

				// Compile the final answer data to return.
				foreach ( $row_ids as $r ) {
					foreach ( $column_ids as $c ) {

						$answer_key = "r{$r}_c{$c}";
						$total      = ! empty( $counts[ $r ] ) ? array_sum( $counts[ $r ] ) : 0;
						$count      = $counts[ $r ][ $c ];

						$data['answers'][ $answer_key ] = [
							'count'   => $count,
							'percent' => ! empty( $count ) && ! empty( $total ) ? round( ( $count / $total ) * 100 ) : 0,
							'highest' => ! empty( $count ) ? max( $counts[ $r ] ) === $count : false,
						];
					}
				}
				break;

			case 'net_promoter_score':
				// Define initial values.
				$data['nps'] = [
					'detractors' => [
						'count'   => 0,
						'percent' => 0,
					],
					'passives'   => [
						'count'   => 0,
						'percent' => 0,
					],
					'promoters'  => [
						'count'   => 0,
						'percent' => 0,
					],
					'score'      => 0,
				];

				for ( $i = 0; $i < 11; $i++ ) {
					$data['answers'][ $i ] = [
						'value'   => $i,
						'count'   => 0,
						'percent' => 0,
					];
				}

				// Loop through each answer for process.
				foreach ( $answers as $answer ) {
					$data['answers'][ (int) $answer->value ]['count']++;
				}

				// Loop through each answer.
				if ( ! empty( $data['answers'] ) ) {
					foreach ( $data['answers'] as $key => $item ) {
						$percent                            = round( ( $item['count'] / count( $answers ) ) * 100 );
						$data['answers'][ $key ]['percent'] = $percent;

						// Compile/format values needed for Chart JS.
						$data['chart']['labels'][] = $item['value'];
						$data['chart']['totals'][] = $item['count'];
						$data['chart']['data'][]   = $percent;

						// Assign NPS category.
						if ( $item['value'] >= 9 ) {
							$data['nps']['promoters']['count'] += $item['count'];
						} elseif ( $item['value'] < 9 && $item['value'] > 6 ) {
							$data['nps']['passives']['count'] += $item['count'];
						} else {
							$data['nps']['detractors']['count'] += $item['count'];
						}
					}
				}

				// Calculate NPS category percentages.
				foreach ( $data['nps'] as $key => $nps_category ) {
					if ( $key === 'score' ) {
						continue;
					}
					$data['nps'][ $key ]['percent'] = round( ( $nps_category['count'] / count( $answers ) ) * 100 );
				}

				// Calculate raw NPS score.
				$data['nps']['score'] = round( ( ( $data['nps']['promoters']['count'] - $data['nps']['detractors']['count'] ) / count( $answers ) ) * 100, 2 );
				break;
		}

		/**
		 * Return the final array of data, filterable.
		 *
		 * @since 1.11.0
		 *
		 * @param array $data    Survey data for a given field.
		 * @param int   $form_id Form ID.
		 */
		return apply_filters( 'wpforms_surveys_reporting_fields_get_survey_field_data', $data, $form_id );
	}

	/**
	 * Get ignored entry ids.
	 * Return ids of abandoned and partial entries.
	 *
	 * @since 1.7.0
	 *
	 * @param int $form_id Form id.
	 *
	 * @return array
	 */
	private static function get_ignored_entry_ids( $form_id ) {

		$entry_handler = wpforms()->get( 'entry' );

		if ( ! $entry_handler ) {
			return [];
		}

		$abandoned = $entry_handler->get_entries(
			[
				'form_id' => $form_id,
				'status'  => 'abandoned',
				'select'  => 'entry_ids',
			]
		);

		$partial = $entry_handler->get_entries(
			[
				'form_id' => $form_id,
				'status'  => 'partial',
				'select'  => 'entry_ids',
			]
		);

		$trash = $entry_handler->get_entries(
			[
				'form_id' => $form_id,
				'status'  => 'trash',
				'select'  => 'entry_ids',
			]
		);

		return wp_list_pluck( array_merge( $abandoned, $partial, $trash ), 'entry_id' );
	}

	/**
	 * Return array of fields in a form that have survey reporting enabled.
	 *
	 * @since 1.0.0
	 *
	 * @param array $form_data Form data and settings.
	 * @param bool  $ids       Return field IDs when true, otherwise field arrays.
	 *
	 * @return bool|array
	 */
	public static function get_survey_fields( $form_data, $ids = false ) {

		if ( empty( $form_data['fields'] ) ) {
			return false;
		}

		$fields    = $form_data['fields'];
		$field_ids = [];

		if ( ! empty( $form_data['settings']['survey_enable'] ) ) {

			foreach ( $fields as $id => $field ) {
				if ( ! in_array( $field['type'], self::get_survey_field_types(), true ) ) {
					unset( $fields[ $id ] );
				} else {
					$field_ids[] = $id;
				}
			}
		} else {
			foreach ( $fields as $id => $field ) {
				if ( ! self::field_has_survey( $field ) ) {
					unset( $fields[ $id ] );
				} else {
					$field_ids[] = $id;
				}
			}
		}

		if ( $ids ) {
			return $field_ids;
		}

		return $fields;
	}

	/**
	 * Output HTML markup for a badge that indicates the field type.
	 *
	 * @since 1.0.0
	 *
	 * @param string $type Field type slug.
	 *
	 * @return string
	 */
	public static function get_field_badge_markup( $type ) {

		$badge = [];

		switch ( $type ) {
			case 'text':
				$badge['name'] = esc_html__( 'Single Line Text', 'wpforms-surveys-polls' );
				$badge['icon'] = 'fa-text-width';
				break;

			case 'textarea':
				$badge['name'] = esc_html__( 'Paragraph Text', 'wpforms-surveys-polls' );
				$badge['icon'] = 'fa-paragraph';
				break;

			case 'select':
				$badge['name'] = esc_html__( 'Dropdown', 'wpforms-surveys-polls' );
				$badge['icon'] = 'fa-caret-square-o-down';
				break;

			case 'radio':
				$badge['name'] = esc_html__( 'Multiple Choice', 'wpforms-surveys-polls' );
				$badge['icon'] = 'fa-list-ul';
				break;

			case 'checkbox':
				$badge['name'] = esc_html__( 'Checkboxes', 'wpforms-surveys-polls' );
				$badge['icon'] = 'fa-check-square-o';
				break;

			case 'rating':
				$badge['name'] = esc_html__( 'Rating', 'wpforms-surveys-polls' );
				$badge['icon'] = 'fa-star';
				break;

			case 'likert_scale':
				$badge['name'] = esc_html__( 'Likert Scale', 'wpforms-surveys-polls' );
				$badge['icon'] = 'fa-ellipsis-h';
				break;

			case 'net_promoter_score':
				$badge['name'] = esc_html__( 'Net Promoter Score', 'wpforms-surveys-polls' );
				$badge['icon'] = 'fa-tachometer';
				break;
		}

		$badge = apply_filters( 'wpforms_surveys_reporting_fields_get_field_badge_markup', $badge );

		return sprintf(
			'<span class="badge"><i class="fa %s" aria-hidden="true"></i> %s</span>',
			sanitize_html_class( $badge['icon'] ),
			esc_html( $badge['name'] )
		);
	}

	/**
	 * Check if the provided fields from a form contain survey.
	 *
	 * @since 1.0.0
	 *
	 * @param array $fields Found fields.
	 *
	 * @return bool
	 */
	public static function fields_has_survey( $fields ) {

		if ( ! empty( $fields ) && is_array( $fields ) ) {
			foreach ( $fields as $field ) {
				if ( self::field_has_survey( $field ) ) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Check if a specific field has survey reporting enabled.
	 *
	 * @since 1.0.0
	 *
	 * @param array $field     Field data and settings.
	 * @param array $form_data Form data and settings.
	 *
	 * @return bool
	 */
	public static function field_has_survey( $field, $form_data = [] ) {

		if ( isset( $field['survey'] ) && $field['survey'] === '1' ) {
			return true;
		} elseif ( ! empty( $form_data['settings']['survey_enable'] ) && in_array( $field['type'], self::get_survey_field_types(), true ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Get an array of field types that support Surveys.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public static function get_survey_field_types() {

		return apply_filters(
			'wpforms_surveys_reporting_fields_get_survey_field_types',
			[ 'text', 'textarea', 'select', 'radio', 'checkbox', 'rating', 'likert_scale', 'net_promoter_score' ]
		);
	}

	/**
	 * Determine if it's the field with multiple feature.
	 *
	 * @since 1.6.3
	 *
	 * @param array $field Field data.
	 *
	 * @return bool
	 */
	public static function is_multiple( $field ) {

		if ( empty( $field['type'] ) ) {
			return false;
		}

		if (
			$field['type'] === 'checkbox' ||
			( $field['type'] === 'select' && ! empty( $field['multiple'] )
		) ) {
			return true;
		}

		return false;
	}
}
