<?php
/**
 * Split bill for table orders: equal shares or fixed amount per person.
 *
 * @package Maca_Menu_List
 */

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

/**
 * Whether bill splitting is enabled in settings.
 *
 * @return bool
 */
function maca_menulist_payments_allow_bill_split() {
    return get_option('maca_menulist_payments_allow_bill_split', '1') === '1'
        && maca_menulist_payments_guest_flow_enabled();
}

/**
 * Valid bill split modes.
 *
 * @return array<int, string>
 */
function maca_menulist_bill_split_modes() {
    return array('equal', 'fixed');
}

/**
 * Min/max people when splitting equally.
 *
 * @return array{min: int, max: int}
 */
function maca_menulist_bill_split_people_limits() {
    return array(
        'min' => 2,
        'max' => 10,
    );
}

/**
 * Bill split mode on an order: equal, fixed, or empty.
 *
 * @param object $order Order row.
 * @return string
 */
function maca_menulist_order_bill_split_mode($order) {
    if (!$order) {
        return '';
    }

    $mode = isset($order->bill_split_mode) ? sanitize_key((string) $order->bill_split_mode) : '';

    if (in_array($mode, maca_menulist_bill_split_modes(), true)) {
        return $mode;
    }

    if (maca_menulist_order_bill_split_count($order) >= maca_menulist_bill_split_people_limits()['min']) {
        return 'equal';
    }

    return '';
}

/**
 * Number of people (equal split) on an order.
 *
 * @param object $order Order row.
 * @return int
 */
function maca_menulist_order_bill_split_count($order) {
    if (!$order || !isset($order->bill_split_count)) {
        return 0;
    }

    return max(0, (int) $order->bill_split_count);
}

/**
 * Fixed amount per person (fixed split mode).
 *
 * @param object $order Order row.
 * @return float
 */
function maca_menulist_order_bill_split_fixed_amount($order) {
    if (!$order || !isset($order->bill_split_fixed_amount)) {
        return 0.0;
    }

    return round(max(0, (float) $order->bill_split_fixed_amount), 2);
}

/**
 * Number of recorded split payments.
 *
 * @param object $order Order row.
 * @return int
 */
function maca_menulist_order_bill_split_paid_shares($order) {
    if (!$order || !isset($order->bill_split_paid_shares)) {
        return 0;
    }

    return max(0, (int) $order->bill_split_paid_shares);
}

/**
 * Whether the order uses bill splitting.
 *
 * @param object $order Order row.
 * @return bool
 */
function maca_menulist_order_bill_split_is_active($order) {
    $mode = maca_menulist_order_bill_split_mode($order);

    if ($mode === 'equal') {
        return maca_menulist_order_bill_split_count($order) >= maca_menulist_bill_split_people_limits()['min'];
    }

    if ($mode === 'fixed') {
        return maca_menulist_order_bill_split_fixed_amount($order) > 0;
    }

    return false;
}

/**
 * Whether split settings are locked (a payment already recorded).
 *
 * @param object $order Order row.
 * @return bool
 */
function maca_menulist_order_bill_split_is_locked($order) {
    if (!maca_menulist_order_bill_split_is_active($order)) {
        return false;
    }

    if (maca_menulist_order_bill_split_paid_shares($order) > 0) {
        return true;
    }

    $paid_amount = isset($order->payment_amount) ? (float) $order->payment_amount : 0.0;

    return $paid_amount > 0.01;
}

/**
 * Whether guests may change split settings.
 *
 * @param object $order Order row.
 * @return bool
 */
function maca_menulist_order_bill_split_can_change($order) {
    if (!maca_menulist_payments_allow_bill_split()) {
        return false;
    }

    if (!maca_menulist_order_bill_split_is_active($order)) {
        return maca_menulist_order_payment_timing_allows_action($order)
            && !maca_menulist_order_is_payment_complete($order)
            && sanitize_key((string) ($order->payment_status ?? 'none')) !== 'pending';
    }

    return !maca_menulist_order_bill_split_is_locked($order)
        && !maca_menulist_order_is_payment_complete($order);
}

