from datetime import datetime
import math
from pathlib import Path

from PyQt5.QtCore import Qt, QRectF, QTimer
from PyQt5.QtGui import (
    QBrush,
    QColor,
    QFont,
    QLinearGradient,
    QPainter,
    QPainterPath,
    QPen,
    QPixmap,
    QRadialGradient,
)
from PyQt5.QtWidgets import (
    QFrame,
    QGridLayout,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QSizePolicy,
    QVBoxLayout,
    QWidget,
)

from core.voice_privacy import voice_privacy_foundation


GREEN = "#00f58a"
PURPLE = "#9b4dff"
PURPLE_LIGHT = "#c89cff"
CYAN = "#18e6d2"
WHITE = "#f4efff"
MUTED = "#a89bc4"
PANEL = "rgba(7, 7, 22, 225)"
PANEL_SOFT = "rgba(10, 7, 28, 190)"
BORDER = "rgba(155, 77, 255, 165)"


def _greeting():
    hour = datetime.now().hour

    if hour < 12:
        return "Good morning, Ray."

    if hour < 18:
        return "Good afternoon, Ray."

    return "Good evening, Ray."


class VoicePrivacyBanner(QFrame):
    """Live local voice privacy and runtime-state banner."""

    def __init__(self):
        super().__init__()

        self.setObjectName("voicePrivacyBanner")
        self.setFixedHeight(52)
        self.setMinimumWidth(310)
        self.setSizePolicy(
            QSizePolicy.Preferred,
            QSizePolicy.Fixed,
        )

        root = QHBoxLayout(self)
        root.setContentsMargins(18, 6, 18, 6)
        root.setSpacing(12)

        self.icon = QLabel("◉")
        self.icon.setAlignment(Qt.AlignCenter)

        text_column = QVBoxLayout()
        text_column.setSpacing(0)

        self.top = QLabel()
        self.bottom = QLabel()

        text_column.addWidget(self.top)
        text_column.addWidget(self.bottom)

        root.addWidget(self.icon)
        root.addLayout(text_column)

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.refresh)
        self.timer.start(400)

        self.refresh()

    def refresh(self):
        snapshot = voice_privacy_foundation.snapshot()
        state = str(snapshot.state).upper()

        if state == "ERROR":
            accent = "#ff5c70"
            top_text = "VOICE ERROR"
            bottom_text = snapshot.error or "VOICE POLICY ERROR"

        elif state == "LISTENING":
            accent = GREEN
            top_text = "VOICE LISTENING"
            bottom_text = "MICROPHONE OPEN  •  LOCAL ONLY"

        elif state == "PROCESSING":
            accent = CYAN
            top_text = "VOICE PROCESSING"
            bottom_text = "AUDIO DISCARDED  •  LOCAL ONLY"

        elif state == "READY":
            accent = PURPLE_LIGHT
            top_text = "VOICE READY"
            bottom_text = "MANUAL CAPTURE  •  LOCAL ONLY"

        else:
            accent = MUTED
            top_text = "VOICE MODE OFF"
            bottom_text = "MICROPHONE CLOSED"

        self.setStyleSheet(f"""
            QFrame#voicePrivacyBanner {{
                background: rgba(4, 7, 18, 220);
                border: 1px solid {accent};
                border-radius: 13px;
            }}
            QLabel {{
                background: transparent;
                border: none;
            }}
        """)

        self.icon.setStyleSheet(
            f"color:{accent}; font-size:24px; font-weight:900;"
        )

        self.top.setText(top_text)
        self.top.setStyleSheet(
            f"color:{accent}; font-size:14px; font-weight:900;"
        )

        self.bottom.setText(bottom_text)
        self.bottom.setStyleSheet(
            "color:#aa9dc6; font-size:9px; font-weight:800;"
        )


