using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using DC_A95;
using MesWork;

namespace MesWork.Pages
{
    /// <summary>
    /// 称重作业页面 — 双工位实时称重监控
    /// 信号灯12个(4×3)，500ms轮询PLC数据
    /// </summary>
    public partial class UC_Weighing : UserControl
    {
        // ── 信号灯名称（12个 = 4×3网格）──
        private static readonly string[] SignalNames =
        {
            "工件到位", "读取完成", "开始称重", "称重完成",
            "请求工作", "允许工作", "请求保存", "保存完成",
            "等待放行", "PLC心跳",  "PC心跳",  "合格标志"
        };

        /// <summary>信号灯对应的 tagTypeCodeID</summary>
        private static readonly int[] SignalTagCodes =
        {
            2,       // 工件到位
            100001,  // 读取完成
            100002,  // 开始称重
            100003,  // 称重完成
            44,      // 请求工作
            66,      // 允许工作
            19,      // 请求保存
            20,      // 保存完成
            100004,  // 等待放行
            14,      // PLC心跳
            15,      // PC心跳
            21       // 合格标志 (Int: 0=无, 1=合格, 2=不合格)
        };

        private static readonly Color C_SignalOn    = Color.FromArgb(16, 185, 129);   // 绿色
        private static readonly Color C_SignalOff   = Color.FromArgb(209, 213, 219);  // 灰色
        private static readonly Color C_SignalBad   = Color.FromArgb(239, 68, 68);    // 红色(不合格)

        /// <summary>报警代码文本映射</summary>
        private static readonly string[] AlarmCodeTexts =
        {
            "无报警",                 // 0
            "获取工厂数据接口调用失败", // 1
            "上传工厂数据接口调用失败", // 2
            "发动机号错误"            // 3
        };

        /// <summary>工位1 信号灯面板 [0-11]</summary>
        private Panel[] _s1Signals;
        /// <summary>工位2 信号灯面板 [0-11]</summary>
        private Panel[] _s2Signals;

        /// <summary>PLC数据轮询定时器 500ms</summary>
        private Timer _tmrPLC;

        /// <summary>工位1的opName (如OP10)</summary>
        private string _op1;
        /// <summary>工位2的opName (如OP20)</summary>
        private string _op2;

        private bool _isApplyingMetricLayout;

        public UC_Weighing()
        {
            InitializeComponent();
            if (IsInDesignMode())
            {
                ApplyDesignerPreviewLayout();
                ApplyWeighingType();
                return;
            }
            Resize += (s, e) => ApplyMetricCardLayout();
            pnl_Stations.Resize += (s, e) => ApplyMetricCardLayout();
            ApplyMetricCardLayout();
            InitSignalLights();
            ApplyWeighingType();
            InitPLCTimer();
        }

