#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <functional>
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "json.hpp"   // nlohmann::json (单头文件版)
#include "pq_app.h"
#include "tinyxml2.h" // tinyxml2

using json = nlohmann::json;
namespace fs = std::filesystem;

struct ValueMeta {
    std::string name;
    std::string desc;
    std::string doPath;
    std::string daPath;
    std::string wiring = "common"; // star/delta/common
    std::string topic;
    std::string dataType;
    std::string itemName;
    std::string seqValue;
    bool hasOffset = false;
    int offset = 0;

    bool isSequence = false;
    int seqStart = 0;   // 例如 %2,50% 里的 2
    int seqEnd = 0;     // 例如 %2,50% 里的 50
    std::string seqPrefixName; // 例如 MAX_V
};

static std::string readAll(const fs::path& p) {
    std::ifstream ifs(p, std::ios::binary);
    if (!ifs) throw std::runtime_error("Failed to open: " + p.string());
    std::ostringstream oss;
    oss << ifs.rdbuf();
    return oss.str();
}

static std::string ltrimBOM(std::string s) {
    const std::string bom = "\xEF\xBB\xBF";
    if (s.rfind(bom, 0) == 0) s.erase(0, bom.size());
    return s;
}

static std::string collapseRepeatedAlternatives(const std::string& s) {
    std::vector<std::string> parts;
    std::unordered_set<std::string> seen;
    size_t start = 0;
    while (true) {
        size_t pos = s.find("||", start);
        std::string part = pos == std::string::npos ? s.substr(start) : s.substr(start, pos - start);
        if (!seen.count(part)) {
            seen.insert(part);
            parts.push_back(part);
        }
        if (pos == std::string::npos) break;
        start = pos + 2;
    }
    std::ostringstream oss;
    for (size_t i = 0; i < parts.size(); ++i) {
        if (i) oss << "||";
        oss << parts[i];
    }
    return oss.str();
}

static std::string toStr(const json& v) {
    if (v.is_null()) return "null";
    if (v.is_boolean()) return v.get<bool>() ? "true" : "false";
    if (v.is_number_integer()) return std::to_string(v.get<long long>());
    if (v.is_number_unsigned()) return std::to_string(v.get<unsigned long long>());
    if (v.is_number_float()) {
        std::ostringstream oss;
        oss.setf(std::ios::fixed);
        oss.precision(6);
        oss << v.get<double>();
        std::string s = oss.str();
        while (s.find('.') != std::string::npos && !s.empty() && s.back() == '0') s.pop_back();
        if (!s.empty() && s.back() == '.') s.pop_back();
        if (s.empty()) s = "0";
        return s;
    }
    if (v.is_string()) return v.get<std::string>();
    return v.dump();
}

static void flattenJson(const json& j, const std::string& prefix,
                        std::vector<std::pair<std::string, std::string>>& out) {
    if (j.is_object()) {
        for (auto it = j.begin(); it != j.end(); ++it) {
            std::string key = prefix.empty() ? it.key() : (prefix + "_" + it.key());
            flattenJson(it.value(), key, out);
        }
        return;
    }
    if (j.is_array()) {
        std::ostringstream oss;
        for (size_t i = 0; i < j.size(); ++i) {
            if (i) oss << ",";
            oss << toStr(j[i]);
        }
        out.push_back({prefix, oss.str()});
        return;
    }
    out.push_back({prefix, toStr(j)});
}

// 读取 XML：Value/@name -> 完整元信息（desc/DO/DA）
static std::unordered_map<std::string, std::vector<ValueMeta>> loadValueMetas(const fs::path& xmlPath) {
    std::unordered_map<std::string, std::vector<ValueMeta>> mp;
    tinyxml2::XMLDocument doc;

    std::string xmlContent = readAll(xmlPath);

    if (doc.Parse(xmlContent.c_str()) != tinyxml2::XML_SUCCESS) {
        throw std::runtime_error("Failed to parse xml: " + xmlPath.string());
    }

    std::regex seqRe(R"(^(.*)%(\d+),(\d+)%$)");

    std::function<void(tinyxml2::XMLElement*, std::string, std::string, std::string, std::string, std::string)> walk =
        [&](tinyxml2::XMLElement* e,
            std::string wiring,
            std::string topic,
            std::string dataType,
            std::string itemName,
            std::string seqValue) {
        for (; e; e = e->NextSiblingElement()) {
            std::string nodeName = e->Name() ? e->Name() : "";
            std::string nextWiring = wiring;
            std::string nextTopic = topic;
            std::string nextDataType = dataType;
            std::string nextItemName = itemName;
            std::string nextSeqValue = seqValue;
            if (nodeName == "Topic") {
                const char* name = e->Attribute("name");
                nextTopic = name ? name : "";
            } else if (nodeName == "DataType") {
                const char* value = e->Attribute("value");
                nextDataType = value ? value : "";
            } else if (nodeName == "Item") {
                const char* name = e->Attribute("name");
                nextItemName = name ? name : "";
            }
            if (nodeName == "Sequence") {
                const char* seqValue = e->Attribute("value");
                nextSeqValue = seqValue ? seqValue : "";
                if (seqValue && std::string(seqValue) == "7") nextWiring = "star";
                else if (seqValue && std::string(seqValue) == "112") nextWiring = "delta";
            }

            if (nodeName == "Value") {
                const char* name = e->Attribute("name");
                const char* desc = e->Attribute("desc");
                const char* doPath = e->Attribute("DO");
                const char* daPath = e->Attribute("DA");
                const char* offset = e->Attribute("Offset");

                if (name) {
                    ValueMeta vm;
                    vm.name = name;
                    vm.desc = desc ? desc : "";
                    vm.doPath = doPath ? collapseRepeatedAlternatives(doPath) : "";
                    vm.daPath = daPath ? collapseRepeatedAlternatives(daPath) : "";
                    vm.wiring = nextWiring;
                    vm.topic = nextTopic;
                    vm.dataType = nextDataType;
                    vm.itemName = nextItemName;
                    vm.seqValue = nextSeqValue;
                    if (offset) {
                        vm.hasOffset = true;
                        vm.offset = std::stoi(offset);
                    }

                    std::smatch m;
                    if (std::regex_match(vm.name, m, seqRe)) {
                        vm.isSequence = true;
                        vm.seqPrefixName = m[1].str();   // 例如 MAX_V
                        vm.seqStart = std::stoi(m[2].str());
                        vm.seqEnd = std::stoi(m[3].str());
                    }

                    mp[vm.name].push_back(vm);
                }
            }
            walk(e->FirstChildElement(), nextWiring, nextTopic, nextDataType, nextItemName, nextSeqValue);
        }
    };

    walk(doc.RootElement(), "common", "", "", "", "");
    return mp;
}

// 相位
static const std::vector<std::string> PHASES = {"A", "B", "C", "T"};
static std::unordered_map<std::string, int> phaseRank() {
    std::unordered_map<std::string, int> m;
    for (int i = 0; i < (int)PHASES.size(); ++i) m[PHASES[i]] = i;
    return m;
}

// 从 key 中提取 “相位之后的 metricName”
// 例如：Value_V_A_MAX_V1 -> MAX_V1
static std::string metricAfterPhase(const std::string& key) {
    std::string base = key;
    auto bpos = base.find('[');
    if (bpos != std::string::npos) base = base.substr(0, bpos);

    std::vector<std::string> parts;
    std::stringstream ss(base);
    std::string tok;
    while (std::getline(ss, tok, '_')) parts.push_back(tok);

    auto pr = phaseRank();
    int lastPhaseI = -1;
    for (int i = 0; i < (int)parts.size(); ++i) {
        if (pr.find(parts[i]) != pr.end()) lastPhaseI = i;
    }
    if (lastPhaseI == -1 || lastPhaseI + 1 >= (int)parts.size()) return "";

    std::string m = parts[lastPhaseI + 1];
    for (int i = lastPhaseI + 2; i < (int)parts.size(); ++i) m += "_" + parts[i];
    return m;
}

// 尝试把 key 识别成序列：
// 1) ..._<number>
// 2) ..._<LETTERS><number> (如 ..._V2, ..._VA50)
static bool parseSequenceKey(const std::string& key, std::string& groupId, int& idx) {
    auto posBracket = key.find('[');
    std::string base = (posBracket == std::string::npos) ? key : key.substr(0, posBracket);

    static const std::regex re_underscore_num(R"(^(.*)_(\d+)$)");
    static const std::regex re_letters_num(R"(^([A-Za-z]+)(\d+)$)");

    std::smatch m;
    if (std::regex_match(base, m, re_underscore_num)) {
        groupId = m[1].str();
        idx = std::stoi(m[2].str());
        return true;
    }

    auto lastUnd = base.rfind('_');
    if (lastUnd != std::string::npos) {
        std::string pre = base.substr(0, lastUnd);
        std::string last = base.substr(lastUnd + 1);
        std::smatch m2;
        if (std::regex_match(last, m2, re_letters_num)) {
            groupId = pre + "_" + m2[1].str();
            idx = std::stoi(m2[2].str());
            return true;
        }
    }
    return false;
}