class LaptopRuneButton(QPushButton):
    """Compact Presence Console navigation control."""

    def __init__(
        self,
        icon,
        title,
        sub,
        target_index,
        stack,
        compact=False,
    ):
        super().__init__()

        self.target_index = target_index
        self.stack = stack

        if compact:
            self.setText(f"{icon}  {title}")
            self.setMinimumHeight(48)
        else:
            self.setText(f"{icon}\n{title}\n{sub}")
            self.setMinimumHeight(74)

        self.setCursor(Qt.PointingHandCursor)
        self.setSizePolicy(
            QSizePolicy.Expanding,
            QSizePolicy.Fixed,
        )

        self.setStyleSheet("""
            QPushButton {
                background: rgba(8, 5, 25, 225);
                color: #c69cff;
                border: 1px solid rgba(155, 77, 255, 185);
                border-radius: 13px;
                font-family: "DejaVu Sans Mono", monospace;
                font-size: 13px;
                font-weight: 900;
                padding: 8px 12px;
            }
            QPushButton:hover {
                background: rgba(34, 9, 66, 235);
                color: #ffffff;
                border: 1px solid #b76cff;
            }
            QPushButton:pressed {
                background: rgba(0, 245, 138, 65);
                color: #00f58a;
                border: 1px solid #00f58a;
            }
        """)

        self.clicked.connect(self.open_target)

    def open_target(self):
        if self.stack is not None:
            self.stack.setCurrentIndex(self.target_index)


class PresenceCard(QFrame):
    def __init__(self, title, accent=PURPLE):
        super().__init__()

        self.setObjectName("presenceCard")
        self.setStyleSheet(f"""
            QFrame#presenceCard {{
                background: {PANEL};
                border: 1px solid {BORDER};
                border-radius: 18px;
            }}
            QLabel {{
                background: transparent;
                border: none;
            }}
        """)

        self.column = QVBoxLayout(self)
        self.column.setContentsMargins(22, 18, 22, 18)
        self.column.setSpacing(10)

        heading = QLabel(title)
        heading.setStyleSheet(
            f"color:{accent}; font-size:12px; font-weight:900;"
        )

        self.column.addWidget(heading)


