<?php
/**
 * Invoice Functions.
 *
 * @package     EDD\Pro\Invoices
 * @copyright   Copyright (c) 2025, Sandhills Development, LLC
 * @license     https://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       3.5.0
 */

/**
 * Retrieves the invoice URL for a given order.
 *
 * @param int  $order_id ID of the order.
 * @param bool $new_tab  Whether the link is opening in a new tab (admin links, emails).
 *                       If true, a "Home" link will be added to the invoice, replacing the "Back" button.
 *
 * @since 3.5.0
 * @return string
 */
function edd_invoices_get_invoice_url( $order_id, $new_tab = false ) {
	$args  = array(
		'edd_action' => 'view_invoice',
		'payment_id' => urlencode( $order_id ),
	);
	$key   = false;
	$order = edd_get_order( intval( $order_id ) );
	if ( ! empty( $order->payment_key ) ) {
		$key = $order->payment_key;
	}
	if ( $key && ! empty( $order->email ) ) {
		$args['invoice'] = urlencode( md5( $order_id . $key . $order->email ) );
	}
	if ( $new_tab ) {
		$args['home'] = true;
	}

	return add_query_arg(
		$args,
		trailingslashit( home_url() )
	);
}

/**
 * Retrieves the URL to edit the data associated with an invoice.
 *
 * @param int $order_id ID of the order.
 *
 * @since 3.5.0
 * @return string Returns an empty string if the invoice page is not set.
 */
function edd_invoices_get_invoice_form_url( $order_id ) {
	$invoice_page = edd_get_option( 'edd-invoices-page' );
	if ( ! $invoice_page ) {
		return '';
	}

	$args  = array(
		'payment_id' => urlencode( $order_id ),
	);
	$key   = false;
	$order = edd_get_order( intval( $order_id ) );
	if ( ! empty( $order->payment_key ) ) {
		$key = $order->payment_key;
	}
	if ( $key && ! empty( $order->email ) ) {
		$args['invoice'] = urlencode( md5( $order_id . $key . $order->email ) );
	}

	return add_query_arg(
		$args,
		get_permalink( $invoice_page )
	);
}

/**
 * Returns an array of order statuses that support invoice generation.
 *
 * @since 3.5.0
 * @return array
 */
function edd_invoices_get_invoiceable_order_statuses() {
	$statuses = array(
		'publish',
		'complete',
		'revoked',
		'refunded',
		'partially_refunded',
	);

	/**
	 * Filters the order statuses that support invoice generation.
	 *
	 * @param array $statuses
	 */
	return apply_filters( 'edd_invoices_acceptable_payment_statuses', $statuses );
}

/**
 * Check whether an order can have an invoice generated.
 *
 * @since 3.5.0
 * @param int $order_id The order ID.
 * @return bool
 */
function edd_invoices_order_has_invoice( $order_id ) {
	$has_invoice = ! empty( $order_id ) && in_array( edd_get_payment_status( $order_id ), edd_invoices_get_invoiceable_order_statuses(), true );
	$order       = edd_get_order( $order_id );
	if ( $has_invoice && edd_get_option( 'edd-invoices-disable-free', false ) ) {
		$has_invoice = 0.00 !== (float) $order->total;
	}
	if ( 'refund' === $order->type ) {
		$has_invoice = false;
	}

	/**
	 * Allow devs to filter this result.
	 *
	 * @since 3.5.0
	 * @param bool $has_invoice Whether an invoice can be generated for the order.
	 * @param int  $order_id    The order ID.
	 */
	return (bool) apply_filters( 'edd_invoices_order_has_invoice', $has_invoice, $order_id );
}

/**
 * Gets the order billing address.
 *
 * @param EDD_Payment $payment EDD_Payment object.
 * @return array
 * @since 3.5.0
 */
function edd_invoices_get_order_address( $payment ) {
	$address = array_fill_keys(
		array( 'name', 'line1', 'line2', 'city', 'state', 'zip', 'country' ),
		''
	);

	$addresses = edd_get_order_addresses(
		array(
			'order_id' => $payment->id,
			'type'     => 'billing',
			'number'   => 1,
		)
	);
	if ( ! empty( $addresses[0] ) ) {
		$address = reset( $addresses );

		return array(
			'id'      => $address->id,
			'name'    => $address->name,
			'line1'   => $address->address,
			'line2'   => $address->address2,
			'city'    => $address->city,
			'state'   => $address->region,
			'zip'     => $address->postal_code,
			'country' => $address->country,
		);
	}

	// We can at least attempt to get the name from the customer record.
	$customer        = edd_get_customer( $payment->customer_id );
	$address['name'] = $customer->name;

	return $address;
}

