#if !CUSTOMER_BUILD
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using Ounibo.Catalog.Core;

namespace Ounibo.Catalog.App
{
    public class ParamRow
    {
        public string Code { get; set; }
        public string NameZh { get; set; }
        public string NameEn { get; set; }
        public string Unit { get; set; }
        public string Type { get; set; }
        public string Values { get; set; }
        public string Keywords { get; set; }
        public string Display { get; set; }   // 显示名映射, 格式 值:显示,值:显示 (如 :基本型,M:附磁石 M)
    }

    public class RuleRow
    {
        public string CondParam { get; set; }
        public string CondOp { get; set; }
        public string CondValue { get; set; }
        public string ThenParam { get; set; }
        public string ThenOp { get; set; }
        public string ThenValues { get; set; }
    }

    public class AttachmentRow
    {
        public string Kind { get; set; }
        public string Lang { get; set; }
        public string Source { get; set; }
        public string Model { get; set; }
    }

    public class SeriesChoice
    {
        public Series Series;
        public override string ToString()
        {
            return Series.code + " - " + Series.nameZh + " (" + (Series.variants == null ? 0 : Series.variants.Count) + " 变体)";
        }
    }

    /// <summary>
    /// 维护入口内的 Builder (M2): 5 步向导
    /// 导入 CSV+STEP 目录 → 参数定义 → 编码确认 → 选型规则 → 附件与生成 .opc。
    /// </summary>
    public class BuilderWindow : Window
    {
        MainWindow _owner;
        TextBox _tbSeriesCode, _tbSeriesZh, _tbSeriesEn, _tbCatName, _tbVer;
        TextBox _tbCsv, _tbStepDir;
        TextBlock _lbPreviewCount;
        DataGrid _gridPreview, _gridParams, _gridRules, _gridAttach;
        TextBox _tbOut;
        TextBlock _tbResult;
        TextBox _tbSrcDir;
        ComboBox _cbSeries;
        string _loadedMetaPath;
        BindingList<ParamRow> _paramRows = new BindingList<ParamRow>();
        BindingList<RuleRow> _ruleRows = new BindingList<RuleRow>();
        BindingList<AttachmentRow> _attRows = new BindingList<AttachmentRow>();
        string[] _header;
        List<string[]> _csvRows = new List<string[]>();

        public BuilderWindow(MainWindow owner)
        {
            _owner = owner;
            Title = "目录制作器 Builder (维护入口)";
            Width = 900; Height = 720; MinWidth = 760; MinHeight = 560;
            WindowStartupLocation = WindowStartupLocation.CenterOwner;
            Owner = owner;

            var root = new DockPanel { Margin = new Thickness(10) };

            // 系列与目录信息
            var info = new Grid { Margin = new Thickness(0, 0, 0, 8) };
            for (int i = 0; i < 4; i++) info.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
            info.RowDefinitions.Add(new RowDefinition()); info.RowDefinitions.Add(new RowDefinition()); info.RowDefinitions.Add(new RowDefinition());
            _tbSeriesCode = AddField(info, 0, 0, "系列代码 Series code");
            _tbSeriesZh = AddField(info, 1, 0, "系列中文名 Name (zh)");
            _tbSeriesEn = AddField(info, 2, 0, "系列英文名 Name (en)");
            _tbCatName = AddField(info, 3, 0, "分类名 Category");
            _tbVer = AddField(info, 0, 1, "目录版本 Version");
            var cfgBtns = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 2, 0, 0) };
            var btnSaveCfg = new Button { Content = "保存配置", Width = 100, Margin = new Thickness(0, 0, 8, 0) };
            btnSaveCfg.Click += (s, e) => SaveConfig();
            var btnLoadCfg = new Button { Content = "加载配置", Width = 100 };
            btnLoadCfg.Click += (s, e) => LoadConfig();
            cfgBtns.Children.Add(btnSaveCfg); cfgBtns.Children.Add(btnLoadCfg);
            Grid.SetColumn(cfgBtns, 1); Grid.SetRow(cfgBtns, 1);
            info.Children.Add(cfgBtns);
            DockPanel.SetDock(info, Dock.Top);
            root.Children.Add(info);

