<?php
/**
 * The no-JavaScript picker select: its bounded option list, its selected-row injection,
 * and the honest disclosure of its bound.
 *
 * WITH JavaScript there is NO CAP. The pickers search the database through
 * wrenchpress/v1/search, so any active customer or vehicle is findable.
 *
 * WITHOUT JavaScript the server has to render every option into the page, and it cannot
 * render an unbounded number of them. So the no-JS select is bounded to NOJS_LIMIT rows,
 * and this plugin does not pretend otherwise: cap_notice() says so on screen, in those
 * words, naming the true total. The island REMOVES that notice when it boots, because with
 * the island running the cap does not exist and leaving the notice up would be a lie in the
 * other direction.
 *
 * SELECTED-ROW INJECTION IS A BUG FIX, NOT A NICETY. Read this before touching it.
 *
 * The old code rendered the first 500 ACTIVE rows and marked the saved one selected. If the
 * saved owner was ARCHIVED, or simply sat outside that window, NO option matched, the
 * browser fell back to the placeholder, and the form came up showing nothing over a real,
 * correct id. The user then either had to reassign the vehicle to somebody else to get the
 * form to submit, or (before the value="" fix) silently saved a 0. This is reachable on a
 * real install today: archiving a customer does not archive their cars.
 *
 * So the selected id is resolved through Repository::get(), which is STATUS-AGNOSTIC, and
 * prepended as an option when the bounded list does not already contain it. An archived
 * owner is not offered as a NEW choice anywhere (the search endpoint excludes archived rows
 * and so does list_active), but a saved one is never silently dropped.
 *
 * @package WrenchPress
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class WrenchPress_Picker_Options
 */
class WrenchPress_Picker_Options {

	/**
	 * How many rows the server renders into a no-JavaScript picker select.
	 *
	 * The ONLY bound left in the picker stack, and it applies to the no-JS path alone. It
	 * replaces the old WrenchPress_Vehicles_Admin_Page::PICKER_LIMIT and the four
	 * hardcoded 'per_page' => 500 calls, which bounded the JavaScript path too and made a
	 * customer past 500 unselectable in the entire admin.
	 */
	const NOJS_LIMIT = 500;

	/**
	 * The bounded customer options for a no-JS select, with the selected row injected.
	 *
	 * @param int $selected_id Currently selected customer id (0 when nothing is chosen).
	 * @return array<int, array{value:int,label:string}> Options in label order.
	 */
	public static function customer_options( $selected_id ) {
		$selected_id = absint( $selected_id );
		$rows        = WrenchPress_Customers_Repository::list_active(
			array( 'per_page' => self::NOJS_LIMIT )
		);

		$options = array();
		$present = false;

		foreach ( $rows as $row ) {
			if ( (int) $row->id === $selected_id ) {
				$present = true;
			}

			$options[] = array(
				'value' => (int) $row->id,
				'label' => WrenchPress_Customers_Repository::label( $row ),
			);
		}

		if ( $selected_id > 0 && ! $present ) {
			$selected = WrenchPress_Customers_Repository::get( $selected_id );

			if ( $selected ) {
				array_unshift(
					$options,
					array(
						'value' => (int) $selected->id,
						'label' => WrenchPress_Customers_Repository::label( $selected ),
					)
				);
			}
		}

		return $options;
	}

	/**
	 * The bounded vehicle options for a no-JS select, with the selected row injected.
	 *
	 * @param int $selected_id Currently selected vehicle id (0 when nothing is chosen).
	 * @return array<int, array{value:int,label:string,customer_id:int}> Options in label order.
	 */
	public static function vehicle_options( $selected_id ) {
		$selected_id = absint( $selected_id );
		$rows        = WrenchPress_Vehicles_Repository::list_active(
			array( 'per_page' => self::NOJS_LIMIT )
		);

		$options = array();
		$present = false;

		foreach ( $rows as $row ) {
			if ( (int) $row->id === $selected_id ) {
				$present = true;
			}

			$options[] = array(
				'value'       => (int) $row->id,
				'label'       => WrenchPress_Vehicles_List_Table::vehicle_label( $row ),
				'customer_id' => (int) $row->customer_id,
			);
		}

		if ( $selected_id > 0 && ! $present ) {
			$selected = WrenchPress_Vehicles_Repository::get( $selected_id );

			if ( $selected ) {
				array_unshift(
					$options,
					array(
						'value'       => (int) $selected->id,
						'label'       => WrenchPress_Vehicles_List_Table::vehicle_label( $selected ),
						'customer_id' => (int) $selected->customer_id,
					)
				);
			}
		}

		return $options;
	}

