<?php
/**
 * Plugin Name: Digital Lychee Donation Notifications for Stripe
 * Description: Adds a Stripe Payment Link donation button and sends custom admin email notifications via Stripe webhooks, including Gift Aid/custom field values.
 * Version: 1.0.3
 * Requires at least: 6.0
 * Requires PHP: 8.3
 * Author: Digital Lychee Creative Limited
 * Author URI: https://digitallychee.com
 * License: GPLv3
 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
 * Text Domain: dl-custom-stripe-donations
 * Domain Path: /languages
 * 
 * Copyright (C) 2026 Digital Lychee Creative Limited
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

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

final class DLDNS_Donation_Notifications
{
    const REST_NAMESPACE = 'dldns/v1';
    const OPTION_NAME = 'dldns_settings';
    const MENU_SLUG = 'dldns-stripe-donations';
    const VERSION = '1.0.2';

    public function __construct()
    {
        add_shortcode('dldns_donation_button', [$this, 'render_donation_button']);
        // Backwards-compatible shortcode name, now outputs the Payment Link button only.
        add_shortcode('dldns_donation_form', [$this, 'render_donation_button']);

        add_action('rest_api_init', [$this, 'register_routes']);
        add_action('admin_menu', [$this, 'register_admin_menu']);
        add_action('admin_init', [$this, 'register_settings']);
        add_action('admin_notices', [$this, 'maybe_show_admin_notices']);
        add_action('wp_enqueue_scripts', [$this, 'register_frontend_styles']);
        add_action('wp_enqueue_scripts', [$this, 'dlcsd_enqueue_fontawesome']);
    }

    public static function activate(): void
    {
        $defaults = self::default_settings();
        $existing = get_option(self::OPTION_NAME, []);
        if (!is_array($existing)) {
            $existing = [];
        }
        update_option(self::OPTION_NAME, array_merge($defaults, $existing));
    }

    private static function default_settings(): array
    {
        return [
            'payment_link_url' => '',
            'stripe_webhook_secret' => '',
            'admin_email' => get_option('admin_email'),
            'button_text' => 'Donate securely',
            'button_css_classes' => 'csd-donation-button',
            'button_icon_enabled' => '0',
            'button_icon_class' => '',
            'button_icon_position' => 'after',
            'button_icon_gap' => '0.5em',
            'fontawesome_version' => 'none',
            'button_open_new_tab' => '0',
            'enable_default_button_css' => '1',
            'gift_aid_field_key' => 'gift_aid',
            'gift_aid_yes_values' => 'yes,y,true,1,add gift aid,gift aid',
            'email_subject' => 'New donation received',
            'email_template' => self::default_email_template(),
        ];
    }

    private static function default_email_template(): string
    {
        return '<p>A new donation has been completed.</p>
<table cellpadding="6" cellspacing="0" border="0">
<tr><td><strong>Donation amount:</strong></td><td>{currency} {amount}</td></tr>
<tr><td><strong>Donor name:</strong></td><td>{donor_name}</td></tr>
<tr><td><strong>Donor email:</strong></td><td>{donor_email}</td></tr>
<tr><td><strong>Gift Aid selected:</strong></td><td>{gift_aid}</td></tr>
<tr><td><strong>Payment Link:</strong></td><td>{payment_link}</td></tr>
<tr><td><strong>Checkout Session ID:</strong></td><td>{checkout_session_id}</td></tr>
<tr><td><strong>Payment Intent ID:</strong></td><td>{payment_intent_id}</td></tr>
</table>
<p><strong>All Stripe custom fields:</strong></p>
{custom_fields_table}
<p>You can view the payment in your Stripe Dashboard.</p>';
    }

    public function register_admin_menu(): void
    {
        add_menu_page(
            'Stripe Donations',
            'Stripe Donations',
            'manage_options',
            self::MENU_SLUG,
            [$this, 'render_settings_page'],
            'dashicons-heart',
            56
        );
    }

    public function register_settings(): void
    {
        register_setting('dldns_settings_group', self::OPTION_NAME, [
            'sanitize_callback' => [$this, 'sanitize_settings'],
            'default' => self::default_settings(),
        ]);
    }

    public function sanitize_settings($input): array
    {
        $existing = $this->get_settings();
        $input = is_array($input) ? $input : [];

        $settings = [];
        $settings['payment_link_url'] = esc_url_raw($input['payment_link_url'] ?? '');
        $settings['stripe_webhook_secret'] = isset($input['stripe_webhook_secret'])
            ? trim(sanitize_text_field($input['stripe_webhook_secret']))
            : ($existing['stripe_webhook_secret'] ?? '');

        $settings['admin_email'] = $this->sanitize_recipient_emails(
	        $input['admin_email'] ?? get_option('admin_email')
        );
        $settings['button_text'] = sanitize_text_field($input['button_text'] ?? 'Donate securely');
        $settings['button_css_classes'] = dldns_sanitize_html_class_list($input['button_css_classes'] ?? 'csd-donation-button');
        $settings['button_icon_enabled'] = !empty($input['button_icon_enabled']) ? '1' : '0';

        $settings['button_icon_class'] = isset($input['button_icon_class'])
            ? dldns_sanitize_html_class_list($input['button_icon_class'])
            : '';

        $allowed_fontawesome_versions = ['none', 'already_loaded', '4', '5', '6', '7'];

        $settings['fontawesome_version'] = in_array($input['fontawesome_version'] ?? 'none', $allowed_fontawesome_versions, true)
            ? $input['fontawesome_version']
            : 'none';

        $settings['button_icon_position'] = isset($input['button_icon_position']) && in_array($input['button_icon_position'], ['before', 'after'], true)
            ? $input['button_icon_position']
            : 'after';
            $settings['button_icon_gap'] = sanitize_text_field(
    $input['button_icon_gap'] ?? '0.5em'
);
        $settings['button_open_new_tab'] = !empty($input['button_open_new_tab']) ? '1' : '0';
        $settings['enable_default_button_css'] = !empty($input['enable_default_button_css']) ? '1' : '0';
        $settings['gift_aid_field_key'] = sanitize_key($input['gift_aid_field_key'] ?? 'gift_aid');
        $settings['gift_aid_yes_values'] = sanitize_text_field($input['gift_aid_yes_values'] ?? 'yes,y,true,1,add gift aid,gift aid');
        $settings['email_subject'] = sanitize_text_field($input['email_subject'] ?? 'New donation received');

        // Allow standard safe HTML from the WordPress editor.
        $settings['email_template'] = wp_kses_post($input['email_template'] ?? self::default_email_template());

        return array_merge($existing, $settings);
    }

    private function sanitize_recipient_emails($emails): string
{
	$emails = is_string($emails) ? $emails : '';
	$parts  = preg_split('/[,;]+/', $emails);
	$valid  = [];

	foreach ($parts as $email) {
		$email = sanitize_email(trim($email));

		if (is_email($email)) {
			$valid[] = $email;
		}
	}

	return implode(', ', array_unique($valid));
}

    public function render_settings_page(): void
    {
        if (!current_user_can('manage_options')) {
            return;
        }

        $settings = $this->get_settings();
        $webhook_url = rest_url(self::REST_NAMESPACE . '/stripe-webhook');
        ?>
        <div class="wrap">
            <h1>Stripe Donations</h1>
            <p>This plugin outputs a simple donation button that links to your Stripe Payment Link. Successful donations are
                picked up by a Stripe webhook and emailed to your chosen admin address.</p>

            <form method="post" action="options.php">
                <?php settings_fields('dldns_settings_group'); ?>

                <h2>Stripe Payment Link</h2>
                <table class="form-table" role="presentation">
                    <tr>
                        <th scope="row"><label for="csd_payment_link_url">Stripe Payment Link URL</label></th>
                        <td>
                            <input id="csd_payment_link_url" class="regular-text code" type="url"
                                name="<?php echo esc_attr(self::OPTION_NAME); ?>[payment_link_url]"
                                value="<?php echo esc_attr($settings['payment_link_url']); ?>"
                                placeholder="https://buy.stripe.com/...">
                            <p class="description">Create this in Stripe as a Payment Link with <strong>Customers choose what to
                                    pay</strong>. Add your Gift Aid choice as a Stripe custom field.</p>
                        </td>
                    </tr>
                </table>

                <h2>Button settings</h2>
                <table class="form-table" role="presentation">
                    <tr>
                        <th scope="row"><label for="csd_button_text">Default button text</label></th>
                        <td><input id="csd_button_text" class="regular-text" type="text"
                                name="<?php echo esc_attr(self::OPTION_NAME); ?>[button_text]"
                                value="<?php echo esc_attr($settings['button_text']); ?>"></td>
                    </tr>
                    <tr>
                        <th scope="row"><label for="csd_button_css_classes">Button CSS classes</label></th>
                        <td>
                            <input id="csd_button_css_classes" class="regular-text" type="text"
                                name="<?php echo esc_attr(self::OPTION_NAME); ?>[button_css_classes]"
                                value="<?php echo esc_attr($settings['button_css_classes']); ?>">
                            <p class="description">Space-separated classes. Example:
                                <code>button button-primary my-donation-button</code>. The shortcode can override this per
                                button.</p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">
                            <label for="button_icon_enabled">Show Button Icon</label>
                        </th>
                        <td>
                            <label>
                                <input type="checkbox" id="button_icon_enabled" name="<?php echo esc_attr(self::OPTION_NAME); ?>[button_icon_enabled]"
                                    value="1" <?php checked($settings['button_icon_enabled'] ?? '', '1'); ?>>
                                Show an icon on the donation button
                            </label>
                        </td>
                    </tr>

                    <tr>
                        <th scope="row">
                            <label for="fontawesome_version">Font Awesome Version</label>
                        </th>
                        <td>
                            <select id="fontawesome_version" name="<?php echo esc_attr(self::OPTION_NAME); ?>[fontawesome_version]">
                                <option value="none" <?php selected($settings['fontawesome_version'] ?? 'none', 'none'); ?>>Do
                                    not load Font Awesome</option>
                                <option value="already_loaded" <?php selected($settings['fontawesome_version'] ?? '', 'already_loaded'); ?>>Already loaded by theme/plugin</option>
                                <option value="4" <?php selected($settings['fontawesome_version'] ?? '', '4'); ?>>Load Font
                                    Awesome 4</option>
                                <option value="5" <?php selected($settings['fontawesome_version'] ?? '', '5'); ?>>Load Font
                                    Awesome 5</option>
                                <option value="6" <?php selected($settings['fontawesome_version'] ?? '', '6'); ?>>Load Font
                                    Awesome 6</option>
                                <option value="7" <?php selected($settings['fontawesome_version'] ?? '', '7'); ?>>Load Font
                                    Awesome 7</option>
                            </select>
                        </td>
                    </tr>

                    <tr>
                        <th scope="row">
                            <label for="button_icon_class">Button Icon Class</label>
                        </th>
                        <td>
                            <input type="text" id="button_icon_class" name="<?php echo esc_attr(self::OPTION_NAME); ?>[button_icon_class]"
                                value="<?php echo esc_attr($settings['button_icon_class'] ?? ''); ?>" class="regular-text"
                                placeholder="fa-solid fa-arrow-up-right-from-square">
                            <p class="description">
                                Examples:
                                FA7/FA6: <code>fa-solid fa-arrow-up-right-from-square</code>
                                FA5: <code>fas fa-external-link-alt</code>
                                FA4: <code>fa fa-external-link</code>
                            </p>
                        </td>
                    </tr>

                    <tr>
                        <th scope="row">
                            <label for="button_icon_position">Icon Position</label>
                        </th>
                        <td>
                            <select id="button_icon_position" name="<?php echo esc_attr(self::OPTION_NAME); ?>[button_icon_position]">
                                <option value="before" <?php selected($settings['button_icon_position'] ?? 'after', 'before'); ?>>Before text</option>
                                <option value="after" <?php selected($settings['button_icon_position'] ?? 'after', 'after'); ?>>After text</option>
                            </select>
                        </td>
                    </tr>
                    <tr>
    <th scope="row">
        <label for="button_icon_gap">Icon Gap</label>
    </th>
    <td>
        <input
            type="text"
            id="button_icon_gap"
            name="<?php echo esc_attr(self::OPTION_NAME); ?>[button_icon_gap]"
            value="<?php echo esc_attr($settings['button_icon_gap'] ?? '0.5em'); ?>"
            class="small-text"
        >
        <p class="description">
            Examples: <code>0.5em</code>, <code>8px</code>, <code>1rem</code>
        </p>
    </td>
</tr>
                    <tr>
                        <th scope="row">Open link</th>
                        <td>
                            <label><input type="checkbox" name="<?php echo esc_attr(self::OPTION_NAME); ?>[button_open_new_tab]"
                                    value="1" <?php checked($settings['button_open_new_tab'], '1'); ?>> Open Stripe in a new
                                tab</label>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Default button CSS</th>
                        <td>
                            <label><input type="checkbox"
                                    name="<?php echo esc_attr(self::OPTION_NAME); ?>[enable_default_button_css]" value="1" <?php checked($settings['enable_default_button_css'], '1'); ?>> Add basic button styling for
                                <code>.csd-donation-button</code></label>
                        </td>
                    </tr>
                </table>

                <h2>Webhook settings</h2>
                <table class="form-table" role="presentation">
                    <tr>
                        <th scope="row"><label for="csd_stripe_webhook_secret">Stripe webhook signing secret</label></th>
                        <td>
                            <input id="csd_stripe_webhook_secret" class="regular-text" type="password" autocomplete="off"
                                name="<?php echo esc_attr(self::OPTION_NAME); ?>[stripe_webhook_secret]"
                                value="<?php echo esc_attr($settings['stripe_webhook_secret']); ?>" placeholder="whsec_...">
                            <p class="description">In Stripe, add the webhook URL below and listen for
                                <code>checkout.session.completed</code>.</p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row">Webhook URL</th>
                        <td>
                            <code><?php echo esc_html($webhook_url); ?></code>
                            <p class="description">Copy this into Stripe Dashboard → Developers → Webhooks.</p>
                        </td>
                    </tr>
                </table>

                <h2>Gift Aid detection</h2>
                <table class="form-table" role="presentation">
                    <tr>
                        <th scope="row"><label for="csd_gift_aid_field_key">Gift Aid custom field key</label></th>
                        <td>
                            <input id="csd_gift_aid_field_key" class="regular-text" type="text"
                                name="<?php echo esc_attr(self::OPTION_NAME); ?>[gift_aid_field_key]"
                                value="<?php echo esc_attr($settings['gift_aid_field_key']); ?>">
                            <p class="description">Set this to the Stripe custom field key used for Gift Aid, for example
                                <code>gift_aid</code>. Stripe custom field keys are usually lowercase with underscores.</p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><label for="csd_gift_aid_yes_values">Values treated as “yes”</label></th>
                        <td>
                            <input id="csd_gift_aid_yes_values" class="regular-text" type="text"
                                name="<?php echo esc_attr(self::OPTION_NAME); ?>[gift_aid_yes_values]"
                                value="<?php echo esc_attr($settings['gift_aid_yes_values']); ?>">
                            <p class="description">Comma-separated values. Useful if the Stripe field is a dropdown with labels
                                such as <code>Yes</code> and <code>No</code>.</p>
                        </td>
                    </tr>
                </table>

                <h2>Email notification</h2>
                <table class="form-table" role="presentation">
                    <tr>
                        <th scope="row"><label for="csd_admin_email">Send notifications to</label></th>
                        <td>
                            <input id="csd_admin_email" class="regular-text" type="text"
                                name="<?php echo esc_attr(self::OPTION_NAME); ?>[admin_email]"
                                value="<?php echo esc_attr($settings['admin_email']); ?>">
                            <p class="description">One or more email addresses, separated by commas.</p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><label for="csd_email_subject">Email subject</label></th>
                        <td><input id="csd_email_subject" class="regular-text" type="text"
                                name="<?php echo esc_attr(self::OPTION_NAME); ?>[email_subject]"
                                value="<?php echo esc_attr($settings['email_subject']); ?>"></td>
                    </tr>
                    <tr>
                        <th scope="row">Email body</th>
                        <td>
                            <?php
                            wp_editor(
                                $settings['email_template'],
                                'csd_email_template_editor',
                                [
                                    'textarea_name' => self::OPTION_NAME . '[email_template]',
                                    'textarea_rows' => 14,
                                    'media_buttons' => false,
                                    'teeny' => false,
                                    'tinymce' => true,
                                    'quicktags' => true,
                                ]
                            );
                            ?>
                            <p class="description">HTML is supported. Available placeholders:</p>
                            <p><code>{amount}</code> <code>{currency}</code> <code>{donor_name}</code>
                                <code>{donor_email}</code> <code>{gift_aid}</code> <code>{checkout_session_id}</code>
                                <code>{payment_intent_id}</code> <code>{payment_link}</code> <code>{custom_fields_table}</code>
                                <code>{custom_fields_text}</code></p>
                        </td>
                    </tr>
                </table>

                <?php submit_button('Save settings'); ?>
            </form>

            <hr>
            <h2>Shortcode</h2>
            <p>Add this shortcode wherever you want the donation button:</p>
            <p><code>[dldns_donation_button]</code></p>
            <p>Optional overrides:</p>
            <p><code>[dldns_donation_button text="Donate now" class="button my-custom-class"]</code></p>
        </div>
        <?php
    }

    public function maybe_show_admin_notices(): void
    {
        if (!current_user_can('manage_options')) {
            return;
        }

        $screen = function_exists('get_current_screen') ? get_current_screen() : null;
        if (!$screen || $screen->id !== 'toplevel_page_' . self::MENU_SLUG) {
            return;
        }

        $settings = $this->get_settings();

        if (empty($settings['payment_link_url'])) {
            echo '<div class="notice notice-warning"><p><strong>Custom Stripe Donations:</strong> Add your Stripe Payment Link URL before using the donation button.</p></div>';
        }

        if (empty($settings['stripe_webhook_secret'])) {
            echo '<div class="notice notice-warning"><p><strong>Custom Stripe Donations:</strong> Add your Stripe webhook signing secret so successful donations can trigger admin emails.</p></div>';
        }
    }

    public function register_frontend_styles(): void
    {
        $settings = $this->get_settings();
        if (($settings['enable_default_button_css'] ?? '1') !== '1') {
            return;
        }

        wp_register_style('csd-donation-button', false, [], self::VERSION);
        wp_enqueue_style('csd-donation-button');
        wp_add_inline_style('csd-donation-button', '
            .csd-donation-button {
                display: inline-block;
                padding: 0.85em 1.25em;
                border-radius: 6px;
                text-decoration: none;
                font-weight: 700;
                line-height: 1.2;
                cursor: pointer;
            }
        ');
    }

    public function register_routes(): void
    {
        register_rest_route(self::REST_NAMESPACE, '/stripe-webhook', [
            'methods' => 'POST',
            'callback' => [$this, 'handle_stripe_webhook'],
            'permission_callback' => '__return_true',
        ]);
    }

    public function render_donation_button($atts = []): string
    {
        $settings = $this->get_settings();
        $atts = shortcode_atts([
            'text' => $settings['button_text'] ?? 'Donate securely',
            'class' => $settings['button_css_classes'] ?? 'csd-donation-button',
            'url' => $settings['payment_link_url'] ?? '',
            'target' => (($settings['button_open_new_tab'] ?? '0') === '1') ? '_blank' : '_self',
        ], $atts, 'dldns_donation_button');

        $url = esc_url($atts['url']);
        if (empty($url)) {
            if (current_user_can('manage_options')) {
                return '<p><strong>Stripe Donations:</strong> Please add a Payment Link URL in the Stripe Donations settings.</p>';
            }
            return '';
        }

        $classes = dldns_sanitize_html_class_list($atts['class']);
        $target = ($atts['target'] === '_blank') ? '_blank' : '_self';
        $rel = ($target === '_blank') ? ' rel="noopener noreferrer"' : '';

        $icon_html = '';

        if (!empty($settings['button_icon_enabled']) && !empty($settings['button_icon_class'])) {
$gap = esc_attr($settings['button_icon_gap'] ?? '0.5em');

$icon_html = sprintf(
    '<span class="dlcsd-button-icon %s" style="margin-left:%s;" aria-hidden="true"></span>',
    esc_attr(
        dldns_sanitize_html_class_list(
            $settings['button_icon_class']
        )
    ),
    $gap
);
        }

        $button_inner = sprintf(
            '<span class="dlcsd-button-text">%s</span>',
            esc_html($atts['text'])
        );

        if ('before' === ($settings['button_icon_position'] ?? 'after') && $icon_html) {
            $button_inner = $icon_html . $button_inner;
        } elseif ($icon_html) {
            $button_inner .= $icon_html;
        }

        return sprintf(
            '<a href="%s" class="%s" target="%s"%s>%s</a>',
            esc_url($url),
            esc_attr($classes),
            esc_attr($target),
            $rel,
            $button_inner
        );
    }

    public function handle_stripe_webhook(WP_REST_Request $request)
    {
        $settings = $this->get_settings();

        if (empty($settings['stripe_webhook_secret'])) {
            return $this->error_response('Webhook secret is not configured.', 500);
        }

        $payload = $request->get_body();
        $sig_header = $request->get_header('stripe_signature');
        if (!$sig_header && isset($_SERVER['HTTP_STRIPE_SIGNATURE'])) {
            $sig_header = sanitize_text_field(wp_unslash($_SERVER['HTTP_STRIPE_SIGNATURE']));
        }

        if (!$this->verify_stripe_signature($payload, (string) $sig_header, $settings['stripe_webhook_secret'])) {
            return $this->error_response('Invalid Stripe signature.', 400);
        }

        $event = json_decode($payload, true);
        if (!is_array($event)) {
            return $this->error_response('Invalid JSON payload.', 400);
        }

        $event_id = (string) ($event['id'] ?? '');
        if ($event_id && $this->event_already_processed($event_id)) {
            return new WP_REST_Response(['received' => true, 'duplicate' => true], 200);
        }

        if (($event['type'] ?? '') === 'checkout.session.completed') {
            $session = $event['data']['object'] ?? [];
            if (is_array($session)) {
                $this->send_admin_donation_email($session);
            }
        }

        if ($event_id) {
            $this->mark_event_processed($event_id);
        }

        return new WP_REST_Response(['received' => true], 200);
    }

    private function send_admin_donation_email(array $session): void
    {
        $settings = $this->get_settings();
        $recipients = $this->parse_recipient_emails($settings['admin_email'] ?? get_option('admin_email'));
        if (empty($recipients)) {
            $recipients = [get_option('admin_email')];
        }

        $custom_fields = $this->extract_custom_fields($session['custom_fields'] ?? []);
        $gift_aid = $this->detect_gift_aid($custom_fields, $settings);

        $amount = isset($session['amount_total']) ? number_format(((int) $session['amount_total']) / 100, 2) : 'Unknown';
        $currency = isset($session['currency']) ? strtoupper((string) $session['currency']) : '';
        $customer_details = $session['customer_details'] ?? [];
        $donor_email = $customer_details['email'] ?? ($session['customer_email'] ?? 'Not provided');
        $donor_name = $customer_details['name'] ?? 'Not provided';
        $payment_link = $session['payment_link'] ?? 'Not available';
        $payment_intent = $session['payment_intent'] ?? 'Not available';
        $checkout_session_id = $session['id'] ?? 'Not available';

        $placeholders = [
            '{amount}' => esc_html($amount),
            '{currency}' => esc_html($currency),
            '{donor_name}' => esc_html($donor_name),
            '{donor_email}' => esc_html($donor_email),
            '{gift_aid}' => esc_html($gift_aid),
            '{checkout_session_id}' => esc_html($checkout_session_id),
            '{payment_intent_id}' => esc_html($payment_intent),
            '{payment_link}' => esc_html($payment_link),
            '{custom_fields_table}' => $this->build_custom_fields_table($custom_fields),
            '{custom_fields_text}' => esc_html($this->build_custom_fields_text($custom_fields)),
        ];

        $subject = $this->replace_placeholders($settings['email_subject'] ?? 'New donation received', $placeholders, true);
        $message = $this->replace_placeholders($settings['email_template'] ?? self::default_email_template(), $placeholders, false);

        $headers = ['Content-Type: text/html; charset=UTF-8'];
        if (is_email($donor_email)) {
            $safe_name = sanitize_text_field((string) $donor_name);
            $headers[] = 'Reply-To: ' . $safe_name . ' <' . sanitize_email($donor_email) . '>';
        }

        wp_mail($recipients, $subject, $message, $headers);
    }

    private function extract_custom_fields($fields): array
    {
        $output = [];
        if (!is_array($fields)) {
            return $output;
        }

        foreach ($fields as $field) {
            if (!is_array($field)) {
                continue;
            }

            $key = (string) ($field['key'] ?? '');
            $label = $field['label']['custom'] ?? ($field['label']['type'] ?? $key);
            $type = (string) ($field['type'] ?? '');
            $value = '';

            if ($type === 'dropdown') {
                $value = $field['dropdown']['value'] ?? '';
            } elseif ($type === 'text') {
                $value = $field['text']['value'] ?? '';
            } elseif ($type === 'numeric') {
                $value = $field['numeric']['value'] ?? '';
            }

            if ($key !== '') {
                $output[$key] = [
                    'label' => (string) $label,
                    'value' => (string) $value,
                    'type' => $type,
                ];
            }
        }

        return $output;
    }

    private function detect_gift_aid(array $custom_fields, array $settings): string
    {
        $gift_key = sanitize_key($settings['gift_aid_field_key'] ?? 'gift_aid');
        $raw_value = '';

        if ($gift_key && isset($custom_fields[$gift_key])) {
            $raw_value = $custom_fields[$gift_key]['value'] ?? '';
        } else {
            foreach ($custom_fields as $key => $field) {
                $haystack = strtolower($key . ' ' . ($field['label'] ?? ''));
                if (strpos($haystack, 'gift') !== false && strpos($haystack, 'aid') !== false) {
                    $raw_value = $field['value'] ?? '';
                    break;
                }
            }
        }

        if ($raw_value === '') {
            return 'Not provided';
        }

        $normalised = strtolower(trim((string) $raw_value));
        $yes_values = array_filter(array_map('trim', explode(',', strtolower((string) ($settings['gift_aid_yes_values'] ?? 'yes,y,true,1')))));

        return in_array($normalised, $yes_values, true) ? 'Yes' : 'No';
    }

    private function build_custom_fields_table(array $custom_fields): string
    {
        if (empty($custom_fields)) {
            return '<p>No custom fields were provided.</p>';
        }

        $html = '<table cellpadding="6" cellspacing="0" border="1" style="border-collapse:collapse;">';
        $html .= '<thead><tr><th align="left">Field</th><th align="left">Value</th></tr></thead><tbody>';
        foreach ($custom_fields as $key => $field) {
            $label = $field['label'] ?: $key;
            $html .= '<tr><td><strong>' . esc_html($label) . '</strong><br><small>' . esc_html($key) . '</small></td><td>' . esc_html($field['value']) . '</td></tr>';
        }
        $html .= '</tbody></table>';

        return $html;
    }

    private function build_custom_fields_text(array $custom_fields): string
    {
        if (empty($custom_fields)) {
            return 'No custom fields were provided.';
        }

        $lines = [];
        foreach ($custom_fields as $key => $field) {
            $label = $field['label'] ?: $key;
            $lines[] = $label . ' (' . $key . '): ' . $field['value'];
        }
        return implode("\n", $lines);
    }

    private function replace_placeholders(string $content, array $placeholders, bool $plain_text = false): string
    {
        $replaced = strtr($content, $placeholders);
        return $plain_text ? wp_strip_all_tags($replaced) : wp_kses_post($replaced);
    }

    private function verify_stripe_signature(string $payload, string $signature_header, string $secret): bool
    {
        if ($payload === '' || $signature_header === '' || $secret === '') {
            return false;
        }

        $timestamp = null;
        $signatures = [];
        foreach (explode(',', $signature_header) as $part) {
            $pieces = explode('=', trim($part), 2);
            if (count($pieces) !== 2) {
                continue;
            }
            if ($pieces[0] === 't') {
                $timestamp = $pieces[1];
            } elseif ($pieces[0] === 'v1') {
                $signatures[] = $pieces[1];
            }
        }

        if (!$timestamp || empty($signatures)) {
            return false;
        }

        // Reject very old webhook attempts. Stripe's default tolerance is 300 seconds.
        if (abs(time() - (int) $timestamp) > 300) {
            return false;
        }

        $signed_payload = $timestamp . '.' . $payload;
        $expected = hash_hmac('sha256', $signed_payload, $secret);

        foreach ($signatures as $signature) {
            if (hash_equals($expected, $signature)) {
                return true;
            }
        }

        return false;
    }

    private function parse_recipient_emails(string $emails): array
    {
        $parts = preg_split('/[,;]+/', $emails);
        $valid = [];
        foreach ($parts as $email) {
            $email = sanitize_email(trim($email));
            if (is_email($email)) {
                $valid[] = $email;
            }
        }
        return array_values(array_unique($valid));
    }

    private function event_already_processed(string $event_id): bool
    {
        $events = get_option('dldns_processed_stripe_events', []);
        return is_array($events) && in_array($event_id, $events, true);
    }

    private function mark_event_processed(string $event_id): void
    {
        $events = get_option('dldns_processed_stripe_events', []);
        if (!is_array($events)) {
            $events = [];
        }
        $events[] = $event_id;
        $events = array_values(array_unique($events));
        if (count($events) > 100) {
            $events = array_slice($events, -100);
        }
        update_option('dldns_processed_stripe_events', $events, false);
    }

    private function get_settings(): array
    {
        $options = get_option(self::OPTION_NAME, []);
        if (!is_array($options)) {
            $options = [];
        }
        return array_merge(self::default_settings(), $options);
    }

    private function error_response(string $message, int $status): WP_REST_Response
    {
        return new WP_REST_Response(['error' => $message], $status);
    }

    public function dlcsd_enqueue_fontawesome(): void
{
    $options = $this->get_settings();
    $version = $options['fontawesome_version'] ?? 'none';

    if ('none' === $version || 'already_loaded' === $version) {
        return;
    }

    $fontawesome_files = [
        '4' => [
            'path'    => 'assets/fontawesome/fa4/css/font-awesome.min.css',
            'version' => '4.7.0',
        ],
        '5' => [
            'path'    => 'assets/fontawesome/fa5/css/all.min.css',
            'version' => '5.15.4',
        ],
        '6' => [
            'path'    => 'assets/fontawesome/fa6/css/all.min.css',
            'version' => '6.7.2',
        ],
        '7' => [
            'path'    => 'assets/fontawesome/fa7/css/all.min.css',
            'version' => '7.2.0',
        ],
    ];

    if (empty($fontawesome_files[$version])) {
        return;
    }

    wp_enqueue_style(
        'dlcsd-fontawesome-' . $version,
        plugin_dir_url(__FILE__) . $fontawesome_files[$version]['path'],
        [],
        $fontawesome_files[$version]['version']
    );
    }
}

if (!function_exists('dldns_sanitize_html_class_list')) {
    function dldns_sanitize_html_class_list($classes): string
    {
        $classes = is_string($classes) ? $classes : '';
        $parts = preg_split('/\s+/', $classes);
        $clean = [];
        foreach ($parts as $part) {
            $part = sanitize_html_class($part);
            if ($part !== '') {
                $clean[] = $part;
            }
        }
        return implode(' ', array_unique($clean));
    }
}

register_activation_hook(__FILE__, ['DLDNS_Donation_Notifications', 'activate']);
new DLDNS_Donation_Notifications();