/**
 * Split a monetary total into N cent-accurate shares (equal mode).
 *
 * @param float $total Total amount.
 * @param int   $count Number of shares (>= 2).
 * @return array<int, float>
 */
function maca_menulist_split_amount_into_shares($total, $count) {
    $count = max(2, (int) $count);
    $total_cents = max(0, (int) round((float) $total * 100));
    $base_cents = intdiv($total_cents, $count);
    $remainder = $total_cents - ($base_cents * $count);
    $shares = array();

    for ($i = 0; $i < $count; $i++) {
        $cents = $base_cents + ($i < $remainder ? 1 : 0);
        $shares[] = round($cents / 100, 2);
    }

    return $shares;
}

/**
 * Per-share amounts for equal split (full order total).
 *
 * @param object $order Order with lines.
 * @return array<int, float>
 */
function maca_menulist_order_bill_split_share_amounts($order) {
    if (maca_menulist_order_bill_split_mode($order) !== 'equal') {
        return array();
    }

    if (!maca_menulist_order_bill_split_is_active($order)) {
        return array();
    }

    $group = maca_menulist_order_bill_split_group_orders($order);
    if ($group && count($group) >= 2) {
        return maca_menulist_combined_bill_split_share_amounts($group);
    }

    return maca_menulist_split_amount_into_shares(
        maca_menulist_order_payment_total($order),
        maca_menulist_order_bill_split_count($order)
    );
}

/**
 * Combined split-bill group for an order, when applicable.
 *
 * @param object $order Order row.
 * @return array<int, object>|null
 */
function maca_menulist_order_bill_split_group_orders($order) {
    if (!$order || !maca_menulist_order_bill_split_is_active($order)) {
        return null;
    }

    if (!function_exists('maca_menulist_discover_combined_payment_group_orders')) {
        return null;
    }

    return maca_menulist_discover_combined_payment_group_orders($order);
}

/**
 * Total amount a split bill should cover (combined total when relevant).
 *
 * @param object $order Order row with lines.
 * @return float
 */
function maca_menulist_order_bill_split_target_total($order) {
    $group = maca_menulist_order_bill_split_group_orders($order);

    if ($group && count($group) >= 2) {
        return maca_menulist_combined_bill_split_payment_total($group);
    }

    return maca_menulist_order_payment_total($order);
}

/**
 * Sum already recorded for completed split shares (not pending checkout holds).
 *
 * @param object $order Order row with lines for equal split totals.
 * @return float
 */
function maca_menulist_order_bill_split_recorded_paid_amount($order) {
    if (!maca_menulist_order_bill_split_is_active($order)) {
        return round(max(0, (float) ($order->payment_amount ?? 0)), 2);
    }

    $paid_shares = maca_menulist_order_bill_split_paid_shares($order);
    if ($paid_shares <= 0) {
        return 0.0;
    }

    if (maca_menulist_order_bill_split_mode($order) === 'equal') {
        $shares = maca_menulist_order_bill_split_share_amounts($order);
        $sum    = 0.0;

        for ($i = 0; $i < $paid_shares && $i < count($shares); $i++) {
            $sum += (float) $shares[ $i ];
        }

        return round($sum, 2);
    }

    return round(max(0, (float) ($order->payment_amount ?? 0)), 2);
}

/**
 * Remaining amount to pay on a split order.
 *
 * @param object $order Order row.
 * @return float
 */
function maca_menulist_order_bill_split_remaining($order) {
    $group = maca_menulist_order_bill_split_group_orders($order);
    if ($group && count($group) >= 2) {
        return maca_menulist_combined_bill_split_remaining($group);
    }

    $total = round(maca_menulist_order_bill_split_target_total($order), 2);
    $paid = round(max(0, (float) ($order->payment_amount ?? 0)), 2);

    if (maca_menulist_order_bill_split_is_complete($order)) {
        return 0.0;
    }

    return round(max(0, $total - $paid), 2);
}

/**
 * Amount for the next split payment.
 *
 * @param object $order Order row.
 * @return float
 */