class ShireCenterCore(QWidget):
    """Face-first SHIRE presence with truthful expression states."""

    EXPRESSION_FILES = {
        "neutral": "shire_neutral.png",
        "blink_half": "shire_blink_half.png",
        "blink_closed": "shire_blink_closed.png",
        "smile": "shire_smile.png",
        "thinking": "shire_thinking.png",
        "listening": "shire_listening.png",
        "focused": "shire_focused.png",
        "acknowledge": "shire_acknowledge.png",
        "concerned": "shire_concerned.png",
        "surprised": "shire_surprised.png",
        "sentinel_standby": "shire_sentinel_standby.png",
        "sentinel_active": "shire_sentinel_active.png",
        "sentinel_lockdown": "shire_sentinel_lockdown.png",
    }

    NORMAL_BLINK_STATES = {
        "neutral",
        "smile",
        "focused",
        "acknowledge",
        "concerned",
    }

    def __init__(self):
        super().__init__()

        self.root = Path(__file__).resolve().parent.parent
        self.expression_dir = (
            self.root
            / "assets"
            / "shire"
            / "expressions"
        )

        self.fallback_face_path = (
            self.root
            / "assets"
            / "shire"
            / "shire_face_main.png"
        )

        self.fallback_face = (
            QPixmap(str(self.fallback_face_path))
            if self.fallback_face_path.exists()
            else QPixmap()
        )

        self.expression_faces = {}

        for state, filename in self.EXPRESSION_FILES.items():
            path = self.expression_dir / filename
            portrait = (
                QPixmap(str(path))
                if path.exists()
                else QPixmap()
            )

            if portrait.isNull():
                portrait = self.fallback_face

            self.expression_faces[state] = portrait

        self.face_path = (
            self.expression_dir
            / self.EXPRESSION_FILES["neutral"]
        )

        self.face = self.expression_faces["neutral"]
        self.expression_state = "neutral"

        self.tick = 0
        self.animation_interval_ms = 125
        self._face_cache = {}

        self._blink_intervals = (
            38,
            52,
            44,
            60,
            34,
            49,
        )

        self._blink_interval_index = 0
        self._blink_ticks_remaining = (
            self._blink_intervals[0]
        )
        self._blink_phase = -1
        self._blink_duration_ms = 90

        self.blink_timer = QTimer(self)
        self.blink_timer.setSingleShot(True)
        self.blink_timer.setTimerType(Qt.PreciseTimer)
        self.blink_timer.timeout.connect(
            self._finish_blink
        )

        self.setMinimumSize(390, 470)
        self.setSizePolicy(
            QSizePolicy.Expanding,
            QSizePolicy.Expanding,
        )

        self.timer = QTimer(self)
        self.timer.setTimerType(Qt.CoarseTimer)
        self.timer.timeout.connect(self.pulse)
        self.timer.start(self.animation_interval_ms)

    def pulse(self):
        self.tick = (self.tick + 1) % 28800

        if self.expression_state in self.NORMAL_BLINK_STATES:
            if self._blink_phase < 0:
                self._blink_ticks_remaining -= 1

                if self._blink_ticks_remaining <= 0:
                    self._blink_phase = 0
                    self.blink_timer.start(
                        self._blink_duration_ms
                    )
        else:
            if self.blink_timer.isActive():
                self.blink_timer.stop()

            self._blink_phase = -1

        self.update()

    def _finish_blink(self):
        self._blink_phase = -1
        self._schedule_next_blink()
        self.update()

    def _schedule_next_blink(self):
        self._blink_interval_index = (
            self._blink_interval_index + 1
        ) % len(self._blink_intervals)

        self._blink_ticks_remaining = (
            self._blink_intervals[
                self._blink_interval_index
            ]
        )

    def set_expression_state(self, state):
        """Select one approved expression without inventing state."""

        state = str(state).strip().lower()

        if state not in self.expression_faces:
            return False

        self.expression_state = state
        self.face = self.expression_faces[state]

        if self.blink_timer.isActive():
            self.blink_timer.stop()

        self._blink_phase = -1
        self._schedule_next_blink()
        self.update()

        return True

    def current_expression_state(self):
        """Return the portrait state currently being rendered."""

        if (
            self.expression_state in self.NORMAL_BLINK_STATES
            and self._blink_phase == 0
        ):
            return "blink_closed"

        return self.expression_state

    def _current_face(self):
        state = self.current_expression_state()

        return self.expression_faces.get(
            state,
            self.fallback_face,
        )

    def showEvent(self, event):
        super().showEvent(event)

        if not self.timer.isActive():
            self.timer.start(
                self.animation_interval_ms
            )

        self.update()

    def hideEvent(self, event):
        if self.timer.isActive():
            self.timer.stop()

        if self.blink_timer.isActive():
            self.blink_timer.stop()

        self._blink_phase = -1
        self._schedule_next_blink()

        super().hideEvent(event)

    def resizeEvent(self, event):
        self._face_cache.clear()
        super().resizeEvent(event)

    def _scaled_face_for(
        self,
        state,
        portrait_size,
    ):
        face = self.expression_faces.get(
            state,
            self.fallback_face,
        )

        if face.isNull():
            return QPixmap()

        target_size = max(
            1,
            int(
                math.ceil(
                    float(portrait_size) * 1.025
                )
            ),
        )

        cache_key = (
            state,
            int(face.cacheKey()),
            target_size,
        )

        cached = self._face_cache.get(
            cache_key
        )

        if cached is not None and not cached.isNull():
            return cached

        scaled = face.scaled(
            target_size,
            target_size,
            Qt.KeepAspectRatioByExpanding,
            Qt.SmoothTransformation,
        )

        if scaled.isNull():
            return face

        if len(self._face_cache) >= 18:
            self._face_cache.clear()

        self._face_cache[cache_key] = scaled

        return scaled

    def paintEvent(self, event):
        del event

        painter = QPainter(self)
        painter.setRenderHint(
            QPainter.Antialiasing,
            True,
        )
        painter.setRenderHint(
            QPainter.SmoothPixmapTransform,
            True,
        )

        w = self.width()
        h = self.height()
        cx = w / 2
        cy = h * 0.47

        seconds = (
            self.tick
            * self.animation_interval_ms
            / 1000.0
        )

        aura = QRadialGradient(
            cx,
            cy,
            min(w, h) * 0.48,
        )
        aura.setColorAt(
            0.0,
            QColor(80, 25, 135, 115),
        )
        aura.setColorAt(
            0.55,
            QColor(37, 7, 82, 60),
        )
        aura.setColorAt(
            1.0,
            QColor(0, 0, 0, 0),
        )

        painter.setPen(Qt.NoPen)
        painter.setBrush(QBrush(aura))
        painter.drawEllipse(
            QRectF(
                cx - min(w, h) * 0.48,
                cy - min(w, h) * 0.48,
                min(w, h) * 0.96,
                min(w, h) * 0.96,
            )
        )

        pulse_offset = (
            math.sin(seconds * 0.75) + 1.0
        ) / 2.0

        for index in range(5):
            radius = (
                min(w, h)
                * (0.34 + index * 0.065)
                + pulse_offset * 4.0
            )

            alpha = max(
                35,
                145 - index * 22,
            )

            painter.setPen(
                QPen(
                    QColor(
                        155,
                        77,
                        255,
                        alpha,
                    ),
                    1.4 if index else 2.2,
                )
            )

            painter.setBrush(Qt.NoBrush)
            painter.drawEllipse(
                QRectF(
                    cx - radius,
                    cy - radius,
                    radius * 2,
                    radius * 2,
                )
            )

        base_portrait_size = min(
            w * 0.72,
            h * 0.72,
        )

        breath = math.sin(
            (2.0 * math.pi * seconds) / 6.6
        )

        drift_x = (
            2.5
            * math.sin(
                ((2.0 * math.pi * seconds) / 11.2)
                + 0.35
            )
        )

        drift_y = (
            2.8
            * math.sin(
                (2.0 * math.pi * seconds) / 8.3
            )
        )

        breathing_scale = 1.0 + (
            breath * 0.008
        )

        portrait_size = (
            base_portrait_size
            * breathing_scale
        )

        portrait_rect = QRectF(
            cx - portrait_size / 2 + drift_x,
            cy - portrait_size / 2 + drift_y,
            portrait_size,
            portrait_size,
        )

        clip_path = QPainterPath()
        clip_path.addEllipse(portrait_rect)

        painter.save()
        painter.setClipPath(clip_path)

        rendered_state = (
            self.current_expression_state()
        )

        scaled = self._scaled_face_for(
            rendered_state,
            base_portrait_size,
        )

        if not scaled.isNull():
            source_size = min(
                float(scaled.width()),
                float(scaled.height()),
            )

            source_x = max(
                0.0,
                (
                    scaled.width()
                    - source_size
                ) / 2.0,
            )

            source_y = max(
                0.0,
                (
                    scaled.height()
                    - source_size
                ) / 2.0,
            )

            painter.drawPixmap(
                portrait_rect,
                scaled,
                QRectF(
                    source_x,
                    source_y,
                    source_size,
                    source_size,
                ),
            )
        else:
            painter.fillRect(
                portrait_rect,
                QColor(7, 5, 20),
            )

            painter.setPen(
                QPen(
                    QColor(155, 77, 255),
                    2,
                )
            )

            painter.setFont(
                QFont(
                    "DejaVu Sans Mono",
                    25,
                    QFont.Bold,
                )
            )

            painter.drawText(
                portrait_rect,
                Qt.AlignCenter,
                "SHIRE\nPRESENCE",
            )

        painter.restore()

        painter.setPen(
            QPen(
                QColor(190, 118, 255, 230),
                3,
            )
        )
        painter.setBrush(Qt.NoBrush)
        painter.drawEllipse(portrait_rect)

        lower_gradient = QLinearGradient(
            0,
            h * 0.62,
            0,
            h,
        )
        lower_gradient.setColorAt(
            0.0,
            QColor(0, 0, 0, 0),
        )
        lower_gradient.setColorAt(
            1.0,
            QColor(1, 1, 8, 220),
        )

        painter.setPen(Qt.NoPen)
        painter.setBrush(
            QBrush(lower_gradient)
        )
        painter.drawRect(
            QRectF(
                0,
                h * 0.60,
                w,
                h * 0.40,
            )
        )

        painter.setPen(
            QColor(PURPLE_LIGHT)
        )
        painter.setFont(
            QFont(
                "DejaVu Sans Mono",
                22,
                QFont.Bold,
            )
        )
        painter.drawText(
            QRectF(
                0,
                h - 72,
                w,
                30,
            ),
            Qt.AlignCenter,
            "SHIRE",
        )

        painter.setPen(QColor(GREEN))
        painter.setFont(
            QFont(
                "DejaVu Sans Mono",
                10,
                QFont.Bold,
            )
        )
        painter.drawText(
            QRectF(
                0,
                h - 40,
                w,
                22,
            ),
            Qt.AlignCenter,
            "PRESENCE ONLINE  •  APPROVAL GATE ACTIVE",
        )


