import { DeesElement, property, html, customElement, type TemplateResult, css, state, cssManager } from '@design.estate/dees-element';
import * as appstate from '../appstate.js';
import * as interfaces from '../../dist_ts_interfaces/index.js';
import { viewHostCss } from './shared/css.js';
import { type IStatsTile } from '@design.estate/dees-catalog';

declare global {
  interface HTMLElementTagNameMap {
    'ops-view-network': OpsViewNetwork;
  }
}

interface INetworkRequest {
  id: string;
  timestamp: number;
  method: string;
  url: string;
  hostname: string;
  port: number;
  protocol: 'http' | 'https' | 'tcp' | 'udp';
  statusCode?: number;
  duration: number;
  bytesIn: number;
  bytesOut: number;
  remoteIp: string;
  route?: string;
}

@customElement('ops-view-network')
export class OpsViewNetwork extends DeesElement {
  /** How far back the traffic chart shows */
  private static readonly CHART_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
  /** How often a new data point is added */
  private static readonly UPDATE_INTERVAL_MS = 1000; // 1 second
  /** Derived: max data points the buffer holds */
  private static readonly MAX_DATA_POINTS = OpsViewNetwork.CHART_WINDOW_MS / OpsViewNetwork.UPDATE_INTERVAL_MS;

  @state()
  accessor statsState = appstate.statsStatePart.getState()!;

  @state()
  accessor networkState = appstate.networkStatePart.getState()!;


  @state()
  accessor networkRequests: INetworkRequest[] = [];

  @state()
  accessor trafficDataIn: Array<{ x: string | number; y: number }> = [];

  @state()
  accessor trafficDataOut: Array<{ x: string | number; y: number }> = [];
  
  // Track if we need to update the chart to avoid unnecessary re-renders
  private lastChartUpdate = 0;
  private chartUpdateThreshold = OpsViewNetwork.UPDATE_INTERVAL_MS; // Minimum ms between chart updates

  private trafficUpdateTimer: any = null;
  private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
  private historyLoaded = false; // Whether server-side throughput history has been loaded
  private visibilityHandler: (() => void) | null = null;

  constructor() {
    super();
    this.subscribeToStateParts();
    this.initializeTrafficData();
    this.updateNetworkData();
    this.startTrafficUpdateTimer();
  }