function maca_menulist_order_bill_split_next_amount($order) {
    $remaining = maca_menulist_order_bill_split_remaining($order);

    if ($remaining <= 0) {
        return 0.0;
    }

    $mode = maca_menulist_order_bill_split_mode($order);

    if ($mode === 'fixed') {
        $fixed = maca_menulist_order_bill_split_fixed_amount($order);

        return round(min($fixed, $remaining), 2);
    }

    $shares = maca_menulist_order_bill_split_share_amounts($order);

    if (empty($shares)) {
        return $remaining;
    }

    $index = maca_menulist_order_bill_split_paid_shares($order);

    if ($index >= count($shares)) {
        return $remaining;
    }

    return round(min((float) $shares[ $index ], $remaining), 2);
}

/**
 * Whether all split payments are complete.
 *
 * @param object     $order       Order row.
 * @param float|null $paid_amount Optional accumulated paid amount.
 * @param int|null   $paid_shares Optional payment count.
 * @return bool
 */
function maca_menulist_order_bill_split_is_complete($order, $paid_amount = null, $paid_shares = null) {
    if (!maca_menulist_order_bill_split_is_active($order)) {
        return false;
    }

    $group = maca_menulist_order_bill_split_group_orders($order);
    if ($group && count($group) >= 2) {
        return maca_menulist_combined_bill_split_is_complete($group);
    }

    $total = round(maca_menulist_order_bill_split_target_total($order), 2);
    $paid = $paid_amount !== null
        ? round(max(0, (float) $paid_amount), 2)
        : round(max(0, (float) ($order->payment_amount ?? 0)), 2);

    if ($paid + 0.009 >= $total) {
        return true;
    }

    if (maca_menulist_order_bill_split_mode($order) !== 'equal') {
        return false;
    }

    $count = maca_menulist_order_bill_split_count($order);
    $shares_paid = $paid_shares !== null
        ? max(0, (int) $paid_shares)
        : maca_menulist_order_bill_split_paid_shares($order);

    return $shares_paid >= $count;
}

/**
 * Whether the order is fully paid (single or split payment).
 *
 * payment_status alone is not enough: a partial split payment may still be in progress.
 *
 * @param object $order Order row (with lines when total is needed).
 * @return bool
 */
function maca_menulist_order_is_payment_complete($order) {
    if (!$order) {
        return false;
    }

    if (maca_menulist_order_bill_split_is_active($order)) {
        return maca_menulist_order_bill_split_is_complete($order);
    }

    $total = round(maca_menulist_order_payment_total($order), 2);
    $paid = round(max(0, (float) ($order->payment_amount ?? 0)), 2);

    if ($total <= 0) {
        return sanitize_key((string) ($order->payment_status ?? 'none')) === 'paid';
    }

    return $paid + 0.009 >= $total;
}

/**
 * Configure or clear bill split on an order.
 *
 * @param int                  $order_id Order ID.
 * @param array<string, mixed> $args     mode (equal|fixed|''), people, fixed_amount.
 * @return true|WP_Error
 */
