package com.aps.service.mp;

import com.google.ortools.Loader;
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPObjective;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;

/**
 * 多阶BOM + MRP 逻辑 MIP 排产
 * 三级结构：成品(P1,P2) → 半成品(S1,S2,S3) → 原材料(R1,R2,R3)
 * 产线：L1、L2、L3，产品可在指定产线生产（支持多对多映射）
 * 特性：BOM自动展开、采购提前期、多级库存平衡、多产线生产、需求缺口(shortfall)
 *
 * 重构说明：
 *   - 生产变量从 2D [item][day] 扩展为 3D [item][line][day]
 *   - 支持产品在多条产线生产（如P1可在L1或L3生产）
 *   - 通过 canProduce[][] 矩阵控制产品-产线兼容性
 *   - 同一产品在不同产线可有不同生产效率
 *   - 引入 shortfall 变量，需求可部分满足，避免产能不足时无解
 */
public class BomMpsScheduling {

    // ========== 内部数据类 ==========

    /**
     * 产品层配置：封装某一层产品的所有参数（支持多产线）
     */
    static class LayerConfig {
        String[] names;              // 产品名称数组
        String[] lineNames;          // 产线名称数组
        double[] prodCost;           // 单位生产成本
        double[] setupCost;          // 换型成本
        double[] holdCost;           // 库存持有成本
        double[] initInv;            // 初始库存
        double[] lineCapacity;       // 各产线日产能（小时）
        double[][] prodRateByLine;   // 按产线生产效率 [item][line]（件/小时）
        boolean[][] canProduce;      // 产品-产线兼容矩阵 [item][line]
        double shortfallPenalty;     // 缺口惩罚成本（元/件），仅成品层使用

        LayerConfig(String[] names, String[] lineNames,
                    double[] prodCost, double[] setupCost,
                    double[] holdCost, double[] initInv,
                    double[] lineCapacity, double[][] prodRateByLine,
                    boolean[][] canProduce, double shortfallPenalty) {
            this.names = names;
            this.lineNames = lineNames;
            this.prodCost = prodCost;
            this.setupCost = setupCost;
            this.holdCost = holdCost;
            this.initInv = initInv;
            this.lineCapacity = lineCapacity;
            this.prodRateByLine = prodRateByLine;
            this.canProduce = canProduce;
            this.shortfallPenalty = shortfallPenalty;
        }

        int size() { return names.length; }
        int lineCount() { return lineNames.length; }
        boolean canProduce(int item, int line) { return canProduce[item][line]; }
    }

    /**
     * 产品层变量：封装某一层的所有决策变量
     * 生产变量为三维数组 [item][line][day]
     */
    static class LayerVariables {
        MPVariable[][][] production;   // 产量变量 [item][line][day]
        MPVariable[][] inventory;      // 库存变量 [item][day]
        MPVariable[][][] switchVar;    // 生产开关变量 [item][line][day]
        MPVariable[][] purchase;       // 采购变量 [item][day]（仅原材料层用）
        MPVariable[][] shortfall;      // 需求缺口变量 [item][day]（仅成品层用）
    }

    // ========== 通用方法 ==========