/**
 * Gets the order custom meta.
 *
 * @param \EDD\Orders\Order $payment The order object.
 * @param string            $key     The meta key to retrieve.
 * @return bool|string
 * @since 3.5.0
 */
function edd_invoices_get_custom_order_meta( $payment, $key ) {
	return edd_get_order_meta( $payment->id, $key, true );
}

/**
 * Gets the order items data specific to the invoice.
 *
 * @since 3.5.0
 * @param \EDD\Orders\Order $order The order object.
 * @return array
 */
function edd_invoices_get_order_items( $order ) {
	$invoice_items = array();

	$items = edd_get_order_items(
		array(
			'order_id' => $order->id,
		)
	);
	if ( empty( $items ) ) {
		return $invoice_items;
	}

	foreach ( $items as $item ) {
		$invoice_items[ $item->cart_index ] = array(
			'item_id'  => intval( $item->product_id ),
			'price'    => $item->total,
			'name'     => $item->product_name,
			'price_id' => $item->price_id,
			'quantity' => intval( $item->quantity ),
		);
		if ( edd_get_order_meta( $order->id, '_edd_sl_is_renewal', true ) && $item->discount > 0 ) {
			$invoice_items[ $item->cart_index ]['name'] .= '<div class="renewal-discount">' . __( 'License Renewal Discount:', 'easy-digital-downloads' ) . ' ' . edd_currency_filter( edd_format_amount( edd_negate_amount( $item->discount ) ), $order->currency ) . '</div>';
		}
	}

	/**
	 * Filters the order items data specific to the invoice.
	 *
	 * @since 3.5.2
	 * @param array             $invoice_items The order items data.
	 * @param \EDD\Orders\Order $order         The order object.
	 */
	return apply_filters( 'edd_invoices_get_order_items', $invoice_items, $order );
}

/**
 * Gets the order discounts.
 *
 * @since 3.5.0
 * @param \EDD\Orders\Order $order The order/payment object.
 * @return bool|array
 */
function edd_invoices_get_order_discounts( $order ) {
	$discounts = edd_get_order_adjustments(
		array(
			'object_id'   => $order->id,
			'object_type' => 'order',
			'type'        => 'discount',
		)
	);
	if ( ! $discounts ) {
		return false;
	}

	$output = array();
	foreach ( $discounts as $discount ) {
		$name = $discount->description;
		if ( 'percent' === edd_get_discount_type( $discount->type_id ) ) {
			$rate  = edd_format_discount_rate( 'percent', edd_get_discount_amount( $discount->type_id ) );
			$name .= "&nbsp;({$rate})";
		}
		$output[ $discount->id ] = array(
			'name'   => __( 'Discount', 'easy-digital-downloads' ) . ' &mdash; ' . $name,
			'amount' => edd_currency_filter( edd_format_amount( edd_negate_amount( $discount->total ) ), $order->currency ),
		);
	}

	return $output;
}

/**
 * Gets the order date for the invoice.
 *
 * @since 3.5.0
 * @param \EDD\Orders\Order $order The order object.
 * @return string|bool
 */
function edd_invoices_get_order_date( $order ) {
	/**
	 * In EDD 3.0, the `date_created` is the correct date to use, but this may change in the future.
	 *
	 * @link https://github.com/awesomemotive/easy-digital-downloads/issues/9053
	 */
	if ( ! empty( $order->date_created ) ) {
		return $order->date_created;
	}

	// Subscriptions use the payment date.
	if ( ! empty( $order->date ) ) {
		return $order->date;
	}

	return false;
}

/**
 * Gets the tax rate for the order.
 *
 * @since 3.5.0
 * @param \EDD\Orders\Order $order The order object.
 * @return float|bool
 */
function edd_invoices_get_tax_rate( $order ) {
	$rate = $order->get_tax_rate();

	return ! empty( $rate ) ? floatval( $rate ) : false;
}

/**
 * Gets the filename for the generated PDF.
 *
 * @since  3.5.0
 * @param int $order_id The order ID.
 * @return string
 */
function edd_invoices_get_pdf_filename( $order_id ) {
	/**
	 * Optionally filter the file name. `.pdf` will be appended after the filter is applied, so should not be included in the filter.
	 *
	 * @since 3.5.0
	 * @param $filename The name of the file. Default is `invoice-payment_number`.
	 * @param $order_id The order ID.
	 */
	$filename = apply_filters( 'edd_invoices_pdf_filename', 'invoice-' . edd_get_payment_number( $order_id ), $order_id );

	return $filename . '.pdf';
}

