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

declare( strict_types=1 );

namespace TextTango\Forms;

defined( 'ABSPATH' ) || exit;

/**
 * Fluent Forms submissions (spec §21).
 *
 * Fluent Forms hands over the submitted data keyed by the field names set when
 * the form was built, which is the shape this plugin wants — so the adapter is
 * mostly guarding against the parts of its API that changed names between major
 * versions.
 */
final class FluentForms implements Provider {

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

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

	/**
	 * {@inheritDoc}
	 */
	public static function is_active(): bool {
		return defined( 'FLUENTFORM' ) || function_exists( 'wpFluentForm' );
	}

	/**
	 * {@inheritDoc}
	 *
	 * @return array<string, string>
	 */
	public static function forms(): array {
		global $wpdb;

		if ( ! self::is_active() ) {
			return array();
		}

		$table = $wpdb->prefix . 'fluentform_forms';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Another plugin's table; the name is built from $wpdb->prefix and cannot be parameterised.
		$rows = $wpdb->get_results( "SELECT id, title FROM {$table} ORDER BY title ASC", ARRAY_A );

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

		$forms = array();

		foreach ( $rows as $row ) {
			$forms[ (string) $row['id'] ] = (string) $row['title'];
		}

		return $forms;
	}

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

		if ( ! self::is_active() ) {
			return array();
		}

		$table = $wpdb->prefix . 'fluentform_forms';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Another plugin's table; the name is built from $wpdb->prefix.
		$fields = (string) $wpdb->get_var( $wpdb->prepare( "SELECT form_fields FROM {$table} WHERE id = %d", (int) $form_id ) );

		$decoded = json_decode( $fields, true );

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

		return array_values( array_unique( self::names_in( $decoded['fields'] ) ) );
	}

	/**
	 * Field names, following containers down.
	 *
	 * Fluent Forms nests: a two-column row is a field whose own `columns` hold
	 * more fields. Reading only the top level would miss every field on any form
	 * laid out in columns, which is most of them.
	 *
	 * @param array<int, mixed> $fields Field definitions.
	 * @return string[]
	 */
	private static function names_in( array $fields ): array {
		$names = array();

		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) ) {
				continue;
			}

			$name = trim( (string) ( $field['attributes']['name'] ?? '' ) );

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

			foreach ( (array) ( $field['columns'] ?? array() ) as $column ) {
				if ( is_array( $column ) && isset( $column['fields'] ) && is_array( $column['fields'] ) ) {
					$names = array_merge( $names, self::names_in( $column['fields'] ) );
				}
			}
		}

		return $names;
	}

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

		// The name before Fluent Forms 5 moved to slash-separated hooks. Both are
		// registered rather than one guessed, since a site can be on either.
		add_action( 'fluentform_submission_inserted', array( $this, 'submitted' ), 10, 3 );
	}

	/**
	 * Normalise a submission and hand it on.
	 *
	 * @param int                  $entry_id  Entry id.
	 * @param array<string, mixed> $form_data Submitted values.
	 * @param object|array         $form      The form.
	 */
	public function submitted( $entry_id, $form_data, $form ): void {
		if ( ! is_array( $form_data ) ) {
			return;
		}

		$values = array();

		foreach ( $form_data as $name => $value ) {
			if ( ! is_string( $name ) ) {
				continue;
			}

			if ( is_array( $value ) ) {
				$value = implode( ', ', array_filter( array_map( 'strval', $value ), 'strlen' ) );
			}

			if ( is_scalar( $value ) ) {
				$values[ $name ] = (string) $value;
			}
		}

		$id    = is_object( $form ) ? ( $form->id ?? '' ) : ( is_array( $form ) ? ( $form['id'] ?? '' ) : '' );
		$title = is_object( $form ) ? ( $form->title ?? '' ) : ( is_array( $form ) ? ( $form['title'] ?? '' ) : '' );

		Forms::handle( new Submission( self::slug(), (string) $id, (string) $title, $values ) );
	}
}
