<?php
/**
 * Shared helper functions.
 *
 * @package Lucrativeit_Subscription_Recurring_Billing
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * The capability required to manage the plugin.
 *
 * @return string
 */
function lucrsure_admin_capability() {
	return apply_filters( 'lucrsure_admin_capability', 'manage_options' );
}

/**
 * Public plugin name.
 *
 * @return string
 */
function lucrsure_plugin_name() {
	return __( 'Lucrativeit Subscription & Recurring Billing', 'lucrativeit-subscription-recurring-billing' );
}

/**
 * Short label for the WordPress admin menu.
 *
 * @return string
 */
function lucrsure_plugin_menu_label() {
	return __( 'Lucrativeit Subscription & Recurring Billing', 'lucrativeit-subscription-recurring-billing' );
}

/**
 * Whether the Pro add-on is active.
 *
 * The Pro plugin should define LUCRSURE_PRO_VERSION on load.
 *
 * @return bool
 */
function lucrsure_is_pro() {
	if ( defined( 'LUCRSURE_PRO_VERSION' ) && LUCRSURE_PRO_VERSION ) {
		return true;
	}

	// Pro add-on is present when it defines LSPP_VERSION and bootstraps successfully.
	if ( defined( 'LSPP_VERSION' ) && LSPP_VERSION && class_exists( 'LSPP_Plugin' ) ) {
		return true;
	}

	return (bool) apply_filters( 'lucrsure_is_pro', false );
}

/**
 * Whether the Pro add-on plugin file is present on disk.
 *
 * @return bool
 */
function lucrsure_pro_plugin_installed() {
	$candidates = array(
		WP_PLUGIN_DIR . '/lucrativeit-subscription-recurring-billing-pro/lucrativeit-subscription-plus-pro.php',
		WP_PLUGIN_DIR . '/Lucrativeit-subscription-recurring-billing-pro/lucrativeit-subscription-plus-pro.php',
		WP_PLUGIN_DIR . '/lucrativeit-subscription-plus-pro/lucrativeit-subscription-plus-pro.php',
		WP_PLUGIN_DIR . '/Lucrativeit-subscription-plus-pro/lucrativeit-subscription-plus-pro.php',
	);

	foreach ( $candidates as $file ) {
		if ( file_exists( $file ) ) {
			return true;
		}
	}

	return false;
}

/**
 * Whether the Pro add-on plugin is active in WordPress.
 *
 * @return bool
 */
function lucrsure_pro_plugin_is_active() {
	if ( ! function_exists( 'is_plugin_active' ) ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
	}

	$basenames = array(
		'lucrativeit-subscription-recurring-billing-pro/lucrativeit-subscription-plus-pro.php',
		'Lucrativeit-subscription-recurring-billing-pro/lucrativeit-subscription-plus-pro.php',
		'lucrativeit-subscription-plus-pro/lucrativeit-subscription-plus-pro.php',
		'Lucrativeit-subscription-plus-pro/lucrativeit-subscription-plus-pro.php',
	);

	foreach ( $basenames as $basename ) {
		if ( is_plugin_active( $basename ) ) {
			return true;
		}
	}

	return false;
}

/**
 * Relative basename of the Pro add-on plugin file when present on disk.
 *
 * @return string Empty when not found.
 */
function lucrsure_pro_plugin_basename() {
	$candidates = array(
		'lucrativeit-subscription-recurring-billing-pro/lucrativeit-subscription-plus-pro.php',
		'Lucrativeit-subscription-recurring-billing-pro/lucrativeit-subscription-plus-pro.php',
		'lucrativeit-subscription-plus-pro/lucrativeit-subscription-plus-pro.php',
		'Lucrativeit-subscription-plus-pro/lucrativeit-subscription-plus-pro.php',
	);

	foreach ( $candidates as $basename ) {
		if ( file_exists( WP_PLUGIN_DIR . '/' . $basename ) ) {
			return $basename;
		}
	}

	return '';
}

/**
 * URL for upgrading to the Pro add-on.
 *
 * @return string
 */
function lucrsure_pro_url() {
	return apply_filters( 'lucrsure_pro_url', 'https://lucrativeit.com/subscription-plus/pro' );
}

/**
 * The list of modules used by the role/permission system and navigation.
 *
 * @return array<string, array{label:string, icon:string, menu:bool}>
 */