function maca_menulist_db_set_order_bill_split($order_id, $args = array(), $total_override = null) {
    global $wpdb;

    if (!maca_menulist_payments_allow_bill_split()) {
        return new WP_Error('disabled', __('Bill splitting is not enabled.', 'maca-restu-pro'));
    }

    maca_menulist_db_ensure_table_order_tables();

    $order_id = absint($order_id);
    $order = maca_menulist_db_get_table_order($order_id);

    if (!$order) {
        return new WP_Error('not_found', __('Order not found.', 'maca-restu-pro'));
    }

    if (maca_menulist_order_is_payment_complete($order)) {
        return new WP_Error('paid', __('This order is already paid.', 'maca-restu-pro'));
    }

    if (sanitize_key((string) ($order->payment_status ?? 'none')) === 'pending') {
        return new WP_Error('pending', __('Wait for the current online payment to finish or cancel it.', 'maca-restu-pro'));
    }

    $mode = isset($args['mode']) ? sanitize_key((string) $args['mode']) : '';
    $people = isset($args['people']) ? absint($args['people']) : 0;
    $fixed_amount = isset($args['fixed_amount']) ? round((float) $args['fixed_amount'], 2) : 0.0;

    if ($mode === '' && $people > 0) {
        $mode = 'equal';
    }

    if ($mode === '' && $people === 0 && $fixed_amount <= 0) {
        $mode = '';
    }

    if ($mode !== '' && maca_menulist_order_bill_split_is_locked($order)) {
        return new WP_Error('locked', __('Bill split cannot be changed after a payment has been made.', 'maca-restu-pro'));
    }

    $limits = maca_menulist_bill_split_people_limits();
    $order_with_lines = maca_menulist_db_attach_table_order_lines(array($order))[0];
    $order_total = $total_override !== null
        ? round(max(0, (float) $total_override), 2)
        : round(maca_menulist_order_payment_total($order_with_lines), 2);

    $data = array(
        'bill_split_mode' => '',
        'bill_split_count' => 0,
        'bill_split_fixed_amount' => 0,
        'bill_split_paid_shares' => 0,
    );

    if ($mode === 'equal') {
        if ($people < $limits['min'] || $people > $limits['max']) {
            return new WP_Error(
                'invalid_count',
                sprintf(
                    /* translators: 1: minimum people, 2: maximum people */
                    __('Choose between %1$d and %2$d people.', 'maca-restu-pro'),
                    $limits['min'],
                    $limits['max']
                )
            );
        }

        $data['bill_split_mode'] = 'equal';
        $data['bill_split_count'] = $people;
    } elseif ($mode === 'fixed') {
        if ($fixed_amount < 1) {
            return new WP_Error('invalid_amount', __('Enter an amount of at least 1.', 'maca-restu-pro'));
        }

        if ($order_total > 0 && $fixed_amount > $order_total) {
            return new WP_Error(
                'invalid_amount',
                sprintf(
                    /* translators: %s: order total */
                    __('Amount per person cannot exceed the order total (%s).', 'maca-restu-pro'),
                    maca_menulist_format_price($order_total)
                )
            );
        }

        $data['bill_split_mode'] = 'fixed';
        $data['bill_split_fixed_amount'] = $fixed_amount;
    }

    $orders_table = maca_menulist_db_table_orders_table();
    $updated = $wpdb->update(
        $orders_table,
        $data,
        array('id' => $order_id),
        array('%s', '%d', '%f', '%d'),
        array('%d')
    );

    if ($updated === false) {
        return new WP_Error('db', __('Could not save bill split.', 'maca-restu-pro'));
    }

    return true;
}

/**
 * Set equal bill split count (legacy helper).
 *
 * @param int $order_id Order ID.
 * @param int $count    Number of people (2–10), or 0 to clear.
 * @return true|WP_Error
 */
function maca_menulist_db_set_order_bill_split_count($order_id, $count) {
    $count = absint($count);

    if ($count === 0) {
        return maca_menulist_db_set_order_bill_split($order_id, array('mode' => ''));
    }

    return maca_menulist_db_set_order_bill_split(
        $order_id,
        array(
            'mode' => 'equal',
            'people' => $count,
        )
    );
}

/**
 * Record a payment toward an order (full amount or one split payment).
 *
 * @param int                  $order_id Order ID.
 * @param array<string, mixed> $args     payment_provider, payment_method, payment_external_id, payment_amount, payment_currency.
 * @return bool
 */
