import {
  DeesElement,
  css,
  cssManager,
  customElement,
  html,
  property,
  state,
  type TemplateResult,
} from '@design.estate/dees-element';

import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-chart-log.demo.js';
import { themeDefaultStyles } from '../../00theme.js';
import { DeesServiceLibLoader, type IXtermSearchAddon, CDN_BASE, CDN_VERSIONS } from '../../../services/index.js';

// Type imports (no runtime overhead)
import type { Terminal } from 'xterm';
import type { FitAddon } from 'xterm-addon-fit';

declare global {
  interface HTMLElementTagNameMap {
    'dees-chart-log': DeesChartLog;
  }
}

export interface ILogEntry {
  timestamp: string;
  level: 'debug' | 'info' | 'warn' | 'error' | 'success';
  message: string;
  source?: string;
}

export interface ILogMetrics {
  debug: number;
  info: number;
  warn: number;
  error: number;
  success: number;
  total: number;
  rate: number; // logs per second (rolling average)
}

@customElement('dees-chart-log')
export class DeesChartLog extends DeesElement {
  public static demo = demoFunc;
  public static demoGroup = 'Chart';

  @property()
  accessor label: string = 'Server Logs';

  @property({ type: String })
  accessor mode: 'structured' | 'raw' = 'structured';

  @property({ type: Array })
  accessor logEntries: ILogEntry[] = [];

  @property({ type: Boolean })
  accessor autoScroll: boolean = true;

  @property({ type: Number })
  accessor maxEntries: number = 10000;

  @property({ type: Array })
  accessor highlightKeywords: string[] = [];

  @property({ type: Boolean })
  accessor showMetrics: boolean = true;

  @state()
  accessor searchQuery: string = '';

  @state()
  accessor filterMode: boolean = false;

  @state()
  accessor metrics: ILogMetrics = { debug: 0, info: 0, warn: 0, error: 0, success: 0, total: 0, rate: 0 };

  @state()
  accessor terminalReady: boolean = false;

  // Buffer of all log entries for filter mode
  private logBuffer: ILogEntry[] = [];

  // Track trailing hidden entries count for live updates in filter mode
  private trailingHiddenCount: number = 0;

  // xterm instances
  private terminal: Terminal | null = null;
  private fitAddon: FitAddon | null = null;
  private searchAddon: IXtermSearchAddon | null = null;
  private resizeObserver: ResizeObserver | null = null;
  private terminalThemeSubscription: any = null;
  private domtoolsInstance: any = null;

  // Rate calculation
  private rateBuffer: number[] = [];
  private rateInterval: ReturnType<typeof setInterval> | null = null;