    /**
     * 创建单层产品的决策变量（支持多产线）
     */
    static LayerVariables createLayerVariables(MPSolver solver, LayerConfig config,
                                               int numDays, boolean isPurchaseLayer) {
        LayerVariables vars = new LayerVariables();
        int n = config.size();
        int numLines = config.lineCount();

        if (isPurchaseLayer) {
            // 原材料层：只有采购量和库存变量
            vars.purchase = new MPVariable[n][numDays];
            vars.inventory = new MPVariable[n][numDays];
            for (int i = 0; i < n; i++) {
                for (int t = 0; t < numDays; t++) {
                    vars.purchase[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                            "pr_" + config.names[i] + "_d" + (t + 1));
                    vars.inventory[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                            "ir_" + config.names[i] + "_d" + (t + 1));
                }
            }
        } else {
            // 生产层：产量[item][line][day]、库存[item][day]、开关[item][line][day]
            vars.production = new MPVariable[n][numLines][numDays];
            vars.inventory = new MPVariable[n][numDays];
            vars.switchVar = new MPVariable[n][numLines][numDays];

            String prefix = config.names[0].startsWith("P") ? "xp" : "xs";
            String invPrefix = config.names[0].startsWith("P") ? "ip" : "is";
            String swPrefix = config.names[0].startsWith("P") ? "yp" : "ys";

            for (int i = 0; i < n; i++) {
                for (int t = 0; t < numDays; t++) {
                    vars.inventory[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                            invPrefix + "_" + config.names[i] + "_d" + (t + 1));
                }
                for (int l = 0; l < numLines; l++) {
                    if (config.canProduce(i, l)) {
                        for (int t = 0; t < numDays; t++) {
                            vars.production[i][l][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                                    prefix + "_" + config.names[i] + "_" + config.lineNames[l] + "_d" + (t + 1));
                            vars.switchVar[i][l][t] = solver.makeBoolVar(
                                    swPrefix + "_" + config.names[i] + "_" + config.lineNames[l] + "_d" + (t + 1));
                        }
                    }
                }
            }

            // 为成品层创建 shortfall 变量
            if (config.shortfallPenalty > 0) {
                vars.shortfall = new MPVariable[n][numDays];
                for (int i = 0; i < n; i++) {
                    for (int t = 0; t < numDays; t++) {
                        vars.shortfall[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
                                "short_" + config.names[i] + "_d" + (t + 1));
                    }
                }
            }
        }
        return vars;
    }

    /**
     * 添加目标函数系数（生产层：按item-line组合）
     */
    static void addProductionObjective(MPObjective obj, LayerVariables vars,
                                       LayerConfig config, int numDays) {
        for (int i = 0; i < config.size(); i++) {
            for (int l = 0; l < config.lineCount(); l++) {
                if (config.canProduce(i, l)) {
                    for (int t = 0; t < numDays; t++) {
                        obj.setCoefficient(vars.production[i][l][t], config.prodCost[i]);
                        obj.setCoefficient(vars.switchVar[i][l][t], config.setupCost[i]);
                    }
                }
            }
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(vars.inventory[i][t], config.holdCost[i]);
            }
        }