function maca_menulist_db_apply_order_payment($order_id, $args = array()) {
    global $wpdb;

    maca_menulist_db_ensure_table_order_tables();

    $order_id = absint($order_id);
    $order = maca_menulist_db_get_table_order($order_id);

    if (!$order) {
        return false;
    }

    $order_with_lines = maca_menulist_db_attach_table_order_lines(array($order))[0];
    $amount = isset($args['payment_amount']) ? round(max(0, (float) $args['payment_amount']), 2) : 0.0;

    if ($amount <= 0 && maca_menulist_order_bill_split_is_active($order_with_lines)) {
        $amount = maca_menulist_order_bill_split_next_amount($order_with_lines);
    }

    if ($amount <= 0) {
        $amount = maca_menulist_order_payment_total($order_with_lines);
    }

    if (!maca_menulist_order_bill_split_is_active($order_with_lines)) {
        $existing_paid = round(max(0, (float) ($order->payment_amount ?? 0)), 2);
        $order_total = round(maca_menulist_order_payment_total($order_with_lines), 2);

        if ($amount <= 0) {
            $amount = $order_total;
        }

        $accumulated = $existing_paid > 0 && $amount < $order_total
            ? round($existing_paid + $amount, 2)
            : round($amount, 2);

        if ($order_total > 0 && $accumulated + 0.009 < $order_total) {
            $args['payment_amount'] = $accumulated;

            return maca_menulist_db_apply_order_payment_partial($order_id, $args);
        }

        $args['payment_amount'] = $accumulated > 0 ? $accumulated : $order_total;

        return maca_menulist_db_mark_table_order_paid($order_id, $args);
    }

    $stored_status = sanitize_key((string) ($order->payment_status ?? 'none'));
    $existing_paid = round(max(0, (float) ($order->payment_amount ?? 0)), 2);

    // Pending checkout stores the next share in payment_amount — do not add it twice when Stripe completes.
    if ($stored_status === 'pending') {
        $existing_paid = maca_menulist_order_bill_split_recorded_paid_amount($order_with_lines);
    }

    $accumulated = round($existing_paid + $amount, 2);
    $paid_shares = maca_menulist_order_bill_split_paid_shares($order) + 1;
    $complete = maca_menulist_order_bill_split_is_complete($order_with_lines, $accumulated, $paid_shares);

    if ($complete) {
        $group = maca_menulist_order_bill_split_group_orders($order_with_lines);
        if ($group && count($group) >= 2) {
            $args['payment_amount'] = maca_menulist_combined_bill_split_payment_total($group);
        } else {
            $args['payment_amount'] = maca_menulist_order_bill_split_target_total($order_with_lines);
        }

        $partial = maca_menulist_db_apply_order_payment_partial($order_id, $args, $paid_shares);
        if (!$partial) {
            return false;
        }

        if ($group && count($group) >= 2) {
            maca_menulist_apply_combined_group_payment_complete($group, $args);

            return true;
        }

        return maca_menulist_db_mark_table_order_paid($order_id, $args);
    }

    $args['payment_amount'] = $accumulated;

    return maca_menulist_db_apply_order_payment_partial($order_id, $args, $paid_shares);
}

/**
 * Store a partial payment (split bill or accumulated amount).
 *
 * @param int                  $order_id    Order ID.
 * @param array<string, mixed> $args        provider, method, external_id, amount, currency.
 * @param int|null             $paid_shares Bill split share count when applicable.
 * @return bool
 */
function maca_menulist_db_apply_order_payment_partial($order_id, $args = array(), $paid_shares = null) {
    global $wpdb;

    maca_menulist_db_ensure_table_order_tables();

    $order_id = absint($order_id);
    $order = maca_menulist_db_get_table_order($order_id);

    if (!$order) {
        return false;
    }

    $provider = isset($args['payment_provider']) ? sanitize_key((string) $args['payment_provider']) : 'manual';
    $method = isset($args['payment_method']) ? sanitize_key((string) $args['payment_method']) : '';
    $external_id = isset($args['payment_external_id']) ? sanitize_text_field((string) $args['payment_external_id']) : '';
    $amount = isset($args['payment_amount']) ? round(max(0, (float) $args['payment_amount']), 2) : 0.0;
    $currency = isset($args['payment_currency']) ? sanitize_text_field((string) $args['payment_currency']) : maca_menulist_get_price_currency();

    if ($method === '' && $provider === 'stripe') {
        $method = 'online';
    }
    if ($method === '' && $provider === 'manual') {
        $method = 'manual';
    }

    $orders_table = maca_menulist_db_table_orders_table();

    $data = array(
        'payment_status' => 'partial',
        'payment_provider' => $provider !== '' ? $provider : null,
        'payment_external_id' => $external_id !== '' ? $external_id : null,
        'payment_amount' => $amount,
        'payment_currency' => $currency,
        'status_updated_at' => maca_menulist_wp_now_mysql(),
    );

    $formats = array('%s', '%s', '%s', '%f', '%s', '%s');

    if ($paid_shares !== null) {
        $data['bill_split_paid_shares'] = max(0, (int) $paid_shares);
        $formats[] = '%d';
    }

    if ($method !== '') {
        $data['payment_method'] = $method;
        $formats[] = '%s';
    }

    $updated = (bool) $wpdb->update(
        $orders_table,
        $data,
        array('id' => $order_id),
        $formats,
        array('%d')
    );

    if ($updated && function_exists('maca_menulist_db_record_order_payment_transaction')) {
        maca_menulist_db_record_order_payment_transaction($order_id, $args);
    }

    return $updated;
}

