<?php
/**
 * Block checkout — saved address selector.
 *
 * @package Vanquish\MultipleAddresses\Frontend
 */

namespace Vanquish\MultipleAddresses\Frontend;

use Vanquish\MultipleAddresses\Com\Access;
use Vanquish\MultipleAddresses\Com\Addresses;
use Vanquish\MultipleAddresses\Com\Settings;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Saved-address selection for WooCommerce's block (React) checkout.
 *
 * The classic checkout hooks do not fire there, and — importantly — WooCommerce
 * Blocks exposes NO slot fill anywhere near the address forms, so the rich UI
 * used by CheckoutPage (dropdown + inline add/edit/delete modal) cannot be
 * rendered there at all.
 *
 * What IS supported is the Additional Checkout Fields API: a `select` registered
 * at the `address` location renders natively inside both the billing and the
 * shipping address form. So the block checkout gets address *selection*, and
 * customers add/edit/delete their addresses in the My Account address book.
 *
 * Two consequences of that API worth knowing:
 *   - One field definition serves both forms, so the same option list appears in
 *     billing and shipping. We therefore offer every saved address in both
 *     (which is also useful: people do ship to their billing address).
 *   - The API has no "do not persist" option, so the chosen address id is stored
 *     in customer and order meta. That is harmless — it records which saved
 *     address the order was placed with.
 */
class BlockCheckout {

	/**
	 * Additional checkout field id (namespaced, as the API requires).
	 */
	const FIELD_ID = 'vanmuad/saved-address';

	/**
	 * Address book service.
	 *
	 * @var Addresses
	 */
	private $addresses;

	/**
	 * Settings service.
	 *
	 * @var Settings
	 */
	private $settings;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->addresses = new Addresses();
		$this->settings  = new Settings();

		add_action( 'woocommerce_init', array( $this, 'register_field' ) );
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ) );

		// Decides what the dropdown starts on. Registered unconditionally: it is
		// cheap, and it must also run on the Store API requests that render the
		// form, not only on the page load that registers the field.
		add_filter( 'woocommerce_get_default_value_for_' . self::FIELD_ID, array( $this, 'default_value' ), 10, 3 );
	}

	/**
	 * The address the dropdown starts on, per address form.
	 *
	 * WooCommerce persists this field against the customer like any other address
	 * field, so a returning customer would otherwise land on whatever they picked
	 * last — which reads as an arbitrary address being preselected. The merchant's
	 * setting decides instead: preselect the customer's default for THIS form, or
	 * nothing at all. That also matches how the classic checkout behaves.
	 *
	 * @param mixed  $value     Value WooCommerce would use.
	 * @param string $group     'billing', 'shipping' or 'other'.
	 * @param mixed  $wc_object WC_Customer or WC_Order the value belongs to.
	 * @return string
	 */
	public function default_value( $value, $group, $wc_object ) {
		if ( ! $this->settings->is_enabled( 'preselect_default_address', false ) ) {
			return '';
		}

		$user_id = 0;

		if ( $wc_object instanceof \WC_Customer ) {
			$user_id = (int) $wc_object->get_id();
		} elseif ( $wc_object instanceof \WC_Order ) {
			$user_id = (int) $wc_object->get_customer_id();
		}

		if ( ! $user_id ) {
			$user_id = get_current_user_id();
		}

		if ( ! $user_id ) {
			return '';
		}

		$purpose = 'shipping' === $group ? 'shipping' : 'billing';
		$default = $this->addresses->get_default( $user_id, $purpose );

		return $default ? (string) $default['id'] : '';
	}

	/**
	 * Register the "use a saved address" select in the address forms.
	 *
	 * Registered only when the current customer actually has saved addresses:
	 * an empty dropdown would just be noise. The condition is evaluated per
	 * request and holds for the Store API requests too, since those carry the
	 * same authenticated user.
	 *
	 * @return void
	 */
	public function register_field() {
		if ( ! function_exists( 'woocommerce_register_additional_checkout_field' ) ) {
			return;
		}

		$options = $this->options();

		if ( empty( $options ) ) {
			return;
		}

		woocommerce_register_additional_checkout_field(
			array(
				'id'       => self::FIELD_ID,
				'label'    => __( 'Use a saved address', 'vanquish-multiple-addresses-for-woocommerce' ),
				'location' => 'address',
				'type'     => 'select',
				'required' => false,
				'options'  => $options,
			)
		);
	}

	/**
	 * Pass the saved addresses to the script that applies the selection.
	 *
	 * @return void
	 */
	public function enqueue() {
		if ( ! function_exists( 'is_checkout' ) || ! is_checkout() || ! $this->is_block_checkout() ) {
			return;
		}

		$addresses = $this->addresses->all( get_current_user_id() );

		if ( empty( $addresses ) ) {
			return;
		}

		wp_enqueue_script(
			'vanmuad-block-checkout',
			VANMUAD_URL . 'assets/js/vanmuad-block-checkout.js',
			array( 'wp-data' ),
			VANMUAD_VERSION,
			true
		);

		// Only the address fields are needed client-side.
		$payload = array();
		foreach ( $addresses as $id => $address ) {
			$payload[ $id ] = $address['fields'];
		}

		/*
		 * What each form should start on. The server-side default filter is not
		 * enough on its own: WooCommerce persists this field against the customer
		 * like any other address field, and the stored value wins, so a returning
		 * customer kept landing on whatever they picked last. The script settles
		 * it in the store, where the outcome is ours to decide.
		 */
		$preselect = array(
			'billing'  => '',
			'shipping' => '',
		);

		if ( $this->settings->is_enabled( 'preselect_default_address', false ) ) {
			foreach ( array_keys( $preselect ) as $purpose ) {
				$default               = $this->addresses->get_default( get_current_user_id(), $purpose );
				$preselect[ $purpose ] = $default ? (string) $default['id'] : '';
			}
		}

		wp_localize_script(
			'vanmuad-block-checkout',
			'vanmuadBlocks',
			array(
				'fieldKey'  => self::FIELD_ID,
				'addresses' => $payload,
				'preselect' => $preselect,
			)
		);
	}

	// -----------------------------------------------------------------------
	// Internals
	// -----------------------------------------------------------------------

	/**
	 * Whether the checkout page is built with the checkout block.
	 *
	 * Checked before enqueuing, so a classic-checkout store does not pay for the
	 * `wp-data` dependency chain that this script would otherwise pull in.
	 *
	 * @return bool
	 */
	private function is_block_checkout() {
		if ( ! function_exists( 'wc_get_page_id' ) ) {
			return false;
		}

		$page_id = wc_get_page_id( 'checkout' );

		return $page_id > 0 && has_block( 'woocommerce/checkout', $page_id );
	}

	/**
	 * The dropdown options: every saved address of the current customer.
	 *
	 * The same list is offered in both address forms, which is exactly right now
	 * that an address is not tied to a purpose. (It would have been offered in
	 * both regardless: WooCommerce's Additional Checkout Fields API states that a
	 * field registered at the `address` location "will appear in both the
	 * shipping and the billing address", with one definition and one set of
	 * options rendered twice.)
	 *
	 * @return array
	 */
	private function options() {
		// No options means the field is never registered, which is how a customer
		// with no address book — or one denied it — sees nothing here.
		if ( ! is_user_logged_in() || ! Access::can_use() ) {
			return array();
		}

		$options = array();

		foreach ( $this->addresses->all( get_current_user_id() ) as $address ) {
			$options[] = array(
				'value' => (string) $address['id'],
				'label' => AddressAjax::label( $address ),
			);
		}

		return $options;
	}
}