  async connectedCallback() {
    await super.connectedCallback();

    // Pause/resume traffic timer when tab visibility changes
    this.visibilityHandler = () => {
      if (document.hidden) {
        this.stopTrafficUpdateTimer();
      } else {
        this.startTrafficUpdateTimer();
      }
    };
    document.addEventListener('visibilitychange', this.visibilityHandler);

    // When network view becomes visible, ensure we fetch network data
    await appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null);
  }

  async disconnectedCallback() {
    await super.disconnectedCallback();
    this.stopTrafficUpdateTimer();
    if (this.visibilityHandler) {
      document.removeEventListener('visibilitychange', this.visibilityHandler);
      this.visibilityHandler = null;
    }
  }

  private subscribeToStateParts() {
    // Subscribe and track unsubscribe functions
    const statsUnsubscribe = appstate.statsStatePart.select().subscribe((state) => {
      this.statsState = state;
      this.updateNetworkData();
    });
    this.rxSubscriptions.push(statsUnsubscribe);
    
    const networkUnsubscribe = appstate.networkStatePart.select().subscribe((state) => {
      this.networkState = state;
      this.updateNetworkData();
    });
    this.rxSubscriptions.push(networkUnsubscribe);
  }
  
  private initializeTrafficData() {
    const now = Date.now();
    const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetwork;

    // Initialize with empty data points for both in and out
    const emptyData = Array.from({ length: MAX_DATA_POINTS }, (_, i) => {
      const time = now - ((MAX_DATA_POINTS - 1 - i) * UPDATE_INTERVAL_MS);
      return {
        x: new Date(time).toISOString(),
        y: 0,
      };
    });

    this.trafficDataIn = [...emptyData];
    this.trafficDataOut = emptyData.map(point => ({ ...point }));
  }

  /**
   * Load server-side throughput history into the chart.
   * Called once when history data first arrives from the Rust engine.
   * This pre-populates the chart so users see historical data immediately
   * instead of starting from all zeros.
   */
  private loadThroughputHistory() {
    const history = this.networkState.throughputHistory;
    if (!history || history.length === 0) return;

    this.historyLoaded = true;

    // Convert history points to chart data format (bytes/sec → Mbit/s)
    const historyIn = history.map(p => ({
      x: new Date(p.timestamp).toISOString(),
      y: Math.round((p.in * 8) / 1000000 * 10) / 10,
    }));
    const historyOut = history.map(p => ({
      x: new Date(p.timestamp).toISOString(),
      y: Math.round((p.out * 8) / 1000000 * 10) / 10,
    }));

    const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetwork;

    // Use history as the chart data, keeping the most recent points within the window
    const sliceStart = Math.max(0, historyIn.length - MAX_DATA_POINTS);
    this.trafficDataIn = historyIn.slice(sliceStart);
    this.trafficDataOut = historyOut.slice(sliceStart);

    // If fewer than MAX_DATA_POINTS, pad the front with zeros
    if (this.trafficDataIn.length < MAX_DATA_POINTS) {
      const now = Date.now();
      const padCount = MAX_DATA_POINTS - this.trafficDataIn.length;
      const firstTimestamp = this.trafficDataIn.length > 0
        ? new Date(this.trafficDataIn[0].x).getTime()
        : now;

      const padIn = Array.from({ length: padCount }, (_, i) => ({
        x: new Date(firstTimestamp - ((padCount - i) * UPDATE_INTERVAL_MS)).toISOString(),
        y: 0,
      }));
      const padOut = padIn.map(p => ({ ...p }));

      this.trafficDataIn = [...padIn, ...this.trafficDataIn];
      this.trafficDataOut = [...padOut, ...this.trafficDataOut];
    }
  }

  public static styles = [
    cssManager.defaultStyles,
    viewHostCss,
    css`
      .networkContainer {
        display: flex;
        flex-direction: column;
        gap: 24px;
      }


      .protocolBadge {
        display: inline-flex;
        align-items: center;
        padding: 4px 8px;
        border-radius: 4px;
        font-size: 12px;
        font-weight: 500;
      }

      .protocolBadge.http {
        background: ${cssManager.bdTheme('#e3f2fd', '#1a2c3a')};
        color: ${cssManager.bdTheme('#1976d2', '#5a9fd4')};
      }

      .protocolBadge.https {
        background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')};
        color: ${cssManager.bdTheme('#388e3c', '#66bb6a')};
      }

      .protocolBadge.tcp {
        background: ${cssManager.bdTheme('#fff3e0', '#3a2a1a')};
        color: ${cssManager.bdTheme('#f57c00', '#ff9933')};
      }

      .protocolBadge.smtp {
        background: ${cssManager.bdTheme('#f3e5f5', '#2a1a3a')};
        color: ${cssManager.bdTheme('#7b1fa2', '#ba68c8')};
      }

      .protocolBadge.dns {
        background: ${cssManager.bdTheme('#e0f2f1', '#1a3a3a')};
        color: ${cssManager.bdTheme('#00796b', '#4db6ac')};
      }

      .protocolBadge.h1 {
        background: ${cssManager.bdTheme('#e3f2fd', '#1a2c3a')};
        color: ${cssManager.bdTheme('#1976d2', '#5a9fd4')};
      }

      .protocolBadge.h2 {
        background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')};
        color: ${cssManager.bdTheme('#388e3c', '#66bb6a')};
      }

      .protocolBadge.h3 {
        background: ${cssManager.bdTheme('#f3e5f5', '#2a1a3a')};
        color: ${cssManager.bdTheme('#7b1fa2', '#ba68c8')};
      }

      .protocolBadge.unknown {
        background: ${cssManager.bdTheme('#f5f5f5', '#2a2a2a')};
        color: ${cssManager.bdTheme('#757575', '#999999')};
      }

      .suppressionBadge {
        display: inline-flex;
        align-items: center;
        padding: 2px 6px;
        border-radius: 3px;
        font-size: 11px;
        font-weight: 500;
        background: ${cssManager.bdTheme('#fff3e0', '#3a2a1a')};
        color: ${cssManager.bdTheme('#f57c00', '#ff9933')};
        margin-left: 4px;
      }

      .statusBadge {
        display: inline-flex;
        align-items: center;
        padding: 4px 8px;
        border-radius: 4px;
        font-size: 12px;
        font-weight: 500;
      }

      .statusBadge.success {
        background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')};
        color: ${cssManager.bdTheme('#388e3c', '#66bb6a')};
      }

      .statusBadge.error {
        background: ${cssManager.bdTheme('#ffebee', '#3a1a1a')};
        color: ${cssManager.bdTheme('#d32f2f', '#ff6666')};
      }

      .statusBadge.warning {
        background: ${cssManager.bdTheme('#fff3e0', '#3a2a1a')};
        color: ${cssManager.bdTheme('#f57c00', '#ff9933')};
      }
    `,
  ];

  public render() {
    return html`
      <ops-sectionheading>Network Activity</ops-sectionheading>
      
      <div class="networkContainer">
        <!-- Stats Grid -->
        ${this.renderNetworkStats()}

        <!-- Traffic Chart -->
        <dees-chart-area
          .label=${'Network Traffic'}
          .series=${[
            {
              name: 'Inbound',
              data: this.trafficDataIn,
              color: '#22c55e',
            },
            {
              name: 'Outbound',
              data: this.trafficDataOut,
              color: '#8b5cf6',
            }
          ]}
          .realtimeMode=${true}
          .rollingWindow=${OpsViewNetwork.CHART_WINDOW_MS}
          .yAxisFormatter=${(val: number) => `${val} Mbit/s`}
        ></dees-chart-area>

        <!-- Top IPs Section -->
        ${this.renderTopIPs()}

        <!-- Backend Protocols Section -->
        ${this.renderBackendProtocols()}

        <!-- Requests Table -->
        <dees-table
          .data=${this.networkRequests}
          .displayFunction=${(req: INetworkRequest) => ({
            Time: new Date(req.timestamp).toLocaleTimeString(),
            Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`,
            Method: req.method,
            'Host:Port': `${req.hostname}:${req.port}`,
            Path: this.truncateUrl(req.url),
            Status: this.renderStatus(req.statusCode),
            Duration: `${req.duration}ms`,
            'In/Out': `${this.formatBytes(req.bytesIn)} / ${this.formatBytes(req.bytesOut)}`,
            'Remote IP': req.remoteIp,
          })}
          .dataActions=${[
            {
              name: 'View Details',
              iconName: 'fa:magnifyingGlass',
              type: ['inRow', 'doubleClick', 'contextmenu'],
              actionFunc: async (actionData) => {
                await this.showRequestDetails(actionData.item);
              }
            }
          ]}
          heading1="Recent Network Activity"
          heading2="Recent network requests"
          searchable
          .pagination=${true}
          .paginationSize=${50}
          dataName="request"
        ></dees-table>
      </div>
    `;
  }

  private async showRequestDetails(request: INetworkRequest) {
    const { DeesModal } = await import('@design.estate/dees-catalog');
    
    await DeesModal.createAndShow({
      heading: 'Request Details',
      content: html`
        <div style="padding: 20px;">
          <dees-dataview-codebox
            .heading=${'Request Information'}
            progLang="json"
            .codeToDisplay=${JSON.stringify({
              id: request.id,
              timestamp: new Date(request.timestamp).toISOString(),
              protocol: request.protocol,
              method: request.method,
              url: request.url,
              hostname: request.hostname,
              port: request.port,
              statusCode: request.statusCode,
              duration: `${request.duration}ms`,
              bytesIn: request.bytesIn,
              bytesOut: request.bytesOut,
              remoteIp: request.remoteIp,
              route: request.route,
            }, null, 2)}
          ></dees-dataview-codebox>
        </div>
      `,
      menuOptions: [
        {
          name: 'Copy Request ID',
          iconName: 'lucide:Copy',
          action: async () => {
            await navigator.clipboard.writeText(request.id);
          }
        }
      ]
    });
  }


  private renderStatus(statusCode?: number): TemplateResult {
    if (!statusCode) {
      return html`<span class="statusBadge warning">N/A</span>`;
    }
    
    const statusClass = statusCode >= 200 && statusCode < 300 ? 'success' :
                       statusCode >= 400 ? 'error' : 'warning';
    
    return html`<span class="statusBadge ${statusClass}">${statusCode}</span>`;
  }

  private truncateUrl(url: string, maxLength = 50): string {
    if (url.length <= maxLength) return url;
    return url.substring(0, maxLength - 3) + '...';
  }


  private formatNumber(num: number): string {
    if (num >= 1000000) {
      return (num / 1000000).toFixed(1) + 'M';
    } else if (num >= 1000) {
      return (num / 1000).toFixed(1) + 'K';
    }
    return num.toFixed(0);
  }

  private formatBytes(bytes: number): string {
    const units = ['B', 'KB', 'MB', 'GB'];
    let size = bytes;
    let unitIndex = 0;
    
    while (size >= 1024 && unitIndex < units.length - 1) {
      size /= 1024;
      unitIndex++;
    }
    
    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }
  
  private formatBitsPerSecond(bytesPerSecond: number): string {
    const bitsPerSecond = bytesPerSecond * 8; // Convert bytes to bits
    const units = ['bit/s', 'kbit/s', 'Mbit/s', 'Gbit/s'];
    let size = bitsPerSecond;
    let unitIndex = 0;
    
    while (size >= 1000 && unitIndex < units.length - 1) {
      size /= 1000; // Use 1000 for bits (not 1024)
      unitIndex++;
    }
    
    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }

  private calculateThroughput(): { in: number; out: number } {
    // Use real throughput data from network state
    return {
      in: this.networkState.throughputRate.bytesInPerSecond,
      out: this.networkState.throughputRate.bytesOutPerSecond,
    };
  }

  private renderNetworkStats(): TemplateResult {
    // Use server-side requests/sec from SmartProxy's Rust engine
    const reqPerSec = this.networkState.requestsPerSecond || 0;
    const throughput = this.calculateThroughput();
    const activeConnections = this.statsState.serverStats?.activeConnections || 0;

    // Build trend data from pre-computed history (mutated in updateNetworkData, not here)
    const trendData = [...this.requestsPerSecHistory];
    while (trendData.length < 20) {
      trendData.unshift(0);
    }

    const tiles: IStatsTile[] = [
      {
        id: 'connections',
        title: 'Active Connections',
        value: activeConnections,
        type: 'number',
        icon: 'lucide:Plug',
        color: activeConnections > 100 ? '#f59e0b' : '#22c55e',
        description: `Total: ${this.networkState.requestsTotal || this.statsState.serverStats?.totalConnections || 0}`,
        actions: [
          {
            name: 'View Details',
            iconName: 'fa:magnifyingGlass',
            action: async () => {
            },
          },
        ],
      },
      {
        id: 'requests',
        title: 'Requests/sec',
        value: reqPerSec,
        type: 'trend',
        icon: 'lucide:ChartLine',
        color: '#3b82f6',
        trendData: trendData,
        description: `Total: ${this.formatNumber(this.networkState.requestsTotal || 0)} requests`,
      },
      {
        id: 'throughputIn',
        title: 'Throughput In',
        value: this.formatBitsPerSecond(throughput.in),
        unit: '',
        type: 'number',
        icon: 'lucide:Download',
        color: '#22c55e',
        description: `Total: ${this.formatBytes(this.networkState.totalBytes?.in || 0)}`,
      },
      {
        id: 'throughputOut',
        title: 'Throughput Out',
        value: this.formatBitsPerSecond(throughput.out),
        unit: '',
        type: 'number',
        icon: 'lucide:Upload',
        color: '#8b5cf6',
        description: `Total: ${this.formatBytes(this.networkState.totalBytes?.out || 0)}`,
      },
    ];

    return html`
      <dees-statsgrid 
        .tiles=${tiles}
        .minTileWidth=${200}
        .gridActions=${[
          {
            name: 'Export Data',
            iconName: 'lucide:FileOutput',
            action: async () => {
              console.log('Export feature coming soon');
            },
          },
        ]}
      ></dees-statsgrid>
    `;
  }

  
  private renderTopIPs(): TemplateResult {
    if (this.networkState.topIPs.length === 0) {
      return html``;
    }

    // Build per-IP bandwidth lookup
    const bandwidthByIP = new Map<string, { in: number; out: number }>();
    if (this.networkState.throughputByIP) {
      for (const entry of this.networkState.throughputByIP) {
        bandwidthByIP.set(entry.ip, { in: entry.in, out: entry.out });
      }
    }

    // Calculate total connections across all top IPs
    const totalConnections = this.networkState.topIPs.reduce((sum, ipData) => sum + ipData.count, 0);

    return html`
      <dees-table
        .data=${this.networkState.topIPs}
        .displayFunction=${(ipData: { ip: string; count: number }) => {
          const bw = bandwidthByIP.get(ipData.ip);
          return {
            'IP Address': ipData.ip,
            'Connections': ipData.count,
            'Bandwidth In': bw ? this.formatBitsPerSecond(bw.in) : '0 bit/s',
            'Bandwidth Out': bw ? this.formatBitsPerSecond(bw.out) : '0 bit/s',
            'Share': totalConnections > 0 ? ((ipData.count / totalConnections) * 100).toFixed(1) + '%' : '0%',
          };
        }}
        heading1="Top Connected IPs"
        heading2="IPs with most active connections and bandwidth"
        .pagination=${false}
        dataName="ip"
      ></dees-table>
    `;
  }

  private renderBackendProtocols(): TemplateResult {
    const backends = this.networkState.backends;
    if (!backends || backends.length === 0) {
      return html``;
    }

    return html`
      <dees-table
        .data=${backends}
        .displayFunction=${(item: interfaces.data.IBackendInfo) => {
          const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors;
          const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, '');

          return {
            'Backend': item.backend,
            'Domain': item.domain || '-',
            'Protocol': html`
              <span class="protocolBadge ${protocolClass}">${item.protocol.toUpperCase()}</span>
              ${item.h2Suppressed ? html`<span class="suppressionBadge" title="H2 suppressed: ${item.h2ConsecutiveFailures ?? 0} failures, cooldown ${item.h2CooldownRemainingSecs ?? 0}s">H2 suppressed</span>` : ''}
              ${item.h3Suppressed ? html`<span class="suppressionBadge" title="H3 suppressed: ${item.h3ConsecutiveFailures ?? 0} failures, cooldown ${item.h3CooldownRemainingSecs ?? 0}s">H3 suppressed</span>` : ''}
            `,
            'Active': item.activeConnections,
            'Total': this.formatNumber(item.totalConnections),
            'Avg Connect': item.avgConnectTimeMs > 0 ? `${item.avgConnectTimeMs.toFixed(1)}ms` : '-',
            'Pool Hit Rate': item.poolHitRate > 0 ? `${(item.poolHitRate * 100).toFixed(1)}%` : '-',
            'Errors': totalErrors > 0
              ? html`<span class="statusBadge error">${totalErrors}</span>`
              : html`<span class="statusBadge success">0</span>`,
            'Cache Age': item.cacheAgeSecs != null ? `${Math.round(item.cacheAgeSecs)}s` : '-',
          };
        }}
        .dataActions=${[
          {
            name: 'View Details',
            iconName: 'lucide:info',
            type: ['inRow', 'doubleClick', 'contextmenu'] as any,
            actionFunc: async (actionData: any) => {
              await this.showBackendDetails(actionData.item);
            }
          }
        ]}
        heading1="Backend Protocols"
        heading2="Auto-detected backend protocols and connection pool health"
        searchable
        .pagination=${false}
        dataName="backend"
      ></dees-table>
    `;
  }

  private async showBackendDetails(backend: interfaces.data.IBackendInfo) {
    const { DeesModal } = await import('@design.estate/dees-catalog');

    await DeesModal.createAndShow({
      heading: `Backend: ${backend.backend}`,
      content: html`
        <div style="padding: 20px;">
          <dees-dataview-codebox
            .heading=${'Backend Details'}
            progLang="json"
            .codeToDisplay=${JSON.stringify({
              backend: backend.backend,
              domain: backend.domain,
              protocol: backend.protocol,
              activeConnections: backend.activeConnections,
              totalConnections: backend.totalConnections,
              avgConnectTimeMs: backend.avgConnectTimeMs,
              poolHitRate: backend.poolHitRate,
              errors: {
                connect: backend.connectErrors,
                handshake: backend.handshakeErrors,
                request: backend.requestErrors,
                h2Failures: backend.h2Failures,
              },
              suppression: {
                h2Suppressed: backend.h2Suppressed,
                h3Suppressed: backend.h3Suppressed,
                h2CooldownRemainingSecs: backend.h2CooldownRemainingSecs,
                h3CooldownRemainingSecs: backend.h3CooldownRemainingSecs,
                h2ConsecutiveFailures: backend.h2ConsecutiveFailures,
                h3ConsecutiveFailures: backend.h3ConsecutiveFailures,
              },
              h3Port: backend.h3Port,
              cacheAgeSecs: backend.cacheAgeSecs,
            }, null, 2)}
          ></dees-dataview-codebox>
        </div>
      `,
      menuOptions: [
        {
          name: 'Copy Backend Key',
          iconName: 'lucide:Copy',
          action: async () => {
            await navigator.clipboard.writeText(backend.backend);
          }
        }
      ]
    });
  }

  private async updateNetworkData() {
    // Track requests/sec history for the trend sparkline (moved out of render)
    const reqPerSec = this.networkState.requestsPerSecond || 0;
    this.requestsPerSecHistory.push(reqPerSec);
    if (this.requestsPerSecHistory.length > 20) {
      this.requestsPerSecHistory.shift();
    }

    // Only update if connections changed significantly
    const newConnectionCount = this.networkState.connections.length;
    const oldConnectionCount = this.networkRequests.length;
    
    // Check if we need to update the network requests array
    const shouldUpdate = newConnectionCount !== oldConnectionCount || 
                        newConnectionCount === 0 ||
                        (newConnectionCount > 0 && this.networkRequests.length === 0);
    
    if (shouldUpdate) {
      // Convert connection data to network requests format
      if (newConnectionCount > 0) {
        this.networkRequests = this.networkState.connections.map((conn, index) => ({
          id: conn.id,
          timestamp: conn.startTime,
          method: 'GET', // Default method for proxy connections
          url: '/',
          hostname: conn.remoteAddress,
          port: conn.protocol === 'https' ? 443 : 80,
          protocol: conn.protocol === 'https' || conn.protocol === 'http' ? conn.protocol : 'tcp',
          statusCode: conn.state === 'connected' ? 200 : undefined,
          duration: Date.now() - conn.startTime,
          bytesIn: conn.bytesReceived,
          bytesOut: conn.bytesSent,
          remoteIp: conn.remoteAddress,
          route: 'proxy',
        }));
      } else {
        this.networkRequests = [];
      }
    }
    
    // Load server-side throughput history into chart (once)
    if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {
      this.loadThroughputHistory();
    }
  }
  
  private startTrafficUpdateTimer() {
    this.stopTrafficUpdateTimer(); // Clear any existing timer
    this.trafficUpdateTimer = setInterval(() => {
      this.addTrafficDataPoint();
    }, OpsViewNetwork.UPDATE_INTERVAL_MS);
  }
  
  private addTrafficDataPoint() {
    const now = Date.now();
    
    // Throttle chart updates to avoid excessive re-renders
    if (now - this.lastChartUpdate < this.chartUpdateThreshold) {
      return;
    }
    
    const throughput = this.calculateThroughput();
    
    // Convert to Mbps (bytes * 8 / 1,000,000)
    const throughputInMbps = (throughput.in * 8) / 1000000;
    const throughputOutMbps = (throughput.out * 8) / 1000000;
    
    // Add new data points
    const timestamp = new Date(now).toISOString();
    
    const newDataPointIn = {
      x: timestamp,
      y: Math.round(throughputInMbps * 10) / 10
    };
    
    const newDataPointOut = {
      x: timestamp,
      y: Math.round(throughputOutMbps * 10) / 10
    };
    
    // In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
    if (this.trafficDataIn.length >= OpsViewNetwork.MAX_DATA_POINTS) {
      this.trafficDataIn.shift();
      this.trafficDataOut.shift();
    }
    this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
    this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
    
    this.lastChartUpdate = now;
  }
  
  private stopTrafficUpdateTimer() {
    if (this.trafficUpdateTimer) {
      clearInterval(this.trafficUpdateTimer);
      this.trafficUpdateTimer = null;
    }
  }
}