class VoiceOrb(QWidget):
    """Visual mirror of the approved manual voice state."""

    def __init__(self):
        super().__init__()

        self.setMinimumHeight(104)
        self.setMaximumHeight(116)
        self.setSizePolicy(
            QSizePolicy.Expanding,
            QSizePolicy.Fixed,
        )

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update)
        self.timer.start(300)

    def paintEvent(self, event):
        del event

        snapshot = voice_privacy_foundation.snapshot()
        state = str(snapshot.state).upper()

        listening = state == "LISTENING"
        processing = state == "PROCESSING"
        ready = state == "READY"
        error = state == "ERROR"

        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing, True)

        w = self.width()
        h = self.height()
        cx = w / 2
        cy = 42

        if error:
            orb_color = QColor("#ff5c70")
        elif listening:
            orb_color = QColor(GREEN)
        elif processing:
            orb_color = QColor(CYAN)
        elif ready:
            orb_color = QColor(PURPLE_LIGHT)
        else:
            orb_color = QColor(PURPLE)

        for index, width_ratio in enumerate(
            (0.42, 0.32, 0.23)
        ):
            rect_width = w * width_ratio

            painter.setPen(
                QPen(
                    QColor(
                        orb_color.red(),
                        orb_color.green(),
                        orb_color.blue(),
                        105 - index * 20,
                    ),
                    1.4,
                )
            )

            painter.setBrush(Qt.NoBrush)
            painter.drawEllipse(
                QRectF(
                    cx - rect_width / 2,
                    cy - 25 - index * 3,
                    rect_width,
                    50 + index * 6,
                )
            )

        painter.setPen(QPen(orb_color, 2))
        painter.setBrush(QColor(18, 7, 42, 240))
        painter.drawEllipse(
            QRectF(cx - 31, cy - 31, 62, 62)
        )

        painter.setPen(QPen(orb_color, 4))
        painter.setBrush(Qt.NoBrush)
        painter.drawRoundedRect(
            QRectF(cx - 8, cy - 16, 16, 29),
            8,
            8,
        )
        painter.drawLine(
            int(cx),
            int(cy + 14),
            int(cx),
            int(cy + 24),
        )
        painter.drawLine(
            int(cx - 9),
            int(cy + 24),
            int(cx + 9),
            int(cy + 24),
        )

        if listening:
            state_text = "LISTENING"
            detail_text = "Speech is filling the top prompt"

        elif processing:
            state_text = "PROCESSING"
            detail_text = "Finalising local transcript"

        elif ready:
            state_text = "VOICE READY"
            detail_text = "Use MIC in the top prompt bar"

        elif error:
            state_text = "VOICE ERROR"
            detail_text = snapshot.error or "Check voice configuration"

        else:
            state_text = "MICROPHONE OFF"
            detail_text = "Voice capture is unavailable"

        painter.setPen(orb_color)
        painter.setFont(
            QFont("DejaVu Sans Mono", 11, QFont.Bold)
        )
        painter.drawText(
            QRectF(0, 76, w, 18),
            Qt.AlignCenter,
            state_text,
        )

        painter.setPen(QColor(MUTED))
        painter.setFont(
            QFont("DejaVu Sans Mono", 8, QFont.Bold)
        )
        painter.drawText(
            QRectF(0, 94, w, 16),
            Qt.AlignCenter,
            detail_text,
        )