        private void ApplyMetricCardLayout()
        {
            if (IsInDesignMode() || _isApplyingMetricLayout || pnl_Stations.ClientSize.Width <= 0)
                return;

            _isApplyingMetricLayout = true;
            try
            {
                int logHeight = Math.Max(210, Math.Min(280, Height / 4));
                if (pnl_Log.Height != logHeight)
                    pnl_Log.Height = logHeight;

                pnl_Stations.Padding = new Padding(12, 12, 12, 10);

                int availableWidth = pnl_Stations.ClientSize.Width - pnl_Stations.Padding.Left - pnl_Stations.Padding.Right;
                int stationGap = 14;
                int stationWidth = Math.Max(620, (availableWidth - stationGap) / 2);
                pnl_Station1.Width = stationWidth;

                LayoutStationHeaderSection(
                    pnl_Station1,
                    pnl_S1_Header,
                    lbl_S1_Title,
                    lbl_S1_EngineLabel,
                    lbl_S1_EngineVal,
                    lbl_S1_ModelLabel,
                    lbl_S1_ModelVal,
                    lbl_S1_OrderLabel,
                    lbl_S1_OrderVal,
                    lbl_S1_PalletLabel,
                    lbl_S1_PalletVal);
                LayoutStationHeaderSection(
                    pnl_Station2,
                    pnl_S2_Header,
                    lbl_S2_Title,
                    lbl_S2_EngineLabel,
                    lbl_S2_EngineVal,
                    lbl_S2_ModelLabel,
                    lbl_S2_ModelVal,
                    lbl_S2_OrderLabel,
                    lbl_S2_OrderVal,
                    lbl_S2_PalletLabel,
                    lbl_S2_PalletVal);

                LayoutStationMetricCards(
                    pnl_Station1,
                    pnl_S1_WeightBox,
                    label1,
                    lbl_S1_Weight,
                    lbl_S1_WeightUnit,
                    pnl_S1_WaterBox,
                    lbl_S1_WaterTitle,
                    lbl_S1_Info1,
                    lbl_S1_WaterUnit,
                    pnl_S1_MetricBox,
                    pnl_S1_DensityBox,
                    lbl_S1_DensityTitle,
                    lbl_S1_DensityVal,
                    lbl_S1_DensityUnit,
                    pnl_S1_AddOilBox,
                    lbl_S1_AddOilTitle,
                    lbl_S1_Info2,
                    lbl_S1_AddOilUnit,
                    pnl_S1_ExtractOilBox,
                    lbl_S1_ExtractOilTitle,
                    lbl_S1_ExtractOilVal,
                    lbl_S1_ExtractOilUnit,
                    pnl_S1_OilReleaseBox,
                    lbl_S1_OilReleaseTitle,
                    lbl_S1_OilReleaseVal,
                    lbl_S1_OilReleaseUnit,
                    pnl_S1_ResidualOilBox,
                    lbl_S1_ResidualOilTitle,
                    lbl_S1_ResidualOilVal,
                    lbl_S1_ResidualOilUnit,
                    pnl_S1_QualityBox,
                    lbl_S1_QualityTitle,
                    lbl_S1_QualityVal);
                LayoutStationMetricCards(
                    pnl_Station2,
                    pnl_S2_WeightBox,
                    lbl_S2_WeightTitle,
                    lbl_S2_Weight,
                    lbl_S2_WeightUnit,
                    pnl_S2_WaterBox,
                    lbl_S2_WaterTitle,
                    lbl_S2_Info1,
                    lbl_S2_WaterUnit,
                    pnl_S2_MetricBox,
                    pnl_S2_DensityBox,
                    lbl_S2_DensityTitle,
                    lbl_S2_DensityVal,
                    lbl_S2_DensityUnit,
                    pnl_S2_AddOilBox,
                    lbl_S2_AddOilTitle,
                    lbl_S2_Info2,
                    lbl_S2_AddOilUnit,
                    pnl_S2_ExtractOilBox,
                    lbl_S2_ExtractOilTitle,
                    lbl_S2_ExtractOilVal,
                    lbl_S2_ExtractOilUnit,
                    pnl_S2_OilReleaseBox,
                    lbl_S2_OilReleaseTitle,
                    lbl_S2_OilReleaseVal,
                    lbl_S2_OilReleaseUnit,
                    pnl_S2_ResidualOilBox,
                    lbl_S2_ResidualOilTitle,
                    lbl_S2_ResidualOilVal,
                    lbl_S2_ResidualOilUnit,
                    pnl_S2_QualityBox,
                    lbl_S2_QualityTitle,
                    lbl_S2_QualityVal);

                ConfigureAlarmLine(pnl_Station1, lbl_S1_AlarmLabel, lbl_S1_AlarmVal);
                ConfigureAlarmLine(pnl_Station2, lbl_S2_AlarmLabel, lbl_S2_AlarmVal);
            }
            finally
            {
                _isApplyingMetricLayout = false;
            }
        }

        private void LayoutStationMetricCards(
            Panel station,
            Panel weightBox,
            Label weightTitle,
            Label weightValue,
            Label weightUnit,
            Panel waterBox,
            Label waterTitle,
            Label waterValue,
            Label waterUnit,
            Panel metricBox,
            Panel densityBox,
            Label densityTitle,
            Label densityValue,
            Label densityUnit,
            Panel addOilBox,
            Label addOilTitle,
            Label addOilValue,
            Label addOilUnit,
            Panel extractOilBox,
            Label extractOilTitle,
            Label extractOilValue,
            Label extractOilUnit,
            Panel oilReleaseBox,
            Label oilReleaseTitle,
            Label oilReleaseValue,
            Label oilReleaseUnit,
            Panel residualOilBox,
            Label residualOilTitle,
            Label residualOilValue,
            Label residualOilUnit,
            Panel qualityBox,
            Label qualityTitle,
            Label qualityValue)
        {
            int margin = 20;
            int gap = 16;
            int stationWidth = Math.Max(620, station.ClientSize.Width);
            int contentWidth = Math.Max(560, stationWidth - margin * 2);
            int topCardWidth = Math.Max(250, (contentWidth - gap) / 2);
            int metricCardWidth = Math.Max(165, (contentWidth - gap * 2) / 3);
            int weightY = 262;
            int metricY = 408;
            int metricRowGap = 12;

            ConfigureWeightCard(weightBox, weightTitle, weightValue, weightUnit, topCardWidth, 128, 41F);
            weightBox.Location = new Point(margin, weightY);
            ConfigureMetricCard(station, waterBox, waterTitle, waterValue, waterUnit, "水含量", "ppm", margin + topCardWidth + gap, weightY, topCardWidth, 128, 28F);

            metricBox.BackColor = Color.White;
            metricBox.Location = new Point(margin, metricY);
            metricBox.Size = new Size(contentWidth, 160);
            if (metricBox.Parent != station)
                station.Controls.Add(metricBox);

            ConfigureMetricCard(metricBox, densityBox, densityTitle, densityValue, densityUnit, "油密度", "KG/L", 0, 0, metricCardWidth, 74, 18F);
            ConfigureMetricCard(metricBox, addOilBox, addOilTitle, addOilValue, addOilUnit, "加油量", "L", metricCardWidth + gap, 0, metricCardWidth, 74, 18F);
            ConfigureMetricCard(metricBox, extractOilBox, extractOilTitle, extractOilValue, extractOilUnit, "抽油量", "L", (metricCardWidth + gap) * 2, 0, metricCardWidth, 74, 18F);
            ConfigureMetricCard(metricBox, oilReleaseBox, oilReleaseTitle, oilReleaseValue, oilReleaseUnit, "放油量", "L", 0, 74 + metricRowGap, metricCardWidth, 74, 18F);
            ConfigureMetricCard(metricBox, residualOilBox, residualOilTitle, residualOilValue, residualOilUnit, "残油量", "L", metricCardWidth + gap, 74 + metricRowGap, metricCardWidth, 74, 18F);
            ConfigureMetricCard(metricBox, qualityBox, qualityTitle, qualityValue, null, "合格结果", "", (metricCardWidth + gap) * 2, 74 + metricRowGap, metricCardWidth, 74, 18F);
        }