function lucrsure_get_modules() {
	$modules = array(
		'dashboard'     => array( 'label' => __( 'Dashboard', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'grid', 'menu' => true ),
		'customers'     => array( 'label' => __( 'Customers', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'users', 'menu' => true ),
		'branches'      => array( 'label' => __( 'Branches', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'pin', 'menu' => true ),
		'plan-categories' => array( 'label' => __( 'Plan Categories', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'folder', 'menu' => true ),
		'plans'         => array( 'label' => __( 'Plans', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'tag', 'menu' => true ),
		'subscriptions' => array( 'label' => __( 'Subscriptions', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'card', 'menu' => true ),
		'billing'       => array( 'label' => __( 'Billing', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'invoice', 'menu' => true ),
		'reports'       => array( 'label' => __( 'Reports', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'chart', 'menu' => true ),
		'settings'      => array( 'label' => __( 'Settings', 'lucrativeit-subscription-recurring-billing' ), 'icon' => 'gear', 'menu' => true ),
	);

	/**
	 * Filter the list of plugin modules (used by the role system and sidebar nav).
	 *
	 * Add-ons may add their own modules (e.g. a Reports page). Each module is
	 * an array with `label`, `icon` and `menu` keys.
	 *
	 * @param array $modules Module map keyed by slug.
	 */
	return apply_filters( 'lucrsure_get_modules', $modules );
}

/**
 * Normalised report filters for the current request.
 *
 * Defaults are unfiltered. Extensions (e.g. the Pro add-on) may populate
 * branch, plan and customer filters via `lucrsure_reports_filters`.
 *
 * @param string $context `view` reads GET; `export` reads POST.
 * @return array{branch_id:int,plan_id:int,customer_id:int}
 */
function lucrsure_get_report_filters( $context = 'view' ) {
	$context  = sanitize_key( $context );
	$defaults = array(
		'branch_id'   => 0,
		'plan_id'     => 0,
		'customer_id' => 0,
	);

	/**
	 * Filter report query filters.
	 *
	 * Open extension point — any add-on may supply filters. The free UI does
	 * not expose controls; the Pro add-on registers the request readers.
	 *
	 * @param array  $filters { branch_id, plan_id, customer_id }.
	 * @param string $context `view` or `export`.
	 */
	return LUCRSURE_Reports::normalize_filters(
		apply_filters( 'lucrsure_reports_filters', $defaults, $context )
	);
}

/**
 * Report date range for the current request.
 *
 * Default range is the last 30 days. Extensions may override via
 * `lucrsure_reports_date_range` (used by the Pro add-on for custom ranges).
 *
 * @param string $context `view` reads GET; `export` reads POST.
 * @return array{from:string,to:string,from_attr:string,to_attr:string}
 */
function lucrsure_get_report_date_range( $context = 'view' ) {
	$context      = sanitize_key( $context );
	$today        = current_time( 'Y-m-d' );
	$default_from = gmdate( 'Y-m-d', strtotime( '-30 days', strtotime( $today ) ) );
	$range        = array(
		'from'      => LUCRSURE_Reports::start_of_day( $default_from, $default_from ),
		'to'        => LUCRSURE_Reports::end_of_day( $today, $today ),
		'from_attr' => $default_from,
		'to_attr'   => $today,
	);

	/**
	 * Filter the report date range.
	 *
	 * Open extension point — code in this plugin is fully usable; add-ons
	 * may widen the range when they provide their own UI.
	 *
	 * @param array  $range   { from, to, from_attr, to_attr }.
	 * @param string $context `view` or `export`.
	 */
	return apply_filters( 'lucrsure_reports_date_range', $range, $context );
}

/**
 * Available subscription billing intervals.
 *
 * @return array<string,string>
 */
function lucrsure_billing_intervals() {
	return array(
		'day'       => __( 'Daily', 'lucrativeit-subscription-recurring-billing' ),
		'week'      => __( 'Weekly', 'lucrativeit-subscription-recurring-billing' ),
		'month'     => __( 'Monthly', 'lucrativeit-subscription-recurring-billing' ),
		'year'      => __( 'Yearly', 'lucrativeit-subscription-recurring-billing' ),
		'lifetime'  => __( 'Lifetime', 'lucrativeit-subscription-recurring-billing' ),
	);
}

/**
 * Whether the post-install onboarding prompt should appear.
 *
 * @return bool
 */
function lucrsure_should_show_onboarding() {
	$plans = new LUCRSURE_Plan_Repository();
	$cust  = new LUCRSURE_Customer_Repository();
	$empty = 0 === $plans->count() && 0 === $cust->count();

	if ( get_option( 'lucrsure_onboarding_dismissed' ) ) {
		return false;
	}

	if ( get_option( 'lucrsure_demo_data_imported' ) ) {
		return false;
	}

	return $empty;
}

/**
 * Admin URL that reopens the setup wizard on an empty site (capability + nonce).
 *
 * @return string
 */
function lucrsure_reopen_setup_url() {
	return wp_nonce_url(
		admin_url( 'admin-post.php?action=lucrsure_reopen_setup' ),
		'lucrsure_reopen_setup'
	);
}

/**
 * Whether the current request is the setup wizard admin page.
 *
 * @return bool
 */
function lucrsure_is_setup_screen() {
	if ( ! is_admin() || ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
		return false;
	}

	return 'lucrsure-setup' === sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
}

/**
 * Subscription statuses with labels.
 *
 * @return array<string,string>
 */
function lucrsure_subscription_statuses() {
	return array(
		'pending'  => __( 'Pending payment', 'lucrativeit-subscription-recurring-billing' ),
		'active'   => __( 'Active', 'lucrativeit-subscription-recurring-billing' ),
		'trialing' => __( 'Trialing', 'lucrativeit-subscription-recurring-billing' ),
		'past_due' => __( 'Past due', 'lucrativeit-subscription-recurring-billing' ),
		'canceled' => __( 'Canceled', 'lucrativeit-subscription-recurring-billing' ),
		'paused'   => __( 'Paused', 'lucrativeit-subscription-recurring-billing' ),
	);
}

/**
 * Initial subscription status for a public checkout.
 *
 * @param string $gateway_id Gateway id.
 * @param int    $trial_days Plan trial length.
 * @return string
 */
function lucrsure_public_checkout_subscription_status( $gateway_id, $trial_days = 0 ) {
	if ( (int) $trial_days > 0 ) {
		return 'trialing';
	}

	return 'pending';
}

/**
 * Invoice statuses with labels.
 *
 * @return array<string,string>
 */
function lucrsure_invoice_statuses() {
	return array(
		'draft'   => __( 'Draft', 'lucrativeit-subscription-recurring-billing' ),
		'unpaid'  => __( 'Unpaid', 'lucrativeit-subscription-recurring-billing' ),
		'paid'    => __( 'Paid', 'lucrativeit-subscription-recurring-billing' ),
		'overdue' => __( 'Overdue', 'lucrativeit-subscription-recurring-billing' ),
		'void'    => __( 'Void', 'lucrativeit-subscription-recurring-billing' ),
	);
}

/**
 * Currency symbols for common currencies.
 *
 * @param string $code Currency code.
 * @return string
 */
function lucrsure_currency_symbol( $code ) {
	$symbols = array(
		'USD' => '$',
		'EUR' => "\u{20AC}",
		'GBP' => "\u{00A3}",
		'BDT' => "\u{09F3}",
		'INR' => "\u{20B9}",
		'AUD' => 'A$',
		'CAD' => 'C$',
	);
	$code = strtoupper( $code );
	return isset( $symbols[ $code ] ) ? $symbols[ $code ] : $code . ' ';
}

/**
 * Format a money amount for display.
 *
 * @param float|string $amount   Amount.
 * @param string       $currency Currency code.
 * @return string
 */
function lucrsure_format_money( $amount, $currency = '' ) {
	if ( empty( $currency ) ) {
		$settings = get_option( 'lucrsure_settings', array() );
		$currency = isset( $settings['default_currency'] ) ? $settings['default_currency'] : 'USD';
	}
	return lucrsure_currency_symbol( $currency ) . number_format( (float) $amount, 2 );
}

/**
 * Whether a plan has an active special price.
 *
 * @param array $plan Plan row.
 * @return bool
 */
function lucrsure_plan_has_special_price( $plan ) {
	return ! empty( $plan['special_price_enabled'] ) && (float) ( $plan['special_price'] ?? 0 ) > 0;
}

/**
 * Effective price charged for a plan (special price when enabled).
 *
 * @param array $plan Plan row.
 * @return float
 */
function lucrsure_plan_effective_price( $plan ) {
	if ( lucrsure_plan_has_special_price( $plan ) ) {
		return (float) $plan['special_price'];
	}
	return (float) ( $plan['price'] ?? 0 );
}

/**
 * Formatted regular and effective prices for display.
 *
 * @param array $plan Plan row.
 * @return array{effective:string,regular:string,has_special:bool}
 */
function lucrsure_plan_format_price_display( $plan ) {
	$currency = isset( $plan['currency'] ) ? $plan['currency'] : '';

	if ( lucrsure_plan_has_special_price( $plan ) ) {
		return array(
			'effective'   => lucrsure_format_money( lucrsure_plan_effective_price( $plan ), $currency ),
			'regular'     => lucrsure_format_money( $plan['price'], $currency ),
			'has_special' => true,
		);
	}

	return array(
		'effective'   => lucrsure_format_money( $plan['price'], $currency ),
		'regular'     => '',
		'has_special' => false,
	);
}

/**
 * Default admin footer copyright line.
 *
 * Use `{year}` as a placeholder for the current year.
 *
 * @return string
 */
function lucrsure_default_footer_text() {
	return '© {year} ' . lucrsure_plugin_name() . '. All rights reserved.';
}

/**
 * Resolved admin footer copyright line from settings.
 *
 * @return string
 */
function lucrsure_footer_text() {
	$settings = get_option( 'lucrsure_settings', array() );
	$text     = ( isset( $settings['footer_text'] ) && '' !== trim( (string) $settings['footer_text'] ) )
		? (string) $settings['footer_text']
		: lucrsure_default_footer_text();

	return str_replace( '{year}', gmdate( 'Y' ), $text );
}

/**
 * Build a URL to a plugin admin page/tab.
 *
 * @param string $page Module slug.
 * @param array  $args Extra query args.
 * @return string
 */
function lucrsure_admin_url( $page = 'dashboard', $args = array() ) {
	$base = array( 'page' => 'lucrsure-' . $page );
	if ( 'dashboard' === $page ) {
		$base = array( 'page' => LUCRSURE_PLUGIN_SLUG );
	}
	return add_query_arg( array_merge( $base, $args ), admin_url( 'admin.php' ) );
}

/**
 * Admin URL with a nonce for safe GET navigation (edit/view/filter links).
 *
 * @param string $page   Module slug.
 * @param array  $args   Extra query args.
 * @param string $action Nonce action name.
 * @return string
 */
function lucrsure_admin_nonce_url( $page = 'dashboard', $args = array(), $action = 'lucrsure_admin_form' ) {
	return wp_nonce_url( lucrsure_admin_url( $page, $args ), $action );
}

/**
 * Default email templates (subject + short intro used when building full HTML defaults).
 *
 * @return array<string,array{subject:string,body:string}>
 */
function lucrsure_default_email_templates() {
	return array(
		'invoice_payment_request' => array(
			'subject' => 'Payment due: {{invoice_number}} — {{package_name}}',
			'body'    => "Dear {{customer_name}},\n\nYour {{package_name}} renewal invoice {{invoice_number}} from {{company_name}} is ready.\n\n**Amount due:** {{currency}} {{amount}}\n**Due date:** {{due_date}}\n\nPlease complete payment by the due date.",
		),
		'overdue_payment_reminder' => array(
			'subject' => 'Reminder: Invoice {{invoice_number}} is overdue',
			'body'    => "Dear {{customer_name}},\n\nThis is a friendly reminder that invoice {{invoice_number}} for {{package_name}} ({{currency}} {{amount}}) from {{company_name}} is now overdue.\n\n**Due date:** {{due_date}}\n\nPlease complete payment at your earliest convenience.",
		),
		'payment_success' => array(
			'subject' => 'Payment received for {{invoice_number}}',
			'body'    => "Dear {{customer_name}},\n\nWe have received your payment of {{currency}} {{amount}} for invoice {{invoice_number}} ({{package_name}}). Thank you for your continued subscription with {{company_name}}!",
		),
		'subscription_active' => array(
			'subject' => 'Welcome! Your {{package_name}} subscription is now active',
			'body'    => "Dear {{customer_name}},\n\nWelcome! Your {{package_name}} subscription with {{company_name}} is now **{{subscription_status}}**.\n\n**Amount:** {{currency}} {{amount}}\n**Billing cycle:** {{billing_cycle}}\n**Start date:** {{start_date}}\n**Next renewal:** {{next_billing_date}}",
		),
	);
}

/**
 * Ensure a template body starts with the standard customer greeting.
 *
 * @param string $body Template body.
 * @return string
 */
function lucrsure_email_ensure_greeting( $body ) {
	$body = trim( (string) $body );
	if ( '' === $body ) {
		return 'Dear {{customer_name}},';
	}
	if ( preg_match( '/^Dear\s+\{\{customer_name\}\}/i', $body ) ) {
		return $body;
	}

	return "Dear {{customer_name}},\n\n" . $body;
}

/**
 * Basic email layout for the free tier (no header, logo, or footer).
 *
 * @param array|null $brand Optional branding colors.
 * @return string
 */
function lucrsure_basic_email_layout_html( $brand = null ) {
	if ( null === $brand ) {
		$brand = lucrsure_default_email_branding();
	}

	return '<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"></head>
<body style="margin:0;padding:0;background:' . esc_attr( $brand['body_bg'] ) . ';font-family:Arial,Helvetica,sans-serif;">
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background:' . esc_attr( $brand['body_bg'] ) . ';padding:24px 16px;">
<tr><td align="center">
<table cellpadding="0" cellspacing="0" border="0" width="600" style="max-width:600px;width:100%;background:' . esc_attr( $brand['content_bg'] ) . ';border-radius:8px;">
<tr><td style="padding:32px 28px;">
{{content}}
{{info_box}}
{{cta}}
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>';
}

/**
 * Branded email layout with header and footer placeholders (Pro).
 *
 * @param array|null $brand Optional branding colors.
 * @return string
 */
function lucrsure_default_email_layout_html( $brand = null ) {
	if ( null === $brand ) {
		$brand = lucrsure_default_email_branding();
	}

	return '<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"></head>
<body style="margin:0;padding:0;background:' . esc_attr( $brand['body_bg'] ) . ';font-family:Arial,Helvetica,sans-serif;">
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background:' . esc_attr( $brand['body_bg'] ) . ';padding:32px 16px;">
<tr><td align="center">
<table cellpadding="0" cellspacing="0" border="0" width="600" style="max-width:600px;width:100%;background:' . esc_attr( $brand['content_bg'] ) . ';border-radius:10px;overflow:hidden;">
{{header}}
<tr><td style="padding:36px 40px 12px;">
{{content}}
{{info_box}}
{{cta}}
</td></tr>
{{footer}}
</table>
</td></tr>
</table>
</body>
</html>';
}

/**
 * Intro text used when rendering or editing a template body.
 *
 * @param string $body         Stored template body.
 * @param string $template_key Template key.
 * @return string
 */
function lucrsure_email_intro_body( $body, $template_key = '' ) {
	if ( lucrsure_email_is_legacy_body( $body ) ) {
		return $body;
	}

	$defaults = lucrsure_default_email_templates();
	if ( $template_key && isset( $defaults[ $template_key ]['body'] ) ) {
		return $defaults[ $template_key ]['body'];
	}

	return wp_strip_all_tags( $body );
}

/**
 * Whether a template body is a legacy short intro (not full HTML).
 *
 * @param string $body Template body.
 * @return bool
 */
function lucrsure_email_is_legacy_body( $body ) {
	if ( stripos( $body, '<!DOCTYPE' ) !== false || stripos( $body, '<html' ) !== false ) {
		return false;
	}
	$block_markers = array( '{{header}}', '{{greeting}}', '{{info_box}}', '{{cta}}', '{{footer}}' );
	foreach ( $block_markers as $marker ) {
		if ( false !== strpos( $body, $marker ) ) {
			return false;
		}
	}
	return true;
}

/**
 * Allowed HTML tags for admin-edited email templates.
 *
 * @return array
 */
function lucrsure_email_allowed_html() {
	$allowed = wp_kses_allowed_html( 'post' );
	$tags    = array( 'table', 'tr', 'td', 'th', 'tbody', 'thead', 'tfoot', 'div', 'span', 'p', 'a', 'img', 'br', 'strong', 'em', 'h1', 'h2', 'h3', 'h4', 'html', 'head', 'body', 'meta', 'style', 'title', 'link' );
	$attrs   = array(
		'style'       => true,
		'class'       => true,
		'id'          => true,
		'width'       => true,
		'height'      => true,
		'align'       => true,
		'valign'      => true,
		'cellpadding' => true,
		'cellspacing' => true,
		'border'      => true,
		'bgcolor'     => true,
		'colspan'     => true,
		'rowspan'     => true,
		'href'        => true,
		'src'         => true,
		'alt'         => true,
		'target'      => true,
		'rel'         => true,
		'charset'     => true,
		'name'        => true,
		'content'     => true,
		'lang'        => true,
	);

	foreach ( $tags as $tag ) {
		if ( ! isset( $allowed[ $tag ] ) ) {
			$allowed[ $tag ] = array();
		}
		$allowed[ $tag ] = array_merge( $allowed[ $tag ], $attrs );
	}

	return $allowed;
}

/**
 * Sanitize admin-edited email HTML while preserving layout tags and inline styles.
 *
 * @param string $html Raw HTML.
 * @return string
 */
function lucrsure_sanitize_email_html( $html ) {
	$doctype = '';
	if ( preg_match( '/^<!DOCTYPE[^>]*>/i', $html, $matches ) ) {
		$doctype = $matches[0] . "\n";
		$html    = (string) substr( $html, strlen( $matches[0] ) );
	}

	return $doctype . wp_kses( $html, lucrsure_email_allowed_html() );
}

/**
 * Data placeholders available to email templates.
 *
 * @return string[]
 */
function lucrsure_email_placeholders() {
	return array(
		'{{customer_name}}',
		'{{invoice_number}}',
		'{{package_name}}',
		'{{amount}}',
		'{{currency}}',
		'{{due_date}}',
		'{{payment_url}}',
		'{{company_name}}',
		'{{billing_cycle}}',
		'{{start_date}}',
		'{{next_billing_date}}',
		'{{subscription_status}}',
	);
}

/**
 * Allowed HTML tags for admin-edited email templates.
 *
 * @param array $invoice Invoice row.
 * @return string
 */
function lucrsure_invoice_package_name( array $invoice ) {
	if ( ! empty( $invoice['line_items'] ) ) {
		$items = json_decode( $invoice['line_items'], true );
		if ( is_array( $items ) && ! empty( $items[0]['description'] ) ) {
			return (string) $items[0]['description'];
		}
	}

	if ( empty( $invoice['subscription_id'] ) ) {
		return '';
	}

	$subs = new LUCRSURE_Subscription_Repository();
	$sub  = $subs->find( (int) $invoice['subscription_id'] );
	if ( ! $sub ) {
		return '';
	}

	$plans = new LUCRSURE_Plan_Repository();
	$plan  = $plans->find( (int) $sub['plan_id'] );

	return $plan ? (string) $plan['name'] : '';
}

/**
 * Build replacement data for invoice-related emails.
 *
 * @param array      $invoice  Invoice row.
 * @param array|null $customer Optional customer row.
 * @return array
 */
function lucrsure_invoice_email_data( array $invoice, $customer = null ) {
	if ( null === $customer && ! empty( $invoice['customer_id'] ) ) {
		$customers = new LUCRSURE_Customer_Repository();
		$customer  = $customers->find( (int) $invoice['customer_id'] );
	}

	$gateway_id = ! empty( $invoice['gateway'] ) ? $invoice['gateway'] : 'manual';

	return array(
		'customer_name'  => $customer ? (string) $customer['name'] : '',
		'invoice_number' => (string) $invoice['invoice_number'],
		'invoice_id'     => (int) $invoice['id'],
		'package_name'   => lucrsure_invoice_package_name( $invoice ),
		'amount'         => $invoice['amount'],
		'currency'       => (string) $invoice['currency'],
		'due_date'       => $invoice['due_date'] ?? '',
		'gateway'        => $gateway_id,
		'payment_url'    => lucrsure_email_payment_url( $gateway_id, $invoice['payment_url'] ?? '' ),
	);
}

/**
 * Whether payment-received customer emails are enabled.
 *
 * @return bool
 */
function lucrsure_payment_received_notifications_enabled() {
	$settings = get_option( 'lucrsure_settings', array() );
	if ( ! isset( $settings['notifications']['payment_received'] ) ) {
		return true;
	}

	return (bool) $settings['notifications']['payment_received'];
}

/**
 * Return a payment URL for emails, suppressing links for manual/offline gateways.
 *
 * @param string $gateway_id Gateway identifier.
 * @param string $payment_url Payment URL from the gateway.
 * @return string
 */
function lucrsure_email_payment_url( $gateway_id, $payment_url = '' ) {
	if ( 'manual' === $gateway_id ) {
		return '';
	}

	return (string) $payment_url;
}

/**
 * Format a plan billing cycle for display.
 *
 * @param array $plan Plan row.
 * @return string
 */
function lucrsure_format_billing_cycle( array $plan ) {
	$intervals = lucrsure_billing_intervals();
	$label     = isset( $intervals[ $plan['billing_interval'] ] )
		? $intervals[ $plan['billing_interval'] ]
		: $plan['billing_interval'];
	$count     = max( 1, (int) $plan['interval_count'] );

	if ( $count > 1 ) {
		return sprintf(
			/* translators: 1: interval count, 2: interval label */
			__( 'Every %1$d %2$s', 'lucrativeit-subscription-recurring-billing' ),
			$count,
			strtolower( $label )
		);
	}

	return $label;
}

/**
 * Build replacement data for subscription emails.
 *
 * @param array $subscription Subscription row.
 * @return array
 */
function lucrsure_subscription_email_data( array $subscription ) {
	$plans     = new LUCRSURE_Plan_Repository();
	$customers = new LUCRSURE_Customer_Repository();

	$plan     = $plans->find( (int) $subscription['plan_id'] );
	$customer = $customers->find( (int) $subscription['customer_id'] );

	if ( ! $customer ) {
		return array();
	}

	$amount   = $plan
		? lucrsure_plan_effective_price( $plan ) * max( 1, (int) $subscription['quantity'] )
		: (float) $subscription['mrr'];
	$currency = $plan ? $plan['currency'] : '';
	$statuses = lucrsure_subscription_statuses();
	$status   = isset( $subscription['status'], $statuses[ $subscription['status'] ] )
		? $statuses[ $subscription['status'] ]
		: ucfirst( (string) ( $subscription['status'] ?? '' ) );

	return array(
		'customer_name'       => $customer['name'],
		'package_name'        => $plan ? $plan['name'] : '',
		'amount'              => $amount,
		'currency'            => $currency,
		'billing_cycle'       => $plan ? lucrsure_format_billing_cycle( $plan ) : '',
		'start_date'          => ! empty( $subscription['start_date'] ) ? $subscription['start_date'] : ( $subscription['current_period_start'] ?? '' ),
		'next_billing_date'   => $subscription['current_period_end'] ?? '',
		'subscription_status' => $status,
		'gateway'             => $subscription['gateway'] ?? '',
	);
}

/**
 * Upgrade stored email templates when legacy plain-text defaults are detected.
 */
function lucrsure_maybe_upgrade_email_templates() {
	$stored = get_option( 'lucrsure_email_templates' );
	if ( ! is_array( $stored ) ) {
		return;
	}

	$legacy_bodies = array(
		'invoice_payment_request'  => "Hello {{customer_name}},\n\nYour subscription renewal invoice is ready.\n\n**Amount due:** {{currency}} {{amount}}\n**Due date:** {{due_date}}\n\n[Pay Now]({{payment_url}})",
		'overdue_payment_reminder' => "Hello {{customer_name}},\n\nThis is a reminder that invoice {{invoice_number}} for {{currency}} {{amount}} is overdue.\n\n[Pay Now]({{payment_url}})",
		'payment_success'          => "Hello {{customer_name}},\n\nWe have received your payment of {{currency}} {{amount}} for invoice {{invoice_number}}. Thank you!",
		'invoice_payment_request|short'  => 'Your subscription renewal invoice is ready. Please review the details below and complete payment by the due date.',
		'overdue_payment_reminder|short' => 'This is a friendly reminder that your invoice is now overdue. Please complete your payment at your earliest convenience.',
		'payment_success|short'          => 'We have received your payment. Thank you for your continued subscription!',
		'subscription_active|short'      => 'Thank you for subscribing! Your subscription is now active. Please find your plan details below.',
		'invoice_payment_request|no_greeting'  => "Your {{package_name}} renewal invoice {{invoice_number}} from {{company_name}} is ready.\n\n**Amount due:** {{currency}} {{amount}}\n**Due date:** {{due_date}}\n\nPlease complete payment by the due date. [Pay Now]({{payment_url}})",
		'overdue_payment_reminder|no_greeting' => "This is a friendly reminder that invoice {{invoice_number}} for {{package_name}} ({{currency}} {{amount}}) from {{company_name}} is now overdue.\n\n**Due date:** {{due_date}}\n\n[Pay Now]({{payment_url}})",
		'invoice_payment_request|pay_link'     => "Dear {{customer_name}},\n\nYour {{package_name}} renewal invoice {{invoice_number}} from {{company_name}} is ready.\n\n**Amount due:** {{currency}} {{amount}}\n**Due date:** {{due_date}}\n\nPlease complete payment by the due date. [Pay Now]({{payment_url}})",
		'overdue_payment_reminder|pay_link'    => "Dear {{customer_name}},\n\nThis is a friendly reminder that invoice {{invoice_number}} for {{package_name}} ({{currency}} {{amount}}) from {{company_name}} is now overdue.\n\n**Due date:** {{due_date}}\n\n[Pay Now]({{payment_url}})",
		'payment_success|no_greeting'          => 'We have received your payment of {{currency}} {{amount}} for invoice {{invoice_number}} ({{package_name}}). Thank you for your continued subscription with {{company_name}}!',
		'subscription_active|no_greeting'      => "Welcome! Your {{package_name}} subscription with {{company_name}} is now **{{subscription_status}}**.\n\n**Amount:** {{currency}} {{amount}}\n**Billing cycle:** {{billing_cycle}}\n**Start date:** {{start_date}}\n**Next renewal:** {{next_billing_date}}",
	);

	$defaults = lucrsure_default_email_templates();
	$changed  = false;

	foreach ( $legacy_bodies as $key => $legacy_body ) {
		$tpl_key = ( false !== strpos( $key, '|' ) ) ? strstr( $key, '|', true ) : $key;
		if ( ! isset( $defaults[ $tpl_key ] ) ) {
			continue;
		}
		if ( isset( $stored[ $tpl_key ]['body'] ) && $stored[ $tpl_key ]['body'] === $legacy_body ) {
			$stored[ $tpl_key ]['body'] = $defaults[ $tpl_key ]['body'];
			$changed                    = true;
		}
	}

	foreach ( $defaults as $key => $tpl ) {
		if ( ! isset( $stored[ $key ] ) ) {
			$stored[ $key ] = $tpl;
			$changed        = true;
		}
	}

	foreach ( $stored as $key => $tpl ) {
		if ( ! is_string( $key ) || ! isset( $tpl['body'] ) || ! is_string( $tpl['body'] ) ) {
			continue;
		}
		if ( ! lucrsure_email_is_legacy_body( $tpl['body'] ) ) {
			$stored[ $key ]['body'] = isset( $defaults[ $key ]['body'] )
				? $defaults[ $key ]['body']
				: lucrsure_email_intro_body( $tpl['body'], $key );
			$changed                = true;
			continue;
		}

		$with_greeting = lucrsure_email_ensure_greeting( $stored[ $key ]['body'] );
		if ( $with_greeting !== $stored[ $key ]['body'] ) {
			$stored[ $key ]['body'] = $with_greeting;
			$changed                = true;
		}
	}

	if ( $changed ) {
		update_option( 'lucrsure_email_templates', $stored );
	}

	if ( false !== get_option( 'lucrsure_email_layout', false ) ) {
		delete_option( 'lucrsure_email_layout' );
	}
}

/**
 * Create plan category table and plan columns on existing installs.
 */
function lucrsure_maybe_upgrade_plan_schema() {
	$version = (string) get_option( 'lucrsure_plan_schema_version', '0' );
	if ( '3' === $version ) {
		return;
	}

	global $wpdb;

	require_once ABSPATH . 'wp-admin/includes/upgrade.php';

	$prefix          = lucrsure_table_prefix();
	$charset_collate = $wpdb->get_charset_collate();

	if ( version_compare( $version, '1', '<' ) ) {
		dbDelta(
			"CREATE TABLE {$prefix}plan_categories (
			id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
			name VARCHAR(191) NOT NULL DEFAULT '',
			slug VARCHAR(191) NOT NULL DEFAULT '',
			description TEXT NULL,
			is_active TINYINT(1) NOT NULL DEFAULT 1,
			created_at DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
			updated_at DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY  (id),
			KEY slug (slug),
			KEY is_active (is_active)
		) {$charset_collate};"
		);

		$plans_table = lucrsure_table( 'plans' );

		$category_col = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $plans_table, 'category_id' )
		);
		if ( empty( $category_col ) ) {
			$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
				$wpdb->prepare( 'ALTER TABLE %i ADD COLUMN category_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 AFTER wc_product_id', $plans_table ) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
			);
		}

		$thumbnail_col = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $plans_table, 'thumbnail_id' )
		);
		if ( empty( $thumbnail_col ) ) {
			$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
				$wpdb->prepare( 'ALTER TABLE %i ADD COLUMN thumbnail_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 AFTER category_id', $plans_table ) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
			);
		}

		$version = '1';
	}

	$plans_table = lucrsure_table( 'plans' );
	$popular_col = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
		$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $plans_table, 'is_popular' )
	);
	if ( empty( $popular_col ) ) {
		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->prepare( 'ALTER TABLE %i ADD COLUMN is_popular TINYINT(1) NOT NULL DEFAULT 0 AFTER is_active', $plans_table ) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
		);
	}

	$special_price_col = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
		$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $plans_table, 'special_price' )
	);
	if ( empty( $special_price_col ) ) {
		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->prepare( 'ALTER TABLE %i ADD COLUMN special_price DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER price', $plans_table ) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
		);
	}

	$special_enabled_col = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
		$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $plans_table, 'special_price_enabled' )
	);
	if ( empty( $special_enabled_col ) ) {
		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
			$wpdb->prepare( 'ALTER TABLE %i ADD COLUMN special_price_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER special_price', $plans_table ) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
		);
	}

	update_option( 'lucrsure_plan_schema_version', '3' );
}