// ✅ 合并序列：但如果 metricName 在 xmlNames 里存在（比如 V1/MAX_V1/G_V1），则禁止合并
static std::vector<std::pair<std::string, std::string>>
mergeSequences(const std::vector<std::pair<std::string, std::string>>& flat,
               const std::unordered_set<std::string>& xmlNames) {
    struct SeqGroup { size_t firstPos; std::map<int, std::string> idx2val; };

    std::unordered_map<std::string, SeqGroup> groups;
    std::vector<bool> isSeq(flat.size(), false);
    std::vector<std::string> key2group(flat.size());

    for (size_t i = 0; i < flat.size(); ++i) {
        const auto& k = flat[i].first;

        // ✅ 如果 XML 定义了该指标（例如 V1/MAX_V1/...），就不要当作序列
        std::string mname = metricAfterPhase(k);
        if (!mname.empty() && xmlNames.count(mname)) {
            continue;
        }

        std::string gid; int idx = 0;
        if (parseSequenceKey(k, gid, idx)) {
            isSeq[i] = true;
            key2group[i] = gid;
            auto& g = groups[gid];
            if (g.idx2val.empty()) g.firstPos = i;
            g.idx2val[idx] = flat[i].second;
        }
    }

    std::unordered_map<std::string, bool> mergeable;
    for (auto& kv : groups) {
        mergeable[kv.first] = (kv.second.idx2val.size() >= 2);
    }

    std::vector<std::pair<std::string, std::string>> out;
    std::unordered_map<std::string, bool> emitted;

    for (size_t i = 0; i < flat.size(); ++i) {
        if (isSeq[i] && mergeable[key2group[i]]) {
            const std::string& gid = key2group[i];
            if (emitted[gid]) continue;
            emitted[gid] = true;

            const auto& g = groups[gid];
            int start = g.idx2val.begin()->first;
            int end = g.idx2val.rbegin()->first;

            std::ostringstream oss;
            for (int x = start; x <= end; ++x) {
                if (x != start) oss << ",";
                auto it = g.idx2val.find(x);
                oss << (it == g.idx2val.end() ? "null" : it->second);
            }

            std::ostringstream key;
            key << gid << "[" << start << "-" << end << "]";
            out.push_back({key.str(), oss.str()});
        } else {
            out.push_back(flat[i]);
        }
    }

    return out;
}

struct ParsedKey {
    std::string phase;    // A/B/C/T/""
    std::string stat;     // 95值/平均值/最大值/最小值
    std::string baseName; // 去掉 G_/MAX_/MIN_ 后的 name
};

static ParsedKey parseForSort(const std::string& key) {
    std::string base = key;
    auto bpos = base.find('[');
    if (bpos != std::string::npos) base = base.substr(0, bpos);

    std::vector<std::string> parts;
    std::stringstream ss(base);
    std::string tok;
    while (std::getline(ss, tok, '_')) parts.push_back(tok);

    auto pr = phaseRank();
    int lastPhaseI = -1;
    std::string phase;
    for (int i = 0; i < (int)parts.size(); ++i) {
        if (pr.find(parts[i]) != pr.end()) {
            lastPhaseI = i;
            phase = parts[i];
        }
    }

    std::string metric;
    if (lastPhaseI != -1 && lastPhaseI + 1 < (int)parts.size()) {
        metric = parts[lastPhaseI + 1];
        for (int i = lastPhaseI + 2; i < (int)parts.size(); ++i) metric += "_" + parts[i];
    } else {
        metric = parts.empty() ? "" : parts.back();
    }

    ParsedKey pk;
    pk.phase = phase;
    pk.stat = "平均值";
    pk.baseName = metric;

    auto startsWith = [&](const std::string& s, const std::string& pre) {
        return s.size() >= pre.size() && s.compare(0, pre.size(), pre) == 0;
    };

    if (startsWith(metric, "G_")) { pk.stat = "95值"; pk.baseName = metric.substr(2); }
    else if (startsWith(metric, "MAX_")) { pk.stat = "最大值"; pk.baseName = metric.substr(4); }
    else if (startsWith(metric, "MIN_")) { pk.stat = "最小值"; pk.baseName = metric.substr(4); }

    return pk;
}

static std::string cleanDescSuffix(std::string desc) {
    static const std::regex re(R"((平均值|最大值|最小值|95值)$)");
    return std::regex_replace(desc, re, "");
}

static int getDisplayWidth(const std::string& s) {
    int w = 0;
    for (size_t i = 0; i < s.size(); ) {
        unsigned char c = static_cast<unsigned char>(s[i]);

        if (c < 0x80) {          // ASCII
            w += 1;
            ++i;
        } else if ((c & 0xE0) == 0xC0 && i + 1 < s.size()) {   // 2字节UTF-8
            w += 2;
            i += 2;
        } else if ((c & 0xF0) == 0xE0 && i + 2 < s.size()) {   // 3字节UTF-8，中文常见
            w += 2;
            i += 3;
        } else if ((c & 0xF8) == 0xF0 && i + 3 < s.size()) {   // 4字节UTF-8
            w += 2;
            i += 4;
        } else {
            w += 1;
            ++i;
        }
    }
    return w;
}

static void appendPad(std::ostringstream& oss, const std::string& s, int targetWidth) {
    oss << s;
    int pad = targetWidth - getDisplayWidth(s);
    if (pad < 1) pad = 1;   // 至少留 1 个空格，避免粘连
    oss << std::string(pad, ' ');
}

static bool startsWithStr(const std::string& s, const std::string& pre) {
    return s.size() >= pre.size() && s.compare(0, pre.size(), pre) == 0;
}

// 从类似 Value_V_A_MAX_V[2-50] 中取出区间
static bool parseKeyRange(const std::string& key, int& start, int& end) {
    std::smatch m;
    std::regex re(R"(\[(\d+)-(\d+)\])");
    if (std::regex_search(key, m, re)) {
        start = std::stoi(m[1].str());
        end = std::stoi(m[2].str());
        return true;
    }
    return false;
}

// 给普通项找 meta；找不到时，再去匹配序列模板项
static std::string trimRightUnderscore(std::string s) {
    while (!s.empty() && s.back() == '_') {
        s.pop_back();
    }
    return s;
}

static std::string rawMetricNameFromKey(const std::string& key) {
    return metricAfterPhase(key);
}

static std::string preferredMetaName(const std::string& rawMetric, const ParsedKey& pk) {
    if (startsWithStr(rawMetric, "G_")) return pk.baseName;
    return rawMetric.empty() ? pk.baseName : rawMetric;
}

static std::string secondaryMetaName(const std::string& rawMetric, const ParsedKey& pk) {
    std::string preferred = preferredMetaName(rawMetric, pk);
    std::string secondary = (preferred == pk.baseName) ? rawMetric : pk.baseName;
    return secondary == preferred ? "" : secondary;
}

static bool isNeutralDesc(const std::string& desc) {
    return desc.find("95值") == std::string::npos &&
           desc.find("平均值") == std::string::npos &&
           desc.find("最大值") == std::string::npos &&
           desc.find("最小值") == std::string::npos;
}

static bool statMatchesMeta(const ValueMeta& vm, const ParsedKey& pk) {
    return !pk.stat.empty() && vm.desc.find(pk.stat) != std::string::npos;
}

static bool daMatchesPhase(const std::string& daPath, const std::string& phase) {
    bool phased = daPath.find("phs*") != std::string::npos;
    if (phase == "A" || phase == "B" || phase == "C") return phased;
    if (phase == "T") return !phased;
    return true;
}

static bool wiringAllowed(const ValueMeta& vm, const std::string& wiring) {
    if (wiring.empty() || wiring == "any") return true;
    return vm.wiring == wiring || vm.wiring == "common";
}

struct MetaFilter {
    std::string topic;
    std::string dataType;
};

static bool metaFilterAllowed(const ValueMeta& vm, const MetaFilter& filter) {
    if (!filter.topic.empty() && vm.topic != filter.topic) return false;
    if (!filter.dataType.empty() && vm.dataType != filter.dataType) return false;
    return true;
}

static int scoreMetaForKey(const ValueMeta& vm, const ParsedKey& pk) {
    int score = 0;
    if (daMatchesPhase(vm.daPath, pk.phase)) score += 20;
    if (!pk.stat.empty() && vm.desc.find(pk.stat) != std::string::npos) score += 8;
    if (startsWithStr(pk.stat, "95") && isNeutralDesc(vm.desc)) score += 6;
    if (vm.hasOffset) score += 5;
    if (pk.phase == "T" && vm.doPath.find("Tot") != std::string::npos) score += 4;
    if ((pk.phase == "A" || pk.phase == "B" || pk.phase == "C") && vm.doPath.find("Tot") == std::string::npos) score += 4;
    return score;
}

static const ValueMeta* bestMeta(const std::vector<const ValueMeta*>& candidates, const ParsedKey& pk) {
    const ValueMeta* best = nullptr;
    int bestScore = -1;
    bool hasStatMatched = false;
    for (const ValueMeta* vm : candidates) {
        if (statMatchesMeta(*vm, pk)) {
            hasStatMatched = true;
            break;
        }
    }
    for (const ValueMeta* vm : candidates) {
        if (hasStatMatched && !statMatchesMeta(*vm, pk)) continue;
        int score = scoreMetaForKey(*vm, pk);
        if (!best || score > bestScore) {
            best = vm;
            bestScore = score;
        }
    }
    return best;
}