/**
 * Downgrade a split order wrongly marked paid before the combined bill is complete.
 *
 * @param object $order Order row.
 * @return object
 */
function maca_menulist_order_reconcile_mistaken_split_paid_status($order) {
    if (!$order || !maca_menulist_order_bill_split_is_active($order)) {
        return $order;
    }

    if (sanitize_key((string) ($order->payment_status ?? 'none')) !== 'paid') {
        return $order;
    }

    if (maca_menulist_order_bill_split_is_complete($order)) {
        return $order;
    }

    global $wpdb;

    maca_menulist_db_ensure_table_order_tables();
    $orders_table = maca_menulist_db_table_orders_table();

    $wpdb->update(
        $orders_table,
        array('payment_status' => 'partial'),
        array('id' => (int) $order->id),
        array('%s'),
        array('%d')
    );

    $order->payment_status = 'partial';

    return $order;
}

/**
 * Guest/staff bill-split payload for JSON.
 *
 * @param object $order Order with lines.
 * @return array<string, mixed>
 */
function maca_menulist_build_order_bill_split_state($order) {
    $limits = maca_menulist_bill_split_people_limits();
    $mode = maca_menulist_order_bill_split_mode($order);
    $count = maca_menulist_order_bill_split_count($order);
    $fixed_amount = maca_menulist_order_bill_split_fixed_amount($order);
    $active = maca_menulist_order_bill_split_is_active($order);
    $next_amount = $active ? maca_menulist_order_bill_split_next_amount($order) : 0.0;
    $remaining = $active ? maca_menulist_order_bill_split_remaining($order) : 0.0;
    $currency = isset($order->payment_currency) ? (string) $order->payment_currency : '';
    $order_total = round(maca_menulist_order_bill_split_target_total($order), 2);

    $format_amount = static function ($amount) use ($currency) {
        if ($currency !== '' && function_exists('maca_menulist_format_order_total')) {
            return maca_menulist_format_order_total($amount, $currency);
        }

        return maca_menulist_format_price($amount);
    };

    $enabled = maca_menulist_payments_allow_bill_split()
        && maca_menulist_order_payment_timing_allows_action($order)
        && !maca_menulist_order_is_payment_complete($order)
        && sanitize_key((string) ($order->payment_status ?? 'none')) !== 'pending'
        && maca_menulist_table_order_has_checkout_total($order);

    $estimated_payments = 0;
    if ($mode === 'fixed' && $fixed_amount > 0 && $remaining > 0) {
        $estimated_payments = (int) ceil($remaining / $fixed_amount);
    }

    return array(
        'feature_enabled' => maca_menulist_payments_allow_bill_split(),
        'enabled' => $enabled,
        'active' => $active,
        'mode' => $mode,
        'count' => $count,
        'fixed_amount' => $fixed_amount,
        'fixed_amount_label' => $fixed_amount > 0 ? $format_amount($fixed_amount) : '',
        'paid_shares' => maca_menulist_order_bill_split_paid_shares($order),
        'can_change' => maca_menulist_order_bill_split_can_change($order),
        'min_people' => $limits['min'],
        'max_people' => $limits['max'],
        'order_total' => $order_total,
        'order_total_label' => $order_total > 0 ? $format_amount($order_total) : '',
        'remaining' => $remaining,
        'remaining_label' => $remaining > 0 ? $format_amount($remaining) : '',
        'estimated_payments' => $estimated_payments,
        'next_share_amount' => $next_amount,
        'next_share_label' => $next_amount > 0 ? $format_amount($next_amount) : '',
        'complete' => maca_menulist_order_is_payment_complete($order),
    );
}