        private void LayoutStationHeaderSection(
            Panel station,
            Panel headerPanel,
            Label titleLabel,
            Label engineLabel,
            Label engineValue,
            Label modelLabel,
            Label modelValue,
            Label orderLabel,
            Label orderValue,
            Label palletLabel,
            Label palletValue)
        {
            int margin = 20;
            int columnGap = 26;
            int labelWidth = 94;
            int valueGap = 6;
            int headerHeight = 44;
            int rowHeight = 38;
            int topY = headerHeight + 12;
            int contentWidth = Math.Max(560, station.ClientSize.Width - margin * 2);
            int columnWidth = (contentWidth - columnGap) / 2;
            int valueWidth = Math.Max(120, columnWidth - labelWidth - valueGap);

            headerPanel.Height = headerHeight;
            titleLabel.Font = new Font("微软雅黑", 12.5F, FontStyle.Bold);
            titleLabel.Padding = new Padding(18, 0, 0, 0);

            ConfigureInfoPair(engineLabel, engineValue, margin, topY, labelWidth, valueWidth, rowHeight);
            ConfigureInfoPair(modelLabel, modelValue, margin + columnWidth + columnGap, topY, labelWidth, valueWidth, rowHeight);
            ConfigureInfoPair(orderLabel, orderValue, margin, topY + rowHeight, labelWidth, valueWidth, rowHeight);
            ConfigureInfoPair(palletLabel, palletValue, margin + columnWidth + columnGap, topY + rowHeight, labelWidth, valueWidth, rowHeight);
        }

        private void ConfigureAlarmLine(Panel station, Label alarmLabel, Label alarmValue)
        {
            int y = 590;
            alarmLabel.Font = new Font("微软雅黑", 11.5F, FontStyle.Bold);
            alarmLabel.ForeColor = Color.FromArgb(107, 114, 128);
            alarmLabel.Location = new Point(20, y);
            alarmLabel.Size = new Size(96, 34);
            alarmLabel.BringToFront();

            alarmValue.Font = new Font("微软雅黑", 11.5F, FontStyle.Bold);
            alarmValue.Location = new Point(116, y);
            alarmValue.Size = new Size(Math.Max(240, station.ClientSize.Width - 136), 34);
            alarmValue.BringToFront();
        }