static void collectByName(const std::unordered_map<std::string, std::vector<ValueMeta>>& metas,
                          const std::string& name,
                          const std::string& wiring,
                          const MetaFilter& filter,
                          std::vector<const ValueMeta*>& out) {
    if (name.empty()) return;
    auto it = metas.find(name);
    if (it == metas.end()) return;
    for (const auto& vm : it->second) {
        if (wiringAllowed(vm, wiring) && metaFilterAllowed(vm, filter)) out.push_back(&vm);
    }
}

static std::vector<const ValueMeta*> collectSequenceCandidates(
    const std::unordered_map<std::string, std::vector<ValueMeta>>& metas,
    const std::string& wantedName,
    const ParsedKey& pk,
    const std::string& wiring,
    const MetaFilter& filter,
    int keyStart,
    int keyEnd)
{
    std::vector<const ValueMeta*> out;
    std::string wanted = trimRightUnderscore(wantedName);
    for (const auto& kv : metas) {
        for (const ValueMeta& vm : kv.second) {
            if (!vm.isSequence) continue;
            if (!wiringAllowed(vm, wiring)) continue;
            if (!metaFilterAllowed(vm, filter)) continue;
            if (vm.seqStart != keyStart || vm.seqEnd != keyEnd) continue;

            std::string xmlPrefix = trimRightUnderscore(vm.seqPrefixName);
            std::string xmlBase = xmlPrefix;
            if (startsWithStr(xmlBase, "G_")) xmlBase = xmlBase.substr(2);
            else if (startsWithStr(xmlBase, "MAX_")) xmlBase = xmlBase.substr(4);
            else if (startsWithStr(xmlBase, "MIN_")) xmlBase = xmlBase.substr(4);

            if (xmlPrefix == wanted || xmlBase == wanted) out.push_back(&vm);
        }
    }
    return out;
}

static const ValueMeta* findMetaForKey(
    const std::unordered_map<std::string, std::vector<ValueMeta>>& metas,
    const std::string& key,
    const ParsedKey& pk,
    const std::string& wiring = "any",
    const MetaFilter& filter = {})
{
    std::string rawMetric = rawMetricNameFromKey(key);
    std::string preferred = preferredMetaName(rawMetric, pk);
    std::string secondary = secondaryMetaName(rawMetric, pk);

    int keyStart = 0, keyEnd = 0;
    if (parseKeyRange(key, keyStart, keyEnd)) {
        std::string seqPreferred = rawMetric.empty() ? preferred : rawMetric;
        std::string seqSecondary = (seqPreferred == preferred) ? secondary : preferred;
        auto seqCandidates = collectSequenceCandidates(metas, seqPreferred, pk, wiring, filter, keyStart, keyEnd);
        if (seqCandidates.empty()) seqCandidates = collectSequenceCandidates(metas, seqSecondary, pk, wiring, filter, keyStart, keyEnd);
        if (!seqCandidates.empty()) return bestMeta(seqCandidates, pk);
    }

    std::vector<const ValueMeta*> exactCandidates;
    collectByName(metas, preferred, wiring, filter, exactCandidates);
    if (exactCandidates.empty()) collectByName(metas, secondary, wiring, filter, exactCandidates);
    return bestMeta(exactCandidates, pk);
}
// 把 XML 中序列 DA，如 phs*Har[%-2]$mag$f，改显示成 phs*Har[0-48]$mag$f
static std::string rewriteDaRangeForDisplay(const std::string& daPath,
                                            const ValueMeta& vm,
                                            int keyStart,
                                            int keyEnd) {
    if (!vm.isSequence) return daPath;

    // 规则：
    // XML 模板里是 [%-2]
    // key 是 [2-50]
    // 显示要变成 [0-48]
    // 即：keyStart - vm.seqStart, keyEnd - vm.seqStart
    int dispStart = keyStart - vm.seqStart;
    int dispEnd = keyEnd - vm.seqStart;

    std::smatch m;
    std::regex re(R"(\[%-(\d+)\])");
    if (std::regex_search(daPath, m, re)) {
        std::ostringstream oss;
        oss << "[" << dispStart << "-" << dispEnd << "]";
        return std::regex_replace(daPath, re, oss.str(), std::regex_constants::format_first_only);
    }

    return daPath;
}

// 在当前目录找一个后缀文件（找不到就抛）
static fs::path findFirstByExt(const std::string& ext) {
    for (auto& e : fs::directory_iterator(fs::current_path())) {
        if (!e.is_regular_file()) continue;
        if (e.path().extension() == ext) return e.path();
    }
    throw std::runtime_error("No file found with extension: " + ext + " in current folder");
}

static fs::path findFirstInputTxt() {
    for (auto& e : fs::directory_iterator(fs::current_path())) {
        if (!e.is_regular_file()) continue;
        if (e.path().extension() != ".txt") continue;

        std::string name = e.path().filename().string();
        if (name == "final_sorted.txt") continue;   // 跳过输出文件

        return e.path();
    }
    throw std::runtime_error("No input .txt found in current folder");
}

struct HeaderItem {
    std::string key;
    std::string val;
};

static bool isHeaderKey(const std::string& key) {
    return key == "DATA_TYPE" ||
           key == "Monitor" ||
           key == "Value_FLAG" ||
           key == "Value_TIME" ||
           key == "Value_interval";
}

// 【新增】相别排序：A相 -> B相 -> C相 -> T相
static int phaseOrderOf(const std::string& phase)
{
    if (phase == "A") return 1;
    if (phase == "B") return 2;
    if (phase == "C") return 3;
    if (phase == "T") return 0;
    return 99;
}

struct OutItem {
    std::string stat;
    std::string category;
    int phaseOrder;          // 【新增】A/B/C/T 排序
    std::string attrSort;
    std::string phase;
    std::string line;
    std::string label;
    std::string arrayBase;
    std::string valueOffset;
    std::string finalOffset;
    std::string configText;
    std::string finalText;
    std::string doPath;
    std::string daPath;
    std::string wiring = "common"; // star/delta/common
    bool hasOffset = false;
    int offset = 0;
};

struct SheetSpec {
    std::string name;
    std::string topic;
    std::string dataType;
    std::string wiring;
    bool showStatBlocks = false;
    std::string frontType;
};

struct OriginRecord {
    std::string path;
    std::string value;
    std::string reportName;
    int sourceOrder = 0;
};

struct OriginData {
    std::vector<OriginRecord> records;
    std::unordered_map<std::string, std::vector<size_t>> pathToRecords;
    std::unordered_set<size_t> used;
};

struct OriginMatch {
    std::string path;
    std::string value;
    std::string reportName;
};

struct JsonDataBucket {
    std::vector<HeaderItem> headers;
    std::unordered_map<std::string, std::string> valueByKey;
    bool hasData = false;
};

struct WorkbookContext {
    fs::path xmlPath;
    std::unordered_map<std::string, std::vector<ValueMeta>> valueMetas;
    std::unordered_set<std::string> xmlNames;
    std::unordered_map<std::string, JsonDataBucket> dataByType;
    OriginData originData;
    bool hasOrigin = false;
    std::string frontType;
};

static std::string jsonScalarForOutput(const json& v) {
    if (v.is_string()) return v.get<std::string>();
    return v.dump();
}

static std::string reportNameFromRoot(const json& root, const fs::path& originPath, int sourceOrder) {
    if (root.is_object() && root.contains("rpt_id")) return jsonScalarForOutput(root["rpt_id"]);
    if (root.is_object() && root.contains("rpt_name")) return jsonScalarForOutput(root["rpt_name"]);
    if (root.is_object() && root.contains("report_name")) return jsonScalarForOutput(root["report_name"]);
    if (root.is_object() && root.contains("rpt_no")) return "rpt_no=" + jsonScalarForOutput(root["rpt_no"]);
    return originPath.filename().u8string();
}

static std::vector<json> parseJsonRoots(const std::string& text) {
    std::vector<json> roots;
    size_t pos = 0;
    while (pos < text.size()) {
        while (pos < text.size() && std::isspace(static_cast<unsigned char>(text[pos]))) ++pos;
        if (pos >= text.size()) break;

        char open = text[pos];
        if (open != '{' && open != '[') {
            throw std::runtime_error("origin file contains unexpected content outside JSON object");
        }

        bool inString = false;
        bool escape = false;
        int depth = 0;
        size_t end = pos;
        for (; end < text.size(); ++end) {
            char c = text[end];
            if (inString) {
                if (escape) escape = false;
                else if (c == '\\') escape = true;
                else if (c == '"') inString = false;
                continue;
            }
            if (c == '"') {
                inString = true;
            } else if (c == '{' || c == '[') {
                ++depth;
            } else if (c == '}' || c == ']') {
                --depth;
                if (depth == 0) {
                    ++end;
                    break;
                }
            }
        }

        if (depth != 0) throw std::runtime_error("origin file contains incomplete JSON object");
        roots.push_back(json::parse(text.substr(pos, end - pos)));
        pos = end;
    }
    return roots;
}

static void appendOriginRoot(OriginData& data, const fs::path& originPath, int sourceOrder, const json& root) {
    std::string reportName = reportNameFromRoot(root, originPath, sourceOrder);

    const json* mms = &root;
    if (root.is_object() && root.contains("mms_json") && root["mms_json"].is_object()) {
        mms = &root["mms_json"];
    }

    if (!mms->is_object()) {
        throw std::runtime_error("origin file does not contain an object mms_json: " + originPath.string());
    }

    for (auto it = mms->begin(); it != mms->end(); ++it) {
        OriginRecord rec;
        rec.path = it.key();
        rec.value = jsonScalarForOutput(it.value());
        rec.reportName = reportName;
        rec.sourceOrder = sourceOrder;
        size_t idx = data.records.size();
        data.records.push_back(rec);
        data.pathToRecords[rec.path].push_back(idx);
    }
}

