<?php
/**
 * Static code lists.
 *
 * @package Harbor_EU_EInvoicing_For_WooCommerce
 */

defined( 'ABSPATH' ) || exit;

class HEIN_Codes {

	public const TAX_SCHEME          = 'VAT';
	public const TYPE_CODE_INVOICE   = '380';
	public const UNIT_DEFAULT        = 'C62';
	public const SHIPPING_REASON_CODE = 'FC';
	public const FEE_REASON_CODE     = 'ZZZ';
	public const DISCOUNT_REASON_CODE = '95';

	private const EU = [
		'AT',
		'BE',
		'BG',
		'CY',
		'CZ',
		'DE',
		'DK',
		'EE',
		'ES',
		'FI',
		'FR',
		'GR',
		'HR',
		'HU',
		'IE',
		'IT',
		'LT',
		'LU',
		'LV',
		'MT',
		'NL',
		'PL',
		'PT',
		'RO',
		'SE',
		'SI',
		'SK',
	];

	/**
	 * Every scheme here is present in the BR-CL-25 code list in CEN-EN16931-UBL.sch, which is a
	 * fatal closed list. 9906 (IT:VAT), 9902 (DK:VAT) and 9955 (SE:VAT) are NOT in it
	 */
	private const EAS = [
		'AT' => '9914',
		'BE' => '9925',
		'BG' => '9926',
		'CY' => '9928',
		'CZ' => '9929',
		'DE' => '9930',
		'DK' => '0184',
		'EE' => '9931',
		'ES' => '9920',
		'FI' => '0213',
		'FR' => '9957',
		'GR' => '9933',
		'HR' => '9934',
		'HU' => '9910',
		'IE' => '9935',
		'IT' => '0211',
		'LT' => '9937',
		'LU' => '9938',
		'LV' => '9939',
		'MT' => '9943',
		'NL' => '9944',
		'PL' => '9945',
		'PT' => '9946',
		'RO' => '9947',
		'SE' => '0007',
		'SI' => '9949',
		'SK' => '9950',
	];

	/**
	 * PEPPOL-EN16931-P0104..P0111 bind each VATEX code to exactly one category. BR-S-10 and
	 * BR-Z-10 forbid a reason on S and Z; BR-E-10 requires one on E, which the caller supplies
	 * from the exemption_code / exemption_reason settings
	 */
	private const VATEX = [
		'AE' => [ 'VATEX-EU-AE', 'Reverse charge' ],
		'K'  => [ 'VATEX-EU-IC', 'Intra-Community supply' ],
		'G'  => [ 'VATEX-EU-G', 'Export outside the EU' ],
		'O'  => [ 'VATEX-EU-O', 'Not subject to VAT' ],
	];

	private const LEGAL_ID_SCHEMES = [
		'DK' => [ '0184' ],
		'NL' => [ '0106', '0190' ],
	];

	private const GATEWAY_MEANS = [
		'cheque' => '20',
		'cod'    => '10',
	];

	public static function eu_countries(): array {
		return array_values( array_unique( array_map( 'strtoupper', (array) apply_filters( 'hein_eu_countries', self::EU ) ) ) );
	}

	public static function is_eu( string $country ): bool {
		return in_array( strtoupper( $country ), self::eu_countries(), true );
	}

	public static function vat_prefix( string $country ): string {
		$country = strtoupper( $country );
		return 'GR' === $country ? 'EL' : $country;
	}

	public static function country_from_vat_prefix( string $prefix ): string {
		$prefix = strtoupper( $prefix );
		return 'EL' === $prefix ? 'GR' : $prefix;
	}

	/**
	 * GR-R-009/010 run a TIN checksum on the raw endpoint value, so 9933 carries the number
	 * without its EL prefix while BT-31/BT-48 keep it (BR-CO-09 needs the ISO prefix). SE's 0007
	 * is the organisation number: the VAT id minus the SE prefix and the trailing 01
	 */
	public static function endpoint_for_vat( string $vat, string $country ): array {
		$country = strtoupper( $country );
		$vat     = strtoupper( trim( $vat ) );

		if ( '' === $vat || ! isset( self::EAS[ $country ] ) ) {
			return [];
		}

		$id = $vat;

		if ( 'GR' === $country ) {
			$id = preg_replace( '/^(EL|GR)/', '', $id );
		} elseif ( 'SE' === $country ) {
			$id = preg_replace( '/^SE/', '', $id );
			$id = preg_replace( '/01$/', '', $id );
		}

		if ( '' === (string) $id ) {
			return [];
		}

		return [
			'id'     => (string) $id,
			'scheme' => self::EAS[ $country ],
		];
	}

	public static function exemption( string $category ): array {
		$vatex = self::VATEX[ strtoupper( $category ) ] ?? [ null, null ];

		return [
			'code'   => $vatex[0],
			'reason' => $vatex[1],
		];
	}

	public static function unit_code( WC_Order_Item $item ): string {
		$code = (string) apply_filters( 'hein_unit_code', self::UNIT_DEFAULT, $item );
		return '' === trim( $code ) ? self::UNIT_DEFAULT : trim( $code );
	}

	/**
	 * BR-61 and DE-R-023-1 are fatal when 30 or 58 has no cac:PayeeFinancialAccount, DE-R-024-1
	 * demands a cac:CardAccount for 48/54/55 and PEPPOL-R061 demands a PaymentMandate for 49/59,
	 * so the filtered value is re-clamped and the result is always in BR-CL-16
	 */
	public static function payment_means_code( string $gateway_id, bool $has_account ): string {
		$gateway = strtolower( trim( $gateway_id ) );

		if ( 'bacs' === $gateway ) {
			$code = $has_account ? '30' : '1';
		} elseif ( isset( self::GATEWAY_MEANS[ $gateway ] ) ) {
			$code = self::GATEWAY_MEANS[ $gateway ];
		} else {
			$code = '68';
		}

		$code = trim( (string) apply_filters( 'hein_payment_means_code', $code, $gateway_id, $has_account ) );

		if ( ! in_array( $code, self::payment_means_list(), true ) ) {
			$code = '1';
		}

		if ( in_array( $code, [ '30', '58' ], true ) && ! $has_account ) {
			$code = '1';
		}

		if ( in_array( $code, [ '48', '54', '55', '49', '59' ], true ) ) {
			$code = '68';
		}

		return $code;
	}

	public static function legal_id_schemes( string $country ): array {
		return self::LEGAL_ID_SCHEMES[ strtoupper( $country ) ] ?? [];
	}

	public static function unsupported_seller_countries(): array {
		return [ 'GR' ];
	}

	private static function payment_means_list(): array {
		return array_merge(
			array_map( 'strval', range( 1, 70 ) ),
			array_map( 'strval', range( 74, 78 ) ),
			array_map( 'strval', range( 91, 98 ) ),
			[ 'ZZZ' ]
		);
	}
}