/**
 * Add phone and hours columns to branches for existing installs.
 */
function lucrsure_maybe_upgrade_branch_schema() {
	$version = (string) get_option( 'lucrsure_branch_schema_version', '0' );
	if ( '3' === $version ) {
		return;
	}

	global $wpdb;

	$table = lucrsure_table( 'branches' );

	if ( version_compare( $version, '1', '<' ) ) {
		$cols = array(
			'phone'        => 'ADD COLUMN phone VARCHAR(60) NOT NULL DEFAULT \'\' AFTER address',
			'opening_time' => 'ADD COLUMN opening_time VARCHAR(10) NOT NULL DEFAULT \'\' AFTER phone',
			'closing_time' => 'ADD COLUMN closing_time VARCHAR(10) NOT NULL DEFAULT \'\' AFTER opening_time',
		);

		foreach ( $cols as $column => $sql ) {
			$exists = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
				$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $table, $column )
			);
			if ( empty( $exists ) ) {
				$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
					$wpdb->prepare( 'ALTER TABLE %i ' . $sql, $table ) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
				);
			}
		}

		$version = '1';
	}

		if ( version_compare( $version, '2', '<' ) ) {
			$image_col = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
				$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $table, 'image_id' )
			);
			if ( empty( $image_col ) ) {
				$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
					$wpdb->prepare( 'ALTER TABLE %i ADD COLUMN image_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 AFTER closing_time', $table ) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
				);
			}

			$version = '2';
		}

		if ( version_compare( $version, '3', '<' ) ) {
			$amenities_col = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
				$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $table, 'amenities' )
			);
			if ( empty( $amenities_col ) ) {
				$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
					$wpdb->prepare( 'ALTER TABLE %i ADD COLUMN amenities TEXT NULL AFTER image_id', $table ) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
				);
			}

			$version = '3';
		}

		update_option( 'lucrsure_branch_schema_version', '3' );
}