  public static styles = [
    themeDefaultStyles,
    cssManager.defaultStyles,
    css`
      :host {
        display: block;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
      }

      .mainbox {
        position: relative;
        width: 100%;
        height: 400px;
        background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
        border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
        border-radius: 8px;
        display: flex;
        flex-direction: column;
        overflow: hidden;
      }

      .header {
        background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
        padding: 8px 12px;
        border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
        display: flex;
        align-items: center;
        gap: 12px;
        flex-shrink: 0;
        flex-wrap: wrap;
      }

      .title {
        font-weight: 500;
        font-size: 14px;
        color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
        white-space: nowrap;
      }

      .search-box {
        display: flex;
        align-items: center;
        gap: 4px;
        flex: 1;
        min-width: 150px;
        max-width: 300px;
      }

      .search-box input {
        flex: 1;
        padding: 4px 8px;
        font-size: 12px;
        border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
        border-radius: 4px;
        background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
        color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
        outline: none;
      }

      .search-box input:focus {
        border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
      }

      .search-box input::placeholder {
        color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
      }

      .search-nav {
        display: flex;
        gap: 2px;
      }

      .search-nav button {
        padding: 4px 6px;
        font-size: 11px;
        background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
        border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
        border-radius: 3px;
        color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
        cursor: pointer;
        line-height: 1;
      }

      .search-nav button:hover {
        background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 20%)')};
        color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
      }

      .filter-toggle {
        padding: 4px 8px;
        font-size: 11px;
        font-weight: 500;
        background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
        border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
        border-radius: 4px;
        color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
        cursor: pointer;
        transition: all 0.15s;
        white-space: nowrap;
      }

      .filter-toggle:hover {
        background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 20%)')};
        color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
      }

      .filter-toggle.active {
        background: ${cssManager.bdTheme('hsl(45 93% 47%)', 'hsl(45 93% 47%)')};
        border-color: ${cssManager.bdTheme('hsl(45 93% 47%)', 'hsl(45 93% 47%)')};
        color: hsl(0 0% 9%);
      }

      .controls {
        display: flex;
        gap: 6px;
        margin-left: auto;
      }

      .control-button {
        background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
        border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
        border-radius: 4px;
        padding: 4px 10px;
        color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
        cursor: pointer;
        font-size: 12px;
        font-weight: 500;
        transition: all 0.15s;
      }

      .control-button:hover {
        background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 20%)')};
        border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 25%)')};
        color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
      }

      .control-button.active {
        background: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
        border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
        color: white;
      }

      .terminal-container {
        flex: 1;
        overflow: hidden;
        padding: 8px;
        background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
      }

      .terminal-container .xterm {
        height: 100%;
      }

      .loading-state {
        display: flex;
        align-items: center;
        justify-content: center;
        height: 100%;
        color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
        font-style: italic;
        font-size: 13px;
      }

      .metrics-bar {
        background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
        border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
        padding: 6px 12px;
        display: flex;
        gap: 16px;
        font-size: 11px;
        font-weight: 500;
        flex-shrink: 0;
      }

      .metric {
        display: flex;
        align-items: center;
        gap: 4px;
      }

      .metric::before {
        content: '';
        width: 8px;
        height: 8px;
        border-radius: 50%;
      }

      .metric.error::before {
        background: hsl(0 84.2% 60.2%);
      }

      .metric.warn::before {
        background: hsl(25 95% 53%);
      }

      .metric.info::before {
        background: hsl(222.2 47.4% 51.2%);
      }

      .metric.success::before {
        background: hsl(142.1 76.2% 36.3%);
      }

      .metric.debug::before {
        background: hsl(0 0% 63.9%);
      }

      .metric.rate {
        margin-left: auto;
        color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
      }

      .metric.rate::before {
        display: none;
      }
    `,
  ];

  constructor() {
    super();
    domtools.elementBasic.setup();
  }

  public render(): TemplateResult {
    return html`
      <div class="mainbox">
        <div class="header">
          <div class="title">${this.label}</div>
          <div class="search-box">
            <input
              type="text"
              placeholder="Search logs..."
              .value=${this.searchQuery}
              @input=${(e: InputEvent) => this.handleSearchInput(e)}
              @keydown=${(e: KeyboardEvent) => this.handleSearchKeydown(e)}
            />
            <div class="search-nav">
              <button @click=${() => this.searchPrevious()} title="Previous match">↑</button>
              <button @click=${() => this.searchNext()} title="Next match">↓</button>
            </div>
            <button
              class="filter-toggle ${this.filterMode ? 'active' : ''}"
              @click=${() => this.toggleFilterMode()}
              title="${this.filterMode ? 'Switch to highlight mode' : 'Switch to filter mode'}"
            >
              ${this.filterMode ? 'Filter' : 'Highlight'}
            </button>
          </div>
          <div class="controls">
            <button
              class="control-button ${this.autoScroll ? 'active' : ''}"
              @click=${() => this.toggleAutoScroll()}
            >
              Auto Scroll
            </button>
            <button class="control-button" @click=${() => this.clearLogs()}>
              Clear
            </button>
          </div>
        </div>

        <div class="terminal-container">
          ${!this.terminalReady
            ? html`<div class="loading-state">Loading terminal...</div>`
            : ''}
        </div>

        ${this.showMetrics
          ? html`
              <div class="metrics-bar">
                <span class="metric error">errors: ${this.metrics.error}</span>
                <span class="metric warn">warns: ${this.metrics.warn}</span>
                <span class="metric info">info: ${this.metrics.info}</span>
                <span class="metric success">success: ${this.metrics.success}</span>
                <span class="metric debug">debug: ${this.metrics.debug}</span>
                <span class="metric rate">${this.metrics.rate.toFixed(1)} logs/sec</span>
              </div>
            `
          : ''}
      </div>
    `;
  }