            var tabs = new TabControl();
            tabs.Items.Add(BuildStep1());
            tabs.Items.Add(BuildStep2());
            tabs.Items.Add(BuildStep3());
            tabs.Items.Add(BuildStep4());
            tabs.Items.Add(BuildStep5());
            root.Children.Add(tabs);
            Content = root;
            ReloadSeriesList();
        }

        TextBox AddField(Grid g, int col, int row, string hint)
        {
            var tb = new TextBox { Margin = new Thickness(0, 0, 8, 4) };
            Grid.SetColumn(tb, col); Grid.SetRow(tb, row);
            g.Children.Add(tb);
            if (!string.IsNullOrEmpty(hint)) tb.ToolTip = hint;
            return tb;
        }

        // ---- Step1: 导入 ----
        TabItem BuildStep1()
        {
            var panel = new DockPanel { Margin = new Thickness(6) };

            // 数据源目录 (统一路线): 选目录 → 选系列 → 编辑 → 重建全量目录
            var srcRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 6) };
            srcRow.Children.Add(new TextBlock { Text = "数据源目录:", VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 4, 0) });
            _tbSrcDir = new TextBox { Width = 300, Margin = new Thickness(0, 0, 6, 0), Text = DefaultSourceDir() };
            var btnSrc = new Button { Content = "选择...", Width = 60, Margin = new Thickness(0, 0, 10, 0) };
            btnSrc.Click += (s, e) =>
            {
                var dlg = new System.Windows.Forms.FolderBrowserDialog { Description = "选择目录源目录 (含 catalog.json + series\\ + csv\\ + step\\)" };
                if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    _tbSrcDir.Text = dlg.SelectedPath;
                    ReloadSeriesList();
                }
            };
            _cbSeries = new ComboBox { Width = 150, Margin = new Thickness(0, 0, 10, 0) };
            _cbSeries.SelectionChanged += (s, e) => OpenSeriesFromSource();
            var btnRebuild = new Button { Content = "重建全量目录", Width = 120, FontWeight = System.Windows.FontWeights.Bold };
            btnRebuild.Click += (s, e) => RebuildFull();
            srcRow.Children.Add(_tbSrcDir); srcRow.Children.Add(btnSrc); srcRow.Children.Add(_cbSeries); srcRow.Children.Add(btnRebuild);
            DockPanel.SetDock(srcRow, Dock.Top);
            panel.Children.Add(srcRow);

            var top = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 6) };
            var btnCsv = new Button { Content = "选择参数表 CSV", Width = 130, Margin = new Thickness(0, 0, 6, 0) };
            btnCsv.Click += (s, e) => LoadCsv();
            _tbCsv = new TextBox { Width = 320, Margin = new Thickness(0, 0, 10, 0), IsReadOnly = true };
            var btnDir = new Button { Content = "选择 STEP 目录", Width = 130, Margin = new Thickness(0, 0, 6, 0) };
            btnDir.Click += (s, e) => PickStepDir();
            _tbStepDir = new TextBox { Width = 240, IsReadOnly = true };
            top.Children.Add(btnCsv); top.Children.Add(_tbCsv); top.Children.Add(btnDir); top.Children.Add(_tbStepDir);
            var btnImport = new Button { Content = "从当前数据包导入系列", Width = 170, Margin = new Thickness(10, 0, 0, 0) };
            btnImport.Click += (s, e) => ImportFromPackage();
            top.Children.Add(btnImport);
            DockPanel.SetDock(top, Dock.Top);
            panel.Children.Add(top);

            _lbPreviewCount = new TextBlock { Margin = new Thickness(0, 0, 0, 4) };
            DockPanel.SetDock(_lbPreviewCount, Dock.Top);
            panel.Children.Add(_lbPreviewCount);

            _gridPreview = new DataGrid { IsReadOnly = true, AutoGenerateColumns = true };
            panel.Children.Add(_gridPreview);
            var tab = new TabItem { Header = "1. 导入 Import" };
            tab.Content = panel;
            return tab;
        }

        void LoadCsv()
        {
            var dlg = new OpenFileDialog { Filter = "CSV 参数表 (*.csv)|*.csv", Title = "选择参数表 (首列=型号编码, 含 step_file 列)" };
            if (dlg.ShowDialog() != true) return;
            try
            {
                _tbCsv.Text = dlg.FileName;
                PreviewCsv(dlg.FileName);
                AutoParams();
            }
            catch (Exception ex)
            {
                MessageBox.Show("CSV 读取失败: " + ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        void PreviewCsv(string path)
        {
            var lines = File.ReadAllLines(path, System.Text.Encoding.UTF8).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray();
            _header = lines[0].Split(',').Select(x => x.Trim()).ToArray();
            _csvRows = lines.Skip(1).Select(l => l.Split(',')).Where(c => c.Length >= _header.Length && !string.IsNullOrWhiteSpace(c[0])).ToList();

            var dt = new DataTable();
            foreach (var h in _header) dt.Columns.Add(h);
            foreach (var r in _csvRows.Take(50))
            {
                var row = dt.NewRow();
                for (int i = 0; i < _header.Length; i++) row[i] = r[i];
                dt.Rows.Add(row);
            }
            _gridPreview.ItemsSource = dt.DefaultView;
            _lbPreviewCount.Text = string.Format("共 {0} 行数据, 预览前 50 行", _csvRows.Count);
        }

        // ---- 从当前数据包导入系列: 向导数据与运行目录保持一致 ----
        void ImportFromPackage()
        {
            var pkg = _owner.Package;
            if (pkg == null) { MessageBox.Show("主窗口未加载数据包, 请先打开 .opc", Title); return; }
            var cat = pkg.Catalog;

            var picker = new Window { Title = "从当前数据包选择系列", Width = 420, Height = 420, MinWidth = 300, MinHeight = 300, WindowStartupLocation = WindowStartupLocation.CenterOwner, Owner = this };
            var list = new ListBox { Margin = new Thickness(8) };
            foreach (var s in cat.series) list.Items.Add(new SeriesChoice { Series = s });
            var ok = new Button { Content = "确定 OK", Width = 90, IsDefault = true, Margin = new Thickness(8), HorizontalAlignment = HorizontalAlignment.Right };
            var dp = new DockPanel();
            ok.Click += (s2, e2) => picker.DialogResult = true;
            DockPanel.SetDock(ok, Dock.Bottom);
            dp.Children.Add(ok); dp.Children.Add(list);
            picker.Content = dp;
            if (picker.ShowDialog() != true || list.SelectedItem == null) return;
            var series = ((SeriesChoice)list.SelectedItem).Series;

            try
            {
                var tmp = Path.Combine(Path.GetTempPath(), "ounibo_builder", series.code);
                Directory.CreateDirectory(Path.Combine(tmp, "step"));

                // CSV: model_code + 参数列 + step_file
                var sb = new StringBuilder();
                var header = new List<string> { "model_code" };
                foreach (var p in series.parameters ?? new List<ParameterDef>()) header.Add(p.code);
                header.Add("step_file");
                sb.AppendLine(string.Join(",", header));
                foreach (var v in series.variants ?? new List<Variant>())
                {
                    var row = new List<string> { v.modelCode };
                    foreach (var p in series.parameters ?? new List<ParameterDef>())
                    {
                        object val; string vs = v.@params != null && v.@params.TryGetValue(p.code, out val) ? Convert.ToString(val, CultureInfo.InvariantCulture) : "";
                        if (vs.Contains(",") || vs.Contains("\"")) vs = "\"" + vs.Replace("\"", "\"\"") + "\"";
                        row.Add(vs);
                    }
                    row.Add(Path.GetFileName((v.step ?? "").Replace('\\', '/')));
                    sb.AppendLine(string.Join(",", row));
                }
                var csvPath = Path.Combine(tmp, series.code + ".csv");
                File.WriteAllText(csvPath, sb.ToString(), Encoding.UTF8);

                // STEP 导出 (去重)
                var seen = new HashSet<string>();
                foreach (var v in series.variants ?? new List<Variant>())
                {
                    if (v == null || string.IsNullOrEmpty(v.step) || !seen.Add(v.step)) continue;
                    try { File.WriteAllBytes(Path.Combine(tmp, "step", Path.GetFileName(v.step.Replace('\\', '/'))), pkg.ReadAsset(v.step)); }
                    catch { }
                }

                // 填向导字段
                _tbSeriesCode.Text = series.code;
                _tbSeriesZh.Text = series.nameZh ?? "";
                _tbSeriesEn.Text = series.nameEn ?? "";
                _tbVer.Text = cat.catalogVersion ?? "";
                var catName = cat.categories.FirstOrDefault(c2 => c2.series != null && c2.series.Contains(series.code));
                _tbCatName.Text = catName != null ? (catName.nameZh ?? "") : "";
                _tbCsv.Text = csvPath;
                _tbStepDir.Text = Path.Combine(tmp, "step");
                PreviewCsv(csvPath);

                // 参数行
                _paramRows.Clear();
                foreach (var p in series.parameters ?? new List<ParameterDef>())
                {
                    _paramRows.Add(new ParamRow
                    {
                        Code = p.code,
                        NameZh = p.nameZh ?? "",
                        NameEn = p.nameEn ?? "",
                        Unit = p.unit ?? "",
                        Type = p.type == "number" ? "number" : "enum",
                        Values = string.Join(",", (p.values ?? new List<object>()).Select(x => Convert.ToString(x, CultureInfo.InvariantCulture))),
                        Keywords = p.keywords ?? ""
                    });
                }
                _gridParams.ItemsSource = _paramRows;

                // 规则行
                _ruleRows.Clear();
                foreach (var r in series.rules ?? new List<Ounibo.Catalog.Core.Rule>())
                {
                    _ruleRows.Add(new RuleRow
                    {
                        CondParam = r.If != null ? r.If.Param : "",
                        CondOp = r.If != null ? r.If.Op : "eq",
                        CondValue = r.If != null ? ValToStr(r.If.Value) : "",
                        ThenParam = r.Then != null ? r.Then.Param : "",
                        ThenOp = r.Then != null ? r.Then.Op : "in",
                        ThenValues = r.Then != null ? ValToStr(r.Then.Value) : ""
                    });
                }
                _gridRules.ItemsSource = _ruleRows;

                // 附件 (manual 等包内附件导出为文件)
                _attRows.Clear();
                foreach (var a in series.attachments ?? new List<Attachment>())
                {
                    if (string.IsNullOrEmpty(a.path)) continue;
                    var f = Path.Combine(tmp, "manual", Path.GetFileName(a.path.Replace('\\', '/')));
                    try
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(f));
                        File.WriteAllBytes(f, pkg.ReadAsset(a.path));
                        _attRows.Add(new AttachmentRow { Kind = a.kind, Lang = a.lang ?? "", Source = f, Model = "" });
                    }
                    catch { }
                }
                _gridAttach.ItemsSource = _attRows;

                _tbOut.Text = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "sample", series.code + "_edited.opc");
                MessageBox.Show(string.Format("已导入系列 {0}: {1} 个变体, {2} 个参数, {3} 条规则, {4} 个附件\n\n源文件在 {5}", series.code,
                    series.variants == null ? 0 : series.variants.Count, _paramRows.Count, _ruleRows.Count, _attRows.Count, tmp), Title);
            }
            catch (Exception ex)
            {
                MessageBox.Show("导入失败: " + ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        void PickStepDir()
        {
            var dlg = new System.Windows.Forms.FolderBrowserDialog { Description = "选择 STEP 文件目录" };
            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                _tbStepDir.Text = dlg.SelectedPath;
        }

        // ---- 数据源目录 (统一路线) ----

        string DefaultSourceDir()
        {
            // exe: <工作区>\OnebotCatalog\bin\ → 源目录 OnebotCatalog\onebot-data\catalog (自包含布局)
            return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "onebot-data", "catalog"));
        }

        string GetSourceDir()
        {
            var d = _tbSrcDir != null ? _tbSrcDir.Text.Trim() : "";
            return Directory.Exists(d) ? d : null;
        }

        void ReloadSeriesList()
        {
            _cbSeries.Items.Clear();
            var src = GetSourceDir();
            if (src == null) return;
            var dir = Path.Combine(src, "series");
            if (!Directory.Exists(dir)) return;
            foreach (var f in Directory.GetFiles(dir, "*.meta.json").OrderBy(x => x))
            {
                // KC.meta.json → KC (GetFileNameWithoutExtension 只剥 .json, 需再剥 .meta)
                _cbSeries.Items.Add(Path.GetFileName(f).Replace(".meta.json", ""));
            }
        }

        void OpenSeriesFromSource()
        {
            if (_cbSeries.SelectedItem == null) return;
            var src = GetSourceDir();
            if (src == null) return;
            var code = (string)_cbSeries.SelectedItem;
            try
            {
                LoadMetaFile(Path.Combine(src, "series", code + ".meta.json"));
                var csvPath = Path.Combine(src, "csv", code + ".csv");
                if (File.Exists(csvPath)) { _tbCsv.Text = csvPath; PreviewCsv(csvPath); }
                _tbStepDir.Text = Path.Combine(src, "step");
                _tbVer.Text = ReadCatalogVersion(src) ?? "";
                _tbOut.Text = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "sample", code + "_edited.opc");
            }
            catch (Exception ex) { MessageBox.Show("打开系列失败: " + ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Error); }
        }

        string ReadCatalogVersion(string src)
        {
            try
            {
                var f = Path.Combine(src, "catalog.json");
                if (!File.Exists(f)) return null;
                var ser = new System.Web.Script.Serialization.JavaScriptSerializer();
                var cat = ser.Deserialize<Ounibo.Catalog.Core.Catalog>(File.ReadAllText(f, Encoding.UTF8));
                return cat == null ? null : cat.catalogVersion;
            }
            catch { return null; }
        }

        void RebuildFull()
        {
            var src = GetSourceDir();
            if (src == null) { MessageBox.Show("数据源目录无效: " + (_tbSrcDir == null ? "" : _tbSrcDir.Text) + "\n(需含 catalog.json + series\\ + csv\\ + step\\)", Title); return; }
            var ver = ReadCatalogVersion(src) ?? "0.1";
            var outPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "sample", "OnebotCatalog_" + ver + ".opc");
            var res = FullCatalogBuilder.BuildFromSourceDir(src, outPath);
            var sb = new StringBuilder();
            sb.AppendLine(res.Ok ? "全量重建成功: " + outPath : "全量重建失败");
            foreach (var e in res.Errors) sb.AppendLine("  [错误] " + e);
            foreach (var w in res.Warnings) sb.AppendLine("  [警告] " + w);
            if (res.Ok)
            {
                try
                {
                    using (var built = OpcPackage.Open(outPath))
                    {
                        sb.AppendLine("系列数: " + built.Catalog.series.Count + " | 变体数: " + built.Catalog.series.Sum(s2 => s2.variants.Count));
                        var issues = PackageValidator.Validate(built);
                        sb.AppendLine("校验: " + issues.Count(i => i.Severity == "ERROR") + " 错误, " + issues.Count(i => i.Severity == "WARNING") + " 警告");
                    }
                }
                catch (Exception ex) { sb.AppendLine("产物校验失败: " + ex.Message); }
            }
            if (res.Ok && MessageBox.Show(sb.ToString() + "\n\n是否加载到主窗口?", Title, MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
                _owner.ReplacePackage(outPath);
            else
                MessageBox.Show(sb.ToString(), Title, MessageBoxButton.OK, res.Ok ? MessageBoxImage.Information : MessageBoxImage.Error);
        }

        // 自动识别参数 (与 OpcBuilder.AutoDetectParameters 共用逻辑): 数值列 → number, 其他 → enum
        void AutoParams()
        {
            _paramRows.Clear();
            if (string.IsNullOrWhiteSpace(_tbCsv.Text)) return;
            foreach (var d in OpcBuilder.AutoDetectParameters(_tbCsv.Text))
            {
                _paramRows.Add(new ParamRow
                {
                    Code = d.code,
                    NameZh = d.code,
                    NameEn = d.code,
                    Unit = d.unit,
                    Type = d.type,
                    Values = string.Join(",", (d.values ?? new List<object>()).Select(v => Convert.ToString(v, CultureInfo.InvariantCulture))),
                    Keywords = d.keywords ?? ""
                });
            }
            _gridParams.ItemsSource = _paramRows;
        }

        // ---- Step2: 参数定义 ----
        TabItem BuildStep2()
        {
            _gridParams = new DataGrid { AutoGenerateColumns = false, Margin = new Thickness(6) };
            _gridParams.Columns.Add(new DataGridTextColumn { Header = "参数代码 Code", Binding = new System.Windows.Data.Binding("Code"), IsReadOnly = true, Width = 100 });
            _gridParams.Columns.Add(new DataGridTextColumn { Header = "中文名 NameZh", Binding = new System.Windows.Data.Binding("NameZh"), Width = 110 });
            _gridParams.Columns.Add(new DataGridTextColumn { Header = "英文名 NameEn", Binding = new System.Windows.Data.Binding("NameEn"), Width = 110 });
            _gridParams.Columns.Add(new DataGridTextColumn { Header = "单位 Unit", Binding = new System.Windows.Data.Binding("Unit"), Width = 55 });
            _gridParams.Columns.Add(new DataGridComboBoxColumn { Header = "类型 Type", SelectedItemBinding = new System.Windows.Data.Binding("Type"), ItemsSource = new[] { "number", "enum" }, Width = 85 });
            _gridParams.Columns.Add(new DataGridTextColumn { Header = "取值 Values (逗号分隔)", Binding = new System.Windows.Data.Binding("Values"), Width = 170 });
            _gridParams.Columns.Add(new DataGridTextColumn { Header = "搜索关键词 Keywords (拼音/英文)", Binding = new System.Windows.Data.Binding("Keywords"), Width = 150 });
            _gridParams.Columns.Add(new DataGridTextColumn { Header = "显示名映射 Display (值:显示,值:显示)", Binding = new System.Windows.Data.Binding("Display"), Width = 200 });
            _gridParams.ItemsSource = _paramRows;
            var hint = new TextBlock
            {
                Text = "参数定义: Code 不可改; 补全中英文名; Type: number(数值, 支持范围搜索) / enum(枚举); Values 逗号分隔; Keywords 填拼音等供搜索; Display 格式 值:显示,值:显示 (如 :基本型,M:附磁石)。",
                Margin = new Thickness(6), TextWrapping = TextWrapping.Wrap
            };
            var panel = new DockPanel();
            DockPanel.SetDock(hint, Dock.Top);
            panel.Children.Add(hint);
            panel.Children.Add(_gridParams);
            var tab = new TabItem { Header = "2. 参数 Parameters" };
            tab.Content = panel;
            return tab;
        }

        // ---- Step3: 编码确认 ----
        TabItem BuildStep3()
        {
            var panel = new StackPanel { Margin = new Thickness(6) };
            panel.Children.Add(new TextBlock
            {
                Text = "型号编码 = 参数表首列 (model_code), 直接沿用, 不做模板反解。\nSTEP 文件命名 = 型号编码 + .step (对应 step_file 列)。\n生成时将校验: 编码唯一、STEP 文件存在、参数完整。",
                TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 8)
            });
            var tab = new TabItem { Header = "3. 编码 Coding" };
            tab.Content = panel;
            return tab;
        }

        // ---- Step4: 选型规则 ----
        TabItem BuildStep4()
        {
            var panel = new DockPanel { Margin = new Thickness(6) };
            var top = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 6) };
            var btnAdd = new Button { Content = "+ 添加规则", Width = 100, Margin = new Thickness(0, 0, 6, 0) };
            btnAdd.Click += (s, e) => _ruleRows.Add(new RuleRow { CondParam = "", CondOp = "eq", CondValue = "", ThenParam = "", ThenOp = "in", ThenValues = "" });
            var btnDel = new Button { Content = "- 删除所选", Width = 100 };
            btnDel.Click += (s, e) =>
            {
                var r = _gridRules.SelectedItem as RuleRow;
                if (r != null) _ruleRows.Remove(r);
            };
            top.Children.Add(btnAdd); top.Children.Add(btnDel);
            DockPanel.SetDock(top, Dock.Top);
            panel.Children.Add(top);

            var hint = new TextBlock
            {
                Text = "规则: 若 条件(参数 运算符 值) 满足, 则 约束(参数 运算符 值列表) 必须满足, 否则该变体无效。\n例: 条件 bore eq 16 → 约束 stroke in 25,50 (即缸径16时行程仅可选25/50)。in 的值用逗号分隔。",
                TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 6)
            };
            DockPanel.SetDock(hint, Dock.Top);
            panel.Children.Add(hint);

            _gridRules = new DataGrid { AutoGenerateColumns = false };
            _gridRules.Columns.Add(new DataGridTextColumn { Header = "条件参数", Binding = new System.Windows.Data.Binding("CondParam"), Width = 105 });
            _gridRules.Columns.Add(new DataGridComboBoxColumn { Header = "条件运算符", SelectedItemBinding = new System.Windows.Data.Binding("CondOp"), ItemsSource = new[] { "eq", "ne", "in", "gt", "ge", "lt", "le", "between" }, Width = 95 });
            _gridRules.Columns.Add(new DataGridTextColumn { Header = "条件值", Binding = new System.Windows.Data.Binding("CondValue"), Width = 85 });
            _gridRules.Columns.Add(new DataGridTextColumn { Header = "约束参数", Binding = new System.Windows.Data.Binding("ThenParam"), Width = 105 });
            _gridRules.Columns.Add(new DataGridComboBoxColumn { Header = "约束运算符", SelectedItemBinding = new System.Windows.Data.Binding("ThenOp"), ItemsSource = new[] { "eq", "in" }, Width = 95 });
            _gridRules.Columns.Add(new DataGridTextColumn { Header = "约束值(逗号分隔)", Binding = new System.Windows.Data.Binding("ThenValues"), Width = 150 });
            _gridRules.ItemsSource = _ruleRows;
            panel.Children.Add(_gridRules);
            var tab = new TabItem { Header = "4. 规则 Rules" };
            tab.Content = panel;
            return tab;
        }

        // ---- Step5: 附件与生成 ----
        TabItem BuildStep5()
        {
            var panel = new DockPanel { Margin = new Thickness(6) };
            var top = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 6) };
            var btnAtt = new Button { Content = "+ 添加附件", Width = 100, Margin = new Thickness(0, 0, 6, 0) };
            btnAtt.Click += (s, e) =>
            {
                var dlg = new OpenFileDialog { Filter = "附件 (*.png;*.jpg;*.pdf)|*.png;*.jpg;*.pdf", Title = "选择附件 (尺寸图/数据表)" };
                if (dlg.ShowDialog() == true)
                    _attRows.Add(new AttachmentRow { Kind = "dimDrawing", Lang = "zh", Source = dlg.FileName, Model = "" });
            };
            var btnDel = new Button { Content = "- 删除所选", Width = 100, Margin = new Thickness(0, 0, 10, 0) };
            btnDel.Click += (s, e) =>
            {
                var r = _gridAttach.SelectedItem as AttachmentRow;
                if (r != null) _attRows.Remove(r);
            };
            _tbOut = new TextBox { Width = 340, Text = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "sample", "NewCatalog.opc") };
            top.Children.Add(btnAtt); top.Children.Add(btnDel); top.Children.Add(new TextBlock { Text = "输出 .opc:", VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 6, 0) }); top.Children.Add(_tbOut);
            var btnBuild = new Button { Content = "生成数据包 Build", Width = 130, Margin = new Thickness(10, 0, 0, 0) };
            btnBuild.Click += (s, e) => DoBuild();
            top.Children.Add(btnBuild);
            DockPanel.SetDock(top, Dock.Top);
            panel.Children.Add(top);

            _gridAttach = new DataGrid { AutoGenerateColumns = false, Height = 200 };
            _gridAttach.Columns.Add(new DataGridComboBoxColumn { Header = "类型 Kind", SelectedItemBinding = new System.Windows.Data.Binding("Kind"), ItemsSource = new[] { "dimDrawing", "datasheet", "note", "manual" }, Width = 110 });
            _gridAttach.Columns.Add(new DataGridComboBoxColumn { Header = "语言 Lang", SelectedItemBinding = new System.Windows.Data.Binding("Lang"), ItemsSource = new[] { "zh", "en" }, Width = 65 });
            _gridAttach.Columns.Add(new DataGridTextColumn { Header = "源文件 Source", Binding = new System.Windows.Data.Binding("Source"), IsReadOnly = true, Width = 330 });
            _gridAttach.Columns.Add(new DataGridTextColumn { Header = "绑定型号 Model(可选)", Binding = new System.Windows.Data.Binding("Model"), Width = 120 });
            _gridAttach.ItemsSource = _attRows;
            DockPanel.SetDock(_gridAttach, Dock.Top);
            panel.Children.Add(_gridAttach);

            _tbResult = new TextBlock { TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 8, 0, 0) };
            panel.Children.Add(_tbResult);
            var tab = new TabItem { Header = "5. 附件与生成 Build" };
            tab.Content = panel;
            return tab;
        }

        object ToNumOrStr(string s)
        {
            // 前导零的编码 (00/02/03) 保持字符串, 否则会被数值化成 0/2/3 破坏型号匹配
            if (s.Length > 1 && s.StartsWith("0") && s.All(char.IsDigit)) return s;
            double d;
            if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out d)) return d;
            return s;
        }

        List<object> ParseValues(string s)
        {
            // 注意: 保留空串 (枚举值 "" 合法, 如 磁石=无磁石)
            return s.Split(new[] { ',', '，', ';', '；' })
                .Select(x => x.Trim())
                .Select(x => (object)ToNumOrStr(x))
                .ToList();
        }

        Ounibo.Catalog.Core.Rule BuildRule(RuleRow r)
        {
            if (string.IsNullOrWhiteSpace(r.CondParam) || string.IsNullOrWhiteSpace(r.ThenParam)) return null;
            RuleCond If, Then;
            If = new RuleCond { Param = r.CondParam.Trim(), Op = r.CondOp, Value = ToNumOrStr(r.CondValue.Trim()) };
            if (r.ThenOp == "in")
                Then = new RuleCond { Param = r.ThenParam.Trim(), Op = "in", Value = ParseValues(r.ThenValues).ToArray() };
            else
                Then = new RuleCond { Param = r.ThenParam.Trim(), Op = r.ThenOp, Value = ToNumOrStr(r.ThenValues.Trim()) };
            return new Ounibo.Catalog.Core.Rule { If = If, Then = Then };
        }

        // ---- 配置保存/加载 (SeriesMeta 格式, 与源目录 series\*.meta.json 及 CLI --meta 完全通用) ----

        SeriesMeta BuildMetaFromUi()
        {
            return new SeriesMeta
            {
                series = new Series
                {
                    code = _tbSeriesCode.Text.Trim(),
                    nameZh = _tbSeriesZh.Text.Trim(),
                    nameEn = _tbSeriesEn.Text.Trim(),
                    keywords = "",
                    parameters = _paramRows.Where(p => !string.IsNullOrWhiteSpace(p.Code)).Select(p => new ParameterDef
                    {
                        code = p.Code,
                        nameZh = p.NameZh,
                        nameEn = p.NameEn,
                        unit = p.Unit ?? "",
                        type = p.Type == "number" ? "number" : "enum",
                        keywords = p.Keywords ?? "",
                        values = ParseValues(p.Values ?? ""),
                        display = ParseDisplay(p.Display ?? "")
                    }).ToList(),
                    rules = _ruleRows.Select(BuildRule).Where(r => r != null).ToList(),
                    naming = new Naming { stepNameTemplate = "{model}.step" },
                    attachments = new List<Attachment>(),
                    variants = new List<Variant>()
                },
                attachments = _attRows.Where(a => !string.IsNullOrWhiteSpace(a.Source)).Select(a => new BuildAttachmentSpec
                {
                    kind = a.Kind,
                    lang = a.Lang,
                    source = a.Source,
                    model = string.IsNullOrWhiteSpace(a.Model) ? null : a.Model.Trim()
                }).ToList()
            };
        }

        Dictionary<string, string> ParseDisplay(string s)
        {
            var d = new Dictionary<string, string>();
            if (string.IsNullOrWhiteSpace(s)) return d;
            foreach (var part in s.Split(new[] { ',', '，', ';', '；' }))
            {
                var kv = part.Split(new[] { ':' }, 2);
                if (kv.Length == 2) d[kv[0].Trim()] = kv[1].Trim();
            }
            return d.Count == 0 ? null : d;
        }

        string FormatDisplay(Dictionary<string, string> d)
        {
            if (d == null || d.Count == 0) return "";
            return string.Join(",", d.Select(kv => kv.Key + ":" + kv.Value));
        }

        // 保存到源目录 (series\<code>.meta.json) 或另存为
        void SaveConfig()
        {
            var code = _tbSeriesCode.Text.Trim();
            var srcDir = GetSourceDir();
            var target = Directory.Exists(srcDir) && !string.IsNullOrEmpty(code)
                ? Path.Combine(srcDir, "series", code + ".meta.json")
                : null;
            if (target == null || (!File.Exists(target) && !string.IsNullOrEmpty(_loadedMetaPath)))
            {
                // 系列代码改名 → 沿用已加载文件路径
                if (!string.IsNullOrEmpty(_loadedMetaPath)) target = _loadedMetaPath;
            }
            if (target == null || !File.Exists(target))
            {
                var dlg = new SaveFileDialog { Filter = "Builder 配置 (*.json)|*.json", FileName = "builder-config.json", Title = "保存配置" };
                if (dlg.ShowDialog() != true) return;
                target = dlg.FileName;
            }
            try
            {
                var ser = new System.Web.Script.Serialization.JavaScriptSerializer();
                var meta = BuildMetaFromUi();
                // 覆盖已有配置时保留系列关键词 (向导无此字段, 避免清空搜索关键词)
                if (File.Exists(target))
                {
                    try
                    {
                        var old = ser.Deserialize<SeriesMeta>(File.ReadAllText(target, Encoding.UTF8));
                        if (old != null && old.series != null && string.Equals(old.series.code, meta.series.code, StringComparison.OrdinalIgnoreCase))
                            meta.series.keywords = old.series.keywords;
                    }
                    catch { }
                }
                Directory.CreateDirectory(Path.GetDirectoryName(target));
                File.WriteAllText(target, ser.Serialize(meta), Encoding.UTF8);
                _loadedMetaPath = target;
                MessageBox.Show("配置已保存: " + target + (target.IndexOf("series" + Path.DirectorySeparatorChar) >= 0 ? "\n(点【重建全量目录】后生效)" : ""), Title);
            }
            catch (Exception ex) { MessageBox.Show("保存失败: " + ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Error); }
        }

        static string ValToStr(object v)
        {
            var arr = v as object[];
            if (arr != null) return string.Join(",", arr.Select(x => Convert.ToString(x, CultureInfo.InvariantCulture)));
            return v == null ? "" : Convert.ToString(v, CultureInfo.InvariantCulture);
        }

        void LoadConfig()
        {
            var dlg = new OpenFileDialog { Filter = "Builder 配置 (*.json)|*.json", Title = "加载配置" };
            if (dlg.ShowDialog() != true) return;
            try { LoadMetaFile(dlg.FileName); }
            catch (Exception ex) { MessageBox.Show("加载失败: " + ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Error); }
        }

        void LoadMetaFile(string metaPath)
        {
            var ser = new System.Web.Script.Serialization.JavaScriptSerializer();
            var meta = ser.Deserialize<SeriesMeta>(File.ReadAllText(metaPath, Encoding.UTF8));
            if (meta == null || meta.series == null) { MessageBox.Show("配置格式无效", Title); return; }
            _loadedMetaPath = metaPath;
            _tbSeriesCode.Text = meta.series.code ?? "";
            _tbSeriesZh.Text = meta.series.nameZh ?? "";
            _tbSeriesEn.Text = meta.series.nameEn ?? "";

            _paramRows.Clear();
            foreach (var p in meta.series.parameters ?? new List<ParameterDef>())
            {
                _paramRows.Add(new ParamRow
                {
                    Code = p.code,
                    NameZh = p.nameZh,
                    NameEn = p.nameEn,
                    Unit = p.unit ?? "",
                    Type = p.type,
                    Keywords = p.keywords ?? "",
                    Values = string.Join(",", (p.values ?? new List<object>()).Select(v => Convert.ToString(v, CultureInfo.InvariantCulture))),
                    Display = FormatDisplay(p.display)
                });
            }
            _gridParams.ItemsSource = _paramRows;

            _ruleRows.Clear();
            foreach (var r in meta.series.rules ?? new List<Ounibo.Catalog.Core.Rule>())
            {
                _ruleRows.Add(new RuleRow
                {
                    CondParam = r.If != null ? r.If.Param : "",
                    CondOp = r.If != null ? r.If.Op : "eq",
                    CondValue = r.If != null ? ValToStr(r.If.Value) : "",
                    ThenParam = r.Then != null ? r.Then.Param : "",
                    ThenOp = r.Then != null ? r.Then.Op : "in",
                    ThenValues = r.Then != null ? ValToStr(r.Then.Value) : ""
                });
            }
            _gridRules.ItemsSource = _ruleRows;

            _attRows.Clear();
            foreach (var a in meta.attachments ?? new List<BuildAttachmentSpec>())
            {
                _attRows.Add(new AttachmentRow { Kind = a.kind, Lang = a.lang, Source = a.source, Model = a.model ?? "" });
            }
            _gridAttach.ItemsSource = _attRows;
        }

        void DoBuild()
        {
            _tbResult.Text = "";
            var sb = new System.Text.StringBuilder();
            sb.AppendLine("开始打包...");

            var series = new Series
            {
                code = _tbSeriesCode.Text.Trim(),
                nameZh = _tbSeriesZh.Text.Trim(),
                nameEn = _tbSeriesEn.Text.Trim(),
                keywords = "",
                parameters = _paramRows.Where(p => !string.IsNullOrWhiteSpace(p.Code)).Select(p => new ParameterDef
                {
                    code = p.Code,
                    nameZh = string.IsNullOrWhiteSpace(p.NameZh) ? p.Code : p.NameZh,
                    nameEn = string.IsNullOrWhiteSpace(p.NameEn) ? p.Code : p.NameEn,
                    unit = p.Unit ?? "",
                    type = p.Type == "number" ? "number" : "enum",
                    keywords = p.Keywords ?? "",
                    values = ParseValues(p.Values ?? "")
                }).ToList(),
                rules = _ruleRows.Select(BuildRule).Where(r => r != null).ToList(),
                naming = new Naming { stepNameTemplate = "{model}.step" },
                attachments = new List<Attachment>(),
                variants = new List<Variant>()
            };
            if (string.IsNullOrEmpty(series.code)) { MessageBox.Show("请填写系列代码", Title); return; }
            if (_paramRows.Count == 0) { MessageBox.Show("请先在 Step1 导入参数表", Title); return; }

            var res = OpcBuilder.Build(
                _tbOut.Text.Trim(),
                "欧霓博气动目录", _tbVer.Text.Trim(),
                "ACT", _tbCatName.Text.Trim(), _tbCatName.Text.Trim(),
                series,
                _tbCsv.Text, _tbStepDir.Text,
                _attRows.Where(a => !string.IsNullOrWhiteSpace(a.Source)).Select(a => new BuildAttachmentSpec
                {
                    kind = a.Kind, lang = a.Lang, source = a.Source, model = string.IsNullOrWhiteSpace(a.Model) ? null : a.Model.Trim()
                }).ToList());

            if (!res.Ok)
            {
                sb.AppendLine("打包失败:");
                foreach (var e in res.Errors.Take(20)) sb.AppendLine("  [错误] " + e);
                _tbResult.Text = sb.ToString();
                return;
            }
            sb.AppendLine("打包成功: " + res.OutputPath);
            foreach (var w in res.Warnings.Take(10)) sb.AppendLine("  [警告] " + w);

            // 打包后立即校验
            try
            {
                using (var pkg = OpcPackage.Open(res.OutputPath))
                {
                    var issues = PackageValidator.Validate(pkg);
                    sb.AppendLine("校验: " + issues.Count(i => i.Severity == "ERROR") + " 错误, " + issues.Count(i => i.Severity == "WARNING") + " 警告");
                    foreach (var i in issues.Take(15)) sb.AppendLine("  [" + i.Severity + "] " + i.Target + " " + i.Message);
                    sb.AppendLine("变体数: " + pkg.Catalog.series.Sum(s2 => s2.variants.Count));
                }
            }
            catch (Exception ex) { sb.AppendLine("校验失败: " + ex.Message); }
            _tbResult.Text = sb.ToString();

            if (MessageBox.Show("数据包已生成, 是否加载到主窗口?\n注意: 将替换主窗口当前数据包 (若只做了单系列, 加载后目录只剩该系列)", Title, MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
                _owner.ReplacePackage(res.OutputPath);
        }
    }
}
#endif
