<?php
/**
 * Gravity Forms.
 *
 * @package TextTango
 */

declare( strict_types=1 );

namespace TextTango\Forms;

defined( 'ABSPATH' ) || exit;

/**
 * Gravity Forms submissions (spec §21).
 *
 * Gravity Forms keys an entry by numeric field id, and a field can hold several
 * of them — a name field is `1.3` and `1.6`, an address more still. So the
 * label is used as the field name here, and the entry is walked through the
 * form definition to collect every input belonging to one labelled field.
 */
final class GravityForms implements Provider {

	/**
	 * {@inheritDoc}
	 */
	public static function slug(): string {
		return 'gravity';
	}

	/**
	 * {@inheritDoc}
	 */
	public static function label(): string {
		return __( 'Gravity Forms', 'texttango' );
	}

	/**
	 * {@inheritDoc}
	 */
	public static function is_active(): bool {
		return class_exists( 'GFAPI' );
	}

	/**
	 * {@inheritDoc}
	 *
	 * @return array<string, string>
	 */
	public static function forms(): array {
		if ( ! self::is_active() ) {
			return array();
		}

		$forms = array();

		foreach ( \GFAPI::get_forms() as $form ) {
			if ( isset( $form['id'], $form['title'] ) ) {
				$forms[ (string) $form['id'] ] = (string) $form['title'];
			}
		}

		return $forms;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @param string $form_id Form id.
	 * @return string[]
	 */
	public static function fields( string $form_id ): array {
		if ( ! self::is_active() ) {
			return array();
		}

		$form = \GFAPI::get_form( (int) $form_id );

		if ( ! is_array( $form ) || ! isset( $form['fields'] ) || ! is_array( $form['fields'] ) ) {
			return array();
		}

		$names = array();

		foreach ( $form['fields'] as $field ) {
			$label = trim( (string) ( is_object( $field ) ? ( $field->label ?? '' ) : ( $field['label'] ?? '' ) ) );

			if ( '' !== $label ) {
				$names[] = $label;
			}
		}

		return array_values( array_unique( $names ) );
	}

	/**
	 * {@inheritDoc}
	 */
	public function register(): void {
		add_action( 'gform_after_submission', array( $this, 'submitted' ), 10, 2 );
	}

	/**
	 * Normalise a submission and hand it on.
	 *
	 * @param array<string, mixed> $entry The entry.
	 * @param array<string, mixed> $form  The form.
	 */
	public function submitted( $entry, $form ): void {
		if ( ! is_array( $entry ) || ! is_array( $form ) || ! isset( $form['fields'] ) ) {
			return;
		}

		$values = array();

		foreach ( (array) $form['fields'] as $field ) {
			$id    = (string) ( is_object( $field ) ? ( $field->id ?? '' ) : ( $field['id'] ?? '' ) );
			$label = trim( (string) ( is_object( $field ) ? ( $field->label ?? '' ) : ( $field['label'] ?? '' ) ) );

			if ( '' === $id || '' === $label ) {
				continue;
			}

			$values[ $label ] = self::value_for( $entry, $id );
		}

		Forms::handle(
			new Submission(
				self::slug(),
				(string) ( $form['id'] ?? '' ),
				(string) ( $form['title'] ?? '' ),
				$values
			)
		);
	}

	/**
	 * One field's answer, gathering every input it is made of.
	 *
	 * A simple field is `$entry['4']`. A composite one is `$entry['1.3']`,
	 * `$entry['1.6']` and so on, with no `$entry['1']` at all — so reading the
	 * bare id alone would return nothing for exactly the fields most likely to
	 * hold somebody's name.
	 *
	 * @param array<string, mixed> $entry The entry.
	 * @param string               $id    Field id.
	 */
	private static function value_for( array $entry, string $id ): string {
		if ( isset( $entry[ $id ] ) && is_scalar( $entry[ $id ] ) && '' !== (string) $entry[ $id ] ) {
			return (string) $entry[ $id ];
		}

		$parts = array();

		foreach ( $entry as $key => $value ) {
			if ( ! is_scalar( $value ) || '' === (string) $value ) {
				continue;
			}

			if ( str_starts_with( (string) $key, $id . '.' ) ) {
				$parts[] = (string) $value;
			}
		}

		return implode( ' ', $parts );
	}
}
