package com.unibuy.smartdevice.ui.dialog;

import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.InputType;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.TextView;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.unibuy.smartdevice.DialogAbstract;
import com.unibuy.smartdevice.MyApp;
import com.unibuy.smartdevice.R;
import com.unibuy.smartdevice.databinding.DialogCheckoutCashPayBinding;
import com.unibuy.smartdevice.exception.ErrorCode;
import com.unibuy.smartdevice.exception.LogsIOException;
import com.unibuy.smartdevice.external.HttpAPI;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.structure.ReportFlowInfoStructure;
import com.unibuy.smartdevice.structure.ReportFreeGiftCodeStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.SimpleCashAcceptorHandler;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import com.unibuy.smartdevice.tools.Tools;
import com.unibuy.smartdevice.ui.FontendActivity;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

/**
 * 現金支付對話框，處理現金支付相關的邏輯，包括：
 * - 接收硬幣和紙鈔
 * - 計算找零
 * - 與 MDB 設備通訊
 * - 處理支付流程
 */
public class CashPayDialog extends DialogAbstract {

    // 輔助類別，用來儲存找零指令資訊
    private static class ChangeCommand {
        public final String command;
        public final int denomination;
        public final int count;

        public ChangeCommand(String command, int denomination, int count) {
            this.command = command;
            this.denomination = denomination;
            this.count = count;
        }
    }

    public enum Option {
        TOAST(0),
        COUNTDOWN_CANCEL(1),
        SET_COUNTDOWN_TEXT(2),
        OPEN_CASH_IN(3),
        CLOSE_CASH_IN(4),
        CASH_IN(5),
        CHANGE_DISPATCH(6),
        CHANGE_BUY(7),
        REPORT_FLOW(8),
        POST_INVOICE(9),
        REPORT_INVOICE(10),
        GET_CHANGE(11),
        ;

        private int option;

        public int getOption() {
            return option;
        }

        Option(int option) {
            this.option = option;
        }
    }

    public static final java.util.Map<Integer, Option> optionMap = new java.util.HashMap<>();
    static {
        for (Option option : Option.values()) {
            optionMap.put(option.getOption(), option);
        }
    }


    private DialogCheckoutCashPayBinding binding;
    private HttpAPI httpAPI;

    int fourthValueDecimal;
    private int inCash;
    private int OrderAmount;
    SimpleCashAcceptorHandler simpleCashAcceptorHandler;
    private int coin50_count;
    private int coin10_count;
    private int coin5_count;
    private int coin1_count;
    private int note100_count;

    int redundantCash;

    int inCashSAVE;
    private int idleSeconds = 0;
    private Handler checkCountdownHandler = new Handler(Looper.getMainLooper());
    private Runnable checkCountdownRunnable;

    // 標記位：避免重複關閉串口
    private boolean isSerialPortClosed = false;
    private final Object closeLock = new Object();

    // B600失敗出貨 與正常出貨
    private boolean isDispatchDialogShown = false;

    // 標記位：避免重複進入退出流程
    private boolean isProcessingExit = false;
    private boolean isStackingBill = false;