static void appendOriginData(OriginData& data, const fs::path& originPath, int sourceOrder) {
    std::string raw = ltrimBOM(readAll(originPath));
    for (const auto& root : parseJsonRoots(raw)) {
        appendOriginRoot(data, originPath, sourceOrder, root);
    }
}

static OriginData loadOriginData(const std::vector<fs::path>& originPaths) {
    OriginData data;
    for (size_t i = 0; i < originPaths.size(); ++i) {
        appendOriginData(data, originPaths[i], static_cast<int>(i));
    }
    return data;
}

static OriginData loadOriginData(const fs::path& originPath) {
    std::vector<fs::path> paths{originPath};
    return loadOriginData(paths);
}

static const OriginRecord* firstUnusedRecord(OriginData* origin, const std::string& path) {
    if (!origin) return nullptr;
    auto it = origin->pathToRecords.find(path);
    if (it == origin->pathToRecords.end()) return nullptr;
    for (size_t idx : it->second) {
        if (!origin->used.count(idx)) return &origin->records[idx];
    }
    if (!it->second.empty()) return &origin->records[it->second.front()];
    return nullptr;
}

static void markRecordUsed(OriginData* origin, const OriginRecord* rec) {
    if (!origin || !rec) return;
    size_t idx = static_cast<size_t>(rec - origin->records.data());
    if (idx < origin->records.size()) origin->used.insert(idx);
}
static std::string statInstanceSuffix(const std::string& stat) {
    if (stat == "平均值") return "1";
    if (stat == "最大值") return "2";
    if (stat == "最小值") return "3";
    if (stat == "95值") return "4";
    return "";
}

static std::string applyStatInstance(const std::string& doPath, const std::string& stat) {
    std::string suffix = statInstanceSuffix(stat);
    if (suffix.empty()) return doPath;

    size_t dollar = doPath.find('$');
    std::string head = dollar == std::string::npos ? doPath : doPath.substr(0, dollar);
    std::string tail = dollar == std::string::npos ? "" : doPath.substr(dollar);
    if (!head.empty() && head.back() == '0') {
        head.back() = suffix[0];
        return head + tail;
    }
    return doPath;
}

static std::string phaseTokenForDa(const std::string& phase, const std::string& wiring) {
    if (wiring == "delta") {
        if (phase == "A") return "phsAB";
        if (phase == "B") return "phsBC";
        if (phase == "C") return "phsCA";
    }
    if (phase == "A") return "phsA";
    if (phase == "B") return "phsB";
    if (phase == "C") return "phsC";
    return "phs";
}

static std::string concreteDaPath(std::string daPath, const std::string& phase, const std::string& wiring) {
    size_t pos = 0;
    const std::string from = "phs*";
    const std::string to = phaseTokenForDa(phase, wiring);
    while ((pos = daPath.find(from, pos)) != std::string::npos) {
        daPath.replace(pos, from.size(), to);
        pos += to.size();
    }
    return daPath;
}

static std::string makeOriginPath(const std::string& doPath,
                                  const std::string& daPath,
                                  const std::string& stat,
                                  const std::string& phase,
                                  const std::string& wiring) {
    if (doPath.empty() || daPath.empty()) return "";
    return doPath + "$" + concreteDaPath(daPath, phase, wiring);
}

static std::vector<std::string> makeOriginPathCandidates(const std::string& doPath,
                                                         const std::string& daPath,
                                                         const std::string& stat,
                                                         const std::string& phase,
                                                         const std::string& wiring) {
    std::vector<std::string> paths;
    std::string primary = makeOriginPath(doPath, daPath, stat, phase, wiring);
    if (!primary.empty()) paths.push_back(primary);
    return paths;
}static bool parseOriginRangePath(const std::string& path,
                                 std::string& before,
                                 int& start,
                                 int& end,
                                 std::string& after) {
    std::smatch m;
    static const std::regex re(R"(^(.+)\[(\d+)-(\d+)\](.*)$)");
    if (!std::regex_match(path, m, re)) return false;
    before = m[1].str();
    start = std::stoi(m[2].str());
    end = std::stoi(m[3].str());
    after = m[4].str();
    return start <= end;
}

static OriginMatch matchOrigin(OriginData* origin,
                               const std::string& doPath,
                               const std::string& daPath,
                               const std::string& stat,
                               const std::string& phase,
                               const std::string& wiring) {
    OriginMatch match;
    if (!origin) return match;

    for (const std::string& path : makeOriginPathCandidates(doPath, daPath, stat, phase, wiring)) {
        if (path.empty()) continue;

        std::string before, after;
        int start = 0, end = 0;
        if (parseOriginRangePath(path, before, start, end, after)) {
            std::ostringstream values;
            bool any = false;
            std::string reportName;
            for (int i = start; i <= end; ++i) {
                if (i != start) values << ",";
                std::string onePath = before + "[" + std::to_string(i) + "]" + after;
                const OriginRecord* rec = firstUnusedRecord(origin, onePath);
                if (rec) {
                    any = true;
                    values << rec->value;
                    if (reportName.empty()) reportName = rec->reportName;
                    markRecordUsed(origin, rec);
                }
            }
            if (any) {
                match.path = path;
                match.value = values.str();
                match.reportName = reportName;
                return match;
            }
            continue;
        }

        const OriginRecord* rec = firstUnusedRecord(origin, path);
        if (rec) {
            match.path = path;
            match.value = rec->value;
            match.reportName = rec->reportName;
            markRecordUsed(origin, rec);
            return match;
        }
    }

    return match;
}static size_t utf8CharLen(unsigned char c) {
    if (c < 0x80) return 1;
    if ((c & 0xE0) == 0xC0) return 2;
    if ((c & 0xF0) == 0xE0) return 3;
    if ((c & 0xF8) == 0xF0) return 4;
    return 1;
}

static std::vector<std::string> wrapDisplay(const std::string& s, int width) {
    std::vector<std::string> lines;
    if (s.empty()) {
        lines.push_back("");
        return lines;
    }

    std::string current;
    int currentWidth = 0;
    for (size_t i = 0; i < s.size(); ) {
        size_t len = utf8CharLen(static_cast<unsigned char>(s[i]));
        if (i + len > s.size()) len = 1;
        std::string ch = s.substr(i, len);
        int chWidth = getDisplayWidth(ch);

        if (!current.empty() && currentWidth + chWidth > width) {
            lines.push_back(current);
            current.clear();
            currentWidth = 0;
        }

        current += ch;
        currentWidth += chWidth;
        i += len;
    }

    if (!current.empty()) lines.push_back(current);
    return lines;
}

static std::string padCell(const std::string& s, int width) {
    int pad = width - getDisplayWidth(s);
    return s + std::string(pad > 0 ? pad : 0, ' ');
}

static std::string arrayBaseForMeta(const ValueMeta* vm);
static std::string valueOffsetForDa(const std::string& daPath);
static std::string finalOffsetForMeta(const ValueMeta* vm);

static std::string makeCompareLine(const OutItem& item, OriginData* origin) {
    OriginMatch om = matchOrigin(origin, item.doPath, item.daPath, item.stat, item.phase, item.wiring);

    const int NAME_W = 30;
    const int CONFIG_W = 62;
    const int PATH_W = 50;
    const int FINAL_W = 70;
    const int ORIGIN_W = 70;

    std::vector<std::string> nameLines = wrapDisplay(item.label, NAME_W);
    std::vector<std::string> configLines = wrapDisplay(item.configText, CONFIG_W);
    std::vector<std::string> pathLines = wrapDisplay(om.path.empty() ? "" : ("\"" + om.path + "\""), PATH_W);
    std::vector<std::string> finalLines = wrapDisplay(item.finalText, FINAL_W);
    std::vector<std::string> originLines = wrapDisplay(om.value, ORIGIN_W);

    size_t rows = std::max({nameLines.size(), configLines.size(), pathLines.size(), finalLines.size(), originLines.size()});
    std::ostringstream oss;
    for (size_t i = 0; i < rows; ++i) {
        if (i) oss << "\n";
        oss << "| " << padCell(i < nameLines.size() ? nameLines[i] : "", NAME_W)
            << " | " << padCell(i < configLines.size() ? configLines[i] : "", CONFIG_W)
            << " | " << padCell(i < pathLines.size() ? pathLines[i] : "", PATH_W)
            << " | " << padCell(i < finalLines.size() ? finalLines[i] : "", FINAL_W)
            << " | " << padCell(i < originLines.size() ? originLines[i] : "", ORIGIN_W)
            << " |";
    }
    return oss.str();
}