        // 添加 shortfall 惩罚成本
        if (vars.shortfall != null && config.shortfallPenalty > 0) {
            for (int i = 0; i < config.size(); i++) {
                for (int t = 0; t < numDays; t++) {
                    obj.setCoefficient(vars.shortfall[i][t], config.shortfallPenalty);
                }
            }
        }
    }

    /**
     * 添加目标函数系数（采购层）
     */
    static void addPurchaseObjective(MPObjective obj, LayerVariables vars,
                                     LayerConfig config, int numDays) {
        for (int i = 0; i < config.size(); i++) {
            for (int t = 0; t < numDays; t++) {
                obj.setCoefficient(vars.purchase[i][t], config.prodCost[i]);
                obj.setCoefficient(vars.inventory[i][t], config.holdCost[i]);
            }
        }
    }

    /**
     * 添加库存平衡约束（生产层：本期消耗为外生需求，含shortfall缺口）
     * 库存平衡：上期库存 + Σ各产线产量 + shortfall = 本期需求 + 期末库存
     */
    static void addInventoryBalanceWithDemand(MPSolver solver, LayerVariables vars,
                                              LayerConfig config, double[][] demand,
                                              int numDays) {
        for (int i = 0; i < config.size(); i++) {
            for (int t = 0; t < numDays; t++) {
                double prevInv = (t == 0) ? config.initInv[i] : 0;
                MPConstraint c = solver.makeConstraint(demand[i][t] - prevInv, demand[i][t] - prevInv,
                        "inv_" + config.names[i] + "_d" + (t + 1));
                if (t > 0) c.setCoefficient(vars.inventory[i][t - 1], 1);

                // 本期产量 = Σ 各兼容产线产量
                for (int l = 0; l < config.lineCount(); l++) {
                    if (config.canProduce(i, l)) {
                        c.setCoefficient(vars.production[i][l][t], 1);
                    }
                }

                // shortfall 缺口（如果有）
                if (vars.shortfall != null) {
                    c.setCoefficient(vars.shortfall[i][t], 1);
                }

                c.setCoefficient(vars.inventory[i][t], -1);
            }
        }
    }

    /**
     * 添加库存平衡约束（半成品层：本期消耗为BOM展开，产量=Σ各产线）
     */
    static void addInventoryBalanceWithBom(MPSolver solver, LayerVariables vars,
                                           LayerConfig config, LayerVariables upstreamVars,
                                           double[][] bomMatrix, int numDays) {
        for (int s = 0; s < config.size(); s++) {
            for (int t = 0; t < numDays; t++) {
                double rhs = 0;
                MPConstraint c = solver.makeConstraint(0, 0,
                        "inv_" + config.names[s] + "_d" + (t + 1));

                if (t == 0) {
                    rhs -= config.initInv[s];
                } else {
                    c.setCoefficient(vars.inventory[s][t - 1], 1);
                }

                // 本期产量 = Σ 各兼容产线产量
                for (int l = 0; l < config.lineCount(); l++) {
                    if (config.canProduce(s, l)) {
                        c.setCoefficient(vars.production[s][l][t], 1);
                    }
                }

                // 本期消耗：Σ 上游产品i的总产量 × bom系数（-）
                for (int i = 0; i < bomMatrix[s].length; i++) {
                    if (bomMatrix[s][i] != 0) {
                        // 汇总上游产品i在所有产线的产量
                        for (int l = 0; l < upstreamVars.production[i].length; l++) {
                            if (upstreamVars.production[i][l][t] != null) {
                                c.setCoefficient(upstreamVars.production[i][l][t], -bomMatrix[s][i]);
                            }
                        }
                    }
                }

                c.setCoefficient(vars.inventory[s][t], -1);
                c.setBounds(rhs, rhs);
            }
        }
    }

    /**
     * 添加库存平衡约束（原材料层：BOM + 采购提前期）
     */
    static void addInventoryBalanceWithPurchase(MPSolver solver, LayerVariables vars,
                                                LayerConfig config, LayerVariables upstreamVars,
                                                double[][] bomMatrix, int numDays,
                                                int purchaseLeadTime) {
        for (int r = 0; r < config.size(); r++) {
            for (int t = 0; t < numDays; t++) {
                double rhs = 0;
                MPConstraint c = solver.makeConstraint(0, 0,
                        "inv_" + config.names[r] + "_d" + (t + 1));

                if (t == 0) {
                    rhs -= config.initInv[r];
                } else {
                    c.setCoefficient(vars.inventory[r][t - 1], 1);
                }

                // 本期到货（考虑采购提前期）
                int arriveDay = t - purchaseLeadTime;
                if (arriveDay >= 0) {
                    c.setCoefficient(vars.purchase[r][arriveDay], 1);
                }

                // 本期消耗：Σ 半成品s的总产量 × bom系数
                for (int s = 0; s < bomMatrix[r].length; s++) {
                    if (bomMatrix[r][s] != 0) {
                        for (int l = 0; l < upstreamVars.production[s].length; l++) {
                            if (upstreamVars.production[s][l][t] != null) {
                                c.setCoefficient(upstreamVars.production[s][l][t], -bomMatrix[r][s]);
                            }
                        }
                    }
                }

                c.setCoefficient(vars.inventory[r][t], -1);
                c.setBounds(rhs, rhs);
            }
        }
    }

    /**
     * 添加产能约束（按产线：该产线所有可生产产品的耗时之和 ≤ 产能）
     */
    static void addCapacityConstraint(MPSolver solver, LayerVariables vars,
                                      LayerConfig config, int numDays,
                                      String layerPrefix) {
        for (int l = 0; l < config.lineCount(); l++) {
            for (int t = 0; t < numDays; t++) {
                MPConstraint cap = solver.makeConstraint(0, config.lineCapacity[l],
                        "cap_" + layerPrefix + "_" + config.lineNames[l] + "_d" + (t + 1));
                for (int i = 0; i < config.size(); i++) {
                    if (config.canProduce(i, l)) {
                        cap.setCoefficient(vars.production[i][l][t],
                                1.0 / config.prodRateByLine[i][l]);
                    }
                }
            }
        }
    }

    /**
     * 添加生产开关约束（产量 ≤ 开关 × bigM，按item-line组合）
     */
    static void addProductionSwitchConstraint(MPSolver solver, LayerVariables vars,
                                              LayerConfig config, int numDays,
                                              double bigM, String layerPrefix) {
        for (int i = 0; i < config.size(); i++) {
            for (int l = 0; l < config.lineCount(); l++) {
                if (config.canProduce(i, l)) {
                    for (int t = 0; t < numDays; t++) {
                        MPConstraint c = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0,
                                "switch_" + layerPrefix + "_" + config.names[i] + "_" + config.lineNames[l] + "_d" + (t + 1));
                        c.setCoefficient(vars.production[i][l][t], 1);
                        c.setCoefficient(vars.switchVar[i][l][t], -bigM);
                    }
                }
            }
        }
    }

    /**
     * 计算某产品的总产量（跨产线汇总）
     */
    static double getTotalProduction(LayerVariables vars, int item, int day, LayerConfig config) {
        double total = 0;
        for (int l = 0; l < config.lineCount(); l++) {
            if (config.canProduce(item, l)) {
                total += vars.production[item][l][day].solutionValue();
            }
        }
        return total;
    }

    /**
     * 打印生产层结果（按产线分组）
     */
    static void printProductionResult(LayerVariables vars, LayerConfig config,
                                      int dayIndex) {
        String layerLabel = config.names[0].startsWith("P") ? "成品装配" : "半成品加工";
        System.out.println("  ▶ " + layerLabel);
        for (int l = 0; l < config.lineCount(); l++) {
            boolean hasProduction = false;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < config.size(); i++) {
                if (config.canProduce(i, l)) {
                    double qty = vars.production[i][l][dayIndex].solutionValue();
                    if (qty > 0.001) {
                        hasProduction = true;
                        sb.append(String.format("|--> %s: %.0f件 (耗时%.1fh,换型%.0f元)%n",
                                config.names[i], qty, qty / config.prodRateByLine[i][l],
                                vars.switchVar[i][l][dayIndex].solutionValue() * config.setupCost[i]));
                    }
                }
            }
            if (hasProduction) {
                System.out.println("    【" + config.lineNames[l] + "】");
                System.out.print(sb);
            }
        }

        // 打印 shortfall 信息（成品层）
        if (vars.shortfall != null) {
            for (int i = 0; i < config.size(); i++) {
                double sht = vars.shortfall[i][dayIndex].solutionValue();
                if (sht > 0.001) {
                    System.out.printf("    ⚠ %s 未满足需求（shortfall）：%.0f 件%n",
                            config.names[i], sht);
                }
            }
        }
    }

    /**
     * 打印采购到货结果
     */
    static void printPurchaseResult(LayerVariables vars, LayerConfig config,
                                    int arriveDay) {
        System.out.println("  ▶ 原材料到货（第" + (arriveDay + 1) + "天下单）");
        for (int r = 0; r < config.size(); r++) {
            double qty = vars.purchase[r][arriveDay].solutionValue();
            if (qty > 0.001) {
                System.out.printf("    %s：到货 %.0f 件（采购成本 %.0f元）%n",
                        config.names[r], qty, qty * config.prodCost[r]);
            }
        }
    }

    /**
     * 打印库存结果
     */
    static void printInventoryResult(String label, LayerVariables vars,
                                     LayerConfig config, int dayIndex) {
        System.out.print("    " + label + "：");
        for (int i = 0; i < config.size(); i++) {
            System.out.printf("%s=%.0f  ", config.names[i],
                    vars.inventory[i][dayIndex].solutionValue());
        }
        System.out.println();
    }

    /**
     * 计算层成本明细
     * 返回: double[]{生产总成本, 库存持有成本, 换型成本, shortfall成本}
     */
    static double[] calcProductionCost(LayerVariables vars, LayerConfig config, int numDays) {
        double prodCost = 0, holdCost = 0, setupCost = 0, shortfallCost = 0;
        for (int i = 0; i < config.size(); i++) {
            for (int l = 0; l < config.lineCount(); l++) {
                if (config.canProduce(i, l)) {
                    for (int t = 0; t < numDays; t++) {
                        prodCost += vars.production[i][l][t].solutionValue() * config.prodCost[i];
                        setupCost += vars.switchVar[i][l][t].solutionValue() * config.setupCost[i];
                    }
                }
            }
            for (int t = 0; t < numDays; t++) {
                holdCost += vars.inventory[i][t].solutionValue() * config.holdCost[i];
            }
        }
        // shortfall 成本
        if (vars.shortfall != null) {
            for (int i = 0; i < config.size(); i++) {
                for (int t = 0; t < numDays; t++) {
                    shortfallCost += vars.shortfall[i][t].solutionValue() * config.shortfallPenalty;
                }
            }
        }
        return new double[]{prodCost, holdCost, setupCost, shortfallCost};
    }

    /**
     * 计算采购层成本
     * 返回: double[]{采购成本, 库存持有成本}
     */
    static double[] calcPurchaseCost(LayerVariables vars, LayerConfig config, int numDays) {
        double purchaseCost = 0, holdCost = 0;
        for (int i = 0; i < config.size(); i++) {
            for (int t = 0; t < numDays; t++) {
                purchaseCost += vars.purchase[i][t].solutionValue() * config.prodCost[i];
                holdCost += vars.inventory[i][t].solutionValue() * config.holdCost[i];
            }
        }
        return new double[]{purchaseCost, holdCost};
    }

    // ========== 主方法 ==========

    public static void main(String[] args) {
        Loader.loadNativeLibraries();

        // ========== 1. 维度定义 ==========
        int numDays = 3;
        int purchaseLeadTime = 1;
        double bigM = 10000;

        // ========== 2. 产线定义 ==========
        String[] lineNames = {"L1", "L2", "L3"};
        int numLines = lineNames.length;
        double[] lineCapacity = {10, 10, 8};  // 每条产线日产能（小时）

        // ========== 3. 产品层配置 ==========

        // 成品（可在L1或L3生产）
        String[] productNames = {"P1", "P2"};
        int numProducts = productNames.length;
        boolean[][] productCanProduce = {
                {true, false, true},   // P1: L1、L3 可生产
                {true, false, true}    // P2: L1、L3 可生产
        };
        double[][] productProdRateByLine = {
                {10, 0, 8},   // P1在L1效率10件/小时，L3为8
                {8, 0, 6}     // P2在L1效率8件/小时，L3为6
        };

        // shortfall 惩罚成本（高价值产品，优先满足需求）
        double shortfallPenalty = 1000;  // 每缺1件罚1000元

        LayerConfig productConfig = new LayerConfig(
                productNames, lineNames,
                new double[]{10, 15},          // prodCost
                new double[]{200, 300},         // setupCost
                new double[]{1.0, 1.5},         // holdCost
                new double[]{20, 10},           // initInv
                lineCapacity,
                productProdRateByLine,
                productCanProduce,
                shortfallPenalty                // 有缺口惩罚
        );

        // 半成品（可在L2或L3生产）
        String[] semiNames = {"S1", "S2", "S3"};
        int numSemi = semiNames.length;
        boolean[][] semiCanProduce = {
                {false, true, true},   // S1: L2、L3
                {false, true, true},   // S2: L2、L3
                {false, true, true}    // S3: L2、L3
        };
        double[][] semiProdRateByLine = {
                {0, 30, 25},   // S1
                {0, 25, 20},   // S2
                {0, 40, 30}    // S3
        };

        LayerConfig semiConfig = new LayerConfig(
                semiNames, lineNames,
                new double[]{3, 4, 2},
                new double[]{80, 100, 60},
                new double[]{0.3, 0.4, 0.2},
                new double[]{50, 30, 40},
                lineCapacity,
                semiProdRateByLine,
                semiCanProduce,
                0  // 半成品无 shortfall（由成品需求推导）
        );

        // 原材料（外购，无生产）
        String[] materialNames = {"R1", "R2", "R3"};
        int numMaterials = materialNames.length;
        boolean[][] materialCanProduce = {
                {false, false, false},
                {false, false, false},
                {false, false, false}
        };

        LayerConfig materialConfig = new LayerConfig(
                materialNames, lineNames,
                new double[]{1, 1.5, 2},
                null,  // setupCost 不适用
                new double[]{0.1, 0.15, 0.2},
                new double[]{200, 100, 80},
                lineCapacity,
                null,  // prodRateByLine 不适用
                materialCanProduce,
                0  // 原材料无 shortfall
        );

        // ========== 4. BOM 结构 ==========
        // bomProduct[s][i]: 1件成品i 需要半成品s 的数量
        double[][] bomProduct = {
                {2, 0},   // S1
                {0, 1},   // S2
                {1, 2}    // S3
        };

        // bomMaterial[r][s]: 1件半成品s 需要原料r 的数量
        double[][] bomMaterial = {
                {3, 0, 1},   // R1
                {0, 2, 0},   // R2
                {0, 0, 1}    // R3
        };

        // ========== 5. 成品需求 ==========
        double[][] demand = {
                {50, 60, 40},   // P1
                {30, 40, 50}    // P2
        };

        // ========== 6. 创建求解器 ==========
        MPSolver solver = new MPSolver("demo",
                MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING);
        solver.enableOutput();
        // ========== 7. 创建决策变量 ==========
        LayerVariables productVars = createLayerVariables(solver, productConfig, numDays, false);
        LayerVariables semiVars = createLayerVariables(solver, semiConfig, numDays, false);
        LayerVariables materialVars = createLayerVariables(solver, materialConfig, numDays, true);

        // ========== 8. 目标函数 ==========
        MPObjective obj = solver.objective();
        addProductionObjective(obj, productVars, productConfig, numDays);
        addProductionObjective(obj, semiVars, semiConfig, numDays);
        addPurchaseObjective(obj, materialVars, materialConfig, numDays);
        obj.setMinimization();

        // ========== 9. 约束条件 ==========

        // 9.1 成品层：库存平衡（外生需求 + shortfall）
        addInventoryBalanceWithDemand(solver, productVars, productConfig, demand, numDays);

        // 9.2 半成品层：库存平衡（BOM展开）
        addInventoryBalanceWithBom(solver, semiVars, semiConfig,
                productVars, bomProduct, numDays);

        // 9.3 原材料层：库存平衡（BOM + 采购提前期）
        addInventoryBalanceWithPurchase(solver, materialVars, materialConfig,
                semiVars, bomMaterial, numDays, purchaseLeadTime);

        // 9.4 产能约束（按产线）
        addCapacityConstraint(solver, productVars, productConfig, numDays, "prod");
        addCapacityConstraint(solver, semiVars, semiConfig, numDays, "semi");

        // 9.5 生产开关约束
        addProductionSwitchConstraint(solver, productVars, productConfig, numDays, bigM, "prod");
        addProductionSwitchConstraint(solver, semiVars, semiConfig, numDays, bigM, "semi");

        // ========== 10. 求解 ==========
        System.out.println("========== 多阶BOM + MRP 排产求解 ==========");
        System.out.printf("成品%d种，半成品%d种，原料%d种，产线%d条，周期%d天%n",
                numProducts, numSemi, numMaterials, numLines, numDays);
        System.out.printf("产线：%s，成品可生产：P1(L1,L3) P2(L1,L3)，半成品可生产：S1-S3(L2,L3)%n",
                java.util.Arrays.toString(lineNames));
        System.out.printf("需求缺口惩罚：%.0f 元/件%n%n", shortfallPenalty);

        MPSolver.ResultStatus status = solver.solve();

        // ========== 11. 结果输出 ==========
        if (status == MPSolver.ResultStatus.OPTIMAL) {
            System.out.println("✅ 求解成功！全局最优解");
            System.out.printf("最小总成本：%.2f 元%n%n", obj.value());

            boolean hasShortfall = false;
            for (int t = 0; t < numDays; t++) {
                System.out.println("═══════════════════ 第 " + (t + 1) + " 天 ═══════════════════");

                // 成品生产
                printProductionResult(productVars, productConfig, t);

                // 半成品生产
                printProductionResult(semiVars, semiConfig, t);

                // 原材料采购到货
                int arriveDay = t - purchaseLeadTime;
                if (arriveDay >= 0) {
                    printPurchaseResult(materialVars, materialConfig, arriveDay);
                }

                // 期末库存
                System.out.println("  ▶ 期末库存");
                printInventoryResult("成品", productVars, productConfig, t);
                printInventoryResult("半成", semiVars, semiConfig, t);
                printInventoryResult("原料", materialVars, materialConfig, t);
                System.out.println();
            }

            // ========== 12. 成本明细 ==========
            double[] costP = calcProductionCost(productVars, productConfig, numDays);
            double[] costS = calcProductionCost(semiVars, semiConfig, numDays);
            double[] costM = calcPurchaseCost(materialVars, materialConfig, numDays);

            System.out.println("═══════════════════ 成本明细 ═══════════════════");
            System.out.printf("成品生产：%8.2f  (换型 %.0f + 物料加工 %.0f)%n",
                    costP[0] + costP[2], costP[2], costP[0]);
            System.out.printf("半品生产：%8.2f  (换型 %.0f + 物料加工 %.0f)%n",
                    costS[0] + costS[2], costS[2], costS[0]);
            System.out.printf("原料采购：%8.2f%n", costM[0]);
            System.out.printf("库存持有：%8.2f  (成品%.1f + 半成%.1f + 原料%.1f)%n",
                    costP[1] + costS[1] + costM[1], costP[1], costS[1], costM[1]);
            if (costP[3] > 0.001) {
                System.out.printf("⚠ 需求缺口：%8.2f 元%n", costP[3]);
                hasShortfall = true;
            }
            System.out.printf("──────────────────────────────%n");
            System.out.printf("总  成 本：%8.2f 元%n", obj.value());

            if (hasShortfall) {
                System.out.println();
                System.out.println("⚠ 注意：存在需求缺口，部分订单未满足。请检查产能或增加产能投入。");
            }

            System.out.println();
            System.out.println("═══════════════════ 求解统计 ═══════════════════");
            System.out.println("变量数：" + solver.numVariables());
            System.out.println("约束数：" + solver.numConstraints());
            System.out.printf("耗时：%.3f 秒%n", solver.wallTime() / 1000.0);

        } else if (status == MPSolver.ResultStatus.INFEASIBLE) {
            System.out.println("❌ 无解 —— 请检查产能、库存或需求约束是否合理");
        } else {
            System.out.println("求解状态：" + status);
        }
    }
}