	/**
	 * The { value, label } the island PINS into its options array so a saved row is never
	 * absent from the array core renders the collapsed field's text from (D-OTQ-05).
	 *
	 * Resolved through ::get(), which is status-agnostic, so an ARCHIVED owner still comes
	 * back. That is the entire point: this is the hydration mechanism, and it arrives in the
	 * bootstrap before first paint, so there is no round trip and no flash of an empty field
	 * over a real id.
	 *
	 * @param int $selected_id Selected customer id.
	 * @return array{value:string,label:string}|null Null when nothing is selected.
	 */
	public static function selected_customer( $selected_id ) {
		$selected_id = absint( $selected_id );

		if ( 0 === $selected_id ) {
			return null;
		}

		$customer = WrenchPress_Customers_Repository::get( $selected_id );

		if ( ! $customer ) {
			return null;
		}

		return array(
			'value' => (string) (int) $customer->id,
			'label' => WrenchPress_Customers_Repository::label( $customer ),
		);
	}

	/**
	 * The { value, label } the island pins for a selected vehicle. Status-agnostic, for the
	 * same reason as selected_customer().
	 *
	 * @param int $selected_id Selected vehicle id.
	 * @return array{value:string,label:string,customerId:string}|null Null when nothing is selected.
	 */
	public static function selected_vehicle( $selected_id ) {
		$selected_id = absint( $selected_id );

		if ( 0 === $selected_id ) {
			return null;
		}

		$vehicle = WrenchPress_Vehicles_Repository::get( $selected_id );

		if ( ! $vehicle ) {
			return null;
		}

		return array(
			'value'      => (string) (int) $vehicle->id,
			'label'      => WrenchPress_Vehicles_List_Table::vehicle_label( $vehicle ),
			'customerId' => (string) (int) $vehicle->customer_id,
		);
	}

	/**
	 * Total active customers, for the disclosure.
	 *
	 * @return int
	 */
	public static function customer_total() {
		return (int) WrenchPress_Customers_Repository::count_active();
	}

	/**
	 * Total active vehicles, for the disclosure.
	 *
	 * @return int
	 */
	public static function vehicle_total() {
		return (int) WrenchPress_Vehicles_Repository::count_active();
	}

	/**
	 * The truncation notice: an escaped HTML paragraph, or '' when nothing is truncated.
	 *
	 * SERVER-RENDERED, deliberately, and NOT wrapped in <noscript>. The island's enqueue is
	 * guarded on file_exists() of its generated asset file, so a missing or stale build is a
	 * real state in which JavaScript is enabled and the enhancement still does not run. In
	 * that state a <noscript> notice would be hidden and the cap would be silent again. The
	 * island removes this element on boot instead, which is true in every one of those cases.
	 *
	 * It carries the class wrenchpress-picker-cap-notice, which is the contract the island
	 * removes it by.
	 *
	 * @param int    $shown      How many rows the select offers.
	 * @param int    $total      How many active rows exist.
	 * @param string $manage_url Where to go to manage the full list.
	 * @param string $kind       'customers' or 'vehicles'.
	 * @return string Escaped HTML, or ''.
	 */
	public static function cap_notice( $shown, $total, $manage_url, $kind ) {
		$shown = (int) $shown;
		$total = (int) $total;

		if ( $total <= $shown ) {
			return '';
		}

		if ( 'vehicles' === $kind ) {
			$sentence = sprintf(
				/* translators: 1: number of vehicles offered, 2: total active vehicles. */
				esc_html__( 'Without JavaScript this list offers only %1$s of the %2$s active vehicles, and a vehicle outside it cannot be chosen here. With JavaScript on, search finds any of them.', 'pitdesk-repair-crm' ),
				esc_html( number_format_i18n( $shown ) ),
				esc_html( number_format_i18n( $total ) )
			);
			$link = esc_html__( 'Manage vehicles', 'pitdesk-repair-crm' );
		} else {
			$sentence = sprintf(
				/* translators: 1: number of customers offered, 2: total active customers. */
				esc_html__( 'Without JavaScript this list offers only %1$s of the %2$s active customers, and a customer outside it cannot be chosen here. With JavaScript on, search finds any of them.', 'pitdesk-repair-crm' ),
				esc_html( number_format_i18n( $shown ) ),
				esc_html( number_format_i18n( $total ) )
			);
			$link = esc_html__( 'Manage customers', 'pitdesk-repair-crm' );
		}

		return '<p class="description wrenchpress-picker-cap-notice">' . $sentence .
			' <a href="' . esc_url( $manage_url ) . '">' . $link . '</a></p>';
	}
}