/**
 * Billing address column names shared by customers and invoices.
 *
 * @return string[]
 */
function lucrsure_billing_address_keys() {
	return array(
		'billing_address_1',
		'billing_address_2',
		'billing_city',
		'billing_state',
		'billing_postcode',
		'billing_country',
	);
}

/**
 * Sanitize a billing address map from POST or an existing row.
 *
 * @param array $source Raw values keyed by billing_* fields.
 * @return array<string,string>
 */
function lucrsure_sanitize_billing_address( $source ) {
	if ( ! is_array( $source ) ) {
		$source = array();
	}

	$out = array();
	foreach ( lucrsure_billing_address_keys() as $key ) {
		$out[ $key ] = isset( $source[ $key ] ) ? sanitize_text_field( (string) $source[ $key ] ) : '';
	}

	return $out;
}

/**
 * Format a billing address for display (HTML or plain text).
 *
 * @param array  $row       Customer or invoice row.
 * @param string $separator Line separator.
 * @return string
 */
function lucrsure_format_billing_address( $row, $separator = "\n" ) {
	if ( ! is_array( $row ) ) {
		return '';
	}

	$address = lucrsure_sanitize_billing_address( $row );
	$lines   = array_filter(
		array(
			$address['billing_address_1'],
			$address['billing_address_2'],
			trim( implode( ', ', array_filter( array( $address['billing_city'], $address['billing_state'], $address['billing_postcode'] ) ) ) ),
			$address['billing_country'],
		)
	);

	return implode( $separator, $lines );
}

/**
 * Whether a billing address has the required checkout fields.
 *
 * @param array $address Sanitized billing address.
 * @return bool
 */
function lucrsure_billing_address_is_complete( $address ) {
	$address = lucrsure_sanitize_billing_address( $address );
	return '' !== $address['billing_address_1']
		&& '' !== $address['billing_city']
		&& '' !== $address['billing_postcode']
		&& '' !== $address['billing_country'];
}

/**
 * Add billing address columns to customers and invoices for existing installs.
 */
