"""
Legacy Viewer (Dashboard View)
- 상단 20%: 속도계 그룹
- 중간 30%: 신호 카드 그룹
- 하단 50%: 그래프 그룹 (미니 그래프)
"""

from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, 
                               QGroupBox, QFrame, QLabel, QSplitter, QScrollArea,
                               QSizePolicy, QPushButton)
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor

from app.ui.components.signal_components import (
    OnOffSignalLabel, DataSignalLabel, SignalCard
)
from app.core.sync_controller import sync_manager
from app.ui.views.graph_view import GraphView


class DashboardView(QWidget):
    def __init__(self, panel_id):
        super().__init__()
        self.panel_id = panel_id
        
        # 노이즈 필터링 상태
        self.last_valid_pwm = 0
        self._signal_filter_state = {}
        self._signal_valid_sources = {}
        self._source_filtered_last = {}
        
        # 신호 컴포넌트 참조 저장
        self.signal_widgets = {}
        
        self.setup_ui()
        sync_manager.time_changed.connect(self.update_view)
    
    def setup_ui(self):
        main_layout = QVBoxLayout(self)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)
        
        # === 메인 Splitter (수직) ===
        self.main_splitter = QSplitter(Qt.Vertical)
        self.main_splitter.setChildrenCollapsible(False)  # 완전히 접히지 않도록
        self.main_splitter.setHandleWidth(5)
        self.main_splitter.setStyleSheet("""
            QSplitter::handle { background-color: #3E3E42; }
            QSplitter::handle:hover { background-color: #007ACC; }
            QSplitter::handle:pressed { background-color: #005A9E; }
        """)
        
        # === [1] 상단: 신호 카드 그룹 ===
        signal_group = self._create_signal_group()
        self.main_splitter.addWidget(signal_group)
        
        # === [2] 하단: 미니 그래프 그룹 ===
        graph_group = self._create_graph_group()
        self.main_splitter.addWidget(graph_group)
        
        # 초기 비율 설정 (60:40)
        self.main_splitter.setSizes([300, 200])
        
        main_layout.addWidget(self.main_splitter)
    
    # =========================================================================
    # [2] 신호 카드 그룹
    # =========================================================================
    def _create_signal_group(self):
        # 전체 컨테이너 (스크롤 + VD 버튼)
        wrapper = QWidget()
        wrapper_layout = QVBoxLayout(wrapper)
        wrapper_layout.setContentsMargins(0, 0, 0, 0)
        wrapper_layout.setSpacing(0)
        
        scroll = QScrollArea()
        scroll.setWidgetResizable(True)
        scroll.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        scroll.setMinimumHeight(100)
        scroll.setStyleSheet("""
            QScrollArea { background-color: #1E1E1E; border: none; }
            QScrollBar:vertical { background: #2D2D30; width: 8px; }
            QScrollBar::handle:vertical { background: #555; border-radius: 4px; min-height: 20px; }
            QScrollBar:horizontal { background: #2D2D30; height: 8px; }
            QScrollBar::handle:horizontal { background: #555; border-radius: 4px; min-width: 20px; }
        """)
        
        container = QWidget()
        container.setStyleSheet("background-color: #1E1E1E;")
        grid = QGridLayout(container)
        grid.setContentsMargins(3, 3, 3, 3)
        grid.setSpacing(3)
        
        # 카드 생성 - 3열 배치
        # Row 0: ATC | ATO | TWC | FAIL
        # Row 1: RLY(ATO) | DI(ATC) | DO(ATC)
        # Row 2: ETC1 (DATA) | ETC2 (ON/OFF)
        
        # Row 0: INFO, ATC, ATO, TWC (4열)
        self.card_info = self._create_info_card()
        self.card_atc = self._create_atc_card()
        self.card_ato = self._create_ato_card()
        self.card_twc = self._create_twc_card()
        grid.addWidget(self.card_info, 0, 0)
        grid.addWidget(self.card_atc, 0, 1)
        grid.addWidget(self.card_ato, 0, 2)
        grid.addWidget(self.card_twc, 0, 3)
        
        # Row 1: FAIL, RLY(ATO), DI(ATC), DO(ATC)
        self.card_fail = self._create_fail_card()
        self.card_rly = self._create_rly_card()
        self.card_di = self._create_di_card()
        self.card_do = self._create_do_card()
        grid.addWidget(self.card_fail, 1, 0)
        grid.addWidget(self.card_rly, 1, 1)
        grid.addWidget(self.card_di, 1, 2)
        grid.addWidget(self.card_do, 1, 3)
        
        # Row 2: ETC1 (DATA 전용), ETC2 (ON/OFF 전용)
        self.card_etc1 = self._create_etc1_card()  # DATA 컴포넌트
        self.card_etc2 = self._create_etc2_card()  # ON/OFF 컴포넌트
        grid.addWidget(self.card_etc1, 2, 0, 1, 2)  # 2열 차지
        grid.addWidget(self.card_etc2, 2, 2, 1, 2)  # 2열 차지
        
        # 메인 카드 목록 (참조용)
        self.main_cards = [
            self.card_info, self.card_atc, self.card_ato, self.card_twc, self.card_fail,
            self.card_rly, self.card_di, self.card_do,
            self.card_etc1, self.card_etc2
        ]
        
        # VD 카드 (6개) - 토글 가능, 기본값 OFF (숨김)
        self.vd_cards = [
            self._create_vdi_a_card(),
            self._create_vdi_b_card(),
            self._create_vdi_c_card(),
            self._create_vdi_d_card(),
            self._create_vdo_a_card(),
            self._create_vdo_b_card(),
        ]
        
        # VD 카드 배치 (4열, Row 3~4)
        for i, card in enumerate(self.vd_cards):
            card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            row = 3 + (i // 4)
            col = i % 4
            grid.addWidget(card, row, col)
            card.setVisible(False)  # 기본값: 숨김 (OFF)
        
        scroll.setWidget(container)
        wrapper_layout.addWidget(scroll, 1)
        
        # VD 토글 버튼 (오른쪽 하단)
        btn_container = QWidget()
        btn_container.setStyleSheet("background-color: #1E1E1E;")
        btn_layout = QHBoxLayout(btn_container)
        btn_layout.setContentsMargins(5, 2, 5, 2)
        btn_layout.addStretch()
        
        self.vd_visible = False  # VD 카드 표시 상태 - 기본값 OFF
        self.btn_vd_toggle = QPushButton("VD ▲")  # 기본값: 숨김 상태
        self.btn_vd_toggle.setFixedSize(50, 22)
        self.btn_vd_toggle.setStyleSheet("""
            QPushButton {
                background-color: #4C4C4C;
                color: #888888;
                border: 1px solid #666;
                border-radius: 3px;
                font-size: 10px;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #5C5C5C;
            }
            QPushButton:pressed {
                background-color: #3C3C3C;
            }
        """)
        self.btn_vd_toggle.clicked.connect(self._toggle_vd_cards)
        btn_layout.addWidget(self.btn_vd_toggle)
        
        wrapper_layout.addWidget(btn_container)
        
        return wrapper
    
    def _toggle_vd_cards(self):
        """VD 카드들(VDI A/B/C/D, VDO A/B) 표시/숨김 토글"""
        self.vd_visible = not self.vd_visible
        for card in self.vd_cards:
            card.setVisible(self.vd_visible)
        
        if self.vd_visible:
            self.btn_vd_toggle.setText("VD ▼")
            self.btn_vd_toggle.setStyleSheet("""
                QPushButton {
                    background-color: #4C4C4C;
                    color: #00FF00;
                    border: 1px solid #666;
                    border-radius: 3px;
                    font-size: 10px;
                    font-weight: bold;
                }
                QPushButton:hover { background-color: #5C5C5C; }
            """)
        else:
            self.btn_vd_toggle.setText("VD ▲")
            self.btn_vd_toggle.setStyleSheet("""
                QPushButton {
                    background-color: #4C4C4C;
                    color: #888888;
                    border: 1px solid #666;
                    border-radius: 3px;
                    font-size: 10px;
                    font-weight: bold;
                }
                QPushButton:hover { background-color: #5C5C5C; }
            """)

    # -------------------------------------------------------------------------
    # INFO 카드 (상단 상태 요약)
    # -------------------------------------------------------------------------
    # -------------------------------------------------------------------------
    # INFO 카드 (상단 상태 요약)
    # -------------------------------------------------------------------------
    # -------------------------------------------------------------------------
    # INFO 카드 (상단 상태 요약)
    # -------------------------------------------------------------------------
    def _create_info_card(self):
        card = SignalCard("INFO")
        # 커스텀 레이아웃을 위해 add_signal 대신 signal_grid 직접 사용
        grid = card.signal_grid
        
        # 1. TIME (상단, 전체 너비 사용 및 중앙 정렬)
        time_sig = DataSignalLabel("time", "TIME", "")
        time_sig.set_value_style("font-size: 16px; font-weight: bold; color: #00D084;") # 강조
        # DataSignalLabel은 내부적으로 QHBoxLayout을 사용하므로, 
        # 중앙 정렬을 위해 컨테이너나 스타일 조정이 필요할 수 있으나, 
        # 여기서는 Grid의 0행 전체를 차지하게 함.
        grid.addWidget(time_sig, 0, 0, 1, 4, Qt.AlignCenter)
        
        card.signals["time"] = time_sig
        self.signal_widgets["info_time"] = time_sig

        # 2. SPEED (속도계) - 1행, 전체 너비
        from app.ui.components.speedometer import SpeedometerWidget
        self.speedometer = SpeedometerWidget(title="SPEED", unit="km/h", max_value=120)
        grid.addWidget(self.speedometer, 1, 0, 1, 4, Qt.AlignCenter)
        
        card.signals["speed"] = self.speedometer # 필요 시 참조용
        self.signal_widgets["info_speed"] = self.speedometer

        # 3. 나머지 데이터 (LIMIT, DTG, PWM, ATC CODE) - 2행에 2열씩 배치
        # Row 2: LIMIT, DTG
        # Row 3: PWM, ATC CODE
        
        limit_sig = DataSignalLabel("limit", "LIMIT", "km/h")
        dtg_sig = DataSignalLabel("dtg", "DTG", "m")
        pwm_sig = DataSignalLabel("pwm", "PWM", "%")
        atc_sig = DataSignalLabel("atc_code", "ATC CODE", "")
        
        # (위젯, 행, 열, 행스팬, 열스팬)
        grid.addWidget(limit_sig, 2, 0, 1, 2)
        grid.addWidget(dtg_sig, 2, 2, 1, 2)
        grid.addWidget(pwm_sig, 3, 0, 1, 2)
        grid.addWidget(atc_sig, 3, 2, 1, 2)
        
        # signals 등록
        card.signals["limit"] = limit_sig
        card.signals["dtg"] = dtg_sig
        card.signals["pwm"] = pwm_sig
        card.signals["atc_code"] = atc_sig

        self.signal_widgets["info_limit"] = limit_sig
        self.signal_widgets["info_dtg"] = dtg_sig
        self.signal_widgets["info_pwm"] = pwm_sig
        self.signal_widgets["info_atc_code"] = atc_sig
        
        # 레이아웃 마무리 (SignalCard 내부 메서드 호출 대신 수동으로 stretch 설정)
        grid.setRowStretch(4, 1)

        return card
    
    # -------------------------------------------------------------------------
    # ATC 카드
    # -------------------------------------------------------------------------
    def _create_atc_card(self):
        card = SignalCard("ATC")
        
        # DATA: 운전실 상태
        cab = DataSignalLabel("cab", "운전실", description="운전실 선택 상태 (TC1/TC2/OFF)")
        card.add_signal(cab, "cab")
        self.signal_widgets["atc_cab"] = cab
        
        # DATA: ATC ACTIVE, CARR, CODE, CODE FREQ, LIMIT
        for name, desc in [
            ("ATC ACT", "ATC 활성화 상태"),
            ("CARR", "ATC 캐리어 주파수"),
            ("CODE", "ATC 속도 코드"),
            ("FREQ", "코드 주파수"),
            ("LIMIT", "ATC 제한속도"),
        ]:
            sig = DataSignalLabel(name.replace(" ", "_").lower(), name, description=desc)
            card.add_signal(sig, name.replace(" ", "_").lower())
            self.signal_widgets[f"atc_{name.replace(' ', '_').lower()}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # TWC 카드
    # -------------------------------------------------------------------------
    def _create_twc_card(self):
        card = SignalCard("TWC")
        
        # ON/OFF
        for name, desc in [
            ("DCW", "Door Close Warning"),
            ("TWX Tx", "TWC 송신 활성화"),
            ("BERTH", "Train Berth - 정위치 정차"),
            ("W.DOOR", "Wrong Door - 잘못된 도어"),
        ]:
            sig = OnOffSignalLabel(name.replace(" ", "_").lower(), name, desc)
            card.add_signal(sig, name.replace(" ", "_").lower())
            self.signal_widgets[f"twc_{name.replace(' ', '_').lower()}"] = sig
        
        # DATA
        for name, desc in [
            ("열번", "열차 번호"),
            ("현재역", "현재 역 코드"),
            ("다음역", "다음 역 코드"),
            ("종착역", "종착역 코드"),
            ("DOOR", "다음 도어 방향"),
        ]:
            sig = DataSignalLabel(name, name, description=desc)
            card.add_signal(sig, name)
            self.signal_widgets[f"twc_{name}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # ATO 카드
    # -------------------------------------------------------------------------
    def _create_ato_card(self):
        card = SignalCard("ATO")
        
        # ON/OFF
        for name, full_name, desc in [
            ("TASC", "TASC", "Train Automatic Stop Control - 자동정차제어"),
            ("TASC DB", "TASC DB", "TASC Dynamic Brake"),
            ("ATO EB", "ATO EB REQ", "ATO Emergency Brake Request - ATO 비상제동 요청"),
        ]:
            sig = OnOffSignalLabel(name, full_name, desc)
            card.add_signal(sig, name.lower().replace(" ", "_"))
            self.signal_widgets[f"ato_{name.lower().replace(' ', '_')}"] = sig
        
        # DATA
        for name, desc in [
            ("MARKER", "ATO 마커 (PG1/2/3/X)"),
            ("OSC", "OSC 주파수"),
            ("PWM", "PWM 값"),
            ("DTG", "남은거리(m)"),
        ]:
            sig = DataSignalLabel(name.lower(), name, description=desc)
            card.add_signal(sig, name.lower())
            self.signal_widgets[f"ato_{name.lower()}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # RLY (ATO) 카드
    # -------------------------------------------------------------------------
    def _create_rly_card(self):
        card = SignalCard("RLY (ATO)")
        
        for name in ["DR", "BR", "ADC", "ADOL", "ADOR", "OSC", "KUR", "SEL"]:
            sig = OnOffSignalLabel(name, name, f"RLY {name} 릴레이 출력")
            card.add_signal(sig, name.lower())
            self.signal_widgets[f"rly_{name.lower()}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # FAIL 카드
    # -------------------------------------------------------------------------
    def _create_fail_card(self):
        card = SignalCard("FAIL")
        
        for name, desc in [
            ("ATO.R", "ATO Receiver Fail"),
            ("ATO.C", "ATO Controller Fail"),
            ("TCMS", "TCMS Communication Fail"),
            ("TACHO#1", "Tachometer #1 Fail"),
            ("TACHO#2", "Tachometer #2 Fail"),
        ]:
            sig = OnOffSignalLabel(name.replace(".", "_").replace("#", ""), name, desc)
            sig.set_style_override(
                on_style="background-color: #E53935; color: white; border-radius: 3px; padding: 2px 6px; font-weight: bold;",
            )
            card.add_signal(sig, name.replace(".", "_").replace("#", "").lower())
            self.signal_widgets[f"fail_{name.replace('.', '_').replace('#', '').lower()}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # DI (ATC) 카드
    # -------------------------------------------------------------------------
    def _create_di_card(self):
        card = SignalCard("DI (ATC)")
        
        # ON/OFF
        for name in ["HCR", "TCR", "START"]:
            sig = OnOffSignalLabel(name, name, f"DI {name} 입력 신호")
            card.add_signal(sig, name.lower())
            self.signal_widgets[f"di_{name.lower()}"] = sig
        
        # DATA 형태로 표시 (운전모드/마스콘/역전기/PSD/DOOR)
        for name, desc in [
            ("운전모드", "운전 모드"),
            ("마스콘", "마스콘 위치"),
            ("역전기", "역전기 위치"),
            ("PSD", "PSD 상태"),
            ("DOOR", "도어 상태"),
        ]:
            sig = DataSignalLabel(name.lower(), name, description=desc)
            card.add_signal(sig, name.lower())
            self.signal_widgets[f"di_{name.lower()}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # DO (ATC) 카드
    # -------------------------------------------------------------------------
    def _create_do_card(self):
        card = SignalCard("DO (ATC)")
        
        for name in ["EB+", "EB-", "FSB", "ZVR", "EDR", "EDL"]:
            sig = OnOffSignalLabel(name.replace("+", "p").replace("-", "m"), name, f"DO {name} 출력 신호")
            card.add_signal(sig, name.replace("+", "p").replace("-", "m").lower())
            self.signal_widgets[f"do_{name.replace('+', 'p').replace('-', 'm').lower()}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # ETC1 카드 (DATA 컴포넌트만) - 가로 배치
    # -------------------------------------------------------------------------
    def _create_etc1_card(self):
        card = SignalCard("ETC1")
        card.set_horizontal_data_mode(True)  # DATA를 가로로 배치
        
        # DATA
        for name in ["INIT PDT", "MNUL PDT", "TCMS DR", "DIA1", "DIA2", "VER", "DOOR MD"]:
            sig = DataSignalLabel(name.replace(" ", "_").lower(), name, description=f"ETC {name}")
            card.add_signal(sig, name.replace(" ", "_").lower())
            self.signal_widgets[f"etc_{name.replace(' ', '_').lower()}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # ETC2 카드 (ON/OFF 컴포넌트만)
    # -------------------------------------------------------------------------
    def _create_etc2_card(self):
        card = SignalCard("ETC2")
        
        # ON/OFF
        for name in ["SYS ACT", "INCH", "CS", "평상", "회복", "O.SPD", "사전제동", "가속제한", 
                     "-70↑", "+70↑", "TDIR A", "TDIR B"]:
            sig = OnOffSignalLabel(name.replace(" ", "_"), name, f"ETC {name} 신호")
            card.add_signal(sig, name.replace(" ", "_").lower())
            self.signal_widgets[f"etc_{name.replace(' ', '_').lower()}"] = sig
        
        return card
    
    # -------------------------------------------------------------------------
    # VDI A/B/C/D 카드
    # -------------------------------------------------------------------------
    def _create_vdi_a_card(self):
        return self._create_vdi_ab_card("VDI A", "vdi_a")
    
    def _create_vdi_b_card(self):
        return self._create_vdi_ab_card("VDI B", "vdi_b")
    
    def _create_vdi_ab_card(self, title, prefix):
        card = SignalCard(title)
        for name in ["HCR", "TCR", "FA", "AUTO", "MCS", "YARD", "FMC", 
                     "D.OPEN", "D.CLOSE", "MC EB", "MC BR", "MC DR", "FWD", "NEU", "RVS"]:
            sig = OnOffSignalLabel(name.replace(".", "_"), name, f"{title} {name}")
            card.add_signal(sig, name.replace(".", "_").lower())
            self.signal_widgets[f"{prefix}_{name.replace('.', '_').lower()}"] = sig
        return card
    
    def _create_vdi_c_card(self):
        return self._create_vdi_cd_card("VDI C", "vdi_c")
    
    def _create_vdi_d_card(self):
        return self._create_vdi_cd_card("VDI D", "vdi_d")
    
    def _create_vdi_cd_card(self, title, prefix):
        card = SignalCard(title)
        for name in ["PSD OP", "PSD CL", "START", "UNIT1", "EB+FB", "EB-FB", 
                     "FSB FB", "ZVR FB", "EDR FB", "EDL FB", "TC1", "TC2"]:
            sig = OnOffSignalLabel(name.replace("+", "p").replace("-", "m").replace(" ", "_"), 
                                   name, f"{title} {name}")
            card.add_signal(sig, name.replace("+", "p").replace("-", "m").replace(" ", "_").lower())
            self.signal_widgets[f"{prefix}_{name.replace('+', 'p').replace('-', 'm').replace(' ', '_').lower()}"] = sig
        return card
    
    # -------------------------------------------------------------------------
    # VDO A/B 카드
    # -------------------------------------------------------------------------
    def _create_vdo_a_card(self):
        return self._create_vdo_card("VDO A", "vdo_a")
    
    def _create_vdo_b_card(self):
        return self._create_vdo_card("VDO B", "vdo_b")
    
    def _create_vdo_card(self, title, prefix):
        card = SignalCard(title)
        for name in ["EB+", "EB-", "FSB", "ZVR", "EDL", "EDR"]:
            sig = OnOffSignalLabel(name.replace("+", "p").replace("-", "m"), name, f"{title} {name}")
            card.add_signal(sig, name.replace("+", "p").replace("-", "m").lower())
            self.signal_widgets[f"{prefix}_{name.replace('+', 'p').replace('-', 'm').lower()}"] = sig
        return card
    
    # =========================================================================
    # [3] 그래프 그룹 (GraphView 전체 임베드)
    # =========================================================================
    def _create_graph_group(self):
        # GraphView를 그대로 임베드
        self.embedded_graph = GraphView(self.panel_id)
        self.embedded_graph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.embedded_graph.setMinimumHeight(150)
        return self.embedded_graph
    
    def set_data(self, data_list):
        """외부에서 데이터 로드 시 그래프에도 전달"""
        if hasattr(self, 'embedded_graph'):
            self.embedded_graph.set_data(data_list)

        # source 기반 신뢰도 추정 (가짜 0 제거용)
        signal_attr_map = {
            "trainspeed": "trainspeed",
            "pwm_value": "pwm_value",
            "dtg": "dtg",
            "osc_f": "osc_f",
        }
        self._signal_valid_sources = self._infer_valid_sources(data_list, signal_attr_map)
        self._source_filtered_last = {}
    
    # =========================================================================
    # 노이즈 필터링 함수들 (1초 단위 깜박임 필터링)
    # =========================================================================
    def apply_pwm_filter(self, raw_pwm):
        if 15 <= raw_pwm <= 90:
            self.last_valid_pwm = raw_pwm
            return raw_pwm
        if raw_pwm == 0:
            return self.last_valid_pwm
        return 0
    
    def filter_majority_signal(self, signal_name: str, current_value: bool, 
                                window_size: int = 5) -> bool:
        """
        과반수 기반 디지털 신호 필터 (매 프레임 깜박이는 신호용)
        
        최근 window_size개 값 중 과반수가 ON이면 ON 반환
        SYSTEM ACTIVE처럼 매 초 깜박이는 신호에 적용
        """
        state_key = f"maj_{signal_name}"
        if state_key not in self._signal_filter_state:
            self._signal_filter_state[state_key] = {
                'history': [],
            }
        
        state = self._signal_filter_state[state_key]
        state['history'].append(current_value)
        
        # window_size 유지
        if len(state['history']) > window_size:
            state['history'].pop(0)
        
        # 과반수 계산
        on_count = sum(state['history'])
        return on_count > len(state['history']) // 2
    
    def filter_digital_signal(self, signal_name: str, current_value: bool, 
                               stable_frames: int = 3) -> bool:
        """
        디지털 신호 노이즈 필터링 (1초 깜박임 방지) - 완화된 버전
        
        Args:
            signal_name: 신호 이름 (상태 추적용)
            current_value: 현재 값
            stable_frames: 값이 변경되기 위해 필요한 연속 프레임 수 (기본 3프레임)
        
        Logic:
            - 첫 번째 값은 그대로 반영 (초기화)
            - 이후 값이 변경되려면 stable_frames 연속 같은 값이어야 함
            - 깜박임(1-2프레임 변동)은 무시됨
        """
        if signal_name not in self._signal_filter_state:
            # 초기값은 현재 값으로 설정 (첫 프레임부터 정상 표시)
            self._signal_filter_state[signal_name] = {
                'stable_value': current_value,  # 현재 값으로 초기화
                'pending_value': current_value,
                'same_count': 0,
            }
            return current_value
        
        state = self._signal_filter_state[signal_name]
        
        if current_value == state['stable_value']:
            # 안정값과 같으면 유지
            state['pending_value'] = current_value
            state['same_count'] = 0
            return state['stable_value']
        elif current_value == state['pending_value']:
            # 대기값과 같으면 카운트 증가
            state['same_count'] += 1
            if state['same_count'] >= stable_frames:
                state['stable_value'] = current_value
                state['same_count'] = 0
            return state['stable_value']
        else:
            # 새로운 값이면 대기값 갱신
            state['pending_value'] = current_value
            state['same_count'] = 1
            return state['stable_value']
    
    def filter_numeric_signal(self, signal_name: str, current_value: float, 
                               min_valid: float = 0, stable_frames: int = 3) -> float:
        """
        숫자 신호 노이즈 필터링 (1초 깜박임 방지) - 완화된 버전
        
        Args:
            signal_name: 신호 이름
            current_value: 현재 값
            min_valid: 유효 최소값 (기본 0 = 모든 값 허용)
            stable_frames: 값이 변경되기 위해 필요한 연속 프레임 수
        
        Logic:
            - 첫 번째 값은 그대로 반영
            - 이후 값 변경은 stable_frames 연속 같은 값이어야 함
        """
        state_key = f"num_{signal_name}"
        if state_key not in self._signal_filter_state:
            self._signal_filter_state[state_key] = {
                'stable_value': current_value,  # 현재 값으로 초기화
                'pending_value': current_value,
                'same_count': 0,
            }
            return current_value
        
        state = self._signal_filter_state[state_key]
        
        # 같은 값이면 유지
        if current_value == state['stable_value']:
            state['pending_value'] = current_value
            state['same_count'] = 0
            return state['stable_value']
        
        # 다른 값이면 카운트
        if current_value == state['pending_value']:
            state['same_count'] += 1
            if state['same_count'] >= stable_frames:
                state['stable_value'] = current_value
                state['same_count'] = 0
        else:
            state['pending_value'] = current_value
            state['same_count'] = 1
        
        return state['stable_value']
    
    def filter_string_signal(self, signal_name: str, current_value: str, 
                              stable_frames: int = 3) -> str:
        """
        문자열 신호 노이즈 필터링 (1초 깜박임 방지)
        
        Args:
            signal_name: 신호 이름
            current_value: 현재 값
            stable_frames: 안정된 것으로 인정할 연속 프레임 수
        
        Logic:
            - 값이 변경되면 stable_frames 동안 이전 값 유지
            - 새 값이 stable_frames 연속되면 변경 인정
        """
        state_key = f"str_{signal_name}"
        if state_key not in self._signal_filter_state:
            self._signal_filter_state[state_key] = {
                'stable_value': current_value,
                'pending_value': current_value,
                'same_count': 0,
            }
        
        state = self._signal_filter_state[state_key]
        
        if current_value == state['stable_value']:
            # 안정값과 같으면 유지
            state['pending_value'] = current_value
            state['same_count'] = 0
            return state['stable_value']
        elif current_value == state['pending_value']:
            # 대기값과 같으면 카운트 증가
            state['same_count'] += 1
            if state['same_count'] >= stable_frames:
                state['stable_value'] = current_value
                state['same_count'] = 0
            return state['stable_value']
        else:
            # 새로운 값이면 대기값 갱신
            state['pending_value'] = current_value
            state['same_count'] = 1
            return state['stable_value']
    
    def filter_mode_signal(self, signal_name: str, current_mode: str, 
                            stable_frames: int = 3) -> str:
        """
        모드 신호 노이즈 필터링 (FA/AUTO/MCS 등 모드 전환 시)
        
        Logic:
            - None이 아닌 모드가 들어오면 즉시 표시
            - None이 들어오면 stable_frames 동안 이전 모드 유지
            - None이 stable_frames 연속되면 None으로 인정
        """
        state_key = f"mode_{signal_name}"
        if state_key not in self._signal_filter_state:
            self._signal_filter_state[state_key] = {
                'stable_mode': None,
                'none_count': 0,
            }
        
        state = self._signal_filter_state[state_key]
        
        if current_mode is not None:
            state['none_count'] = 0
            state['stable_mode'] = current_mode
            return current_mode
        else:
            state['none_count'] += 1
            if state['none_count'] >= stable_frames:
                state['stable_mode'] = None
            return state['stable_mode']

    def _infer_valid_sources(self, data_list, signal_attr_map, zero_ratio_threshold=0.98, min_samples=50):
        """
        소스별로 '거의 항상 0'인 신호를 찾아 유효 소스를 추정한다.
        - 특정 소스가 해당 신호에서 0 비율이 매우 높고, 다른 소스는 그렇지 않으면 해당 소스를 제외.
        """
        stats = {k: {} for k in signal_attr_map.keys()}
        for d in data_list or []:
            src = getattr(d, "source", None)
            if src is None:
                continue
            for key, attr in signal_attr_map.items():
                val = getattr(d, attr, None)
                if val is None:
                    continue
                s = stats[key].setdefault(src, {"n": 0, "zero": 0})
                s["n"] += 1
                if val == 0:
                    s["zero"] += 1

        valid_sources_map = {}
        for key, by_src in stats.items():
            if not by_src or len(by_src) <= 1:
                valid_sources_map[key] = None
                continue
            ratios = {}
            for src, s in by_src.items():
                if s["n"] < min_samples:
                    continue
                ratios[src] = (s["zero"] / s["n"]) if s["n"] else 0
            if not ratios:
                valid_sources_map[key] = None
                continue
            low_zero = [src for src, r in ratios.items() if r < zero_ratio_threshold]
            high_zero = [src for src, r in ratios.items() if r >= zero_ratio_threshold]
            if low_zero and high_zero:
                valid_sources_map[key] = set(low_zero)
            else:
                valid_sources_map[key] = None
        return valid_sources_map

    def _get_source_filtered_value(self, data, key, attr, default=0):
        """
        source 기반으로 유효 값만 사용. 유효하지 않은 소스면 직전 유효값 유지.
        """
        val = getattr(data, attr, default)
        src = getattr(data, "source", None)
        valid_sources = self._signal_valid_sources.get(key)
        is_valid = (valid_sources is None) or (src in valid_sources)
        if is_valid:
            self._source_filtered_last[key] = val
            return val
        if key in self._source_filtered_last:
            return self._source_filtered_last[key]
        return val if val is not None else default
    
    # =========================================================================
    # 데이터 업데이트
    # =========================================================================
    def update_view(self, index, data, source_id):
        if not data:
            return
        
        # === INFO 카드 업데이트 ===
        speed = self._get_source_filtered_value(data, "trainspeed", "trainspeed", default=0)
        raw_pwm = self._get_source_filtered_value(data, "pwm_value", "pwm_value", default=0)
        filtered_pwm = self.apply_pwm_filter(raw_pwm)
        limit_spd = self.filter_numeric_signal("limitspeed", getattr(data, 'limitspeed', 0))
        raw_dtg = self._get_source_filtered_value(data, "dtg", "dtg", default=0)
        dtg = self.filter_numeric_signal("dtg", raw_dtg, min_valid=1)
        atc_code = getattr(data, 'atc_code', '-')
        time_str = getattr(data, 'time', '-')[-8:]

        if w := self.signal_widgets.get("info_time"):
            w.set_value(time_str)
        if w := self.signal_widgets.get("info_speed"):
            w.set_value(f"{speed:.1f}")
            if hasattr(w, "set_limit"):
                w.set_limit(limit_spd)
            if hasattr(w, "set_atc_code"):
                w.set_atc_code(atc_code)
        if w := self.signal_widgets.get("info_limit"):
            w.set_value(int(limit_spd))
        if w := self.signal_widgets.get("info_dtg"):
            w.set_value(f"{dtg:.0f}")
        if w := self.signal_widgets.get("info_pwm"):
            w.set_value(filtered_pwm)
        if w := self.signal_widgets.get("info_atc_code"):
            w.set_value(str(atc_code))
        
        # === 신호 카드 업데이트 ===
        self._update_signals(data)
    
    def _update_signals(self, data):
        """신호 카드 내 모든 신호 업데이트 (노이즈 필터링 적용)"""
        
        # ===================================================================
        # ATC 카드
        # ===================================================================
        tc1 = self.filter_digital_signal("tc1", getattr(data, 'tc1', False))
        tc2 = self.filter_digital_signal("tc2", getattr(data, 'tc2', False))
        cab_text = "TC1" if tc1 else ("TC2" if tc2 else "OFF")
        if w := self.signal_widgets.get("atc_cab"):
            w.set_value(cab_text)
        
        # ===================================================================
        # DO 카드 (모든 출력 신호에 필터 적용)
        # ===================================================================
        do_signals = [
            ("ebp", "do_ebp"), ("ebm", "do_ebm"), ("fsb", "do_fsb"), 
            ("zvr", "do_zvr"), ("edr", "do_edr"), ("edl", "do_edl")
        ]
        for name, attr in do_signals:
            val = self.filter_digital_signal(attr, getattr(data, attr, False))
            if w := self.signal_widgets.get(f"do_{name}"):
                w.set_status(val)
        
        # ===================================================================
        # DI 카드 (모든 입력 신호에 필터 적용)
        # ===================================================================
        # HCR, TCR, START
        if w := self.signal_widgets.get("di_hcr"):
            w.set_status(self.filter_digital_signal("di_hcr", getattr(data, 'hcr', False)))
        if w := self.signal_widgets.get("di_tcr"):
            w.set_status(self.filter_digital_signal("di_tcr", getattr(data, 'tcr', False)))
        if w := self.signal_widgets.get("di_start"):
            w.set_status(self.filter_digital_signal("di_start", getattr(data, 'ato_start_btn', False)))
        
        # 운전모드 (DATA)
        raw_mode = None
        for m in ["fa", "auto", "mcs", "yard", "fmc"]:
            if self.filter_digital_signal(f"di_{m}", getattr(data, m, False)):
                raw_mode = m.upper()
                break
        mode_text = raw_mode or "없음"
        if w := self.signal_widgets.get("di_운전모드"):
            w.set_value(mode_text)
        
        # PSD
        psd_open = self.filter_digital_signal("psd_open", getattr(data, 'psd_open', False))
        psd_close = self.filter_digital_signal("psd_close", getattr(data, 'psd_close', False))
        psd_text = "열림" if psd_open else ("닫힘" if psd_close else "없음")
        if w := self.signal_widgets.get("di_psd"):
            w.set_value(psd_text)
        
        # DOOR
        door_open = self.filter_digital_signal("door_open", getattr(data, 'door_open', False))
        door_close = self.filter_digital_signal("door_close", getattr(data, 'door_close', False))
        door_text = "열림" if door_open else ("닫힘" if door_close else "없음")
        if w := self.signal_widgets.get("di_door"):
            w.set_value(door_text)
        
        # 마스콘
        mc_eb = self.filter_digital_signal("mascon_eb", getattr(data, 'mascon_eb', False))
        mc_br = self.filter_digital_signal("mascon_br", getattr(data, 'mascon_br', False))
        mc_dr = self.filter_digital_signal("mascon_dr", getattr(data, 'mascon_dr', False))
        raw_mc_mode = "EB" if mc_eb else ("BR" if mc_br else ("DR" if mc_dr else None))
        mc_text = raw_mc_mode or "없음"
        if w := self.signal_widgets.get("di_마스콘"):
            w.set_value(mc_text)
        
        # 역전기
        fwd = self.filter_digital_signal("reversingrod_fwd", getattr(data, 'reversingrod_fwd', False))
        neu = self.filter_digital_signal("reversingrod_neu", getattr(data, 'reversingrod_neu', False))
        rvs = self.filter_digital_signal("reversingrod_rvs", getattr(data, 'reversingrod_rvs', False))
        rev_text = "전진" if fwd else ("중립" if neu else ("후진" if rvs else "없음"))
        if w := self.signal_widgets.get("di_역전기"):
            w.set_value(rev_text)
        
        # ===================================================================
        # ATO 카드
        # ===================================================================
        if w := self.signal_widgets.get("ato_tasc"):
            w.set_status(self.filter_digital_signal("tasc", getattr(data, 'tasc', False)))
        if w := self.signal_widgets.get("ato_tasc_db"):
            w.set_status(self.filter_digital_signal("tascdb", getattr(data, 'tascdb', False)))
        if w := self.signal_widgets.get("ato_ato_eb"):
            w.set_status(self.filter_digital_signal("ato_eb_req", getattr(data, 'ato_eb_req', False)))
        
        # ATO DATA
        if w := self.signal_widgets.get("ato_marker"):
            marker_val = self.filter_string_signal("ato_marker", str(getattr(data, 'marker', '-')))
            w.set_value(marker_val)
        if w := self.signal_widgets.get("ato_osc"):
            raw_osc = self._get_source_filtered_value(data, "osc_f", "osc_f", default=0)
            osc = self.filter_numeric_signal("osc_f", raw_osc, min_valid=0)
            w.set_value(f"{osc:.1f}" if osc else "-")
        if w := self.signal_widgets.get("ato_pwm"):
            pwm_val = self.apply_pwm_filter(self._get_source_filtered_value(data, "pwm_value", "pwm_value", default=0))
            w.set_value(pwm_val)
        if w := self.signal_widgets.get("ato_dtg"):
            dtg_val = self.filter_numeric_signal("dtg_ato", self._get_source_filtered_value(data, "dtg", "dtg", default=0), min_valid=1)
            w.set_value(f"{dtg_val:.0f}")
        
        # ===================================================================
        # RLY (ATO) 카드
        # ===================================================================
        rly_signals = [
            ("dr", "trac_dr"), ("br", "trac_br"), ("adc", "adc"),
            ("adol", "adol"), ("ador", "ador"), ("osc", "osc"),
            ("kur", "kur"), ("sel", "start_enable")
        ]
        for name, attr in rly_signals:
            val = self.filter_digital_signal(f"rly_{attr}", getattr(data, attr, False))
            if w := self.signal_widgets.get(f"rly_{name}"):
                w.set_status(val)
        
        # ===================================================================
        # FAIL 카드
        # ===================================================================
        fail_signals = [
            ("ato_r", "fail_atcr"), ("ato_c", "fail_atoc"), ("tcms", "fail_tcms"),
            ("tacho1", "fail_tacho1"), ("tacho2", "fail_tacho2")
        ]
        for name, attr in fail_signals:
            val = self.filter_digital_signal(f"fail_{attr}", getattr(data, attr, False))
            if w := self.signal_widgets.get(f"fail_{name}"):
                w.set_status(val)
        
        # ===================================================================
        # TWC 카드
        # ===================================================================
        if w := self.signal_widgets.get("twc_dcw"):
            w.set_status(self.filter_digital_signal("door_close_warning", getattr(data, 'door_close_warning', False)))
        if w := self.signal_widgets.get("twc_twx_tx"):
            w.set_status(self.filter_digital_signal("twct_enable", getattr(data, 'twct_enable', False)))
        if w := self.signal_widgets.get("twc_berth"):
            w.set_status(self.filter_digital_signal("trainberth", getattr(data, 'trainberth', False)))
        if w := self.signal_widgets.get("twc_w.door"):
            w.set_status(self.filter_digital_signal("wrongdoor", getattr(data, 'wrongdoor', False)))
        
        # TWC DATA
        if w := self.signal_widgets.get("twc_열번"):
            w.set_value(getattr(data, 'trainno', '-'))
        if w := self.signal_widgets.get("twc_현재역"):
            w.set_value(getattr(data, 'pstn', '-'))
        if w := self.signal_widgets.get("twc_다음역"):
            w.set_value(getattr(data, 'nstn', '-'))
        if w := self.signal_widgets.get("twc_종착역"):
            w.set_value(getattr(data, 'dstn', '-'))
        if w := self.signal_widgets.get("twc_DOOR"):
            w.set_value(getattr(data, 'nextdoor', '-'))
        
        # ===================================================================
        # ETC 카드
        # ===================================================================
        # SYSTEM ACTIVE는 매 프레임 깜박이므로 과반수 필터 사용
        if w := self.signal_widgets.get("etc_sys_act"):
            val = self.filter_majority_signal("system_active", getattr(data, 'system_active', False))
            w.set_status(val)
        
        # 나머지 ETC 신호는 일반 필터 사용
        etc_signals = [
            ("inch", "inching"), ("cs", "trac_cs"),
            ("평상", "nomal"), ("회복", "recovery"), ("o.spd", "over_spd_warning"),
            ("사전제동", "pre_brake"), ("가속제한", "limit_drive"),
            ("-70↑", "sh_stop1"), ("+70↑", "ov_stop1"),
            ("tdir_a", "tacho_dir_a"), ("tdir_b", "tacho_dir_b")
        ]
        for name, attr in etc_signals:
            val = self.filter_digital_signal(f"etc_{attr}", getattr(data, attr, False))
            if w := self.signal_widgets.get(f"etc_{name}"):
                w.set_status(val)
        
        # ETC DATA (문자열 필터 적용)
        if w := self.signal_widgets.get("etc_init_pdt"):
            val = self.filter_string_signal("etc_ipdt", str(getattr(data, 'ipdt', '-')))
            w.set_value(val)
        if w := self.signal_widgets.get("etc_mnul_pdt"):
            val = self.filter_string_signal("etc_mpdt", str(getattr(data, 'mpdt', '-')))
            w.set_value(val)
        if w := self.signal_widgets.get("etc_tcms_dr"):
            val = self.filter_string_signal("etc_tcmsdoor", str(getattr(data, 'tcmsdoor', '-')))
            w.set_value(val)
        if w := self.signal_widgets.get("etc_door_md"):
            val = self.filter_string_signal("etc_doormod", str(getattr(data, 'doormod', '-')))
            w.set_value(val)
        
        # ===================================================================
        # VDI A/B/C/D, VDO A/B - 모든 신호에 필터 적용
        # ===================================================================
        # VDI A
        vdi_a_map = {
            "hcr": "vdia_hcr", "tcr": "vdia_tcr", "fa": "vdia_fa",
            "auto": "vdia_auto", "mcs": "vdia_mcs", "yard": "vdia_yard",
            "fmc": "vdia_fmc", "d_open": "vdia_dooropen", "d_close": "vdia_doorclose",
            "mc_eb": "vdia_masconeb", "mc_br": "vdia_masconbr", "mc_dr": "vdia_mascondr",
            "fwd": "vdia_fwd", "neu": "vdia_neu", "rvs": "vdia_rvs"
        }
        for widget_name, attr in vdi_a_map.items():
            val = self.filter_digital_signal(attr, getattr(data, attr, False))
            if w := self.signal_widgets.get(f"vdi_a_{widget_name}"):
                w.set_status(val)
        
        # VDI B
        vdi_b_map = {
            "hcr": "vdib_hcr", "tcr": "vdib_tcr", "fa": "vdib_fa",
            "auto": "vdib_auto", "mcs": "vdib_mcs", "yard": "vdib_yard",
            "fmc": "vdib_fmc", "d_open": "vdib_dooropen", "d_close": "vdib_doorclose",
            "mc_eb": "vdib_masconeb", "mc_br": "vdib_masconbr", "mc_dr": "vdib_mascondr",
            "fwd": "vdib_fwd", "neu": "vdib_neu", "rvs": "vdib_rvs"
        }
        for widget_name, attr in vdi_b_map.items():
            val = self.filter_digital_signal(attr, getattr(data, attr, False))
            if w := self.signal_widgets.get(f"vdi_b_{widget_name}"):
                w.set_status(val)
        
        # VDI C
        vdi_c_map = {
            "psd_op": "vdic_psdopen", "psd_cl": "vdic_psdclose", "start": "vdic_startbtn",
            "unit1": "vdic_unit1", "ebpfb": "vdic_ebpfb", "ebmfb": "vdic_ebmfb",
            "fsb_fb": "vdic_fsbfb", "zvr_fb": "vdic_zvrfb", "edr_fb": "vdic_edrfb",
            "edl_fb": "vdic_edlfb", "tc1": "vdic_tc1", "tc2": "vdic_tc2"
        }
        for widget_name, attr in vdi_c_map.items():
            val = self.filter_digital_signal(attr, getattr(data, attr, False))
            if w := self.signal_widgets.get(f"vdi_c_{widget_name}"):
                w.set_status(val)
        
        # VDI D
        vdi_d_map = {
            "psd_op": "vdid_psdopen", "psd_cl": "vdid_psdclose", "start": "vdid_startbtn",
            "unit1": "vdid_unit1", "ebpfb": "vdid_ebpfb", "ebmfb": "vdid_ebmfb",
            "fsb_fb": "vdid_fsbfb", "zvr_fb": "vdid_zvrfb", "edr_fb": "vdid_edrfb",
            "edl_fb": "vdid_edlfb", "tc1": "vdid_tc1", "tc2": "vdid_tc2"
        }
        for widget_name, attr in vdi_d_map.items():
            val = self.filter_digital_signal(attr, getattr(data, attr, False))
            if w := self.signal_widgets.get(f"vdi_d_{widget_name}"):
                w.set_status(val)
        
        # VDO A
        vdo_a_map = {
            "ebp": "vdoa_ebp", "ebm": "vdoa_ebm", "fsb": "vdoa_fsb",
            "zvr": "vdoa_zvr", "edl": "vdoa_edl", "edr": "vdoa_edr"
        }
        for widget_name, attr in vdo_a_map.items():
            val = self.filter_digital_signal(attr, getattr(data, attr, False))
            if w := self.signal_widgets.get(f"vdo_a_{widget_name}"):
                w.set_status(val)
        
        # VDO B
        vdo_b_map = {
            "ebp": "vdob_ebp", "ebm": "vdob_ebm", "fsb": "vdob_fsb",
            "zvr": "vdob_zvr", "edl": "vdob_edl", "edr": "vdob_edr"
        }
        for widget_name, attr in vdo_b_map.items():
            val = self.filter_digital_signal(attr, getattr(data, attr, False))
            if w := self.signal_widgets.get(f"vdo_b_{widget_name}"):
                w.set_status(val)


