<?php
/**
 * Free field hydration.
 *
 * @package EntryFoundry
 */

namespace EntryFoundry\Core\Services\Hydrate;

// phpcs:disable Squiz.Commenting, Generic.Commenting.DocComment

final class FieldHydrateStep {
	/** @param array<string,mixed> $template @param array<string,mixed> $context @return array<string,mixed> */
	public function __invoke( array $template, array $context ): array {
		$values         = isset( $context['values'] ) && is_array( $context['values'] ) ? $context['values'] : array();
		$display_values = isset( $context['display_values'] ) && is_array( $context['display_values'] ) ? $context['display_values'] : array();
		$fields         = isset( $context['fields'] ) && is_array( $context['fields'] ) ? $context['fields'] : array();
		foreach ( $template['elements'] as &$element ) {
			if ( 'table' === $element['type'] ) {
				$element = $this->hydrate_table( $element, $fields, $values, $display_values );
				continue;
			}
			if ( 'field' !== $element['type'] ) {
				continue; }
			$field_id           = (int) $element['field_id'];
			$display_mode       = isset( $element['display_mode'] ) ? (string) $element['display_mode'] : 'display';
			$value              = $display_values[ $field_id ][ $display_mode ] ?? $display_values[ $field_id ]['display'] ?? $values[ $field_id ] ?? '';
			$content            = $this->stringify( $value );
			$element['content'] = $this->format_field_content( $element, $content );
		}
		unset( $element );
		return $template;
	}

	/** @param array<string,mixed> $element @param array<int,array<string,mixed>> $fields @param array<int,mixed> $values @param array<int,array<string,mixed>> $display_values @return array<string,mixed> */
	private function hydrate_table( array $element, array $fields, array $values, array $display_values ): array {
		$table_mode = isset( $element['table_mode'] ) ? (string) $element['table_mode'] : 'manual';
		if ( 'entries' !== $table_mode ) {
			if ( isset( $element['rows'] ) && is_array( $element['rows'] ) ) {
				foreach ( $element['rows'] as &$row ) {
					if ( ! is_array( $row ) || ! isset( $row['cells'] ) || ! is_array( $row['cells'] ) ) {
						continue;
					}
					foreach ( $row['cells'] as &$cell ) {
						if ( is_array( $cell ) && isset( $cell['type'] ) && 'field' === $cell['type'] && ! empty( $cell['field_id'] ) ) {
							$field_id         = (int) $cell['field_id'];
							$display_mode     = isset( $cell['display_mode'] ) ? (string) $cell['display_mode'] : 'display';
							$value            = $display_values[ $field_id ][ $display_mode ] ?? $display_values[ $field_id ]['display'] ?? $values[ $field_id ] ?? '';
							$cell['value']    = $this->stringify( $value );
							$cell['field_id'] = $field_id;
						}
					}
					unset( $cell );
				}
				unset( $row );
			}
			return $element;
		}

		$rows         = isset( $element['rows'] ) && is_array( $element['rows'] ) ? $element['rows'] : array();
		$fields_by_id = array();
		foreach ( $fields as $field ) {
			if ( is_array( $field ) && isset( $field['id'] ) ) {
				$fields_by_id[ (int) $field['id'] ] = $field;
			}
		}
		if ( empty( $rows ) ) {
			foreach ( $fields as $field ) {
				if ( ! $this->is_document_field( $field ) ) {
					continue;
				}
				$rows[] = array(
					'field_id'     => (int) $field['id'],
					'label'        => isset( $field['label'] ) ? (string) $field['label'] : '',
					'display_mode' => 'display',
				);
			}
		}

		foreach ( $rows as &$row ) {
			if ( ! is_array( $row ) || ! isset( $row['field_id'] ) ) {
				continue;
			}
			$field_id = (int) $row['field_id'];
			if ( ! isset( $fields_by_id[ $field_id ] ) || ! $this->is_document_field( $fields_by_id[ $field_id ] ) ) {
				$row['is_hidden'] = true;
				continue;
			}
			if ( ! empty( $row['is_hidden'] ) ) {
				continue;
			}
			$display_mode     = isset( $row['display_mode'] ) ? (string) $row['display_mode'] : 'display';
			$value            = $display_values[ $field_id ][ $display_mode ] ?? $display_values[ $field_id ]['display'] ?? $values[ $field_id ] ?? '';
			$row['value']     = $this->stringify( $value );
			$row['is_hidden'] = false;
		}
		unset( $row );
		$rows            = array_merge( $rows, $this->calculation_rows( $element, $rows ) );
		$element['rows'] = $rows;
		return $element;
	}