    @Override
    protected void onCreate(Context context) {
        // 先呼叫父類別的 onCreate 方法，這會執行所有必要的初始化
        // 接著，覆寫父類別的設定，將對話框置中
        Window window = getWindow();
        if (window != null) {
            WindowManager.LayoutParams layoutParams = window.getAttributes();
            layoutParams.gravity = Gravity.CENTER;
            window.setAttributes(layoutParams);
        }
        binding = DialogCheckoutCashPayBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        httpAPI = new HttpAPI(getHandlerMain());

        fourthValueDecimal = 0;

        OrderAmount = Integer.parseInt(String.valueOf(getTotalPrice()));
        inCash = 0;

        // 顯示應付總金額
        binding.textPrice.setText("" + OrderAmount);
        binding.textStatus.setText("已投入的金額:" + inCash);
        binding.textStatus2.setText("未投入的金額:" + OrderAmount);

        // 實例化 Handler，並傳入事件監聽器
        simpleCashAcceptorHandler = new SimpleCashAcceptorHandler(getCtx(),
                event -> new Handler(Looper.getMainLooper()).post(() -> handleMdbEvent(event)));

        // 啟動一個新執行緒來處理連線
        new Thread(() -> {
            String result = simpleCashAcceptorHandler.connect();
            new Handler(Looper.getMainLooper()).post(() -> {
                // Toast.makeText(getCtx(), result, Toast.LENGTH_LONG).show();
                if ("成功連接到 USB 轉接器！".equals(result)) {

                    // 修正點 A：先發送指令禁止所有投幣/投鈔，避免初始化期間誤吞
                    sendMdbCommand("0C00000000", simpleCashAcceptorHandler); // 禁止硬幣

                    messageShow("請稍後再進行投幣...", 3000);

                    simpleCashAcceptorHandler.startReading();
                    
                    // 修正點 B：重新調整初始化序列，先設定配置，最後才開啟 (FFFF)
                    // 340000... 先關閉 -> 設定硬幣 0C -> 查詢狀態 -> 最後才 34FFFF (開啟)
                    sendMdbCommand("370100000002", simpleCashAcceptorHandler, 500, () -> {
                        sendMdbCommand("3703", simpleCashAcceptorHandler, 500, () -> {
                            sendMdbCommand("3704000003000000000000000000000000000000", simpleCashAcceptorHandler, 500, () -> {
                                sendMdbCommand("3705", simpleCashAcceptorHandler, 500, () -> {
                                    sendMdbCommand("0A", simpleCashAcceptorHandler, 500, ()->{
                                        // 全部準備好後，才真正開啟接收能力
                                        sendMdbCommand("0CFFFFFFFF", simpleCashAcceptorHandler, 500, () -> {
                                            // 最後才開啟紙鈔接收
                                            sendMdbCommand("34FFFFFFFF", simpleCashAcceptorHandler, 500, () -> {
                                                getLogs().info("MDB 初始化完成，正式開啟投幣");

                                                // 初始化完畢後 2 秒，再次確認一次拒收指令，防止初始化期間殘留的紙鈔
                                                new Handler(Looper.getMainLooper()).postDelayed(() -> {
                                                    if (!isStackingBill) {
                                                        getLogs().info("執行初始化後保險檢查，清理可能的殘留紙鈔");
                                                        sendMdbCommand("3500", simpleCashAcceptorHandler);
                                                    } else {
                                                        getLogs().info("正在入鈔中，跳過初始化後保險檢查");
                                                    }
                                                }, 2000);
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });

                } else {
                    messageShow(result, 3000);
                    cancel();
                }
            });
        }).start();

        // 取消按鈕
        binding.buttonCancel.setOnClickListener(v -> dialogCancel());

        // 測試指令 按中間圖片
        // binding.testOne.setOnClickListener(v ->
        // showMdbCommandDialog(simpleCashAcceptorHandler));

        // 初始化閒置計時器
        checkCountdownRunnable = new Runnable() {
            @Override
            public void run() {
                idleSeconds++;
                if (idleSeconds > 25) {
                    if (isShowing()) {
                        getLogs().info("[CashPayDialog] 閒置逾時 (" + idleSeconds + "s)，執行自動取消與退幣");
                        dialogCancel();
                    }
                }
                checkCountdownHandler.postDelayed(this, 1000);
            }
        };
        checkCountdownHandler.postDelayed(checkCountdownRunnable, 1000);
    }

    /**
     * [MQTT Finalize 重構]
     * 顯示出貨對話框。
     * 原本會先呼叫 B600 再延遲 2 秒跳轉，此設計因 Race Condition 風險已廢棄。
     * 現在改為直接將支付資訊寫入 MyApp 全域狀態，再跳轉至 DispatchDialog。
     * 完整的交易資料將在所有出貨動作完成後，由 DispatchDialog 統一透過 MQTT Finalize 送出。
     */
    private void showDispatchDialog() {
        if (getCtx() == null) return;

        // 填入現金支付資訊至全域狀態（供 TransactionFinalizeBuilder 使用）
        com.unibuy.smartdevice.structure.ReportFlowInfoStructure flowInfo =
                MyApp.getInstance().getReportFlowInfoData();

        flowInfo.setFlowType(9); // 現金支付
        flowInfo.setFlowSendInfo("收現金:" + inCashSAVE + "元/找零:" + redundantCash + "元");
        flowInfo.setFlowRequestInfo(""); // 現金無原始請求字串
        flowInfo.setOrderId(com.unibuy.smartdevice.tools.Tools.generateTimeBasedUUID());
        flowInfo.setTotalPrice(getTotalPrice());
        flowInfo.setFlowBarCode(""); // 現金交易無條碼
        flowInfo.setFlowStatusType(1); // 1 = 成功
        flowInfo.setGiveChange(redundantCash);
        flowInfo.setUsePoints(0);

        // 現金支付無需等待後端確認，直接跳轉出貨
        if (!isDispatchDialogShown) {
            isDispatchDialogShown = true;
            changeDialog(DispatchDialog.class);
        }
    }


    // 接收設備的回應
    /**
     * 處理 MDB 設備的事件回應
     * 
     * @param event 設備發送的原始事件字串
     */
    private void handleMdbEvent(String event) {
        if (event == null || event.trim().isEmpty()) {
            return;
        }

        String cleanedEvent = event.trim();
        String[] parts = cleanedEvent.split("\\s+");

        // 記錄原始數據
        if (!cleanedEvent.equals("00 00") && !cleanedEvent.equals("00")) {
            getLogs().info("[DEBUG_CASH] 原始報文: " + cleanedEvent);
        }

        // 1. 長報文處理：硬幣庫存 (19 bytes)
        if (parts.length == 19) {
            processCoinInventory(cleanedEvent);
            return;
        }

        // 2. 長報文處理：紙鈔庫存/狀態 (>= 30 bytes)
        if (parts.length >= 30) {
            processLongBillEvent(cleanedEvent);
            return;
        }

        // 3. 短報文處理：使用 Regex 滑動匹配以防止斷字，確保穩定抓取 MDB 指令 (如 08 54)
        java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("[0-9A-F]{2} [0-9A-F]{2}");
        java.util.regex.Matcher matcher = pattern.matcher(cleanedEvent);
        while (matcher.find()) {
            String chunk = matcher.group();
            handleShortMdbChunk(chunk);
        }
    }

    private void processCoinInventory(String chunk) {
        String[] parts = chunk.split("\\s+");
        try {
            coin1_count = Integer.parseInt(parts[2], 16);
            coin5_count = Integer.parseInt(parts[3], 16);
            coin10_count = Integer.parseInt(parts[4], 16);
            coin50_count = Integer.parseInt(parts[6], 16); // 修正: 50元在庫存回報的第 6 個位置

            getLogs().info(String.format("[DEBUG_CASH] 硬幣機庫存(0A)已更新：1元=%d, 5元=%d, 10元=%d, 50元=%d",
                    coin1_count, coin5_count, coin10_count, coin50_count));
        } catch (Exception e) {
            getLogs().error(ErrorCode.UNKNOWN_ERROR, "解析硬幣庫存失敗: " + e.getMessage());
        }
    }

    private void processLongBillEvent(String event) {
        String[] parts = event.trim().split(" ");
        try {
            String fourthValueHex = parts[3];
            fourthValueDecimal = Integer.parseInt(fourthValueHex, 16);
            if (fourthValueDecimal >= 0 && fourthValueDecimal <= 30) {
                note100_count = fourthValueDecimal;
            } else {
                note100_count = 0;
            }
        } catch (Exception e) {
            getLogs().error(ErrorCode.UNKNOWN_ERROR, "解析百鈔庫存失敗: " + e.getMessage());
        }
    }

    private void handleShortMdbChunk(String chunk) {
        idleSeconds = 0;
        String upperChunk = chunk.toUpperCase();
        getLogs().info("[DEBUG_CASH] 處理指令: " + upperChunk);

        switch (upperChunk) {
            case "08 50": // 1 元 (幣管)
                processCoinDetected(1, chunk);
                break;
            case "08 51": // 5 元 (幣管)
                processCoinDetected(5, chunk);
                break;
            case "08 52": // 10 元 (幣管)
                processCoinDetected(10, chunk);
                break;
            case "08 54": // 50 元 (幣管)
                processCoinDetected(50, chunk);
                break;

            case "08 40": // 1 元 (錢箱)
                processCoinFullDetected(1, chunk);
                break;
            case "08 41": // 5 元 (錢箱)
                processCoinFullDetected(5, chunk);
                break;
            case "08 42": // 10 元 (錢箱)
                processCoinFullDetected(10, chunk);
                break;
            case "08 43": // 50 元 (錢箱 - 幣管已滿) - 嚴重修正點
                processCoinFullDetected(50, chunk);
                break;
            case "30 90": // 100元
                processBillDetected(100, chunk);
                break;
            case "30 92": // 500元
                processBillDetected(500, chunk);
                break;
            case "30 93": // 1000元
                processBillDetected(1000, chunk);
                break;
        }
    }

    private void processCoinDetected(int amount, String chunk) {
        getLogs().info(amount + "元收到訊息:" + chunk);
        checkAndAcceptCash(amount, amount == 50 ? "0D14" : (amount == 10 ? "0D12" : "0D11"));
    }

    private void processCoinFullDetected(int amount, String chunk) {
        getLogs().info(amount + "元硬幣機滿掉入錢箱:" + chunk);
        checkAndAcceptCash(amount, ""); 
    }

    private void processBillDetected(int amount, String chunk) {
        getLogs().info("收到" + amount + "元鈔票 訊息:" + chunk);
        if (amount == 100) {
            checkAndAcceptBill(100, "3503", "3500");
        } else {
            checkAndAcceptBill(amount, "3501", "3500");
        }
    }

    private void checkAndAcceptCash(int amount, String rejectCmd) {
        int potential_inCash = inCash + amount;
        int changeNeeded = potential_inCash - OrderAmount;

        // 如果「目前已投+新投」尚未超過應付金額，或者找得開零錢
        if (potential_inCash <= OrderAmount || can_make_change(changeNeeded)) {
            calculate_amount_update_UI(amount);
            getLogs().info(String.format("[DEBUG_CASH] 接受 %d 元。累計: %d / 應付: %d", amount, potential_inCash, OrderAmount));
        } else if (!rejectCmd.isEmpty()) {
            String diagInfo = String.format("(應付:%d, 累計:%d, 需找:%d)", OrderAmount, potential_inCash, changeNeeded);
            getLogs().info("[DEBUG_CASH] 找零不足，拒絕 " + amount + " 元硬幣。系統狀態: " + diagInfo +
                             String.format(" 幣管庫存[1:%d, 5:%d, 10:%d, 50:%d]", coin1_count, coin5_count, coin10_count, coin50_count));
            messageShow("找零不足，無法接受" + amount + "元硬幣。\n" + diagInfo, 5000);
            new Handler(Looper.getMainLooper()).postDelayed(() ->
                    sendMdbCommand(rejectCmd, simpleCashAcceptorHandler), 2000);
        }
    }

    private void checkAndAcceptBill(int amount, String acceptCmd, String rejectCmd) {
        int potential_inCash = inCash + amount;
        int changeNeeded = potential_inCash - OrderAmount;

        // 500/1000 特別庫存檢查
        boolean stockCheck = true;
        if (amount == 500 && note100_count < 3) stockCheck = false;
        if (amount == 1000 && note100_count < 7) stockCheck = false;

        if ((potential_inCash <= OrderAmount || can_make_change(changeNeeded)) && stockCheck) {
            calculate_amount_update_UI(amount);
            new Handler(Looper.getMainLooper()).postDelayed(() ->
                    sendMdbCommand(acceptCmd, simpleCashAcceptorHandler), 800);
        } else {
            messageShow("找零不足，無法接受" + amount + "元鈔票。", 3000);
            new Handler(Looper.getMainLooper()).postDelayed(() ->
                    sendMdbCommand(rejectCmd, simpleCashAcceptorHandler), 500);
        }
    }

    /**
     * 檢查是否有足夠的零錢可以找
     * 
     * @param changeAmount 需要找零的金額
     * @return 是否可以找零
     */
    private boolean can_make_change(int changeAmount) {

        int note100_temp = note100_count;
        int coin50_temp = coin50_count;
        int coin10_temp = coin10_count;
        int coin5_temp = coin5_count;
        int coin1_temp = coin1_count;

        if (changeAmount >= 100 && note100_temp > 0) {
            int hundredsToGive = changeAmount / 100;
            int actualGive = Math.min(hundredsToGive, note100_temp);
            changeAmount -= actualGive * 100;
            note100_temp -= actualGive;
        }
        if (changeAmount >= 50 && coin50_temp > 0) {
            int fiftiesToGive = changeAmount / 50;
            int actualGive = Math.min(fiftiesToGive, coin50_temp);
            changeAmount -= actualGive * 50;
            coin50_temp -= actualGive;
        }
        if (changeAmount >= 10 && coin10_temp > 0) {
            int tensToGive = changeAmount / 10;
            int actualGive = Math.min(tensToGive, coin10_temp);
            changeAmount -= actualGive * 10;
            coin10_temp -= actualGive;
        }
        if (changeAmount >= 5 && coin5_temp > 0) {
            int fivesToGive = changeAmount / 5;
            int actualGive = Math.min(fivesToGive, coin5_temp);
            changeAmount -= actualGive * 5;
            coin5_temp -= actualGive;
        }
        if (changeAmount > 0 && coin1_temp > 0) {
            int onesToGive = changeAmount / 1;
            int actualGive = Math.min(onesToGive, coin1_temp);
            changeAmount -= actualGive * 1;
            coin1_temp -= actualGive;
        }
        return changeAmount == 0;
    }

    /**
     * 計算並更新 UI 顯示的金額 進行B600以及出貨
     * 
     * @param amount 投入的金額
     */
    private void calculate_amount_update_UI(int amount) {
        if (isProcessingExit) {
            getLogs().info("[CashPayDialog] 退出流程中收到金額 " + amount + "，立即執行補退幣");
            performChange(amount, null);
            return;
        }

        inCash += amount;
        int remaining = OrderAmount - inCash; // 未投入的錢
        binding.textStatus.setText("已投入的金額: " + inCash);
        binding.textStatus2.setText("未投入的金額: " + remaining);

        if (inCash >= OrderAmount) {
            if (isProcessingExit) return;
            isProcessingExit = true;

            redundantCash = inCash - OrderAmount; // 要找的錢
            inCashSAVE = inCash;
            binding.textStatus2.setText("付款完成！即將找零:" + redundantCash + "元,並準備出貨");

            binding.buttonCancel.setVisibility(View.GONE);

            // 修改這裡：傳入一個回調函數，在找零完成後執行
            performChange(redundantCash, () -> {
                // 這個回調會在找零完成後執行

                // 顯示出貨畫面進行B600 602
                showDispatchDialog();
                // 使用 父類的取消畫面
                cancel();

            });

            inCash = 0;
        }

    }

    /// ///// 找零方法 有三個 //////////// ///// 找零方法 有三個 /////////

    /**
     * 執行找零操作
     * 
     * @param changeAmount 需要找零的金額
     * @param onComplete   找零完成後的回調
     */
    private void performChange(int changeAmount, Runnable onComplete) {

        int remainingChange = changeAmount; // 剩餘要找的錢

        // -----------------------------
        // 100元紙鈔 - 先檢查是否需要吐鈔
        // -----------------------------
        int hundredsToGive = Math.min(remainingChange / 100, note100_count);
        if (hundredsToGive > 0) {
            // ✅ 關鍵修正：先執行紙鈔機激活準備（按測試頁面的精確延遲時間）
            getLogs().info("[現金支付] 需要吐百鈔，先激活紙鈔機");

            // 按照測試頁面的延遲時間：0ms, 1000ms, 600ms, 1000ms
            sendMdbCommand("3400000000", simpleCashAcceptorHandler, 0, () -> {
                sendMdbCommand("370100000002", simpleCashAcceptorHandler, 1000, () -> {
                    sendMdbCommand("3703", simpleCashAcceptorHandler, 600, () -> {
                        sendMdbCommand("3704000003000000000000000000000000000000", simpleCashAcceptorHandler, 1000,
                                () -> {
                                    getLogs().info("[現金支付] 紙鈔機激活完成，開始生成找零指令");

                                    // 紙鈔機準備完成後，生成全部找零指令
                                    List<ChangeCommand> commands = new ArrayList<>();
                                    int remaining = changeAmount;

                                    // 加入 100元紙鈔指令
                                    String command = "370600000" + String.format("%01X", hundredsToGive);
                                    getLogs().info("[現金支付] 準備吐出 100元紙鈔 x " + hundredsToGive + " 張，指令: " + command);
                                    commands.add(new ChangeCommand(command, 100, hundredsToGive));
                                    remaining -= hundredsToGive * 100;

                                    // 加入其他面額的找零指令
                                    addOtherChangeCommands(commands, remaining);

                                    // 執行找零指令
                                    if (!commands.isEmpty()) {
                                        executeChangeCommands(commands, 0, onComplete);
                                    } else if (onComplete != null) {
                                        onComplete.run();
                                    }
                                });
                    });
                });
            });
            return; // 重要：提前返回，不繼續往下執行
        }

        // 如果不需要吐百鈔，直接生成硬幣找零指令
        List<ChangeCommand> commands = new ArrayList<>();
        addOtherChangeCommands(commands, remainingChange);

        if (!commands.isEmpty()) {
            executeChangeCommands(commands, 0, onComplete);
        } else if (onComplete != null) {
            onComplete.run();
        }
    }

    // 新增輔助方法：加入硬幣找零指令
    /**
     * 添加硬幣找零指令到指令列表
     * 
     * @param commands        指令列表
     * @param remainingChange 剩餘需要找零的金額
     */
    private void addOtherChangeCommands(List<ChangeCommand> commands, int remainingChange) {

        // -----------------------------
        // 50元硬幣
        // -----------------------------
        int fiftiesToGive = Math.min(remainingChange / 50, coin50_count);
        if (fiftiesToGive > 0) {
            String command = "0D" + String.format("%01X", fiftiesToGive) + "4";
            commands.add(new ChangeCommand(command, 50, fiftiesToGive));
            remainingChange -= fiftiesToGive * 50;
        }

        // -----------------------------
        // 10元硬幣
        // -----------------------------
        int tensToGive = Math.min(remainingChange / 10, coin10_count);
        if (tensToGive > 0) {
            String command = "0D" + String.format("%01X", tensToGive) + "2";
            commands.add(new ChangeCommand(command, 10, tensToGive));
            remainingChange -= tensToGive * 10;
        }

        // -----------------------------
        // 5元硬幣
        // -----------------------------
        int fivesToGive = Math.min(remainingChange / 5, coin5_count);
        if (fivesToGive > 0) {
            String command = "0D" + String.format("%01X", fivesToGive) + "1";
            commands.add(new ChangeCommand(command, 5, fivesToGive));
            remainingChange -= fivesToGive * 5;
        }

        // -----------------------------
        // 1元硬幣
        // -----------------------------
        int onesToGive = Math.min(remainingChange, coin1_count);
        if (onesToGive > 0) {
            String command = "0D" + String.format("%01X", onesToGive) + "0";
            commands.add(new ChangeCommand(command, 1, onesToGive));
            remainingChange -= onesToGive;
        }

        // -----------------------------
        // 如果剩餘金額還有 > 0，表示找零不足
        // -----------------------------
        if (remainingChange > 0) {
            getLogs().info("[現金支付] 警告：找零不足，還差 " + remainingChange + "元");
            messageShow("找零不足，無法完成找零。", 3000);
        }
    }

    /**
     * 依序執行找零指令
     * 
     * @param commands   找零指令列表
     * @param index      當前執行的指令索引
     * @param onComplete 所有指令執行完成後的回調
     */
    private void executeChangeCommands(List<ChangeCommand> commands, int index, Runnable onComplete) {
        // 檢查遞迴終止條件。如果當前索引 (index) 等於或超過了找零指令清單的總大小，
        // 代表所有指令都已發送完畢。
        // 檢查遞迴終止條件
        if (index >= commands.size()) {
            // ✅ 所有找零完成後，才關閉紙鈔機
            new Handler(Looper.getMainLooper()).postDelayed(() -> {
                sendMdbCommand("3400000000", simpleCashAcceptorHandler);
            }, 1000);

            if (onComplete != null) {
                onComplete.run();
            }
            return;
        }

        // 取得當前要執行的找零指令物件，該物件位於清單中由 index 指定的位置。
        final ChangeCommand currentCommand = commands.get(index);

        // 使用 Handler.postDelayed 延遲執行，確保每個指令之間有足夠的間隔，
        // 讓硬體有時間處理前一個指令。
        new Handler(Looper.getMainLooper()).postDelayed(() -> {

            // 執行指令前進行空值檢查，確保當前指令和處理器物件都不是 null。
            if (currentCommand != null && simpleCashAcceptorHandler != null) {

                // 發送 MDB 指令給硬幣/鈔票機。指令內容來自當前指令物件 (currentCommand)。
                sendMdbCommand(currentCommand.command, simpleCashAcceptorHandler);
                // messageShow("進行的指令"+currentCommand.command,1000);
                messageShow("吐幣中請稍後", 1000);

                // 根據找零的面額，更新對應的庫存計數。
                switch (currentCommand.denomination) {
                    case 50:
                        // 吐出 50 元硬幣，庫存減少。
                        coin50_count -= currentCommand.count;
                        break;
                    case 10:
                        // 吐出 10 元硬幣，庫存減少。
                        coin10_count -= currentCommand.count;
                        break;
                    case 5:
                        // 吐出 5 元硬幣，庫存減少。
                        coin5_count -= currentCommand.count;
                        break;
                    case 1:
                        // 吐出 1 元硬幣，庫存減少。
                        coin1_count -= currentCommand.count;
                        break;
                    case 100:
                        // 吐出 100 元鈔票，庫存減少。
                        note100_count -= currentCommand.count;
                        break;
                    case 0:
                        // ✅ 紙鈔機準備指令（初始化/配置），不更新庫存
                        break;
                }
            }
            // 遞迴呼叫自己，並將索引加 1，以處理清單中的下一個指令。
            // 這就是讓指令依序執行的關鍵。
            executeChangeCommands(commands, index + 1, onComplete);
        }, 3500); //
    }

    /// ///// 找零方法 有三個 //////////// ///// 找零方法 有三個 /////////

    // 下位機溝通用
    public byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    // 安全關閉串口的核心方法（帶延遲參數）
    /**
     * 安全關閉串口（帶延遲參數）
     * 
     * @param delayBeforeCloseMs 關閉前的延遲時間（毫秒）
     */
    private void safeCloseSerialPort(int delayBeforeCloseMs) {
        synchronized (closeLock) {
            if (isSerialPortClosed) {
                getLogs().info("[CashPayDialog] 串口已經關閉過了，跳過重複關閉");
                return;
            }
            isSerialPortClosed = true;
            getLogs().info("[CashPayDialog] 開始安全關閉串口，延遲: " + delayBeforeCloseMs + "ms");
        }

        if (simpleCashAcceptorHandler != null) {
            // 使用 Handler 確保即使對話框關閉也能執行
            new Handler(Looper.getMainLooper()).postDelayed(() -> {
                new Thread(() -> {
                    try {
                        // 停止接收紙幣和硬幣
                        getLogs().info("[CashPayDialog] 發送停止接收紙幣硬幣指令");
                        sendMdbCommand("0C00000000", simpleCashAcceptorHandler);
                        Thread.sleep(1000);

                        // 停止找零
                        getLogs().info("[CashPayDialog] 發送停止找零指令");
                        sendMdbCommand("3400000000", simpleCashAcceptorHandler);
                        Thread.sleep(1000);

                        // 關閉連線
                        getLogs().info("[CashPayDialog] 關閉串口連線");
                        simpleCashAcceptorHandler.close();

                        getLogs().info("[CashPayDialog] 串口關閉完成");
                    } catch (Exception e) {
                        getLogs().error(ErrorCode.UNKNOWN_ERROR, "[CashPayDialog] 關閉串口時發生錯誤: " + e.getMessage());
                    }
                }).start();
            }, delayBeforeCloseMs);
        }
    }

    /// //////// 取消交易 找零 有二個 ////////
    /**
     * 對話框取消按鈕點擊處理
     */
    public void dialogCancel() {
        if (this.isShowing()) {
            // 如果有投入現金，則退還
            if (inCash > 0) {
                // 顯示退幣訊息
                getLogs().info("取消交易，退還 " + inCash + "元");
                messageShow("取消交易，退還 " + inCash + " 元", 3000);
                // 執行退幣
                performChange(inCash, () -> {
                    // 這個回調會在退幣完成後執行
                    completeCancelProcess();

                });
                // 重置已投入金額
                inCash = 0;
            } else {
                // 如果沒有現金需要退還，直接執行取消流程
                completeCancelProcess();
            }
        }
    }

    /**
     * 取消交易流程，包括退幣和清理資源
     */
    private void completeCancelProcess() {
        getLogs().info("[CashPayDialog] 執行 completeCancelProcess");

        // 清除優惠代碼和購物車
        MyApp.getInstance().setReportFreeGiftCodeStructure(new ReportFreeGiftCodeStructure("", 0, "", 0, 0, ""));
        getSrcHandlerMain().start(getClass().getSimpleName(), FontendActivity.Option.STOP_COUNTDOWN.getOption(), "");
        MyApp.getInstance().getBuyList().clear();

        safeCloseSerialPort(3000);
        cancel();

    }

    /// //////// 取消交易 找零 有二個 ////////

    /**
     * 發送MDB任務
     *
     */
    private void sendMdbCommand(final String hexCommand, final SimpleCashAcceptorHandler handler) {
        new Thread(() -> {
            try {
                byte[] command = hexStringToByteArray(hexCommand);
                int bytesWritten = handler.writeData(command);
                if (bytesWritten > 0) {
                    new Handler(Looper.getMainLooper()).post(() -> {
                    });
                } else {
                    new Handler(Looper.getMainLooper()).post(() -> {
                    });
                }
            } catch (Exception e) {
                new Handler(Looper.getMainLooper()).post(() -> {
                });
            }
        }).start();
    }

    /**
     * 發送MDB任務 參數多兩個 delay跟 Runnable
     *
     */
    private void sendMdbCommand(String command, SimpleCashAcceptorHandler handler, int delay, Runnable onComplete) {
        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            sendMdbCommand(command, handler);
            if (onComplete != null) {
                onComplete.run();
            }
        }, delay);
    }

    /// Cancel時會進入安全關閉
    @Override
    public void dismiss() {
        if (checkCountdownHandler != null && checkCountdownRunnable != null) {
            checkCountdownHandler.removeCallbacks(checkCountdownRunnable);
        }
        getLogs().info("[CashPayDialog] dismiss() 被調用，確保串口安全關閉");
        // 異常關閉場景：給予 3 秒緩衝時間，以防正在找零
        safeCloseSerialPort(3000);
        super.dismiss();
    }

    /// Activity 進背景時 安全關閉
    @Override
    protected void onStop() {
        super.onStop();
        getLogs().info("[CashPayDialog] onStop() 被調用，確保串口安全關閉");
        if (checkCountdownHandler != null && checkCountdownRunnable != null) {
            checkCountdownHandler.removeCallbacks(checkCountdownRunnable);
        }
        // 異常關閉場景：給予 3 秒緩衝時間，以防正在找零
        safeCloseSerialPort(3000);

        Log.d("CashPayDialog", "onStop(): 關閉串口與停止讀取");
    }

    /**
     * 計算購物車總價
     * 
     * @return 總價
     */
    public int getTotalPrice() {
        int totalPrice = 0;
        if (MyApp.getInstance().getBuyList().size() == 0)
            return totalPrice;
        for (BuyStructure buy : MyApp.getInstance().getBuyList()) {
            if (!buy.isFreeGift())
                totalPrice += buy.getFullPrice();
        }
        if (MyApp.getInstance().getReportFreeGiftCodeStructure() != null) {
            switch (MyApp.getInstance().getReportFreeGiftCodeStructure().getMode()) {
                case 0:
                    break;
                case 1:
                    totalPrice -= MyApp.getInstance().getReportFreeGiftCodeStructure().getDiscountAmount();
                    break;
                case 2:
                    totalPrice = (int) (totalPrice
                            * MyApp.getInstance().getReportFreeGiftCodeStructure().getDiscountPercentage());
                    break;
            }
        }
        if (totalPrice == 0)
            totalPrice = 1;
        return totalPrice;
    }

    /// ////////////不重要

    /**
     * 顯示提示訊息
     * 
     * @param message 訊息內容
     * @param second  顯示時間（毫秒）
     */
    void messageShow(String message, int second) {
        Builder builder = new Builder(getCtx());
        final AlertDialog dialog = builder.create();
        View dialogView = View.inflate(getCtx(), R.layout.mydialog1, null);
        TextView t1 = dialogView.findViewById(R.id.text01);
        TextView t2 = dialogView.findViewById(R.id.text02);
        t1.setText("提示!");
        t2.setText(message);
        dialog.setView(dialogView);
        if (dialog.getWindow() != null) {
            dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
            dialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.WRAP_CONTENT);
        }
        dialog.show();
        if (second > 0)
            new Handler().postDelayed(dialog::dismiss, second);
    }

    /**
     * 嘗試重置 Activity 的閒置計時器
     */
    // private void tryResetActivityTimer() {
    // Context context = getContext();
    // while (context != null) {
    // if (context instanceof com.unibuy.smartdevice.ui.ShoppingPlatformActivity) {
    // ((com.unibuy.smartdevice.ui.ShoppingPlatformActivity)
    // context).resetInactivityTimer();
    // return;
    // }
    // if (context instanceof android.content.ContextWrapper) {
    // context = ((android.content.ContextWrapper) context).getBaseContext();
    // } else {
    // break;
    // }
    // }
    // }

    public CashPayDialog(Context context, HandlerMain srcHandlerMain) {
        super(context, srcHandlerMain);
    }

    @Override
    protected Context setCtx() {
        return getContext();
    }

    @Override
    protected Class<? extends DialogAbstract> setCls() {
        return getClass();
    }

    @Override
    protected HandlerMain setHandlerMain() {
        return new ToastHandlerMain(getContext(), getLogs()) {
            @Override
            protected void execute(Context context, int commandCode, String message) {
            }
        };
    }

    /// ////////////不重要

}