/**
 * Gets the company address.
 * Returns the full company address if set; otherwise, compiles the original
 * address settings and creates the full address, and deletes the old settings.
 *
 * @since 3.5.0
 * @return string
 */
function edd_invoices_get_company_address() {
	$address = edd_get_option( 'edd-invoices-company-address' );
	if ( $address ) {
		return $address;
	}

	$address = array();
	$keys    = array( 'address', 'address2', 'city', 'zipcode', 'country' );
	foreach ( $keys as $key ) {
		$line = edd_get_option( 'edd-invoices-' . $key );
		if ( $line ) {
			$address[] = $line;
			edd_delete_option( "edd-invoices-{$key}" );
		}
	}
	if ( ! empty( $address ) ) {
		$address = implode( "\n", $address );
		edd_update_option( 'edd-invoices-company-address', $address );

		return $address;
	}

	// Don't try to get the business address if we're on an admin page.
	if ( edd_is_admin_page() ) {
		return '';
	}

	if ( edd_get_option( 'edd-invoices-use-business-address', false ) ) {
		$address = array(
			edd_get_option( 'business_address' ),
			edd_get_option( 'business_address_2' ),
			edd_get_option( 'business_city' ),
			edd_get_option( 'base_state' ),
			edd_get_option( 'base_country' ),
			edd_get_option( 'business_postal_code' ),
		);
		if ( ! empty( array_filter( $address ) ) ) {
			return implode( "\n", array_filter( $address ) );
		}
	}

	return '';
}

/**
 * Gets the body class attribute for the invoices.
 *
 * @since 3.5.0
 * @param \EDD\Orders\Order $order The order object.
 * @return string
 */
function edd_invoices_get_invoice_body_class( $order ) {
	$body_classes = array(
		strtolower( $order->status ),
		edd_get_option( 'edd-invoices-style' ),
	);

	return implode( ' ', array_map( 'sanitize_html_class', array_filter( $body_classes ) ) );
}

/**
 * During the EDD 3.0 migration, copies the custom invoices meta to the new order meta table.
 *
 * @since 3.5.0
 * @param int   $order_id      The new order ID.
 * @param array $payment_meta  The original payment meta.
 * @param array $meta          The original post meta.
 * @return void
 */
function edd_invoices_30_migration( $order_id, $payment_meta, $meta ) {

	if ( empty( $meta['_edd_payment_meta'] ) ) {
		return;
	}
	$meta = maybe_unserialize( $meta['_edd_payment_meta'][0] );
	if ( empty( $meta['user_info'] ) ) {
		return;
	}
	$user_info = $meta['user_info'];
	$meta_keys = array( 'vat', 'notes', 'company' );
	foreach ( $meta_keys as $key ) {
		if ( ! empty( $user_info['address'][ $key ] ) ) {
			edd_update_order_meta( $order_id, "invoices_{$key}", sanitize_text_field( $user_info['address'][ $key ] ) );
		}
	}
}
add_action( 'edd_30_migrate_order', 'edd_invoices_30_migration', 10, 3 );

/**
 * Whether the current user is permitted to edit the customer billing information on the invoice.
 *
 * Warning: This does not check whether the user has permission to even view the invoice in question,
 * so that permission check should be handled separately.
 *
 * @see EDD\Pro\Invoices\Generator()::current_user_can_view_invoice
 * @see EDD\Pro\Invoices\Generator()::validate_request (calls the above)
 *
 * The general assumption here is that the user ID passed in is either that of an
 * administrator, or that of the customer who "owns" the invoice.
 *
 * @link https://github.com/easydigitaldownloads/edd-invoices/issues/120
 *
 * @since 3.5.0
 *
 * @param int $user_id ID of the user to check.
 *
 * @return bool
 */
function edd_invoices_can_user_edit_invoice_data( $user_id ) {
	// In the event that a `0` user ID is passed.
	if ( $user_id <= 0 ) {
		return false;
	}

	// If there's no invoice page, no one can edit -- not even admins.
	if ( ! edd_get_option( 'edd-invoices-page' ) ) {
		return false;
	}

	// If editing isn't disabled, everyone can edit.
	if ( ! edd_get_option( 'edd-invoices-disable-editing' ) ) {
		return true;
	}

	// If we're here, that means editing has been disabled, so only high privileged users can edit.
	return user_can( $user_id, 'manage_shop_settings' );
}
