<?php

namespace GravityKit\GravityView\QueryFilters\Querying\Field\Source;

use GFAPI;
use GF_Field;
use GravityKit\Lookup\Factory;
use GravityKit\Lookup\Field;
use GravityKit\Lookup\Resolver;
use GravityKit\Lookup\Sources\Base;
use GravityKit\Lookup\Sources\GFEntries;
use GravityKit\Lookup\Sources\PostTypes;
use GravityKit\Lookup\Sources\WPUsers;
use GravityKit\GravityView\QueryFilters\Querying\Field\FieldChoice;
use GravityKit\GravityView\QueryFilters\Querying\Field\FieldCriteria;

/**
 * {@see ChoiceSource} for Gravity Forms Dynamic Lookup (`lookup`) fields.
 *
 * Delegates the search and count to the plugin's own data sources so the database does the
 * filtering — the field's configuration is mapped onto a {@see Base} source, with the search needle
 * and result window pushed down as query parameters rather than fetched and filtered in memory.
 *
 * @since 2.14.0
 */
final class DynamicLookupSource implements ChoiceSource {
	/**
	 * Registers this source on the manager when the Gravity Forms Dynamic Lookup plugin is available.
	 *
	 * @since 2.14.0
	 *
	 * @param ChoiceSourceManager $manager The manager to register on.
	 */
	public static function register( ChoiceSourceManager $manager ): void {
		if ( ! class_exists( Field::class ) ) {
			return;
		}

		$manager->register( new self() );
	}

	/**
	 * {@inheritDoc}
	 *
	 * @since 2.14.0
	 */
	public function supports( int $form_id, string $field_id ): bool {
		$field = $this->field( $form_id, $field_id );

		return $field instanceof GF_Field
		       && 'lookup' === $field->type
		       && class_exists( Factory::class );
	}

	/**
	 * {@inheritDoc}
	 *
	 * @since 2.14.0
	 */
	public function equals( ChoiceSource $other ): bool {
		return $other instanceof self;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @since 2.14.0
	 */
	public function prefers_endpoint(): bool {
		return false;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @since 2.14.0
	 */
	public function search( FieldCriteria $criteria ): array {
		$source = $this->source( $criteria, $criteria->offset() + $criteria->limit() );
		if ( ! $source instanceof Base ) {
			return [];
		}

		$results = array_slice( (array) $source->get_results(), $criteria->offset(), $criteria->limit() );

		return array_values( array_map(
			static fn( array $row ): FieldChoice => new FieldChoice(
				(string) ( $row['value'] ?? '' ),
				(string) ( $row['text'] ?? ( $row['value'] ?? '' ) )
			),
			array_filter( $results, static fn( $row ): bool => is_array( $row ) && isset( $row['value'] ) )
		) );
	}

	/**
	 * {@inheritDoc}
	 *
	 * @since 2.14.0
	 */
	public function count( FieldCriteria $criteria ): int {
		$source = $this->source( $criteria, 1 );
		if ( ! $source instanceof Base ) {
			return 0;
		}

		return (int) $source->get_results_count();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @since 2.14.0
	 */
	public function resolve( FieldCriteria $criteria ): array {
		$wanted = $criteria->values();
		if ( $wanted === [] ) {
			return [];
		}

		$field = $this->field( $criteria->form_id(), $criteria->field_id() );
		if ( ! $field instanceof GF_Field || ! class_exists( Resolver::class ) ) {
			return [];
		}

		$resolver = Resolver::instance();
		$selector = (string) ( $field->displayValue ?? '' );
		$source   = (string) ( $field->lookupSourceType ?? 'gravity_forms' );

		if (
			GFEntries::get_source_type() === $source
			&& ! empty( $field->lookupFieldId )
			&& ! $resolver->is_valid_selector( $selector, $source )
		) {
			$selector = 'field:' . $field->lookupFieldId;
		}

		$out = [];
		foreach ( $wanted as $value ) {
			$label = (string) $resolver->get_display_value( $value, $selector, $source, $field );
			$out[] = new FieldChoice( (string) $value, '' !== $label ? $label : (string) $value );
		}

		return $out;
	}

	/**
	 * Builds the configured lookup data source with the criteria's search needle and a result
	 * window, mapping the field's lookup settings onto the source parameters.
	 *
	 * @since 2.14.0
	 *
	 * @param FieldCriteria $criteria The search criteria.
	 * @param int           $per_page The number of rows to request from the source.
	 *
	 * @return Base|null The source, or null when the field is not a resolvable lookup field.
	 */
	private function source( FieldCriteria $criteria, int $per_page ): ?Base {
		$field = $this->field( $criteria->form_id(), $criteria->field_id() );
		if ( ! $field instanceof GF_Field || 'lookup' !== $field->type || ! class_exists( Factory::class ) ) {
			return null;
		}

		$type   = (string) ( $field->lookupSourceType ?? GFEntries::get_source_type() );
		$common = [
			'search_term'    => $criteria->search(),
			'page'           => 1,
			'per_page'       => max( 1, $per_page ),
			'sort_direction' => $field->sortDirection ?? 'ASC',
			'storage_format' => $field->storageFormat ?? 'id',
			'unique_results' => $field->uniqueResults ?? true,
		];

		switch ( $type ) {
			case WPUsers::get_source_type():
				$params = $common + [
						'role'          => $field->lookupUserGroup ?? '',
						'orderby'       => $field->orderby ?? 'display_name',
						'display_value' => $field->displayValue ?? 'display_name',
					];
				break;
			case PostTypes::get_source_type():
				$params = $common + [
						'post_type'     => $field->lookupPostType ?? 'post',
						'orderby'       => $field->orderby ?? 'display_name',
						'display_value' => $field->displayValue ?? 'display_name',
					];
				break;
			case GFEntries::get_source_type():
			default:
				$type   = GFEntries::get_source_type();
				$params = $common + [
						'form_id'        => (int) ( $field->lookupFormId ?? 0 ),
						'field_id'       => (int) ( $field->lookupFieldId ?? 0 ),
						'limit_to_user'  => $field->limitToCurrentUser ?? false,
						'choice_display' => $field->choiceDisplay ?? 'text',
					];
				break;
		}

		return Factory::create( $type, $params );
	}

	/**
	 * Looks up a field on a form by ID.
	 *
	 * @since 2.14.0
	 *
	 * @param int    $form_id  The form ID.
	 * @param string $field_id The field ID.
	 *
	 * @return GF_Field|null The field, when found.
	 */
	private function field( int $form_id, string $field_id ): ?GF_Field {
		$form = GFAPI::get_form( $form_id );
		if ( ! $form || empty( $form['fields'] ) || ! is_array( $form['fields'] ) ) {
			return null;
		}

		foreach ( $form['fields'] as $field ) {
			if ( (string) $field->id === $field_id ) {
				return $field;
			}
		}

		return null;
	}
}