static bool makeOutItemForWiring(const std::unordered_map<std::string, std::vector<ValueMeta>>& valueMetas,
                                 const std::string& key,
                                 const std::string& val,
                                 const ParsedKey& pk,
                                 const std::string& category,
                                 const std::string& wiring,
                                 const MetaFilter& filter,
                                 bool requireMeta,
                                 OutItem& out) {
    const ValueMeta* vm = findMetaForKey(valueMetas, key, pk, wiring, filter);
    if (requireMeta && !vm) return false;

    std::string desc = vm ? vm->desc : "";
    std::string descClean = cleanDescSuffix(desc);
    std::string attrSort = !descClean.empty() ? descClean : pk.baseName;
    std::string label = (attrSort.empty() ? pk.baseName : attrSort) + pk.stat;

    std::string doDisp = vm ? vm->doPath : "";
    std::string daDisp = vm ? vm->daPath : "";

    int keyStart = 0, keyEnd = 0;
    if (vm && vm->isSequence && parseKeyRange(key, keyStart, keyEnd)) {
        daDisp = rewriteDaRangeForDisplay(daDisp, *vm, keyStart, keyEnd);
    }

    std::ostringstream oss;
    const int DO_W = 32;
    const int DA_W = 38;
    const int DESC_W = 42;

    std::string doStr = "DO=\"" + doDisp + "\"";
    std::string daStr = "DA=\"" + daDisp + "\"";

    std::string showKey = key;
    if (showKey.compare(0, 6, "Value_") == 0) showKey.erase(0, 6);

    appendPad(oss, doStr, DO_W);
    appendPad(oss, daStr, DA_W);
    appendPad(oss, label, DESC_W);

    std::string finalText = showKey + " = " + val;
    oss << finalText;

    out = {
        pk.stat,
        category,
        phaseOrderOf(pk.phase),
        attrSort,
        pk.phase,
        oss.str(),
        label,
        arrayBaseForMeta(vm),
        valueOffsetForDa(vm ? vm->daPath : ""),
        finalOffsetForMeta(vm),
        doStr + " " + daStr,
        finalText,
        doDisp,
        daDisp,
        wiring,
        vm && vm->hasOffset,
        vm ? vm->offset : 0
    };
    return true;
}

static std::string replaceKeyRange(const std::string& key, int start, int end);

static std::string arrayBaseForMeta(const ValueMeta* vm) {
    if (!vm || !vm->isSequence) return "0";
    return std::to_string(vm->seqStart) + "-->" + std::to_string(vm->seqEnd);
}

static std::string valueOffsetForDa(const std::string& daPath) {
    std::smatch m;
    static const std::regex re(R"(%(-?\d+))");
    if (!std::regex_search(daPath, m, re)) return "0";
    return std::to_string(std::stoi(m[1].str()));
}

static std::string finalOffsetForMeta(const ValueMeta* vm) {
    if (!vm || !vm->hasOffset) return "0";
    return std::to_string(vm->offset);
}

static std::string metaNameToKeyName(const ValueMeta& vm) {
    std::smatch m;
    static const std::regex re(R"(^(.*)%(\d+),(\d+)%$)");
    if (std::regex_match(vm.name, m, re)) {
        std::string prefix = m[1].str();
        if (!prefix.empty() && prefix.back() == '_') prefix.pop_back();
        return prefix + "[" + m[2].str() + "-" + m[3].str() + "]";
    }
    return vm.name;
}

static std::string dataKeyForMeta(const ValueMeta& vm, const std::string& phase) {
    std::string key = "Value_" + vm.itemName;
    if (!phase.empty()) key += "_" + phase;
    key += "_" + metaNameToKeyName(vm);
    return key;
}

static std::string lookupNameForMeta(const ValueMeta& vm) {
    std::string name = metaNameToKeyName(vm);
    if (!vm.hasOffset) return name;
    int start = 0, end = 0;
    if (!parseKeyRange(name, start, end)) return name;
    return replaceKeyRange(name, start + vm.offset, end + vm.offset);
}

static std::string lookupDataKeyForMeta(const ValueMeta& vm, const std::string& phase) {
    std::string key = "Value_" + vm.itemName;
    if (!phase.empty()) key += "_" + phase;
    key += "_" + lookupNameForMeta(vm);
    return key;
}

static std::string displayKeyForMeta(const ValueMeta& vm, const std::string& phase) {
    std::string key = vm.itemName;
    if (!phase.empty()) key += "_" + phase;
    key += "_" + metaNameToKeyName(vm);
    return key;
}

static std::vector<std::string> phasesForMeta(const ValueMeta& vm, const std::string& sheetWiring) {
    if (vm.seqValue == "8") return {"T"};
    if (vm.wiring == "star" || vm.wiring == "delta") return {"A", "B", "C"};
    if (sheetWiring == "star" || sheetWiring == "delta") return {"A", "B", "C"};
    return {""};
}

static std::string categoryForKeyAndItem(const std::string& key, const std::string& itemName) {
    if (itemName == "V" || itemName == "I" || itemName == "PQ" || itemName == "F_S" || itemName == "F_L") return itemName;
    if (key.find("_V_") != std::string::npos) return "V";
    if (key.find("_I_") != std::string::npos) return "I";
    if (key.find("PQ_") != std::string::npos || key.find("_PQ_") != std::string::npos) return "PQ";
    return itemName.empty() ? "Z" : itemName;
}

static OutItem makeOutItemFromMeta(const ValueMeta& vm,
                                   const std::string& phase,
                                   const std::string& sheetWiring,
                                   const std::unordered_map<std::string, std::string>& valueByKey) {
    std::string dataKey = dataKeyForMeta(vm, phase);
    auto it = valueByKey.find(lookupDataKeyForMeta(vm, phase));
    std::string val = it == valueByKey.end() ? "" : it->second;

    ParsedKey pk = parseForSort(dataKey);
    if (pk.phase.empty()) pk.phase = phase;

    std::string descClean = cleanDescSuffix(vm.desc);
    std::string attrSort = !descClean.empty() ? descClean : metaNameToKeyName(vm);
    std::string label = vm.desc.empty() ? (attrSort + pk.stat) : vm.desc;
    if (vm.topic == "RTDATA" || vm.dataType == "02" || vm.dataType == "03") {
        label = vm.desc.empty() ? attrSort : vm.desc;
    }

    std::string daDisp = vm.daPath;
    int keyStart = 0, keyEnd = 0;
    if (vm.isSequence && parseKeyRange(dataKey, keyStart, keyEnd)) {
        daDisp = rewriteDaRangeForDisplay(daDisp, vm, keyStart, keyEnd);
    }

    std::string doStr = "DO=\"" + vm.doPath + "\"";
    std::string daStr = "DA=\"" + daDisp + "\"";
    std::string displayKey = displayKeyForMeta(vm, phase);
    std::string finalText = displayKey + " = " + val;
    std::string category = categoryForKeyAndItem(dataKey, vm.itemName);

    return {
        pk.stat,
        category,
        phaseOrderOf(pk.phase),
        attrSort,
        pk.phase,
        "",
        label,
        arrayBaseForMeta(&vm),
        valueOffsetForDa(vm.daPath),
        finalOffsetForMeta(&vm),
        doStr + " " + daStr,
        finalText,
        vm.doPath,
        daDisp,
        sheetWiring,
        vm.hasOffset,
        vm.offset
    };
}

static bool alreadyAddedMetaRow(std::unordered_set<std::string>& seen,
                                const ValueMeta& vm,
                                const std::string& phase,
                                const std::string& sheetWiring) {
    std::string key = vm.topic + "|" + sheetWiring + "|" + vm.itemName + "|" +
                      vm.seqValue + "|" + phase + "|" + vm.name + "|" + vm.doPath + "|" + vm.daPath;
    if (seen.count(key)) return true;
    seen.insert(key);
    return false;
}

static bool specAllowsDataType(const SheetSpec& spec, const std::string& dataType) {
    std::stringstream ss(spec.dataType);
    std::string part;
    while (std::getline(ss, part, ',')) {
        if (part == dataType) return true;
    }
    return false;
}

static std::vector<OutItem> buildItemsForSheet(const std::unordered_map<std::string, std::vector<ValueMeta>>& valueMetas,
                                               const SheetSpec& spec,
                                               const std::unordered_map<std::string, std::string>& valueByKey) {
    std::vector<OutItem> out;
    std::unordered_set<std::string> seen;
    for (const auto& kv : valueMetas) {
        for (const ValueMeta& vm : kv.second) {
            if (vm.topic != spec.topic || !specAllowsDataType(spec, vm.dataType)) continue;
            if (!wiringAllowed(vm, spec.wiring)) continue;
            if (vm.doPath.empty() && vm.daPath.empty()) continue;
            for (const std::string& phase : phasesForMeta(vm, spec.wiring)) {
                if (alreadyAddedMetaRow(seen, vm, phase, spec.wiring)) continue;
                out.push_back(makeOutItemFromMeta(vm, phase, spec.wiring, valueByKey));
            }
        }
    }
    return out;
}
static std::string htmlEscape(const std::string& s) {
    std::string out;
    out.reserve(s.size());
    for (char c : s) {
        if (c == '&') out += "&amp;";
        else if (c == '<') out += "&lt;";
        else if (c == '>') out += "&gt;";
        else if (c == '"') out += "&quot;";
        else out += c;
    }
    return out;
}

static std::vector<std::string> splitCommaValues(const std::string& s) {
    std::vector<std::string> values;
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, ',')) values.push_back(item);
    if (values.empty()) values.push_back("");
    return values;
}

static std::string trimValue(std::string value) {
    value.erase(value.begin(), std::find_if(value.begin(), value.end(), [](unsigned char ch) {
        return std::isspace(ch) == 0;
    }));
    value.erase(std::find_if(value.rbegin(), value.rend(), [](unsigned char ch) {
        return std::isspace(ch) == 0;
    }).base(), value.end());
    return value;
}

