<?php
/**
 * Address form fields and validation.
 *
 * @package Vanquish\MultipleAddresses\Com
 */

namespace Vanquish\MultipleAddresses\Com;

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

/**
 * Builds the address form from WooCommerce's own localized address fields, so
 * the country/state selects, the per-country field order and the required flags
 * all behave exactly like the native checkout — and validates a submission.
 *
 * There is one field set, not one per address type: an address is just an
 * address here, usable for billing, for shipping, or for both.
 */
class AddressForm {

	/**
	 * Fields that describe the person rather than the place. WooCommerce marks
	 * the billing ones required because a checkout cannot complete without them,
	 * but an address book entry is not a checkout: a customer saving a delivery
	 * address should not be forced to type an email. The checkout still asks for
	 * whatever it needs in its own form.
	 *
	 * @return string[]
	 */
	private static function optional_fields() {
		return array( 'email', 'phone' );
	}

	/**
	 * The address form fields, keyed by our own field names (first_name,
	 * address_1, …) rather than WooCommerce's prefixed ones.
	 *
	 * WooCommerce's billing set is used as the source because it is the superset
	 * — it carries the email and phone that the shipping set lacks.
	 *
	 * @param string $country Country to localize for (defaults to the store/base one).
	 * @return array
	 */
	public static function fields( $country = '' ) {
		if ( '' === $country ) {
			$country = self::default_country();
		}

		$wc_fields = WC()->countries->get_address_fields( $country, 'billing_' );

		// Strip WooCommerce's prefix so the keys match Addresses::field_names().
		$fields = array();
		foreach ( $wc_fields as $key => $field ) {
			$name = substr( $key, strlen( 'billing_' ) );

			if ( in_array( $name, self::optional_fields(), true ) ) {
				$field['required'] = false;
			}

			$fields[ $name ] = $field;
		}

		/**
		 * Filter the address form fields.
		 *
		 * @param array $fields Fields keyed by plain name.
		 */
		return apply_filters( 'vanmuad_address_form_fields', $fields );
	}

	/**
	 * Which states each country has, for the script that rebuilds the state
	 * control when the country changes.
	 *
	 * We hand this to our own script rather than depending on WooCommerce's
	 * `wc-country-select`, which localises the same data. That script is only
	 * registered on the frontend, and a dependency WordPress cannot resolve makes
	 * it drop the dependent script silently — which is exactly what happened on
	 * the admin profile screen: the address book rendered and none of its buttons
	 * did anything. Carrying the data ourselves means the editor behaves the same
	 * wherever it is used.
	 *
	 * @return array Country code => ( state code => state name ).
	 */
	public static function country_states() {
		$countries = WC()->countries;

		return array_merge(
			$countries->get_allowed_country_states(),
			$countries->get_shipping_country_states()
		);
	}

	/**
	 * The country a new address defaults to: the plugin setting when set,
	 * otherwise the store's base country.
	 *
	 * @return string
	 */
	public static function default_country() {
		$configured = ( new Settings() )->get( 'default_country', '' );

		if ( is_string( $configured ) && '' !== $configured ) {
			return $configured;
		}

		return WC()->countries->get_base_country();
	}

	/**
	 * Validate and sanitize a submitted address.
	 *
	 * Expects values that were already unslashed by the caller (the AJAX handler
	 * unslashes the whole payload once), so it must not unslash them again.
	 *
	 * @param array $raw Raw submitted values keyed by plain field name.
	 * @return array{fields: array, errors: array} Sanitized fields and error messages.
	 */
	public static function validate( array $raw ) {
		$country = isset( $raw['country'] ) ? sanitize_text_field( $raw['country'] ) : self::default_country();
		$fields  = self::fields( $country );

		$clean  = array();
		$errors = array();

		foreach ( $fields as $name => $field ) {
			$value = isset( $raw[ $name ] ) ? $raw[ $name ] : '';
			$value = is_scalar( $value ) ? trim( (string) $value ) : '';

			$label = isset( $field['label'] ) ? $field['label'] : $name;

			if ( ! empty( $field['required'] ) && '' === $value ) {
				/* translators: %s: field label. */
				$errors[ $name ] = sprintf( __( '%s is a required field.', 'vanquish-multiple-addresses-for-woocommerce' ), $label );
				continue;
			}

			if ( '' === $value ) {
				$clean[ $name ] = '';
				continue;
			}

			// Type-specific checks, mirroring WooCommerce's own address validation.
			if ( 'email' === $name ) {
				if ( ! is_email( $value ) ) {
					$errors[ $name ] = __( 'Please enter a valid email address.', 'vanquish-multiple-addresses-for-woocommerce' );
					continue;
				}
				$clean[ $name ] = sanitize_email( $value );
				continue;
			}

			if ( 'postcode' === $name ) {
				$value = wc_format_postcode( $value, $country );
				if ( ! \WC_Validation::is_postcode( $value, $country ) ) {
					$errors[ $name ] = __( 'Please enter a valid postcode / ZIP.', 'vanquish-multiple-addresses-for-woocommerce' );
					continue;
				}
			}

			/*
			 * Routed through the same filter the storage layer uses, so a field
			 * that needs different cleaning is described in one place. Without it
			 * the single-line default here would flatten a multi-line value before
			 * storage ever got the chance to keep its line breaks.
			 */
			$clean[ $name ] = apply_filters( 'vanmuad_sanitize_address_field', sanitize_text_field( $value ), $name, $value );
		}

		return array(
			'fields' => $clean,
			'errors' => $errors,
		);
	}

	/**
	 * A formatted address for display, using WooCommerce's country-aware
	 * formatting.
	 *
	 * @param array $fields Address fields.
	 * @return string HTML (already escaped by WooCommerce, contains <br/>).
	 */
	public static function format( array $fields ) {
		$formatted = WC()->countries->get_formatted_address( $fields );

		return $formatted ? $formatted : '';
	}

	/**
	 * The same address as plain text, lines separated by newlines.
	 *
	 * For places where the address travels through an attribute and is written
	 * back out by script: as text it can be set with textContent and rendered
	 * with `white-space: pre-line`, so nothing ever has to inject HTML.
	 *
	 * @param array $fields Address fields.
	 * @return string
	 */
	public static function format_plain( array $fields ) {
		$formatted = WC()->countries->get_formatted_address( $fields, "\n" );

		return $formatted ? $formatted : '';
	}
}