function lucrsure_maybe_upgrade_billing_address_schema() {
	if ( '1' === (string) get_option( 'lucrsure_billing_address_schema_version', '0' ) ) {
		return;
	}

	global $wpdb;

	$columns = array(
		'billing_address_1' => "ADD COLUMN billing_address_1 VARCHAR(191) NOT NULL DEFAULT ''",
		'billing_address_2' => "ADD COLUMN billing_address_2 VARCHAR(191) NOT NULL DEFAULT ''",
		'billing_city'      => "ADD COLUMN billing_city VARCHAR(100) NOT NULL DEFAULT ''",
		'billing_state'     => "ADD COLUMN billing_state VARCHAR(100) NOT NULL DEFAULT ''",
		'billing_postcode'  => "ADD COLUMN billing_postcode VARCHAR(30) NOT NULL DEFAULT ''",
		'billing_country'   => "ADD COLUMN billing_country VARCHAR(100) NOT NULL DEFAULT ''",
	);

	foreach ( array( 'customers', 'invoices' ) as $table_key ) {
		$table = lucrsure_table( $table_key );
		$after = 'customers' === $table_key ? 'company' : 'payment_url';

		foreach ( $columns as $column => $definition ) {
			$exists = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter
				$wpdb->prepare( 'SHOW COLUMNS FROM %i LIKE %s', $table, $column )
			);
			if ( ! empty( $exists ) ) {
				$after = $column;
				continue;
			}

			$sql = $definition . ' AFTER ' . $after;
			$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
				$wpdb->prepare( 'ALTER TABLE %i ' . $sql, $table ) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.SchemaChange,PluginCheck.Security.DirectDB.UnescapedDBParameter
			);
			$after = $column;
		}
	}

	update_option( 'lucrsure_billing_address_schema_version', '1', false );
}

/**
 * Default category slugs pre-selected in the setup wizard.
 *
 * @return string[]
 */
function lucrsure_demo_default_category_slugs() {
	return array( 'gym-membership' );
}

/**
 * Import a bundled plugin file into the WordPress media library.
 *
 * @param string $relative_path Path relative to LUCRSURE_PLUGIN_DIR.
 * @param string $title         Attachment title.
 * @return int Attachment ID or 0 on failure.
 */
function lucrsure_import_plugin_attachment( $relative_path, $title = '' ) {
	$file_path = trailingslashit( LUCRSURE_PLUGIN_DIR ) . ltrim( $relative_path, '/' );
	return lucrsure_import_attachment_from_path( $file_path, $title );
}

/**
 * Import a file from an absolute path into the WordPress media library.
 *
 * @param string $file_path Absolute file path.
 * @param string $title     Attachment title.
 * @return int Attachment ID or 0 on failure.
 */
function lucrsure_import_attachment_from_path( $file_path, $title = '' ) {
	if ( ! is_readable( $file_path ) ) {
		return 0;
	}

	require_once ABSPATH . 'wp-admin/includes/file.php';
	require_once ABSPATH . 'wp-admin/includes/media.php';
	require_once ABSPATH . 'wp-admin/includes/image.php';

	$filename   = basename( $file_path );
	$upload_dir = wp_upload_dir();
	if ( ! empty( $upload_dir['error'] ) ) {
		return 0;
	}

	$dest_path = $upload_dir['path'] . '/' . wp_unique_filename( $upload_dir['path'], $filename );
	if ( ! copy( $file_path, $dest_path ) ) {
		return 0;
	}

	$filetype   = wp_check_filetype( $filename, null );
	$attachment = array(
		'post_mime_type' => $filetype['type'],
		'post_title'     => $title ? sanitize_text_field( $title ) : pathinfo( $filename, PATHINFO_FILENAME ),
		'post_content'   => '',
		'post_status'    => 'inherit',
	);
	$attach_id = wp_insert_attachment( $attachment, $dest_path );
	if ( is_wp_error( $attach_id ) || ! $attach_id ) {
		wp_delete_file( $dest_path );
		return 0;
	}

	$attach_data = wp_generate_attachment_metadata( $attach_id, $dest_path );
	wp_update_attachment_metadata( $attach_id, $attach_data );

	return (int) $attach_id;
}

/**
 * Format branch opening/closing times for display.
 *
 * @param string $opening Opening time (HH:MM).
 * @param string $closing Closing time (HH:MM).
 * @return string
 */
function lucrsure_format_branch_hours( $opening, $closing ) {
	$opening = trim( (string) $opening );
	$closing = trim( (string) $closing );

	if ( '' === $opening && '' === $closing ) {
		return '';
	}

	$format = static function ( $time ) {
		if ( ! preg_match( '/^([01]?\d|2[0-3]):([0-5]\d)$/', $time, $matches ) ) {
			return $time;
		}
		$hour   = (int) $matches[1];
		$minute = $matches[2];
		$suffix = $hour >= 12 ? 'PM' : 'AM';
		$hour12 = $hour % 12;
		if ( 0 === $hour12 ) {
			$hour12 = 12;
		}
		return $minute === '00' ? $hour12 . ' ' . $suffix : $hour12 . ':' . $minute . ' ' . $suffix;
	};

	if ( '' !== $opening && '' !== $closing ) {
		return sprintf(
			/* translators: 1: opening time, 2: closing time */
			__( '%1$s – %2$s', 'lucrativeit-subscription-recurring-billing' ),
			$format( $opening ),
			$format( $closing )
		);
	}

	return $format( '' !== $opening ? $opening : $closing );
}

/**
 * Default frontend display settings.
 *
 * @return array
 */
function lucrsure_default_frontend_settings() {
	return array(
		'hero_enabled'         => 1,
		'hero_heading'         => __( 'Professional Services You Can Trust', 'lucrativeit-subscription-recurring-billing' ),
		'hero_subtitle'        => __( 'Join today and get access to premium facilities, expert trainers, and flexible membership plans built for every goal.', 'lucrativeit-subscription-recurring-billing' ),
		'hero_button_text'     => __( 'View Plans', 'lucrativeit-subscription-recurring-billing' ),
		'hero_button_url'      => '',
		'hero_image_id'        => 0,
		'locations_enabled'    => 1,
		'locations_heading'    => __( 'Our Locations', 'lucrativeit-subscription-recurring-billing' ),
		'locations_subtitle'   => __( 'Find a location near you. All branches feature state-of-the-art equipment and expert support.', 'lucrativeit-subscription-recurring-billing' ),
		'locations_branch_ids' => array(),
		'plans_heading'        => __( 'Membership Plans', 'lucrativeit-subscription-recurring-billing' ),
		'plans_subtitle'       => __( 'Choose the perfect plan that fits your fitness goals and lifestyle. No hidden fees, cancel anytime.', 'lucrativeit-subscription-recurring-billing' ),
		'faq_enabled'          => 1,
		'faq_heading'          => __( 'Frequently Asked Questions', 'lucrativeit-subscription-recurring-billing' ),
		'faq_subtitle'         => __( 'Everything you need to know before you subscribe.', 'lucrativeit-subscription-recurring-billing' ),
		'faq_items'            => array(
			array(
				'question' => __( 'Can I switch plans later?', 'lucrativeit-subscription-recurring-billing' ),
				'answer'   => __( 'Yes. You can upgrade or downgrade your membership at any time from your account or by contacting our team.', 'lucrativeit-subscription-recurring-billing' ),
			),
			array(
				'question' => __( 'Is there a free trial?', 'lucrativeit-subscription-recurring-billing' ),
				'answer'   => __( 'Selected plans include a trial period when configured. Check the plan details above for trial availability.', 'lucrativeit-subscription-recurring-billing' ),
			),
			array(
				'question' => __( 'Which payment methods do you accept?', 'lucrativeit-subscription-recurring-billing' ),
				'answer'   => __( 'We support the payment gateways enabled on this site, including card payments and manual invoice options.', 'lucrativeit-subscription-recurring-billing' ),
			),
		),
		'appearance'           => lucrsure_default_frontend_appearance(),
	);
}

/**
 * Default frontend appearance settings.
 *
 * @return array
 */
function lucrsure_default_frontend_appearance() {
	return array(
		'font_family'          => '',
		'text_color'           => '#111827',
		'muted_color'          => '#6b7280',
		'border_color'         => '#e5e7eb',
		'accent_color'         => '#111827',
		'accent_text_color'    => '#ffffff',
		'popular_badge_bg'     => '#111827',
		'popular_badge_text'   => '#ffffff',
		'hero_button_bg'       => '#ffffff',
		'hero_button_text'     => '#111827',
		'hero_title_color'     => '#ffffff',
		'hero_subtitle_color'  => '#e5e7eb',
		'hero_gradient_start'  => '#111827',
		'hero_gradient_end'    => '#374151',
		'check_color'          => '#22c55e',
	);
}

/**
 * Get merged frontend appearance settings.
 *
 * @param array|null $frontend Frontend settings array.
 * @return array
 */
function lucrsure_frontend_appearance( $frontend = null ) {
	if ( null === $frontend ) {
		$frontend = lucrsure_frontend_settings();
	}
	$appearance = isset( $frontend['appearance'] ) && is_array( $frontend['appearance'] )
		? $frontend['appearance']
		: array();
	return array_merge( lucrsure_default_frontend_appearance(), $appearance );
}

/**
 * Sanitize a hex color setting with fallback.
 *
 * @param string $value   Submitted color.
 * @param string $default Default color.
 * @return string
 */
function lucrsure_sanitize_color_setting( $value, $default ) {
	$color = sanitize_hex_color( (string) $value );
	return $color ? $color : $default;
}

/**
 * Escape a CSS font-family value for safe inline output.
 *
 * Allows common font-list syntax while blocking characters that can break
 * out of a CSS declaration (CSS injection).
 *
 * @param string $font_family Raw font-family setting.
 * @return string Escaped font-family list, or empty string when invalid.
 */
function lucrsure_esc_css_font_family( $font_family ) {
	$font_family = sanitize_text_field( (string) $font_family );
	$font_family = wp_strip_all_tags( $font_family );
	// Letters, numbers, spaces, commas, hyphens, underscores, and quotes only.
	$font_family = preg_replace( '/[^a-zA-Z0-9\s,\-_\'"]/', '', $font_family );
	$font_family = trim( preg_replace( '/\s+/', ' ', (string) $font_family ) );

	return $font_family;
}