  public async firstUpdated() {
    this.domtoolsInstance = await this.domtoolsPromise;
    await this.initializeTerminal();

    // Process any initial log entries
    if (this.logEntries.length > 0) {
      for (const entry of this.logEntries) {
        this.writeLogEntry(entry);
      }
    }
  }

  private async initializeTerminal() {
    const libLoader = DeesServiceLibLoader.getInstance();

    const [xtermBundle, fitBundle, searchBundle] = await Promise.all([
      libLoader.loadXterm(),
      libLoader.loadXtermFitAddon(),
      libLoader.loadXtermSearchAddon(),
    ]);

    // Inject xterm CSS into shadow root (needed because shadow DOM doesn't inherit from document.head)
    await this.injectXtermStylesIntoShadow();

    this.terminal = new xtermBundle.Terminal({
      cursorBlink: false,
      disableStdin: true,
      fontSize: 12,
      fontFamily: "'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace",
      theme: this.getTerminalTheme(),
      scrollback: this.maxEntries,
      convertEol: true,
    });

    this.fitAddon = new fitBundle.FitAddon();
    this.searchAddon = new searchBundle.SearchAddon();

    this.terminal.loadAddon(this.fitAddon);
    this.terminal.loadAddon(this.searchAddon);

    const container = this.shadowRoot!.querySelector('.terminal-container') as HTMLElement;
    this.terminal.open(container);

    // Fit after a small delay to ensure proper sizing
    await new Promise((resolve) => requestAnimationFrame(resolve));
    this.fitAddon.fit();

    // Set up resize observer
    this.resizeObserver = new ResizeObserver(() => {
      this.fitAddon?.fit();
    });
    this.resizeObserver.observe(container);

    // Subscribe to theme changes
    this.terminalThemeSubscription = this.domtoolsInstance.themeManager.themeObservable.subscribe(() => {
      if (this.terminal) {
        this.terminal.options.theme = this.getTerminalTheme();
      }
    });

    // Start rate calculation interval
    this.rateInterval = setInterval(() => this.calculateRate(), 1000);

    this.terminalReady = true;
  }

  private getTerminalTheme() {
    const isDark = this.domtoolsInstance?.themeManager?.isDarkMode ?? true;
    return isDark
      ? {
          background: '#0a0a0a',
          foreground: '#e0e0e0',
          cursor: '#e0e0e0',
          selectionBackground: '#404040',
          black: '#000000',
          red: '#ff5555',
          green: '#50fa7b',
          yellow: '#f1fa8c',
          blue: '#6272a4',
          magenta: '#ff79c6',
          cyan: '#8be9fd',
          white: '#f8f8f2',
          brightBlack: '#6272a4',
          brightRed: '#ff6e6e',
          brightGreen: '#69ff94',
          brightYellow: '#ffffa5',
          brightBlue: '#d6acff',
          brightMagenta: '#ff92df',
          brightCyan: '#a4ffff',
          brightWhite: '#ffffff',
        }
      : {
          background: '#ffffff',
          foreground: '#333333',
          cursor: '#333333',
          selectionBackground: '#add6ff',
          black: '#000000',
          red: '#cd3131',
          green: '#00bc00',
          yellow: '#949800',
          blue: '#0451a5',
          magenta: '#bc05bc',
          cyan: '#0598bc',
          white: '#555555',
          brightBlack: '#666666',
          brightRed: '#cd3131',
          brightGreen: '#14ce14',
          brightYellow: '#b5ba00',
          brightBlue: '#0451a5',
          brightMagenta: '#bc05bc',
          brightCyan: '#0598bc',
          brightWhite: '#a5a5a5',
        };
  }