	/** @param array<string,mixed> $element @param array<int,mixed> $rows @return array<int,array<string,mixed>> */
	private function calculation_rows( array $element, array $rows ): array {
		$calculations = isset( $element['calculations'] ) && is_array( $element['calculations'] ) ? $element['calculations'] : array();
		$output       = array();
		$numbers      = array();
		foreach ( $rows as $row ) {
			if ( ! is_array( $row ) || ! empty( $row['is_hidden'] ) ) {
				continue;
			}
			$value = isset( $row['value'] ) ? $this->number_from_value( $row['value'] ) : null;
			if ( null !== $value ) {
				$numbers[] = $value;
			}
		}
		foreach ( $calculations as $calculation ) {
			if ( ! is_array( $calculation ) || empty( $calculation['type'] ) ) {
				continue;
			}
			$type  = (string) $calculation['type'];
			$value = $this->calculate( $type, $numbers );
			if ( null === $value ) {
				continue;
			}
			$output[] = array(
				'label'          => isset( $calculation['label'] ) && '' !== (string) $calculation['label'] ? (string) $calculation['label'] : $this->calculation_label( $type ),
				'value'          => $this->format_number( $value ),
				'is_calculation' => true,
			);
		}
		return $output;
	}

	/** @param mixed $value */
	private function number_from_value( $value ): ?float {
		$normalized = preg_replace( '/[^0-9.\-]/', '', (string) $value );
		if ( null === $normalized || '' === $normalized || '-' === $normalized || '.' === $normalized ) {
			return null;
		}
		return is_numeric( $normalized ) ? (float) $normalized : null;
	}

	/** @param array<int,float> $numbers */
	private function calculate( string $type, array $numbers ): ?float {
		if ( empty( $numbers ) ) {
			return null;
		}
		if ( 'sum' === $type ) {
			return array_sum( $numbers );
		}
		if ( 'average' === $type ) {
			return array_sum( $numbers ) / count( $numbers );
		}
		if ( 'median' === $type ) {
			sort( $numbers, SORT_NUMERIC );
			$count = count( $numbers );
			$mid   = (int) floor( $count / 2 );
			return 0 === $count % 2 ? ( $numbers[ $mid - 1 ] + $numbers[ $mid ] ) / 2 : $numbers[ $mid ];
		}
		return null;
	}

	private function calculation_label( string $type ): string {
		if ( 'average' === $type ) {
			return 'Average';
		}
		if ( 'median' === $type ) {
			return 'Median';
		}
		return 'Total';
	}

	private function format_number( float $value ): string {
		$formatted = number_format( $value, 2, '.', '' );
		return rtrim( rtrim( $formatted, '0' ), '.' );
	}

	/** @param mixed $field */
	private function is_document_field( $field ): bool {
		if ( ! is_array( $field ) || ! isset( $field['id'] ) ) {
			return false;
		}
		$type = isset( $field['type'] ) ? strtolower( (string) $field['type'] ) : '';
		return 'submit' !== $type;
	}

	/** @param mixed $value */
	private function stringify( $value ): string {
		return is_array( $value ) ? implode( ', ', array_map( 'strval', $value ) ) : (string) $value;
	}

	/** @param array<string,mixed> $element */
	private function format_field_content( array $element, string $value ): string {
		$mode  = isset( $element['field_display'] ) ? (string) $element['field_display'] : ( ! empty( $element['show_label'] ) ? 'label_value' : 'value' );
		$label = isset( $element['field_label'] ) ? (string) $element['field_label'] : '';
		if ( 'label' === $mode ) {
			return $label;
		}
		if ( 'label_value' === $mode && '' !== $label ) {
			return $label . ":\n" . $value;
		}
		return $value;
	}
}