class LaptopDashboard(QWidget):
    """SHIRE AIOS face-first Presence Console."""

    def __init__(self, stack):
        super().__init__()

        self.stack = stack
        self.station_drawer = None
        self.build_ui()

    def _open_station(self, target_index):
        self.stack.setCurrentIndex(target_index)

        if self.station_drawer is not None:
            self.station_drawer.setVisible(False)

    def _make_nav_button(
        self,
        icon,
        label,
        target_index,
        compact=True,
    ):
        button = LaptopRuneButton(
            icon,
            label,
            "",
            target_index,
            self.stack,
            compact=compact,
        )

        button.clicked.disconnect()
        button.clicked.connect(
            lambda checked=False, index=target_index:
            self._open_station(index)
        )

        return button

    def _toggle_stations(self):
        visible = not self.station_drawer.isVisible()
        self.station_drawer.setVisible(visible)

    def _build_greeting_card(self):
        card = PresenceCard("SHIRE PRESENCE", PURPLE_LIGHT)

        greeting = QLabel(_greeting())
        greeting.setStyleSheet(
            f"color:{PURPLE_LIGHT}; "
            "font-size:23px; font-weight:900;"
        )

        message = QLabel(
            "The Workshop is ready.\n"
            "No proposal is active.\n\n"
            "How can I help you today?"
        )

        message.setWordWrap(True)
        message.setStyleSheet(
            "color:#d7c8f4; "
            "font-size:14px; "
            "font-weight:700; "
            "line-height:1.35;"
        )

        card.column.addWidget(greeting)
        card.column.addWidget(message)
        card.column.addStretch(1)

        return card

    def _build_status_card(self):
        card = PresenceCard("SYSTEM STATUS", CYAN)

        rows = (
            ("CORE", "ONLINE", GREEN),
            ("SESSION", "LOCAL X11", CYAN),
            ("MICROPHONE", "CLOSED", PURPLE_LIGHT),
            ("AUTHORITY", "RAY APPROVAL", GREEN),
        )

        for title, value, accent in rows:
            row = QHBoxLayout()

            left = QLabel(title)
            left.setStyleSheet(
                "color:#8e82aa; "
                "font-size:9px; "
                "font-weight:900;"
            )

            right = QLabel(value)
            right.setAlignment(Qt.AlignRight)
            right.setStyleSheet(
                f"color:{accent}; "
                "font-size:11px; "
                "font-weight:900;"
            )

            row.addWidget(left)
            row.addStretch(1)
            row.addWidget(right)

            card.column.addLayout(row)

        return card

    def _build_thought_card(self):
        card = PresenceCard("CURRENT THOUGHT", PURPLE_LIGHT)

        concept = QLabel("◌")
        concept.setAlignment(Qt.AlignCenter)
        concept.setMinimumHeight(88)
        concept.setStyleSheet(
            "color:#b66cff; "
            "font-size:64px; "
            "font-weight:500;"
        )

        title = QLabel("NO ACTIVE DESIGN PREVIEW")
        title.setAlignment(Qt.AlignCenter)
        title.setStyleSheet(
            f"color:{PURPLE_LIGHT}; "
            "font-size:12px; "
            "font-weight:900;"
        )

        note = QLabel(
            "Approved STL concepts and Workshop ideas "
            "will appear here."
        )

        note.setWordWrap(True)
        note.setAlignment(Qt.AlignCenter)
        note.setStyleSheet(
            "color:#9e91ba; "
            "font-size:10px; "
            "font-weight:700;"
        )

        card.column.addWidget(concept)
        card.column.addWidget(title)
        card.column.addWidget(note)
        card.column.addStretch(1)

        return card

    def _build_next_card(self):
        card = PresenceCard("NEXT UP", CYAN)

        items = (
            "Open Workshop",
            "Review Sentinel",
            "Continue Education",
        )

        for item in items:
            label = QLabel(f"›  {item}")
            label.setStyleSheet(
                "color:#c8afff; "
                "font-size:12px; "
                "font-weight:800; "
                "padding:5px 0;"
            )
            card.column.addWidget(label)

        card.column.addStretch(1)

        return card

    def _build_station_drawer(self):
        drawer = QFrame()
        drawer.setObjectName("stationDrawer")
        drawer.setVisible(False)

        drawer.setStyleSheet("""
            QFrame#stationDrawer {
                background: rgba(5, 4, 18, 240);
                border: 1px solid rgba(155, 77, 255, 180);
                border-radius: 16px;
            }
        """)

        grid = QGridLayout(drawer)
        grid.setContentsMargins(12, 12, 12, 12)
        grid.setHorizontalSpacing(10)
        grid.setVerticalSpacing(10)

        stations = (
            ("▤", "MEMORY", 3),
            ("◇", "ATLAS", 4),
            ("✦", "EDUCATION", 11),
            ("♟", "AGENTS", 7),
            ("☷", "TASKS", 8),
            ("⚙", "SYSTEM", 6),
            ("⌁", "SETTINGS", 9),
            ("⌨", "COMMAND", 1),
        )

        for index, (icon, label, target) in enumerate(stations):
            button = self._make_nav_button(
                icon,
                label,
                target,
                compact=True,
            )

            grid.addWidget(
                button,
                index // 4,
                index % 4,
            )

        return drawer

    def build_ui(self):
        self.setObjectName("presenceConsole")

        self.setStyleSheet("""
            QWidget#presenceConsole {
                background: #020208;
                color: #f4efff;
                font-family: "DejaVu Sans Mono", monospace;
            }
            QLabel {
                background: transparent;
            }
        """)

        root = QVBoxLayout(self)
        root.setContentsMargins(26, 20, 26, 16)
        root.setSpacing(12)

        header = QHBoxLayout()
        header.setSpacing(18)

        brand_column = QVBoxLayout()
        brand_column.setSpacing(0)

        brand = QLabel("SHIRE AIOS")
        brand.setStyleSheet(
            f"color:{PURPLE}; "
            "font-size:27px; "
            "font-weight:900; "
            "letter-spacing:2px;"
        )

        core_state = QLabel("CORE ONLINE")
        core_state.setStyleSheet(
            f"color:{GREEN}; "
            "font-size:10px; "
            "font-weight:900;"
        )

        brand_column.addWidget(brand)
        brand_column.addWidget(core_state)

        header.addLayout(brand_column)
        header.addStretch(1)
        header.addWidget(VoicePrivacyBanner())

        root.addLayout(header)

        stage = QHBoxLayout()
        stage.setSpacing(14)

        left_column = QVBoxLayout()
        left_column.setSpacing(12)
        left_column.addWidget(self._build_greeting_card(), 3)
        left_column.addWidget(self._build_status_card(), 2)

        center_column = QVBoxLayout()
        center_column.setSpacing(2)

        center_title = QLabel("SHIRE PRESENCE")
        center_title.setAlignment(Qt.AlignCenter)
        center_title.setStyleSheet(
            f"color:{PURPLE_LIGHT}; "
            "font-size:12px; "
            "font-weight:900; "
            "letter-spacing:2px;"
        )

        center_column.addWidget(center_title)
        center_column.addWidget(ShireCenterCore(), 1)
        center_column.addWidget(VoiceOrb())

        right_column = QVBoxLayout()
        right_column.setSpacing(12)
        right_column.addWidget(self._build_thought_card(), 3)
        right_column.addWidget(self._build_next_card(), 2)

        stage.addLayout(left_column, 5)
        stage.addLayout(center_column, 8)
        stage.addLayout(right_column, 5)

        root.addLayout(stage, 1)

        dock = QHBoxLayout()
        dock.setSpacing(10)

        dock.addWidget(
            self._make_nav_button("●", "SHIRE", 1)
        )

        dock.addWidget(
            self._make_nav_button("⚒", "FORGE", 2)
        )

        dock.addWidget(
            self._make_nav_button("♜", "SENTINEL", 10)
        )

        stations_button = QPushButton("☰  STATIONS")
        stations_button.setCursor(Qt.PointingHandCursor)
        stations_button.setMinimumHeight(48)

        stations_button.setStyleSheet("""
            QPushButton {
                background: rgba(8, 5, 25, 225);
                color: #00f58a;
                border: 1px solid rgba(0, 245, 138, 185);
                border-radius: 13px;
                font-family: "DejaVu Sans Mono", monospace;
                font-size: 13px;
                font-weight: 900;
                padding: 8px 12px;
            }
            QPushButton:hover {
                background: rgba(0, 55, 39, 230);
                color: #ffffff;
                border: 1px solid #00f58a;
            }
            QPushButton:pressed {
                background: rgba(155, 77, 255, 75);
            }
        """)

        stations_button.clicked.connect(
            self._toggle_stations
        )

        dock.addWidget(stations_button)

        root.addLayout(dock)

        self.station_drawer = self._build_station_drawer()
        root.addWidget(self.station_drawer)

        hint = QLabel(
            "VOICE PREVIEW READY  •  "
            "Use MIC in the top prompt bar  •  "
            "Audio is processed locally and discarded"
        )

        hint.setAlignment(Qt.AlignCenter)
        hint.setStyleSheet(
            "color:#8f82a8; "
            "font-size:9px; "
            "font-weight:800; "
            "padding:1px;"
        )

        root.addWidget(hint)

    def paintEvent(self, event):
        del event

        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing, True)

        gradient = QLinearGradient(
            0,
            0,
            self.width(),
            self.height(),
        )

        gradient.setColorAt(0.0, QColor(2, 2, 9))
        gradient.setColorAt(0.48, QColor(7, 3, 18))
        gradient.setColorAt(1.0, QColor(2, 2, 8))

        painter.fillRect(
            self.rect(),
            QBrush(gradient),
        )

        painter.setPen(
            QPen(QColor(113, 50, 190, 22), 1)
        )

        spacing = 54

        for x in range(0, self.width(), spacing):
            painter.drawLine(x, 0, x, self.height())

        for y in range(0, self.height(), spacing):
            painter.drawLine(0, y, self.width(), y)

        center_glow = QRadialGradient(
            self.width() / 2,
            self.height() / 2,
            self.width() * 0.42,
        )

        center_glow.setColorAt(
            0.0,
            QColor(106, 25, 180, 50),
        )

        center_glow.setColorAt(
            1.0,
            QColor(0, 0, 0, 0),
        )

        painter.setPen(Qt.NoPen)
        painter.setBrush(QBrush(center_glow))
        painter.drawEllipse(
            QRectF(
                self.width() * 0.14,
                -self.height() * 0.10,
                self.width() * 0.72,
                self.height() * 1.20,
            )
        )