  /**
   * Inject xterm CSS styles into shadow root
   * This is needed because shadow DOM doesn't inherit styles from document.head
   */
  private async injectXtermStylesIntoShadow(): Promise<void> {
    const styleId = 'xterm-shadow-styles';
    if (this.shadowRoot!.getElementById(styleId)) {
      return; // Already injected
    }

    const cssUrl = `${CDN_BASE}/xterm@${CDN_VERSIONS.xterm}/css/xterm.css`;
    const response = await fetch(cssUrl);
    const cssText = await response.text();

    const style = document.createElement('style');
    style.id = styleId;
    style.textContent = cssText;
    this.shadowRoot!.appendChild(style);
  }

  // =====================
  // Structured Log Methods
  // =====================

  /**
   * Add a single structured log entry
   */
  public addLog(level: ILogEntry['level'], message: string, source?: string) {
    const entry: ILogEntry = {
      timestamp: new Date().toISOString(),
      level,
      message,
      source,
    };

    // Add to buffer
    this.logBuffer.push(entry);
    if (this.logBuffer.length > this.maxEntries) {
      this.logBuffer.shift();
    }

    // Handle display based on filter mode
    if (!this.filterMode || !this.searchQuery) {
      // No filtering - show all entries
      this.writeLogEntry(entry);
    } else if (this.entryMatchesFilter(entry)) {
      // Entry matches filter - reset trailing count and write entry
      this.trailingHiddenCount = 0;
      this.writeLogEntry(entry);
    } else {
      // Entry doesn't match - update trailing placeholder
      this.updateTrailingPlaceholder();
    }

    this.updateMetrics(entry.level);
  }

  /**
   * Add multiple structured log entries
   */
  public updateLog(entries?: ILogEntry[]) {
    if (!entries) return;
    for (const entry of entries) {
      // Add to buffer
      this.logBuffer.push(entry);
      if (this.logBuffer.length > this.maxEntries) {
        this.logBuffer.shift();
      }

      // Handle display based on filter mode
      if (!this.filterMode || !this.searchQuery) {
        // No filtering - show all entries
        this.writeLogEntry(entry);
      } else if (this.entryMatchesFilter(entry)) {
        // Entry matches filter - reset trailing count and write entry
        this.trailingHiddenCount = 0;
        this.writeLogEntry(entry);
      } else {
        // Entry doesn't match - update trailing placeholder
        this.updateTrailingPlaceholder();
      }

      this.updateMetrics(entry.level);
    }
  }

  /**
   * Update the trailing hidden placeholder in real-time
   * Clears the last line if a placeholder already exists, then writes updated count
   */
  private updateTrailingPlaceholder() {
    if (!this.terminal) return;

    if (this.trailingHiddenCount > 0) {
      // Clear the previous placeholder line (move up, clear line, move to start)
      this.terminal.write('\x1b[1A\x1b[2K\r');
    }

    this.trailingHiddenCount++;
    this.writeHiddenPlaceholder(this.trailingHiddenCount);

    if (this.autoScroll) {
      this.terminal.scrollToBottom();
    }
  }

  /**
   * Check if a log entry matches the current filter
   */
  private entryMatchesFilter(entry: ILogEntry): boolean {
    if (!this.searchQuery) return true;
    const query = this.searchQuery.toLowerCase();
    return (
      entry.message.toLowerCase().includes(query) ||
      entry.level.toLowerCase().includes(query) ||
      (entry.source?.toLowerCase().includes(query) ?? false)
    );
  }