static std::string lowerValue(std::string value) {
    std::transform(value.begin(), value.end(), value.begin(),
                   [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
    return value;
}

static bool parseNumberValue(const std::string& text, long double& out) {
    std::string s = trimValue(text);
    if (s.empty()) return false;
    errno = 0;
    char* end = nullptr;
    const char* begin = s.c_str();
    long double value = std::strtold(begin, &end);
    if (begin == end) return false;
    while (end && *end && std::isspace(static_cast<unsigned char>(*end))) ++end;
    if (end && *end) return false;
    if (errno == ERANGE) return false;
    if (!std::isfinite(static_cast<double>(value))) return false;
    out = value;
    return true;
}

static std::string fixedSixNumber(long double value) {
    std::ostringstream oss;
    oss.setf(std::ios::fixed, std::ios::floatfield);
    oss << std::setprecision(6) << static_cast<double>(value);
    std::string out = oss.str();
    if (out == "-0.000000") out = "0.000000";
    return out;
}

static std::string roundOriginValueForDisplay(const std::string& value) {
    long double number = 0;
    if (!parseNumberValue(value, number)) return trimValue(value);
    return fixedSixNumber(number);
}

static std::vector<std::string> roundOriginValuesForDisplay(const std::vector<std::string>& values) {
    std::vector<std::string> out;
    out.reserve(values.size());
    for (const auto& value : values) out.push_back(roundOriginValueForDisplay(value));
    return out;
}

static std::string compareValueKey(const std::string& value) {
    std::string trimmed = trimValue(value);
    long double number = 0;
    if (parseNumberValue(trimmed, number)) return fixedSixNumber(number);
    return lowerValue(trimmed);
}

static bool hasNullValue(const std::vector<std::string>& values) {
    for (const auto& value : values) {
        if (lowerValue(trimValue(value)) == "null") return true;
    }
    return false;
}

static bool valuesEqualForCompare(const std::vector<std::string>& finalValues,
                                  const std::vector<std::string>& originValues) {
    if (finalValues.size() != originValues.size()) return false;
    for (size_t i = 0; i < finalValues.size(); ++i) {
        if (compareValueKey(finalValues[i]) != compareValueKey(originValues[i])) return false;
    }
    return true;
}

static std::string matchStyleForValues(bool hasOrigin,
                                       const std::vector<std::string>& finalValues,
                                       const std::vector<std::string>& originDisplayValues) {
    if (hasNullValue(finalValues)) return "MatchRed";
    if (!hasOrigin) return "";
    return valuesEqualForCompare(finalValues, originDisplayValues) ? "MatchGreen" : "MatchOrange";
}

static std::string cellAttrs(const std::string& styleId, const std::string& extra = "") {
    std::string attrs;
    if (!styleId.empty()) attrs += " ss:StyleID=\"" + styleId + "\"";
    attrs += extra;
    return attrs;
}

static bool splitFinalText(const std::string& text, std::string& key, std::string& value) {
    size_t pos = text.find(" = ");
    if (pos == std::string::npos) {
        key = text;
        value.clear();
        return false;
    }
    key = text.substr(0, pos);
    value = text.substr(pos + 3);
    return true;
}

static std::string replaceKeyRange(const std::string& key, int start, int end) {
    std::regex re(R"(\[(\d+)-(\d+)\])");
    std::ostringstream oss;
    oss << "[" << start << "-" << end << "]";
    return std::regex_replace(key, re, oss.str(), std::regex_constants::format_first_only);
}

static std::string offsetFinalKey(const std::string& key, const OutItem& item) {
    if (!item.hasOffset) return key;
    int start = 0, end = 0;
    if (!parseKeyRange(key, start, end)) return key;
    return replaceKeyRange(key, start + item.offset, end + item.offset);
}

static std::string originSeriesKey(const std::string& finalKey, const OutItem& item) {
    int start = 0, end = 0;
    if (!parseKeyRange(item.daPath, start, end)) return finalKey;
    return replaceKeyRange(finalKey, start, end);
}

static void writeSsCell(std::ofstream& ofs, const std::string& value, const std::string& attrs = "") {
    ofs << "<Cell" << attrs << "><Data ss:Type=\"String\">" << htmlEscape(value) << "</Data></Cell>";
}

static void writeSsRowStart(std::ofstream& ofs, int height = 0) {
    if (height > 0) ofs << "<Row ss:AutoFitHeight=\"0\" ss:Height=\"" << height << "\">";
    else ofs << "<Row>";
}

static std::string displayLabelForSheet(const OutItem& item) {
    if (item.phase == "A" || item.phase == "B" || item.phase == "C") {
        return item.phase + "相" + item.label;
    }
    return item.label;
}

static void writeDataWorksheet(std::ofstream& ofs,
                               const std::string& sheetName,
                               const std::string& wiring,
                               const std::vector<HeaderItem>& headers,
                               const std::vector<std::string>& blocks,
                               const std::vector<OutItem>& items,
                               OriginData* origin,
                               bool showStatBlocks = true) {
    const int maxValues = 50;
    const int totalCols = 9 + maxValues;

    ofs << "<Worksheet ss:Name=\"" << htmlEscape(sheetName) << "\"><Table>\n";
    ofs << "<Column ss:Width=\"210\"/><Column ss:Width=\"75\"/><Column ss:Width=\"85\"/><Column ss:Width=\"85\"/>";
    ofs << "<Column ss:Width=\"310\"/><Column ss:Width=\"120\"/><Column ss:Width=\"280\"/>";
    ofs << "<Column ss:Width=\"55\"/><Column ss:Width=\"150\"/>";
    for (int i = 0; i < maxValues; ++i) ofs << "<Column ss:Width=\"90\"/>";
    ofs << "\n";

    writeSsRowStart(ofs);
    writeSsCell(ofs, "头部信息", " ss:StyleID=\"Section\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\"");
    ofs << "</Row>\n";

    for (const auto& h : headers) {
        writeSsRowStart(ofs);
        writeSsCell(ofs, h.key, " ss:StyleID=\"Default\"");
        writeSsCell(ofs, h.val, " ss:StyleID=\"Default\" ss:MergeAcross=\"" + std::to_string(totalCols - 2) + "\"");
        ofs << "</Row>\n";
    }

    writeSsRowStart(ofs);
    writeSsCell(ofs, "名称", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "数组基准", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "取值偏移量", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "最终值偏移", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "配置项", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "报告名", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "原始路径", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "类型", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "字段", " ss:StyleID=\"Head\"");
    for (int i = 1; i <= maxValues; ++i) writeSsCell(ofs, "值" + std::to_string(i), " ss:StyleID=\"Head\"");
    ofs << "</Row>\n";

    auto writeItems = [&](const std::string& statFilter) {
        std::string lastCategory;
        std::string lastPhase;
        for (const auto& item : items) {
            if (!statFilter.empty() && item.stat != statFilter) continue;
            if (item.wiring != wiring) continue;
            if (item.category != lastCategory) {
                writeSsRowStart(ofs);
                writeSsCell(ofs, item.category, " ss:StyleID=\"Sub\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\"");
                ofs << "</Row>\n";
                lastCategory.clear();
                lastPhase.clear();
            }
            if (item.phase != lastPhase) {
                writeSsRowStart(ofs);
                writeSsCell(ofs, item.phase + " 相", " ss:StyleID=\"Sub\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\"");
                ofs << "</Row>\n";
            }

            OriginMatch om = matchOrigin(origin, item.doPath, item.daPath, item.stat, item.phase, item.wiring);
            std::string finalKey, finalValue;
            splitFinalText(item.finalText, finalKey, finalValue);
            finalKey = offsetFinalKey(finalKey, item);
            std::vector<std::string> finalValues = splitCommaValues(finalValue);
            std::vector<std::string> originValuesRaw = splitCommaValues(om.value);
            std::vector<std::string> originValues = roundOriginValuesForDisplay(originValuesRaw);
            bool hasOriginMatch = origin && !om.value.empty();
            std::string rowStyle = matchStyleForValues(hasOriginMatch, finalValues, originValues);
            std::string originKey = originSeriesKey(finalKey, item);

            writeSsRowStart(ofs);
            writeSsCell(ofs, displayLabelForSheet(item), cellAttrs(rowStyle, " ss:MergeDown=\"1\""));
            writeSsCell(ofs, item.arrayBase, cellAttrs(rowStyle, " ss:MergeDown=\"1\""));
            writeSsCell(ofs, item.valueOffset, cellAttrs(rowStyle, " ss:MergeDown=\"1\""));
            writeSsCell(ofs, item.finalOffset, cellAttrs(rowStyle, " ss:MergeDown=\"1\""));
            writeSsCell(ofs, item.configText, cellAttrs(rowStyle, " ss:MergeDown=\"1\""));
            writeSsCell(ofs, om.reportName, cellAttrs(rowStyle, " ss:MergeDown=\"1\""));
            writeSsCell(ofs, om.path.empty() ? "" : om.path, cellAttrs(rowStyle, " ss:MergeDown=\"1\""));
            writeSsCell(ofs, "最终值", cellAttrs(rowStyle));
            writeSsCell(ofs, finalKey, cellAttrs(rowStyle));
            for (int i = 0; i < maxValues; ++i) writeSsCell(ofs, i < (int)finalValues.size() ? finalValues[i] : "", cellAttrs(rowStyle));
            ofs << "</Row>\n";

            writeSsRowStart(ofs);
            writeSsCell(ofs, "原始值", cellAttrs(rowStyle, " ss:Index=\"8\""));
            writeSsCell(ofs, om.value.empty() ? "" : originKey, cellAttrs(rowStyle));
            for (int i = 0; i < maxValues; ++i) writeSsCell(ofs, i < (int)originValues.size() ? originValues[i] : "", cellAttrs(rowStyle));
            ofs << "</Row>\n";

            lastCategory = item.category;
            lastPhase = item.phase;
        }
    };

    if (showStatBlocks) {
        for (const auto& stat : blocks) {
            writeSsRowStart(ofs);
            writeSsCell(ofs, stat, " ss:StyleID=\"Section\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\"");
            ofs << "</Row>\n";
            writeItems(stat);
        }
    } else {
        writeItems("");
    }

    ofs << "</Table></Worksheet>\n";
}

static void writeOriginRecordWorksheet(std::ofstream& ofs,
                                       const OriginData& origin,
                                       const std::string& sheetName,
                                       bool matched,
                                       bool roundValues) {
    const int maxValues = 50;
    ofs << "<Worksheet ss:Name=\"" << htmlEscape(sheetName) << "\"><Table>\n";
    ofs << "<Column ss:Width=\"320\"/><Column ss:Width=\"120\"/><Column ss:Width=\"160\"/>";
    for (int i = 0; i < maxValues; ++i) ofs << "<Column ss:Width=\"90\"/>";
    ofs << "\n";

    writeSsRowStart(ofs);
    writeSsCell(ofs, "原始路径", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "报告名", " ss:StyleID=\"Head\"");
    writeSsCell(ofs, "icd中代表的字段", " ss:StyleID=\"Head\"");
    for (int i = 1; i <= maxValues; ++i) writeSsCell(ofs, "值" + std::to_string(i), " ss:StyleID=\"Head\"");
    ofs << "</Row>\n";

    for (size_t i = 0; i < origin.records.size(); ++i) {
        bool isUsed = origin.used.count(i) != 0;
        if (isUsed != matched) continue;
        const auto& rec = origin.records[i];
        std::vector<std::string> originValues = splitCommaValues(rec.value);
        if (roundValues) originValues = roundOriginValuesForDisplay(originValues);
        writeSsRowStart(ofs);
        writeSsCell(ofs, rec.path);
        writeSsCell(ofs, rec.reportName);
        writeSsCell(ofs, "");
        for (int j = 0; j < maxValues; ++j) writeSsCell(ofs, j < (int)originValues.size() ? originValues[j] : "");
        ofs << "</Row>\n";
    }
    ofs << "</Table></Worksheet>\n";
}

static void writeMatchedWorksheet(std::ofstream& ofs, const OriginData& origin) {
    writeOriginRecordWorksheet(ofs, origin, "已匹配的原始数据", true, false);
}

static void writeUnmatchedWorksheet(std::ofstream& ofs, const OriginData& origin) {
    writeOriginRecordWorksheet(ofs, origin, "未匹配的原始数据", false, true);
}

static std::vector<HeaderItem> headersForSheet(const std::vector<HeaderItem>& headers, const std::string& dataType) {
    std::vector<HeaderItem> out = headers;
    std::string displayDataType = dataType;
    std::replace(displayDataType.begin(), displayDataType.end(), ',', '/');
    bool found = false;
    for (auto& h : out) {
        if (h.key == "DATA_TYPE") {
            h.val = displayDataType;
            found = true;
            break;
        }
    }
    if (!found) out.insert(out.begin(), {"DATA_TYPE", displayDataType});
    return out;
}

static bool specAllowsFrontType(const SheetSpec& spec, const std::string& frontType) {
    return frontType.empty() || spec.frontType.empty() || spec.frontType == frontType;
}

static void sortOutItems(std::vector<OutItem>& items) {
    std::unordered_map<std::string, int> statRank{
        {"95值", 0}, {"平均值", 1}, {"最大值", 2}, {"最小值", 3}
    };
    std::sort(items.begin(), items.end(), [&](const OutItem& a, const OutItem& b) {
        int sa = statRank.count(a.stat) ? statRank[a.stat] : 99;
        int sb = statRank.count(b.stat) ? statRank[b.stat] : 99;
        if (sa != sb) return sa < sb;

        static std::map<std::string,int> catRank = {
            {"V",  0},
            {"I",  1},
            {"PQ", 2},
            {"F_S", 3},
            {"F_L", 4},
            {"Z", 99}
        };

        int ca = catRank.count(a.category) ? catRank[a.category] : 90;
        int cb = catRank.count(b.category) ? catRank[b.category] : 90;
        if (ca != cb) return ca < cb;
        if (a.phaseOrder != b.phaseOrder) return a.phaseOrder < b.phaseOrder;
        if (a.attrSort != b.attrSort) return a.attrSort < b.attrSort;
        return a.configText < b.configText;
    });
}

static void writeExcelWorkbook(const fs::path& outPath,
                               const std::vector<std::string>& blocks,
                               const std::unordered_map<std::string, std::vector<ValueMeta>>& valueMetas,
                               const std::unordered_map<std::string, JsonDataBucket>& dataByType,
                               OriginData* origin,
                               const std::string& frontType) {
    std::ofstream ofs(outPath, std::ios::binary);
    if (!ofs) throw std::runtime_error("Failed to open output file: " + outPath.string());

    ofs << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    ofs << "<?mso-application progid=\"Excel.Sheet\"?>\n";
    ofs << "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\" "
        << "xmlns:o=\"urn:schemas-microsoft-com:office:office\" "
        << "xmlns:x=\"urn:schemas-microsoft-com:office:excel\" "
        << "xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">\n";
    ofs << "<Styles>\n";
    ofs << "<Style ss:ID=\"Default\" ss:Name=\"Normal\"><Alignment ss:Horizontal=\"Left\" ss:Vertical=\"Center\"/><Borders><Border ss:Position=\"Bottom\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"/><Border ss:Position=\"Left\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"/><Border ss:Position=\"Right\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"/><Border ss:Position=\"Top\" ss:LineStyle=\"Continuous\" ss:Weight=\"1\"/></Borders><Font ss:FontName=\"SimSun\" ss:Size=\"11\"/></Style>\n";
    ofs << "<Style ss:ID=\"Head\" ss:Parent=\"Default\"><Font ss:Bold=\"1\"/><Interior ss:Color=\"#D9EAD3\" ss:Pattern=\"Solid\"/></Style>\n";
    ofs << "<Style ss:ID=\"Section\" ss:Parent=\"Default\"><Font ss:Bold=\"1\"/><Interior ss:Color=\"#E8EEF7\" ss:Pattern=\"Solid\"/></Style>\n";
    ofs << "<Style ss:ID=\"Sub\" ss:Parent=\"Default\"><Font ss:Bold=\"1\"/><Interior ss:Color=\"#F4F4F4\" ss:Pattern=\"Solid\"/></Style>\n";
    ofs << "<Style ss:ID=\"MatchGreen\" ss:Parent=\"Default\"><Interior ss:Color=\"#D9EAD3\" ss:Pattern=\"Solid\"/></Style>\n";
    ofs << "<Style ss:ID=\"MatchOrange\" ss:Parent=\"Default\"><Interior ss:Color=\"#FCE4D6\" ss:Pattern=\"Solid\"/></Style>\n";
    ofs << "<Style ss:ID=\"MatchRed\" ss:Parent=\"Default\"><Interior ss:Color=\"#F4CCCC\" ss:Pattern=\"Solid\"/></Style>\n";
    ofs << "</Styles>\n";

    std::vector<SheetSpec> sheets = {
        {"星形接线", "HISDATA", "01", "star", true, "cfg_stat_data"},
        {"角型接线", "HISDATA", "01", "delta", true, "cfg_stat_data"},
        {"星型闪变02", "HISDATA", "02", "star", false, "cfg_stat_data"},
        {"角形闪变02", "HISDATA", "02", "delta", false, "cfg_stat_data"},
        {"星型闪变03", "HISDATA", "03", "star", false, "cfg_stat_data"},
        {"角形闪变03", "HISDATA", "03", "delta", false, "cfg_stat_data"},
        {"星型实时", "RTDATA", "01", "star", false, "cfg_3s_data"},
        {"角形实时", "RTDATA", "01", "delta", false, "cfg_3s_data"},
        {"星型实时闪变02", "RTDATA", "02", "star", false, "cfg_3s_data"},
        {"角形实时闪变02", "RTDATA", "02", "delta", false, "cfg_3s_data"},
        {"星型实时闪变03", "RTDATA", "03", "star", false, "cfg_3s_data"},
        {"角形实时闪变03", "RTDATA", "03", "delta", false, "cfg_3s_data"}
    };

    const std::vector<HeaderItem> emptyHeaders;
    const std::unordered_map<std::string, std::string> emptyValues;
    for (const auto& spec : sheets) {
        if (!specAllowsFrontType(spec, frontType)) continue;
        auto bucketIt = dataByType.find(spec.dataType);
        const std::vector<HeaderItem>& headers = bucketIt == dataByType.end() ? emptyHeaders : bucketIt->second.headers;
        const std::unordered_map<std::string, std::string>& valueByKey =
            bucketIt == dataByType.end() ? emptyValues : bucketIt->second.valueByKey;
        std::vector<OutItem> sheetItems = buildItemsForSheet(valueMetas, spec, valueByKey);
        sortOutItems(sheetItems);
        writeDataWorksheet(ofs, spec.name, spec.wiring, headersForSheet(headers, spec.dataType), blocks, sheetItems, origin, spec.showStatBlocks);
    }
    if (origin) {
        writeMatchedWorksheet(ofs, *origin);
        writeUnmatchedWorksheet(ofs, *origin);
    }

    ofs << "</Workbook>\n";
}

static const std::vector<std::string>& statBlocks() {
    static const std::vector<std::string> blocks = {"95值", "平均值", "最大值", "最小值"};
    return blocks;
}

static void writeWorkbookStage(WorkbookContext& ctx, const fs::path& outPath) {
    if (ctx.hasOrigin) ctx.originData.used.clear();
    OriginData* origin = ctx.hasOrigin ? &ctx.originData : nullptr;
    writeExcelWorkbook(outPath, statBlocks(), ctx.valueMetas, ctx.dataByType, origin, ctx.frontType);
}

static WorkbookContext createWorkbookTemplateFromXml(const fs::path& xmlPath, const fs::path& outPath, const std::string& frontType = "") {
    WorkbookContext ctx;
    ctx.xmlPath = xmlPath;
    ctx.frontType = frontType;
    ctx.valueMetas = loadValueMetas(xmlPath);
    ctx.xmlNames.reserve(ctx.valueMetas.size() * 2);
    for (const auto& kv : ctx.valueMetas) ctx.xmlNames.insert(kv.first);
    writeWorkbookStage(ctx, outPath);
    return ctx;
}

static void collectDataTypedJsonRoots(const json& node, std::vector<const json*>& roots) {
    if (node.is_object()) {
        if (node.contains("DATA_TYPE")) {
            roots.push_back(&node);
            return;
        }
        for (auto it = node.begin(); it != node.end(); ++it) collectDataTypedJsonRoots(it.value(), roots);
    } else if (node.is_array()) {
        for (const auto& item : node) collectDataTypedJsonRoots(item, roots);
    }
}

static std::string normalizeDataType(std::string dataType) {
    dataType.erase(std::remove_if(dataType.begin(), dataType.end(),
                                  [](unsigned char c) { return std::isspace(c) != 0; }),
                   dataType.end());
    if (dataType.size() == 1 && std::isdigit(static_cast<unsigned char>(dataType[0]))) {
        dataType = "0" + dataType;
    }
    return dataType.empty() ? "01" : dataType;
}

static std::string dataTypeFromMerged(const std::vector<std::pair<std::string, std::string>>& merged) {
    for (const auto& kv : merged) {
        if (kv.first == "DATA_TYPE") return normalizeDataType(kv.second);
    }
    return "01";
}

static void applyJsonDataToWorkbook(WorkbookContext& ctx, const json& data, const fs::path& outPath) {
    std::vector<const json*> roots;
    collectDataTypedJsonRoots(data, roots);
    if (roots.empty()) roots.push_back(&data);

    for (const json* root : roots) {
        std::vector<std::pair<std::string, std::string>> flat;
        flattenJson(*root, "", flat);

        auto merged = mergeSequences(flat, ctx.xmlNames);
        std::string dataType = dataTypeFromMerged(merged);
        JsonDataBucket& bucket = ctx.dataByType[dataType];
        if (bucket.hasData) continue;
        bucket.headers.clear();
        bucket.valueByKey.clear();
        bucket.valueByKey.reserve(merged.size() * 2);
        for (const auto& kv : merged) {
            bucket.valueByKey[kv.first] = kv.second;
            if (isHeaderKey(kv.first)) bucket.headers.push_back({kv.first, kv.second});
        }
        bucket.hasData = true;
    }

    writeWorkbookStage(ctx, outPath);
}

static void applyOriginJsonToWorkbook(WorkbookContext& ctx,
                                      const json& originRoot,
                                      const fs::path& originPath,
                                      int sourceOrder,
                                      const fs::path& outPath) {
    appendOriginRoot(ctx.originData, originPath, sourceOrder, originRoot);
    ctx.hasOrigin = true;
    writeWorkbookStage(ctx, outPath);
}

static void addOriginJsonToContext(WorkbookContext& ctx,
                                   const json& originRoot,
                                   const fs::path& originPath,
                                   int sourceOrder) {
    appendOriginRoot(ctx.originData, originPath, sourceOrder, originRoot);
    ctx.hasOrigin = true;
}

static json readJsonDataFile(const fs::path& txtPath) {
    std::string raw = ltrimBOM(readAll(txtPath));
    try {
        return json::parse(raw);
    } catch (...) {
        std::vector<json> roots = parseJsonRoots(raw);
        if (roots.size() == 1) return roots.front();
        return roots;
    }
}

static void applyOriginFileToWorkbook(WorkbookContext& ctx,
                                      const fs::path& originPath,
                                      int sourceOrder,
                                      const fs::path& outPath) {
    std::string raw = ltrimBOM(readAll(originPath));
    for (const auto& root : parseJsonRoots(raw)) {
        applyOriginJsonToWorkbook(ctx, root, originPath, sourceOrder, outPath);
    }
}

static void addOriginFileToContext(WorkbookContext& ctx,
                                   const fs::path& originPath,
                                   int sourceOrder) {
    std::string raw = ltrimBOM(readAll(originPath));
    for (const auto& root : parseJsonRoots(raw)) {
        addOriginJsonToContext(ctx, root, originPath, sourceOrder);
    }
}

bool pqRunParser(const std::string& xmlPath,
                 const std::string& jsonPath,
                 const std::vector<std::string>& originPaths,
                 const std::string& outPath,
                 std::string* err,
                 const std::string& frontType) {
    std::vector<std::string> jsonPaths;
    if (!jsonPath.empty()) jsonPaths.push_back(jsonPath);
    return pqRunParser(xmlPath, jsonPaths, originPaths, outPath, err, frontType);
}

bool pqRunParser(const std::string& xmlPath,
                 const std::vector<std::string>& jsonPaths,
                 const std::vector<std::string>& originPaths,
                 const std::string& outPath,
                 std::string* err,
                 const std::string& frontType) {
    try {
        fs::path xmlFile = fs::u8path(xmlPath);
        fs::path output = outPath.empty() ? fs::path("final_sorted.xls") : fs::u8path(outPath);

        WorkbookContext ctx = createWorkbookTemplateFromXml(xmlFile, output, frontType);
        for (const auto& jsonPath : jsonPaths) {
            if (!jsonPath.empty()) applyJsonDataToWorkbook(ctx, readJsonDataFile(fs::u8path(jsonPath)), output);
        }
        for (size_t i = 0; i < originPaths.size(); ++i) {
            if (!originPaths[i].empty()) addOriginFileToContext(ctx, fs::u8path(originPaths[i]), static_cast<int>(i));
        }
        if (!originPaths.empty()) writeWorkbookStage(ctx, output);
        return true;
    } catch (const std::exception& ex) {
        if (err) *err = ex.what();
        return false;
    }
}

int main(int argc, char** argv) {
    try {
        if (argc == 1) {
            return runPqGuiApp();
        }

        fs::path xmlFile, txtFile;
        std::vector<fs::path> originFiles;
        bool hasOrigin = false;
        const fs::path outPath = "final_sorted.xls";

        if (argc >= 2) {
            // ✅ Windows 中文路径/文件名：用 u8path 解析
            xmlFile = fs::u8path(argv[1]);
            if (argc >= 3) txtFile = fs::u8path(argv[2]);
            if (argc >= 4) {
                hasOrigin = true;
                for (int i = 3; i < argc; ++i) {
                    originFiles.push_back(fs::u8path(argv[i]));
                }
            }
        } else {
            xmlFile = findFirstByExt(".xml");
        }

        std::cout << "[INFO] XML: " << xmlFile << "\n";
        WorkbookContext ctx = createWorkbookTemplateFromXml(xmlFile, outPath);
        std::cout << "[OK] Template generated: " << outPath << "\n";

        if (txtFile.empty()) txtFile = findFirstInputTxt();
        std::cout << "[INFO] TXT: " << txtFile << "\n";
        applyJsonDataToWorkbook(ctx, readJsonDataFile(txtFile), outPath);
        std::cout << "[OK] JSON data applied: " << outPath << "\n";

        if (hasOrigin) {
            std::cout << "[INFO] ORIGIN:";
            for (const auto& originFile : originFiles) std::cout << " " << originFile;
            std::cout << "\n";
            for (size_t i = 0; i < originFiles.size(); ++i) {
                addOriginFileToContext(ctx, originFiles[i], static_cast<int>(i));
                std::cout << "[OK] Origin loaded: " << originFiles[i] << "\n";
            }
            writeWorkbookStage(ctx, outPath);
            std::cout << "[OK] Origin data applied: " << outPath << "\n";
        }

        std::cout << "[OK] Generated: final_sorted.xls\n";
        return 0;

    } catch (const std::exception& ex) {
        std::cerr << "[ERR] " << ex.what() << "\n";
        std::cerr << "Usage:\n"
                  << "  pq_tool <LN_D001.xml> [data.txt] [origin1.txt origin2.txt ...]\n"
                  << "Or put one .xml and one .txt in current folder and run without args.\n";
        return 1;
    }
}
