/**
 * Escape a URL for use inside a CSS url("…") value.
 *
 * Uses esc_url() for late escaping (not esc_url_raw, which is a sanitizer).
 *
 * @param string $url Raw URL.
 * @return string Escaped URL safe for CSS url(), or empty string.
 */
function lucrsure_esc_css_url( $url ) {
	$url = esc_url( (string) $url, array( 'http', 'https' ) );
	if ( '' === $url ) {
		return '';
	}

	// esc_url() HTML-encodes entities; CSS url() needs a decoded string.
	$url = html_entity_decode( $url, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
	// Prevent breaking out of the quoted url() value.
	$url = str_replace( array( '"', "'", '(', ')', '\\', '<', '>', '{', '}', ';' ), '', $url );

	return $url;
}

/**
 * Convert a hex color to RGB components.
 *
 * @param string $hex Hex color.
 * @return array{r:int,g:int,b:int}
 */
function lucrsure_hex_to_rgb( $hex ) {
	$hex = ltrim( (string) $hex, '#' );
	if ( 3 === strlen( $hex ) ) {
		$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
	}

	return array(
		'r' => absint( hexdec( substr( $hex, 0, 2 ) ) ),
		'g' => absint( hexdec( substr( $hex, 2, 2 ) ) ),
		'b' => absint( hexdec( substr( $hex, 4, 2 ) ) ),
	);
}

/**
 * Build inline CSS for frontend appearance variables.
 *
 * Values are escaped late at output time (not only when saved).
 *
 * @return string
 */
function lucrsure_frontend_appearance_css() {
	$appearance = lucrsure_frontend_appearance();
	$vars       = array(
		'--lucrsure-text'                => $appearance['text_color'],
		'--lucrsure-muted'               => $appearance['muted_color'],
		'--lucrsure-border'              => $appearance['border_color'],
		'--lucrsure-accent'              => $appearance['accent_color'],
		'--lucrsure-accent-text'         => $appearance['accent_text_color'],
		'--lucrsure-popular-badge-bg'    => $appearance['popular_badge_bg'],
		'--lucrsure-popular-badge-text'  => $appearance['popular_badge_text'],
		'--lucrsure-hero-btn-bg'         => $appearance['hero_button_bg'],
		'--lucrsure-hero-btn-text'       => $appearance['hero_button_text'],
		'--lucrsure-hero-title-color'    => $appearance['hero_title_color'],
		'--lucrsure-hero-subtitle-color' => $appearance['hero_subtitle_color'],
		'--lucrsure-hero-gradient-start' => $appearance['hero_gradient_start'],
		'--lucrsure-hero-gradient-end'   => $appearance['hero_gradient_end'],
		'--lucrsure-check'               => $appearance['check_color'],
	);

	$rules = array();
	foreach ( $vars as $name => $value ) {
		$color = sanitize_hex_color( (string) $value );
		if ( $color ) {
			$rules[] = $name . ':' . $color;
		}
	}

	$font_family = lucrsure_esc_css_font_family( $appearance['font_family'] );
	if ( '' !== $font_family ) {
		$rules[] = '--lucrsure-font-family:' . $font_family;
		$rules[] = 'font-family:' . $font_family;
	}

	$css = '.lucrsure-category-page,.lucrsure-public,.lucrsure-locations,.lucrsure-hero,.lucrsure-faq{' . implode( ';', $rules ) . '}';

	$start = sanitize_hex_color( (string) $appearance['hero_gradient_start'] );
	if ( $start ) {
		$rgb  = lucrsure_hex_to_rgb( $start );
		$css .= '.lucrsure-hero:not(.has-image){background:linear-gradient(135deg,var(--lucrsure-hero-gradient-start) 0%,var(--lucrsure-hero-gradient-end) 100%)}';
		$css .= '.lucrsure-hero.has-image{background-image:linear-gradient(90deg,rgba(' . $rgb['r'] . ',' . $rgb['g'] . ',' . $rgb['b'] . ',0.92) 0%,rgba(' . $rgb['r'] . ',' . $rgb['g'] . ',' . $rgb['b'] . ',0.72) 45%,rgba(' . $rgb['r'] . ',' . $rgb['g'] . ',' . $rgb['b'] . ',0.35) 100%),var(--lucrsure-hero-image)}';
	}

	return $css;
}

/**
 * Stable HTML anchor IDs for frontend page sections (theme menu links).
 *
 * @return array<string,array{id:string,label:string,setting_key:?string,description:string}>
 */
function lucrsure_frontend_section_anchors() {
	return array(
		'hero'      => array(
			'id'          => 'lucrsure-hero',
			'label'       => __( 'Hero', 'lucrativeit-subscription-recurring-billing' ),
			'setting_key' => 'hero_enabled',
			'description' => __( 'Top banner section', 'lucrativeit-subscription-recurring-billing' ),
		),
		'locations' => array(
			'id'          => 'lucrsure-locations',
			'label'       => __( 'Locations', 'lucrativeit-subscription-recurring-billing' ),
			'setting_key' => 'locations_enabled',
			'description' => __( 'Branch / location cards', 'lucrativeit-subscription-recurring-billing' ),
		),
		'plans'     => array(
			'id'          => 'lucrsure-plans',
			'label'       => __( 'Plans', 'lucrativeit-subscription-recurring-billing' ),
			'setting_key' => null,
			'description' => __( 'Subscription packages and checkout', 'lucrativeit-subscription-recurring-billing' ),
		),
		'faq'       => array(
			'id'          => 'lucrsure-faq',
			'label'       => __( 'FAQ', 'lucrativeit-subscription-recurring-billing' ),
			'setting_key' => 'faq_enabled',
			'description' => __( 'Frequently asked questions', 'lucrativeit-subscription-recurring-billing' ),
		),
	);
}

/**
 * Resolve a frontend section anchor ID.
 *
 * @param string $section Section key (hero, locations, plans, faq).
 * @return string
 */
function lucrsure_frontend_section_id( $section ) {
	$anchors = lucrsure_frontend_section_anchors();
	return isset( $anchors[ $section ]['id'] ) ? $anchors[ $section ]['id'] : '';
}

/**
 * Build a theme menu link for a frontend section anchor.
 *
 * When the target page is the site homepage, only the hash is returned (e.g. #lucrsure-plans).
 * Otherwise the full page URL plus hash is returned.
 *
 * @param string $section  Section key (hero, locations, plans, faq).
 * @param string $page_url Permalink of the page containing the shortcode.
 * @return string
 */
function lucrsure_frontend_menu_anchor_link( $section, $page_url = '' ) {
	$anchor_id = lucrsure_frontend_section_id( $section );
	if ( '' === $anchor_id ) {
		return '';
	}

	$hash = '#' . $anchor_id;
	$page_url = trim( (string) $page_url );

	if ( '' === $page_url ) {
		return $hash;
	}

	$page_id       = url_to_postid( $page_url );
	$front_page_id = (int) get_option( 'page_on_front' );

	if ( $front_page_id > 0 && $page_id === $front_page_id ) {
		return $hash;
	}

	$home_url = trailingslashit( home_url( '/' ) );
	if ( trailingslashit( $page_url ) === $home_url ) {
		return $hash;
	}

	return untrailingslashit( $page_url ) . $hash;
}

/**
 * Merge frontend settings with defaults.
 *
 * @param array $settings Full plugin settings.
 * @return array
 */
function lucrsure_frontend_settings( $settings = null ) {
	if ( null === $settings ) {
		$settings = get_option( 'lucrsure_settings', array() );
	}
	$frontend = isset( $settings['frontend'] ) && is_array( $settings['frontend'] )
		? $settings['frontend']
		: array();
	$merged   = array_merge( lucrsure_default_frontend_settings(), $frontend );
	$merged['appearance'] = array_merge(
		lucrsure_default_frontend_appearance(),
		isset( $merged['appearance'] ) && is_array( $merged['appearance'] ) ? $merged['appearance'] : array()
	);
	return $merged;
}

/**
 * Ensure frontend settings exist on older installs.
 */
function lucrsure_maybe_upgrade_frontend_settings() {
	$settings = get_option( 'lucrsure_settings', array() );
	if ( ! is_array( $settings ) ) {
		$settings = array();
	}

	$frontend = isset( $settings['frontend'] ) && is_array( $settings['frontend'] )
		? $settings['frontend']
		: array();
	$merged   = array_merge( lucrsure_default_frontend_settings(), $frontend );
	$merged['appearance'] = array_merge(
		lucrsure_default_frontend_appearance(),
		isset( $merged['appearance'] ) && is_array( $merged['appearance'] ) ? $merged['appearance'] : array()
	);

	if ( ! isset( $settings['frontend'] ) || $merged !== $frontend ) {
		$settings['frontend'] = $merged;
		update_option( 'lucrsure_settings', $settings );
	}
}

/**
 * Resolve a hero background/image URL.
 *
 * @param int $image_id Attachment ID.
 * @return string
 */
function lucrsure_hero_image_url( $image_id ) {
	$image_id = (int) $image_id;
	if ( $image_id <= 0 ) {
		return '';
	}
	$url = wp_get_attachment_image_url( $image_id, 'large' );
	return $url ? $url : '';
}

/**
 * Normalize FAQ items from frontend settings.
 *
 * @param array $items Raw FAQ items.
 * @return array<int,array{question:string,answer:string}>
 */
function lucrsure_normalize_faq_items( $items ) {
	if ( ! is_array( $items ) ) {
		return array();
	}

	$normalized = array();
	foreach ( $items as $item ) {
		if ( ! is_array( $item ) ) {
			continue;
		}
		$question = isset( $item['question'] ) ? trim( (string) $item['question'] ) : '';
		$answer   = isset( $item['answer'] ) ? trim( (string) $item['answer'] ) : '';
		if ( '' === $question || '' === $answer ) {
			continue;
		}
		$normalized[] = array(
			'question' => $question,
			'answer'   => $answer,
		);
	}

	return $normalized;
}

/**
 * One-time: copy SSLCommerz / Razorpay / PayPal settings from Pro into core.
 *
 * Older installs stored those gateways in `lspp_settings`. They now live in
 * `lucrsure_settings` with the free plugin.
 */
function lucrsure_maybe_migrate_pro_gateways() {
	if ( get_option( 'lucrsure_pro_gateways_migrated' ) ) {
		return;
	}

	$pro = get_option( 'lspp_settings', array() );
	if ( ! is_array( $pro ) || empty( $pro['gateways'] ) || ! is_array( $pro['gateways'] ) ) {
		update_option( 'lucrsure_pro_gateways_migrated', 1, false );
		return;
	}

	$core = get_option( 'lucrsure_settings', array() );
	if ( ! is_array( $core ) ) {
		$core = array();
	}
	if ( ! isset( $core['gateways'] ) || ! is_array( $core['gateways'] ) ) {
		$core['gateways'] = array();
	}

	$keys = array( 'sslcommerz', 'razorpay', 'paypal' );
	$moved = false;

	foreach ( $keys as $key ) {
		if ( empty( $pro['gateways'][ $key ] ) || ! is_array( $pro['gateways'][ $key ] ) ) {
			continue;
		}

		$existing = isset( $core['gateways'][ $key ] ) && is_array( $core['gateways'][ $key ] )
			? $core['gateways'][ $key ]
			: array();

		// Prefer existing free settings when already configured (e.g. API keys present).
		$has_free_config = false;
		foreach ( array( 'store_id', 'store_password', 'key_id', 'key_secret', 'client_id', 'client_secret' ) as $field ) {
			if ( ! empty( $existing[ $field ] ) ) {
				$has_free_config = true;
				break;
			}
		}

		if ( $has_free_config ) {
			continue;
		}

		$core['gateways'][ $key ] = array_merge( $existing, $pro['gateways'][ $key ] );
		$moved                    = true;
	}

	if ( $moved ) {
		update_option( 'lucrsure_settings', $core );
	}

	update_option( 'lucrsure_pro_gateways_migrated', 1, false );
}

/**
 * Merge duplicate plan categories and reassign orphaned plans (runs once).
 */
function lucrsure_maybe_repair_plan_categories() {
	if ( get_option( 'lucrsure_category_repair_version' ) === '2' ) {
		return;
	}

	$cat_repo  = new LUCRSURE_Plan_Category_Repository();
	$plan_repo = new LUCRSURE_Plan_Repository();

	$cat_repo->consolidate_duplicates();

	foreach ( LUCRSURE_Demo_Data::plan_definitions() as $plans ) {
		foreach ( array_keys( $plans ) as $plan_slug ) {
			$plan = $plan_repo->find_by_slug( $plan_slug );
			if ( $plan && empty( $plan['is_active'] ) ) {
				$plan_repo->update( (int) $plan['id'], array( 'is_active' => 1 ) );
			}
		}
	}

	update_option( 'lucrsure_category_repair_version', '2' );
}

/**
 * Merge new notification defaults into stored settings.
 */
function lucrsure_maybe_upgrade_notifications() {
	$settings = get_option( 'lucrsure_settings' );
	if ( ! is_array( $settings ) ) {
		return;
	}

	if ( isset( $settings['notifications']['subscription_active'] ) ) {
		return;
	}

	if ( ! isset( $settings['notifications'] ) || ! is_array( $settings['notifications'] ) ) {
		$settings['notifications'] = array();
	}

	$settings['notifications']['subscription_active'] = 1;
	update_option( 'lucrsure_settings', $settings );
}

/**
 * Default email branding options (Pro).
 *
 * @return array<string,mixed>
 */
function lucrsure_default_email_branding() {
	return array(
		'logo_id'                => 0,
		'logo_width'             => 180,
		'logo_position'          => 'left',
		'header_bg'              => '#ffffff',
		'header_text_color'      => '#1e3a5f',
		'show_company_in_header' => 1,
		'body_bg'                => '#f0f4f8',
		'content_bg'             => '#ffffff',
		'content_text_color'     => '#4b5563',
		'heading_color'          => '#1e3a5f',
		'accent_color'           => '#2563eb',
		'info_box_bg'            => '#eef6fc',
		'icon_circle_bg'         => '#dbeafe',
		'footer_text'            => '',
		'footer_secondary'       => '',
		'footer_bg'              => '#ffffff',
		'footer_text_color'      => '#9ca3af',
		'button_bg'              => '#2563eb',
		'button_text_color'      => '#ffffff',
	);
}

/**
 * Resolved email branding settings.
 *
 * @return array<string,mixed>
 */
function lucrsure_email_branding() {
	$defaults = lucrsure_default_email_branding();

	if ( ! lucrsure_is_pro() ) {
		return $defaults;
	}

	$settings = get_option( 'lucrsure_settings', array() );
	$stored   = isset( $settings['email_branding'] ) && is_array( $settings['email_branding'] )
		? $settings['email_branding']
		: array();

	return wp_parse_args( $stored, $defaults );
}

/**
 * Logo URL for branded emails.
 *
 * @param array $branding Branding settings.
 * @return string
 */
function lucrsure_email_branding_logo_url( array $branding ) {
	if ( empty( $branding['logo_id'] ) ) {
		return '';
	}

	$url = wp_get_attachment_image_url( (int) $branding['logo_id'], 'full' );

	return $url ? $url : '';
}

/**
 * Merge email branding defaults into stored settings.
 */
function lucrsure_maybe_upgrade_email_branding() {
	if ( ! lucrsure_is_pro() ) {
		return;
	}

	$settings = get_option( 'lucrsure_settings' );
	if ( ! is_array( $settings ) ) {
		return;
	}

	if ( isset( $settings['email_branding'] ) && is_array( $settings['email_branding'] ) ) {
		return;
	}

	$settings['email_branding'] = lucrsure_default_email_branding();
	update_option( 'lucrsure_settings', $settings );
}

/**
 * Sanitize email branding settings from POST.
 *
 * @param array $raw Raw input.
 * @return array<string,mixed>
 */
function lucrsure_sanitize_email_branding( array $raw ) {
	$defaults  = lucrsure_default_email_branding();
	$positions = array( 'left', 'center', 'right' );
	$position  = isset( $raw['logo_position'] ) ? sanitize_key( $raw['logo_position'] ) : $defaults['logo_position'];
	$sanitized = array(
		'logo_id'                => isset( $raw['logo_id'] ) ? absint( $raw['logo_id'] ) : 0,
		'logo_width'             => isset( $raw['logo_width'] ) ? max( 40, min( 600, absint( $raw['logo_width'] ) ) ) : $defaults['logo_width'],
		'logo_position'          => in_array( $position, $positions, true ) ? $position : $defaults['logo_position'],
		'header_bg'              => sanitize_hex_color( $raw['header_bg'] ?? $defaults['header_bg'] ) ?: $defaults['header_bg'],
		'header_text_color'      => sanitize_hex_color( $raw['header_text_color'] ?? $defaults['header_text_color'] ) ?: $defaults['header_text_color'],
		'show_company_in_header' => ! empty( $raw['show_company_in_header'] ) ? 1 : 0,
		'footer_text'            => isset( $raw['footer_text'] ) ? sanitize_textarea_field( $raw['footer_text'] ) : '',
		'footer_secondary'       => isset( $raw['footer_secondary'] ) ? sanitize_textarea_field( $raw['footer_secondary'] ) : '',
		'footer_bg'              => sanitize_hex_color( $raw['footer_bg'] ?? $defaults['footer_bg'] ) ?: $defaults['footer_bg'],
		'footer_text_color'      => sanitize_hex_color( $raw['footer_text_color'] ?? $defaults['footer_text_color'] ) ?: $defaults['footer_text_color'],
	);

	return wp_parse_args( $sanitized, $defaults );
}

/**
 * Default PDF invoice branding options (Pro).
 *
 * @return array<string,mixed>
 */
function lucrsure_default_pdf_invoice_settings() {
	return array(
		'logo_id'          => 0,
		'logo_width'       => 160,
		'logo_position'    => 'left',
		'header_company'   => '',
		'footer_text'      => '',
		'footer_secondary' => '',
		'footer_address'   => '',
	);
}

/**
 * Resolved PDF invoice branding settings.
 *
 * @return array<string,mixed>
 */
function lucrsure_pdf_invoice_settings() {
	$defaults = lucrsure_default_pdf_invoice_settings();

	if ( ! lucrsure_is_pro() ) {
		return $defaults;
	}

	$settings = get_option( 'lucrsure_settings', array() );
	$stored   = isset( $settings['pdf_invoice'] ) && is_array( $settings['pdf_invoice'] )
		? $settings['pdf_invoice']
		: array();

	return wp_parse_args( $stored, $defaults );
}

/**
 * Logo URL for PDF invoice settings UI preview.
 *
 * @param array $pdf PDF settings.
 * @return string
 */
function lucrsure_pdf_invoice_logo_url( array $pdf ) {
	if ( empty( $pdf['logo_id'] ) ) {
		return '';
	}

	$url = wp_get_attachment_image_url( (int) $pdf['logo_id'], 'full' );

	return $url ? $url : '';
}

/**
 * Whether a PDF invoice should be attached to an email template.
 *
 * @param string $template_key Email template key.
 * @return bool
 */
function lucrsure_should_attach_pdf_invoice( $template_key ) {
	if ( ! lucrsure_is_pro() ) {
		return false;
	}

	$settings = get_option( 'lucrsure_settings', array() );
	$attach   = isset( $settings['notifications']['attach_pdf_invoice'] )
		? $settings['notifications']['attach_pdf_invoice']
		: false;

	if ( ! $attach ) {
		return false;
	}

	$templates = apply_filters(
		'lucrsure_pdf_invoice_email_templates',
		array(
			'invoice_payment_request',
			'overdue_payment_reminder',
			'payment_success',
		)
	);

	return in_array( $template_key, $templates, true );
}

/**
 * Render a plain textarea for an email template body field.
 *
 * @param string $template_key Template key.
 * @param string $content      Current body text.
 */
function lucrsure_render_template_textarea( $template_key, $content ) {
	$field_name = sanitize_key( $template_key ) . '_body';
	?>
	<textarea
		id="<?php echo esc_attr( 'lucrsure_tpl_' . sanitize_key( $template_key ) . '_body' ); ?>"
		name="<?php echo esc_attr( $field_name ); ?>"
		rows="10"
		class="large-text code lucrsure-template-textarea"
	><?php echo esc_textarea( $content ); ?></textarea>
	<?php
}

/**
 * Sample replacement data for admin email template previews.
 *
 * @param string $template_key Template key.
 * @return array
 */
function lucrsure_email_preview_sample_data( $template_key ) {
	$settings = get_option( 'lucrsure_settings', array() );
	$currency = isset( $settings['default_currency'] ) ? $settings['default_currency'] : 'USD';
	$due      = gmdate( 'Y-m-d H:i:s', strtotime( '+7 days' ) );
	$start    = gmdate( 'Y-m-d H:i:s', strtotime( '-2 days' ) );
	$next     = gmdate( 'Y-m-d H:i:s', strtotime( '+28 days' ) );

	$data = array(
		'customer_name'       => __( 'Jane Customer', 'lucrativeit-subscription-recurring-billing' ),
		'invoice_number'      => 'INV-2026-0001',
		'package_name'        => __( 'Professional Plan', 'lucrativeit-subscription-recurring-billing' ),
		'amount'              => 99.00,
		'currency'            => $currency,
		'due_date'            => $due,
		'payment_url'         => home_url( '/' ),
		'gateway'             => 'stripe',
		'billing_cycle'       => __( 'Monthly', 'lucrativeit-subscription-recurring-billing' ),
		'start_date'          => $start,
		'next_billing_date'   => $next,
		'subscription_status' => __( 'Active', 'lucrativeit-subscription-recurring-billing' ),
	);

	/**
	 * Filter sample data used when previewing email templates in admin.
	 *
	 * @param array  $data         Sample replacement data.
	 * @param string $template_key Template key.
	 */
	return apply_filters( 'lucrsure_email_preview_sample_data', $data, $template_key );
}

/**
 * Merge PDF invoice defaults into stored settings.
 */
function lucrsure_maybe_upgrade_pdf_invoice() {
	if ( ! lucrsure_is_pro() ) {
		return;
	}

	$settings = get_option( 'lucrsure_settings' );
	if ( ! is_array( $settings ) ) {
		return;
	}

	$changed = false;

	if ( ! isset( $settings['pdf_invoice'] ) || ! is_array( $settings['pdf_invoice'] ) ) {
		$settings['pdf_invoice'] = lucrsure_default_pdf_invoice_settings();
		$changed                 = true;
	}

	if ( ! isset( $settings['notifications']['attach_pdf_invoice'] ) ) {
		if ( ! isset( $settings['notifications'] ) || ! is_array( $settings['notifications'] ) ) {
			$settings['notifications'] = array();
		}
		$settings['notifications']['attach_pdf_invoice'] = 0;
		$changed                                       = true;
	}

	if ( $changed ) {
		update_option( 'lucrsure_settings', $settings );
	}
}

/**
 * Sanitize PDF invoice settings from POST.
 *
 * @param array $raw Raw input.
 * @return array<string,mixed>
 */
function lucrsure_sanitize_pdf_invoice_settings( array $raw ) {
	$defaults  = lucrsure_default_pdf_invoice_settings();
	$positions = array( 'left', 'center', 'right' );
	$position  = isset( $raw['logo_position'] ) ? sanitize_key( $raw['logo_position'] ) : $defaults['logo_position'];

	return array(
		'logo_id'          => isset( $raw['logo_id'] ) ? absint( $raw['logo_id'] ) : 0,
		'logo_width'       => isset( $raw['logo_width'] ) ? max( 40, min( 400, absint( $raw['logo_width'] ) ) ) : $defaults['logo_width'],
		'logo_position'    => in_array( $position, $positions, true ) ? $position : $defaults['logo_position'],
		'header_company'   => isset( $raw['header_company'] ) ? sanitize_text_field( $raw['header_company'] ) : '',
		'footer_text'      => isset( $raw['footer_text'] ) ? sanitize_textarea_field( $raw['footer_text'] ) : '',
		'footer_secondary' => isset( $raw['footer_secondary'] ) ? sanitize_textarea_field( $raw['footer_secondary'] ) : '',
		'footer_address'   => isset( $raw['footer_address'] ) ? sanitize_textarea_field( $raw['footer_address'] ) : '',
	);
}

/**
 * Rows for the Basic vs Premium comparison table (Settings → Premium).
 *
 * @return array<int,array{label:string,basic:bool,premium:bool}>
 */
function lucrsure_premium_comparison_rows() {
	$rows = array(
		array(
			'label'   => __( 'Customers, branches & plans', 'lucrativeit-subscription-recurring-billing' ),
			'basic'   => true,
			'premium' => true,
		),
		array(
			'label'   => __( 'Subscriptions & billing', 'lucrativeit-subscription-recurring-billing' ),
			'basic'   => true,
			'premium' => true,
		),
		array(
			'label'   => __( 'Stripe, manual, SSLCommerz, Razorpay & PayPal', 'lucrativeit-subscription-recurring-billing' ),
			'basic'   => true,
			'premium' => true,
		),
		array(
			'label'   => __( 'Basic email templates', 'lucrativeit-subscription-recurring-billing' ),
			'basic'   => true,
			'premium' => true,
		),
		array(
			'label'   => __( 'Email header logo & footer branding', 'lucrativeit-subscription-recurring-billing' ),
			'basic'   => false,
			'premium' => true,
		),
		array(
			'label'   => __( 'PDF invoices & attachments', 'lucrativeit-subscription-recurring-billing' ),
			'basic'   => false,
			'premium' => true,
		),
		array(
			'label'   => __( 'Staff & roles', 'lucrativeit-subscription-recurring-billing' ),
			'basic'   => false,
			'premium' => true,
		),
		array(
			'label'   => __( 'Advanced reports & reminders', 'lucrativeit-subscription-recurring-billing' ),
			'basic'   => false,
			'premium' => true,
		),
	);

	/**
	 * Filter the Basic vs Premium comparison rows shown on Settings → Premium.
	 *
	 * @param array<int,array{label:string,basic:bool,premium:bool}> $rows Comparison rows.
	 */
	return apply_filters( 'lucrsure_premium_comparison_rows', $rows );
}

/**
 * Render an admin notice from a redirect status code.
 *
 * @param string $message Message text.
 * @param string $type    success|error|warning.
 * @param bool   $allow_html When true, allow limited HTML in the message.
 */
function lucrsure_render_notice( $message, $type = 'success', $allow_html = false ) {
	$classes = array(
		'success' => 'lucrsure-notice-success',
		'error'   => 'lucrsure-notice-error',
		'warning' => 'lucrsure-notice-warning',
	);
	$class   = isset( $classes[ $type ] ) ? $classes[ $type ] : $classes['success'];
	$content = $allow_html ? wp_kses_post( $message ) : esc_html( $message );
	printf(
		'<div class="lucrsure-notice %s">%s</div>',
		esc_attr( $class ),
		$content // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	);
}

/**
 * Compute the next period end for a billing interval.
 *
 * @param string $interval day|week|month|year.
 * @param int    $count    Interval multiplier.
 * @param string $from     Datetime to start from (mysql). Defaults to now.
 * @return string MySQL datetime.
 */
function lucrsure_next_period_end( $interval, $count = 1, $from = '' ) {
	$count     = max( 1, (int) $count );
	$timestamp = $from ? strtotime( $from ) : current_time( 'timestamp' );
	if ( 'lifetime' === $interval ) {
		return gmdate( 'Y-m-d H:i:s', strtotime( '+100 years', $timestamp ) );
	}

	$map       = array(
		'day'   => '+%d day',
		'week'  => '+%d week',
		'month' => '+%d month',
		'year'  => '+%d year',
	);
	$modifier  = isset( $map[ $interval ] ) ? $map[ $interval ] : '+%d month';
	$next      = strtotime( sprintf( $modifier, $count ), $timestamp );
	return gmdate( 'Y-m-d H:i:s', $next );
}

/**
 * Percent change between two values.
 *
 * @param float $current  Current value.
 * @param float $previous Previous value.
 * @return float
 */
function lucrsure_percent_change( $current, $previous ) {
	$current  = (float) $current;
	$previous = (float) $previous;

	if ( abs( $previous ) < 0.00001 ) {
		return $current > 0 ? 100.0 : 0.0;
	}

	return round( ( ( $current - $previous ) / $previous ) * 100, 1 );
}

/**
 * Render a KPI trend badge (month-over-month style).
 *
 * @param float $change      Change value (percent or points).
 * @param bool  $invert_good When true, a decrease is shown as positive (e.g. churn).
 * @return string HTML.
 */
function lucrsure_render_kpi_trend( $change, $invert_good = false ) {
	$change = (float) $change;
	$good   = $invert_good ? ( $change <= 0 ) : ( $change >= 0 );
	$class  = $good ? 'up' : 'down';
	$arrow  = $change >= 0 ? '&#8599;' : '&#8600;';
	$sign   = $change >= 0 ? '+' : '';
	$text   = $sign . number_format_i18n( $change, 1 );

	return sprintf(
		'<span class="lucrsure-kpi-trend %s" title="%s">%s %s%%</span>',
		esc_attr( $class ),
		esc_attr__( 'vs 30 days ago', 'lucrativeit-subscription-recurring-billing' ),
		$arrow,
		esc_html( $text )
	);
}