  private writeLogEntry(entry: ILogEntry) {
    if (!this.terminal) return;

    const formatted = this.formatLogEntry(entry);
    this.terminal.writeln(formatted);

    if (this.autoScroll) {
      this.terminal.scrollToBottom();
    }
  }

  private formatLogEntry(entry: ILogEntry): string {
    const timestamp = this.formatTimestamp(entry.timestamp);
    const levelColors: Record<ILogEntry['level'], string> = {
      debug: '\x1b[90m', // Gray
      info: '\x1b[36m', // Cyan
      warn: '\x1b[33m', // Yellow
      error: '\x1b[31m', // Red
      success: '\x1b[32m', // Green
    };
    const reset = '\x1b[0m';
    const dim = '\x1b[2m';

    const levelStr = `${levelColors[entry.level]}[${entry.level.toUpperCase().padEnd(7)}]${reset}`;
    const sourceStr = entry.source ? `${dim}[${entry.source}]${reset} ` : '';
    const messageStr = this.applyHighlights(entry.message);

    return `${dim}${timestamp}${reset} ${levelStr} ${sourceStr}${messageStr}`;
  }

  private formatTimestamp(isoString: string): string {
    const date = new Date(isoString);
    return date.toLocaleTimeString('en-US', {
      hour12: false,
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      fractionalSecondDigits: 3,
    } as Intl.DateTimeFormatOptions);
  }

  private applyHighlights(text: string): string {
    // Collect all keywords to highlight
    const keywords = [...this.highlightKeywords];

    // In filter mode, also highlight the search query
    if (this.filterMode && this.searchQuery) {
      keywords.push(this.searchQuery);
    }

    if (keywords.length === 0) return text;

    let result = text;
    for (const keyword of keywords) {
      // Escape regex special characters
      const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      const regex = new RegExp(`(${escaped})`, 'gi');
      // Yellow background, black text for highlights
      result = result.replace(regex, '\x1b[43m\x1b[30m$1\x1b[0m');
    }
    return result;
  }

  // =====================
  // Raw Log Methods
  // =====================

  /**
   * Write raw data to the terminal (for Docker logs, etc.)
   */
  public writeRaw(data: string) {
    if (!this.terminal) return;
    this.terminal.write(data);
    this.recordLogEvent();

    if (this.autoScroll) {
      this.terminal.scrollToBottom();
    }
  }

  /**
   * Write a raw line to the terminal
   */
  public writelnRaw(line: string) {
    if (!this.terminal) return;
    this.terminal.writeln(line);
    this.recordLogEvent();

    if (this.autoScroll) {
      this.terminal.scrollToBottom();
    }
  }

  // =====================
  // Search Methods
  // =====================

  private handleSearchInput(e: InputEvent) {
    const input = e.target as HTMLInputElement;
    const newQuery = input.value;
    const queryChanged = this.searchQuery !== newQuery;
    this.searchQuery = newQuery;

    if (this.filterMode && queryChanged) {
      // Re-render with filtered logs
      this.reRenderFilteredLogs();
    } else if (this.searchQuery) {
      // Just highlight/search in current view
      this.searchAddon?.findNext(this.searchQuery);
    }
  }

  private handleSearchKeydown(e: KeyboardEvent) {
    if (e.key === 'Enter') {
      if (e.shiftKey) {
        this.searchPrevious();
      } else {
        this.searchNext();
      }
    } else if (e.key === 'Escape') {
      this.searchQuery = '';
      (e.target as HTMLInputElement).value = '';
    }
  }

  /**
   * Search for a query in the terminal
   */
  public search(query: string): void {
    this.searchQuery = query;
    this.searchAddon?.findNext(query);
  }

  /**
   * Find next search match
   */
  public searchNext(): void {
    if (this.searchQuery) {
      this.searchAddon?.findNext(this.searchQuery);
    }
  }

  /**
   * Find previous search match
   */
  public searchPrevious(): void {
    if (this.searchQuery) {
      this.searchAddon?.findPrevious(this.searchQuery);
    }
  }