        private void ConfigureWeightCard(System.Windows.Forms.Panel panel, System.Windows.Forms.Label titleLabel, System.Windows.Forms.Label valueLabel, System.Windows.Forms.Label unitLabel, int width, int height, float valueFontSize)
        {
            panel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(250)))), ((int)(((byte)(252)))));
            panel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            panel.Size = new System.Drawing.Size(width, height);

            titleLabel.AutoSize = false;
            titleLabel.Font = new System.Drawing.Font("宋体", 14F, System.Drawing.FontStyle.Bold);
            titleLabel.ForeColor = System.Drawing.Color.Black;
            titleLabel.Location = new System.Drawing.Point(14, 8);
            titleLabel.Size = new System.Drawing.Size(178, 30);
            titleLabel.Text = "实时重量";
            titleLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
            if (titleLabel.Parent != panel)
                panel.Controls.Add(titleLabel);

            valueLabel.AutoSize = false;
            valueLabel.Font = new System.Drawing.Font("Consolas", valueFontSize, System.Drawing.FontStyle.Bold);
            valueLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(58)))), ((int)(((byte)(95)))));
            valueLabel.Location = new System.Drawing.Point(34, 20);
            valueLabel.Size = new System.Drawing.Size(Math.Max(150, width - 122), height - 40);
            valueLabel.Text = "0.000";
            valueLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            if (valueLabel.Parent != panel)
                panel.Controls.Add(valueLabel);

            unitLabel.AutoSize = false;
            unitLabel.Font = new System.Drawing.Font("微软雅黑", 18F, System.Drawing.FontStyle.Bold);
            unitLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(107)))), ((int)(((byte)(114)))), ((int)(((byte)(128)))));
            unitLabel.Location = new System.Drawing.Point(width - 82, height - 44);
            unitLabel.Size = new System.Drawing.Size(66, 34);
            unitLabel.Text = "KG";
            unitLabel.TextAlign = System.Drawing.ContentAlignment.BottomRight;
            if (unitLabel.Parent != panel)
                panel.Controls.Add(unitLabel);
            unitLabel.BringToFront();
            titleLabel.BringToFront();
        }

        private void ConfigureMetricCard(System.Windows.Forms.Control parent, System.Windows.Forms.Panel panel, System.Windows.Forms.Label titleLabel, System.Windows.Forms.Label valueLabel, System.Windows.Forms.Label unitLabel, string title, string unit, int x, int y, int width, int height, float valueFontSize)
        {
            panel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(250)))), ((int)(((byte)(252)))));
            panel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            panel.Location = new System.Drawing.Point(x, y);
            panel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
            panel.Size = new System.Drawing.Size(width, height);
            if (panel.Parent != parent)
                parent.Controls.Add(panel);

            titleLabel.AutoSize = false;
            titleLabel.Font = new System.Drawing.Font("宋体", height >= 100 ? 14F : 10.5F, System.Drawing.FontStyle.Bold);
            titleLabel.ForeColor = System.Drawing.Color.Black;
            titleLabel.Location = new System.Drawing.Point(12, 7);
            titleLabel.Size = new System.Drawing.Size(width - 24, height >= 100 ? 30 : 22);
            titleLabel.Text = title;
            titleLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
            if (titleLabel.Parent != panel)
                panel.Controls.Add(titleLabel);

            valueLabel.AutoSize = false;
            valueLabel.BorderStyle = System.Windows.Forms.BorderStyle.None;
            valueLabel.BackColor = panel.BackColor;
            valueLabel.Font = new System.Drawing.Font("微软雅黑", valueFontSize, System.Drawing.FontStyle.Bold);
            valueLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(58)))), ((int)(((byte)(95)))));
            int unitWidth = unit == "KG/L" ? 66 : (unit == "ppm" ? 76 : 38);
            int valueRightPadding = unitLabel == null ? 16 : unitWidth + 24;
            valueLabel.Location = new System.Drawing.Point(20, height >= 100 ? 24 : 20);
            valueLabel.Size = new System.Drawing.Size(Math.Max(72, width - 20 - valueRightPadding), height >= 100 ? height - 40 : height - 24);
            valueLabel.Text = NormalizeMetricValue(valueLabel.Text);
            valueLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            if (valueLabel.Parent != panel)
                panel.Controls.Add(valueLabel);

            if (unitLabel != null)
            {
                unitLabel.AutoSize = false;
                unitLabel.Font = new System.Drawing.Font("微软雅黑", height >= 100 ? 18F : 11F, System.Drawing.FontStyle.Bold);
                unitLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(107)))), ((int)(((byte)(114)))), ((int)(((byte)(128)))));
                unitLabel.Location = new System.Drawing.Point(width - unitWidth - 12, height - (height >= 100 ? 42 : 28));
                unitLabel.Size = new System.Drawing.Size(unitWidth, height >= 100 ? 30 : 20);
                unitLabel.Text = unit;
                unitLabel.TextAlign = System.Drawing.ContentAlignment.BottomRight;
                if (unitLabel.Parent != panel)
                    panel.Controls.Add(unitLabel);
                unitLabel.BringToFront();
            }

            titleLabel.BringToFront();
        }

        private void ConfigureInfoPair(Label titleLabel, Label valueLabel, int x, int y, int titleWidth, int valueWidth, int height)
        {
            titleLabel.Font = new Font("微软雅黑", 12F, FontStyle.Bold);
            titleLabel.ForeColor = Color.FromArgb(107, 114, 128);
            titleLabel.Location = new Point(x, y);
            titleLabel.Size = new Size(titleWidth, height);
            titleLabel.TextAlign = ContentAlignment.MiddleLeft;

            valueLabel.Font = new Font("微软雅黑", 13F, FontStyle.Bold);
            valueLabel.ForeColor = Color.FromArgb(30, 58, 95);
            valueLabel.Location = new Point(x + titleWidth + 6, y);
            valueLabel.Size = new Size(valueWidth, height);
            valueLabel.TextAlign = ContentAlignment.MiddleLeft;
        }

        private string NormalizeMetricValue(string text)
        {
            if (string.IsNullOrWhiteSpace(text))
                return "0.00";

            int colonIndex = text.IndexOf('：');
            if (colonIndex >= 0 && colonIndex < text.Length - 1)
                text = text.Substring(colonIndex + 1).Trim();

            text = text.Trim();
            return text == "—" ? "0.00" : text;
        }

        /// <summary>
        /// 根据 App.config 中的 WeighingType 配置切换工位标题
        /// </summary>
        private void ApplyWeighingType()
        {
            // 优先从Excel配置读取工位显示名（OpName → "OP10 — 称重位1"）
            var names   = MesWorkForm.StationDisplayNames;
            var opNames = MesWorkForm.StationOpNames;
            string wType = System.Configuration.ConfigurationManager.AppSettings["WeighingType"] ?? "Hanging";
            bool isGround = wType.Equals("Ground", StringComparison.OrdinalIgnoreCase);

            string dn1 = null, dn2 = null;
            string title1 = opNames.Count >= 1 && names.TryGetValue(opNames[0], out dn1)
                ? dn1
                : (isGround ? "工位1 — 地面称重" : "工位1 — 放油前称重");

            string title2 = opNames.Count >= 2 && names.TryGetValue(opNames[1], out dn2)
                ? dn2
                : (isGround ? "工位2 — 地面称重" : "工位2 — 放油后称重");

            lbl_S1_Title.Text = title1;
            lbl_S2_Title.Text = title2;
        }

        // ====================================================================
        // 信号灯创建（12个 = 4列×3行）
        // ====================================================================

        private void InitSignalLights()
        {
            _s1Signals = CreateSignalGrid(pnl_Station1, 20, 136, 198, 36, 22, 12.5F);
            _s2Signals = CreateSignalGrid(pnl_Station2, 20, 136, 198, 36, 22, 12.5F);
        }

        private Panel[] CreateSignalGrid(Panel parent, int startX, int startY)
        {
            return CreateSignalGrid(parent, startX, startY, 200, 32, 20, 10F);
        }

        private Panel[] CreateSignalGrid(Panel parent, int startX, int startY, int colSpacing, int rowSpacing, int dotSize, float fontSize)
        {
            var signals = new Panel[12];
            for (int i = 0; i < 12; i++)
            {
                int col = i % 4;
                int row = i / 4;
                int x = startX + col * colSpacing;
                int y = startY + row * rowSpacing;

                var dot = new Panel
                {
                    BackColor = C_SignalOff,
                    Location = new Point(x, y + 2),
                    Size = new Size(dotSize, dotSize),
                    Name = $"sig_{parent.Name}_{i}"
                };
                dot.Paint += (s, e) =>
                {
                    var p = (Panel)s;
                    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
                    using (var brush = new SolidBrush(p.BackColor))
                    {
                        e.Graphics.Clear(p.Parent.BackColor);
                        e.Graphics.FillEllipse(brush, 0, 0, p.Width - 1, p.Height - 1);
                    }
                };

                var lbl = new Label
                {
                    Text = SignalNames[i],
                    Font = new Font("微软雅黑", fontSize, FontStyle.Bold),
                    ForeColor = Color.FromArgb(55, 65, 81),
                    Location = new Point(x + dotSize + 8, y),
                    AutoSize = true
                };

                parent.Controls.Add(dot);
                parent.Controls.Add(lbl);
                signals[i] = dot;
            }
            return signals;
        }

        private bool IsInDesignMode()
        {
            return LicenseManager.UsageMode == LicenseUsageMode.Designtime || DesignMode || (Site?.DesignMode ?? false);
        }

        private void ApplyDesignerPreviewLayout()
        {
            MirrorDesignerStationLayout(
                pnl_Station2,
                pnl_S2_WeightBox,
                lbl_S2_WeightTitle,
                lbl_S2_Weight,
                lbl_S2_WeightUnit,
                lbl_S2_Info1,
                pnl_S2_MetricBox,
                lbl_S2_DensityVal,
                lbl_S2_Info2,
                lbl_S2_ExtractOilVal,
                lbl_S2_OilReleaseVal,
                lbl_S2_ResidualOilVal,
                lbl_S2_QualityVal,
                lbl_S2_AlarmLabel,
                lbl_S2_AlarmVal);
        }

        private void MirrorDesignerStationLayout(
            Panel station,
            Panel weightBox,
            Label weightTitle,
            Label weightValue,
            Label weightUnit,
            Label waterValue,
            Panel metricBox,
            Label densityValue,
            Label addOilValue,
            Label extractOilValue,
            Label oilReleaseValue,
            Label residualOilValue,
            Label qualityValue,
            Label alarmLabel,
            Label alarmValue)
        {
            station.Controls.Remove(pnl_S2_WaterBox);
            station.Controls.Add(waterValue);
            station.Controls.Add(metricBox);
            station.Controls.Add(weightBox);
            station.Controls.Add(alarmLabel);
            station.Controls.Add(alarmValue);

            metricBox.Controls.Clear();
            metricBox.Controls.Add(qualityValue);
            metricBox.Controls.Add(residualOilValue);
            metricBox.Controls.Add(oilReleaseValue);
            metricBox.Controls.Add(densityValue);
            metricBox.Controls.Add(extractOilValue);
            metricBox.Controls.Add(addOilValue);

            weightBox.Location = new Point(30, 401);
            weightBox.Size = new Size(599, 164);
            weightTitle.Location = new Point(15, 9);
            weightTitle.Size = new Size(137, 30);
            weightTitle.Font = new Font("宋体", 15F, FontStyle.Bold);
            weightTitle.Text = "实时重量";
            weightTitle.AutoSize = true;
            weightValue.Location = new Point(114, 9);
            weightValue.Size = new Size(327, 144);
            weightValue.Font = new Font("Consolas", 54F, FontStyle.Bold);
            weightValue.TextAlign = ContentAlignment.MiddleRight;
            weightUnit.Location = new Point(456, 60);
            weightUnit.Size = new Size(135, 87);
            weightUnit.Font = new Font("微软雅黑", 20F, FontStyle.Bold);
            weightUnit.TextAlign = ContentAlignment.BottomLeft;

            waterValue.BackColor = Color.FromArgb(248, 250, 252);
            waterValue.BorderStyle = BorderStyle.FixedSingle;
            waterValue.Font = new Font("微软雅黑", 18F, FontStyle.Bold);
            waterValue.Location = new Point(660, 401);
            waterValue.Size = new Size(599, 164);
            waterValue.Text = "水含量：0.00 ppm";
            waterValue.TextAlign = ContentAlignment.MiddleCenter;
            waterValue.AutoSize = false;

            metricBox.Location = new Point(30, 594);
            metricBox.Size = new Size(1230, 174);
            metricBox.BackColor = Color.White;

            ConfigureDesignerMetricLabel(densityValue, "油密度：0.00", 0, 0);
            ConfigureDesignerMetricLabel(addOilValue, "加油量：0.00", 420, 0);
            ConfigureDesignerMetricLabel(extractOilValue, "抽油量：0.00", 840, 0);
            ConfigureDesignerMetricLabel(oilReleaseValue, "放油量：0.00", 0, 93);
            ConfigureDesignerMetricLabel(residualOilValue, "残油量：0.00", 420, 93);
            ConfigureDesignerMetricLabel(qualityValue, "合格结果：--", 840, 93);

            alarmLabel.Font = new Font("微软雅黑", 13F, FontStyle.Bold);
            alarmLabel.Location = new Point(30, 792);
            alarmLabel.Size = new Size(112, 42);
            alarmValue.Font = new Font("微软雅黑", 13F, FontStyle.Bold);
            alarmValue.Location = new Point(180, 792);
            alarmValue.Size = new Size(1080, 42);
            alarmValue.Text = "无报警";
        }

        private void ConfigureDesignerMetricLabel(Label label, string text, int x, int y)
        {
            label.BackColor = Color.FromArgb(248, 250, 252);
            label.BorderStyle = BorderStyle.FixedSingle;
            label.Font = new Font("微软雅黑", 13F, FontStyle.Bold);
            label.ForeColor = Color.FromArgb(55, 65, 81);
            label.Location = new Point(x, y);
            label.Size = new Size(389, 80);
            label.Text = text;
            label.TextAlign = ContentAlignment.MiddleCenter;
            label.AutoSize = false;
        }

        // ====================================================================
        // PLC 轮询定时器 — 500ms
        // ====================================================================

        private void InitPLCTimer()
        {
            _tmrPLC = new Timer { Interval = 500 };
            _tmrPLC.Tick += TmrPLC_Tick;
            var tmrDelay = new Timer { Interval = 3000 };
            tmrDelay.Tick += (s, e) =>
            {
                tmrDelay.Stop();
                tmrDelay.Dispose();

                var ops = MesWorkForm.StationOpNames;
                if (ops != null && ops.Count >= 2)
                {
                    _op1 = ops[0];
                    _op2 = ops[1];
                }
                else if (ops != null && ops.Count == 1)
                {
                    _op1 = ops[0];
                    _op2 = ops[0];
                }

                if (!string.IsNullOrEmpty(_op1))
                {
                    _tmrPLC.Start();
                    AppendLog($"PLC配置读取，工位1={_op1}，工位2={_op2 ?? "无"}");
                }
                else
                {
                    AppendLog("未检测到设备工位配置，PLC轮询未启动");
                }
            };
            tmrDelay.Start();
        }

        private void TmrPLC_Tick(object sender, EventArgs e)
        {
            try
            {
                if (!string.IsNullOrEmpty(_op1))
                    PollStation(1, _op1, _s1Signals,
                        lbl_S1_Weight, lbl_S1_Info1, lbl_S1_Info2,
                        lbl_S1_PalletVal, lbl_S1_AlarmVal);

                if (!string.IsNullOrEmpty(_op2))
                    PollStation(2, _op2, _s2Signals,
                        lbl_S2_Weight, lbl_S2_Info1, lbl_S2_Info2,
                        lbl_S2_PalletVal, lbl_S2_AlarmVal);
            }
            catch { }
        }

        /// <summary>
        /// 轮询单个工位的所有PLC数据
        /// </summary>
        private void PollStation(int station, string opName, Panel[] signals,
            Label lblWeight, Label lblWater, Label lblOil,
            Label lblPallet, Label lblAlarm)
        {
            try
            {
                // ── 前11个布尔信号灯 ──
                for (int i = 0; i < 11; i++)
                {
                    try
                    {
                        var val = PlcLinkForm.ReadPLC(SignalTagCodes[i], opName);
                        bool active = val != null && val.ToString().ToLower()
                            .Replace("true", "1").Replace("false", "0") == "1";
                        var newColor = active ? C_SignalOn : C_SignalOff;
                        if (signals[i].BackColor != newColor)
                        {
                            signals[i].BackColor = newColor;
                            signals[i].Invalidate();
                        }
                    }
                    catch { }
                }

                // ── 第12个：合格标志(21) — Int类型特殊处理 ──
                try
                {
                    var qVal = PlcLinkForm.ReadPLC(21, opName);
                    int qFlag = qVal != null ? Convert.ToInt32(qVal) : 0;
                    Color qColor = qFlag == 1 ? C_SignalOn : (qFlag == 2 ? C_SignalBad : C_SignalOff);
                    if (signals[11].BackColor != qColor)
                    {
                        signals[11].BackColor = qColor;
                        signals[11].Invalidate();
                    }
                    var lblQuality = station == 1 ? lbl_S1_QualityVal : lbl_S2_QualityVal;
                    lblQuality.Text = qFlag == 1 ? "合格" : (qFlag == 2 ? "不合格" : "--");
                    lblQuality.ForeColor = qFlag == 2 ? C_SignalBad : Color.FromArgb(30, 58, 95);
                }
                catch { }

                // ── 实时重量(100220) ──
                try
                {
                    var wv = PlcLinkForm.ReadPLC(100220, opName);
                    if (wv != null)
                    {
                        decimal weight = Convert.ToDecimal(wv);
                        if (WeightCalibrationHelper.Apply(opName, weight, out decimal calibratedWeight, out _, out _))
                        {
                            weight = calibratedWeight;
                        }
                        string newTxt = Convert.ToDouble(weight).ToString("F3");
                        if (lblWeight.Text != newTxt) lblWeight.Text = newTxt;
                    }
                }
                catch { }

                // ── 水含量(100180) ──
                try
                {
                    var sv = PlcLinkForm.ReadPLC(100180, opName);
                    if (sv != null)
                        lblWater.Text = Convert.ToDouble(sv).ToString("F2");
                }
                catch { }

                // ── 托盘号(80) ──
                try
                {
                    var pv = PlcLinkForm.ReadPLC(80, opName);
                    if (pv != null)
                    {
                        string pt = PLC_R.GetString_CleanGarbled(pv);
                        if (!string.IsNullOrEmpty(pt)) lblPallet.Text = pt;
                    }
                }
                catch { }

                // ── 报警代码(112) — Int → 文本映射 ──
                try
                {
                    var av = PlcLinkForm.ReadPLC(112, opName);
                    int alarmCode = av != null ? Convert.ToInt32(av) : 0;
                    string alarmText = alarmCode >= 0 && alarmCode < AlarmCodeTexts.Length
                        ? AlarmCodeTexts[alarmCode] : $"未知({alarmCode})";
                    lblAlarm.Text = alarmText;
                    lblAlarm.ForeColor = alarmCode > 0
                        ? Color.FromArgb(239, 68, 68)   // 红色
                        : Color.FromArgb(107, 114, 128); // 灰色
                }
                catch { }
            }
            catch { }
        }

        // ====================================================================
        // 公共方法 — 供 PLC 通信层 / MIS_Funtion 调用
        // ====================================================================

        public void UpdateSignal(int station, int index, bool active)
        {
            var sigs = station == 1 ? _s1Signals : _s2Signals;
            if (sigs != null && index >= 0 && index < sigs.Length)
            {
                sigs[index].BackColor = active ? C_SignalOn : C_SignalOff;
                sigs[index].Invalidate();
            }
        }

        public void UpdateWeight(int station, double weight)
        {
            var lbl = station == 1 ? lbl_S1_Weight : lbl_S2_Weight;
            if (lbl.InvokeRequired)
                lbl.Invoke(new Action(() => lbl.Text = weight.ToString("F3")));
            else
                lbl.Text = weight.ToString("F3");
        }

        public void UpdateStationInfo(int station, string engineNo, string modelNo, string orderNo)
        {
            Action act = () =>
            {
                if (station == 1)
                {
                    lbl_S1_EngineVal.Text = engineNo ?? "—";
                    lbl_S1_ModelVal.Text = modelNo ?? "—";
                    lbl_S1_OrderVal.Text = orderNo ?? "—";
                }
                else
                {
                    lbl_S2_EngineVal.Text = engineNo ?? "—";
                    lbl_S2_ModelVal.Text = modelNo ?? "—";
                    lbl_S2_OrderVal.Text = orderNo ?? "—";
                }
            };
            if (InvokeRequired) Invoke(act); else act();
        }

        /// <summary>
        /// 更新加油量显示
        /// </summary>
        public void UpdateOilInfo(int station, decimal addOilQty, decimal extractOilQty)
        {
            UpdateOilInfo(station, addOilQty, extractOilQty, 0, 0, 0);
        }

        /// <summary>
        /// 更新称重指标显示
        /// </summary>
        public void UpdateOilInfo(int station, decimal addOilQty, decimal extractOilQty, decimal density, decimal oilReleaseQty, decimal residualOilQty)
        {
            Action act = () =>
            {
                if (station == 1)
                {
                    lbl_S1_Info2.Text = addOilQty.ToString("F2");
                    lbl_S1_ExtractOilVal.Text = extractOilQty.ToString("F2");
                    lbl_S1_DensityVal.Text = density.ToString("F4");
                    lbl_S1_OilReleaseVal.Text = oilReleaseQty.ToString("F2");
                    lbl_S1_ResidualOilVal.Text = residualOilQty.ToString("F2");
                }
                else
                {
                    lbl_S2_Info2.Text = addOilQty.ToString("F2");
                    lbl_S2_ExtractOilVal.Text = extractOilQty.ToString("F2");
                    lbl_S2_DensityVal.Text = density.ToString("F4");
                    lbl_S2_OilReleaseVal.Text = oilReleaseQty.ToString("F2");
                    lbl_S2_ResidualOilVal.Text = residualOilQty.ToString("F2");
                }
            };
            if (InvokeRequired) Invoke(act); else act();
        }

        /// <summary>
        /// 更新放油量和残油量显示
        /// </summary>
        public void UpdateOilResult(int station, decimal oilReleaseQty, decimal residualOilQty)
        {
            Action act = () =>
            {
                if (station == 1)
                {
                    lbl_S1_OilReleaseVal.Text = oilReleaseQty.ToString("F2");
                    lbl_S1_ResidualOilVal.Text = residualOilQty.ToString("F2");
                }
                else
                {
                    lbl_S2_OilReleaseVal.Text = oilReleaseQty.ToString("F2");
                    lbl_S2_ResidualOilVal.Text = residualOilQty.ToString("F2");
                }
            };
            if (InvokeRequired) Invoke(act); else act();
        }

        public void UpdateAuxInfo(int station, string info1, string info2)
        {
            if (station == 1)
            {
                lbl_S1_Info1.Text = info1 ?? "";
                lbl_S1_Info2.Text = info2 ?? "";
            }
            else
            {
                lbl_S2_Info1.Text = info1 ?? "";
                lbl_S2_Info2.Text = info2 ?? "";
            }
        }

        /// <summary>
        /// 追加操作日志（线程安全）
        /// </summary>
        public void AppendLog(string msg)
        {
            if (txt_Log == null) return;
            if (txt_Log.InvokeRequired)
            {
                txt_Log.Invoke(new Action(() => AppendLog(msg)));
                return;
            }
            if (txt_Log.Text.Length > 50000)
                txt_Log.Text = txt_Log.Text.Substring(0, 30000);
            txt_Log.Text = $"{DateTime.Now:HH:mm:ss}  {msg}\r\n{txt_Log.Text}";
        }

        /// <summary>获取工位序号</summary>
        public int GetStationIndex(string opName)
        {
            if (opName == _op1) return 1;
            if (opName == _op2) return 2;
            return 1;
        }
    }
}