  // =====================
  // Control Methods
  // =====================

  private toggleAutoScroll() {
    this.autoScroll = !this.autoScroll;
    if (this.autoScroll && this.terminal) {
      this.terminal.scrollToBottom();
    }
  }

  /**
   * Toggle between filter mode and highlight mode
   */
  private toggleFilterMode() {
    this.filterMode = !this.filterMode;
    this.reRenderFilteredLogs();
  }

  /**
   * Re-render logs based on current filter state
   * In filter mode: show matching logs with placeholders for hidden entries
   * In highlight mode: show all logs
   */
  private reRenderFilteredLogs() {
    if (!this.terminal) return;

    // Clear terminal and re-render
    this.terminal.clear();

    // Reset trailing count for fresh render
    this.trailingHiddenCount = 0;

    if (!this.filterMode || !this.searchQuery) {
      // No filtering - show all entries
      for (const entry of this.logBuffer) {
        const formatted = this.formatLogEntry(entry);
        this.terminal.writeln(formatted);
      }
    } else {
      // Filter mode with placeholders for hidden entries
      let hiddenCount = 0;

      for (const entry of this.logBuffer) {
        if (this.entryMatchesFilter(entry)) {
          // Output placeholder for hidden entries if any
          if (hiddenCount > 0) {
            this.writeHiddenPlaceholder(hiddenCount);
            hiddenCount = 0;
          }
          // Output the matching entry
          const formatted = this.formatLogEntry(entry);
          this.terminal.writeln(formatted);
        } else {
          hiddenCount++;
        }
      }

      // Handle trailing hidden entries
      if (hiddenCount > 0) {
        this.writeHiddenPlaceholder(hiddenCount);
        // Store trailing count for live updates
        this.trailingHiddenCount = hiddenCount;
      }
    }

    if (this.autoScroll) {
      this.terminal.scrollToBottom();
    }
  }

  /**
   * Write a placeholder line showing how many log entries are hidden by filter
   */
  private writeHiddenPlaceholder(count: number) {
    const dim = '\x1b[2m';
    const reset = '\x1b[0m';
    const text = count === 1
      ? `[1 log line hidden by filter ...]`
      : `[${count} log lines hidden by filter ...]`;
    this.terminal?.writeln(`${dim}${text}${reset}`);
  }

  /**
   * Clear all logs and reset metrics
   */
  public clearLogs() {
    this.terminal?.clear();
    this.logBuffer = [];
    this.trailingHiddenCount = 0;
    this.resetMetrics();
  }

  /**
   * Scroll to the bottom of the log
   */
  public scrollToBottom() {
    this.terminal?.scrollToBottom();
  }

  // =====================
  // Metrics Methods
  // =====================

  private updateMetrics(level: ILogEntry['level']) {
    this.metrics = {
      ...this.metrics,
      [level]: this.metrics[level] + 1,
      total: this.metrics.total + 1,
    };
    this.recordLogEvent();
  }

  private recordLogEvent() {
    this.rateBuffer.push(Date.now());
  }

  private calculateRate() {
    const now = Date.now();
    // Keep only events from the last 10 seconds
    this.rateBuffer = this.rateBuffer.filter((t) => now - t < 10000);
    const rate = this.rateBuffer.length / 10;

    if (rate !== this.metrics.rate) {
      this.metrics = { ...this.metrics, rate };
    }
  }

  private resetMetrics() {
    this.metrics = { debug: 0, info: 0, warn: 0, error: 0, success: 0, total: 0, rate: 0 };
    this.rateBuffer = [];
  }

  // =====================
  // Lifecycle
  // =====================

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

    if (this.resizeObserver) {
      this.resizeObserver.disconnect();
    }

    if (this.terminalThemeSubscription) {
      this.terminalThemeSubscription.unsubscribe();
    }

    if (this.rateInterval) {
      clearInterval(this.rateInterval);
    }

    if (this.terminal) {
      this.terminal.dispose();
    }
  }
}
