// lib/reflection/introspection.ts

import { subscribe, AwarenessEvent, AwarenessEventPayload } from '@/lib/awareness';
import { SignalType, type Signal } from './types';
import { _pushLatestSignals } from './memory';
import { isReflectionEnabled, isVerbose } from './flags';

// ========================================
//   Configuration
// ========================================

let INTROSPECTION_INTERVAL = 5000; // 5 seconds (mutable for test overrides)

// ========================================
//   Module State
// ========================================

type SignalAggregator = {
  windowOpenCount: number;
  windowCloseCount: number;
  focusChangeCount: number;
};

let aggregator: SignalAggregator = createEmptyAggregator();
let intervalId: NodeJS.Timeout | null = null;
let isRunning = false;
let unsubscribeFromAwareness: (() => void) | null = null;

let onSignalComputed: ((signal: Signal) => void) | null = null;

// ========================================
//   Private Logic
// ========================================

function createEmptyAggregator(): SignalAggregator {
  return {
    windowOpenCount: 0,
    windowCloseCount: 0,
    focusChangeCount: 0,
  };
}

/**
 * Processes a raw awareness event and updates the aggregator.
 * @param event The awareness event.
 */
function processEvent(event: AwarenessEventPayload): void {
  if (!isReflectionEnabled()) return;

  switch (event.type) {
    case AwarenessEvent.WINDOW_OPENED:
      aggregator.windowOpenCount++;
      break;
    case AwarenessEvent.WINDOW_CLOSED:
      aggregator.windowCloseCount++;
      break;
    case AwarenessEvent.WINDOW_FOCUSED:
      aggregator.focusChangeCount++;
      break;
    // Other events can be processed here for other signals
  }
}

/**
 * Computes signals from the aggregated data and emits them.
 */
function computeAndEmitSignals(): void {
  const now = performance.now();
  const intervalInSeconds = INTROSPECTION_INTERVAL / 1000;

  // 1. Window Churn Signal
  const windowChurn =
    (aggregator.windowOpenCount + aggregator.windowCloseCount) / intervalInSeconds;
  const churnSignal: Signal = {
    type: 'WINDOW_CHURN',
    value: windowChurn,
    timestamp: now,
    metadata: {
      opens: aggregator.windowOpenCount,
      closes: aggregator.windowCloseCount,
      interval: INTROSPECTION_INTERVAL,
    },
  };

  // 2. Focus Stability Signal
  const focusStability = aggregator.focusChangeCount / intervalInSeconds;
  const focusSignal: Signal = {
    type: 'FOCUS_STABILITY',
    value: focusStability,
    timestamp: now,
    metadata: {
      count: aggregator.focusChangeCount,
      interval: INTROSPECTION_INTERVAL,
    },
  };

  // Notify subscribers if present
  if (onSignalComputed) {
    onSignalComputed(churnSignal);
    onSignalComputed(focusSignal);
  }

  if (isVerbose()) {
    console.log('[Reflection/Introspection] Computed Signals:', { churnSignal, focusSignal });
  }

  // push latest signals to memory buffer for tests and introspection consumers
  _pushLatestSignals([churnSignal, focusSignal]);

  // Reset aggregator for the next interval
  aggregator = createEmptyAggregator();
}

// ========================================
//   Public API
// ========================================

/**
 * Starts the introspection engine.
 * Subscribes to the awareness bus and starts the signal computation interval.
 * @param onComputed Callback function to be invoked when a signal is computed.
 */
export function startIntrospection(
  onComputed?: (signal: Signal) => void,
  intervalMs?: number
): void {
  if (isRunning || !isReflectionEnabled()) {
    return;
  }

  if (typeof intervalMs === 'number' && intervalMs > 0) {
    INTROSPECTION_INTERVAL = intervalMs;
  }

  if (isVerbose()) {
    console.log('[Reflection/Introspection] Starting engine...');
  }

  onSignalComputed = onComputed ?? null;
  unsubscribeFromAwareness = subscribe(processEvent);
  intervalId = setInterval(computeAndEmitSignals, INTROSPECTION_INTERVAL);
  isRunning = true;
}

/**
 * Stops the introspection engine.
 */
export function stopIntrospection(): void {
  if (!isRunning || !intervalId) {
    return;
  }

  if (isVerbose()) {
    console.log('[Reflection/Introspection] Stopping engine...');
  }

  clearInterval(intervalId);
  if (unsubscribeFromAwareness) {
    unsubscribeFromAwareness();
    unsubscribeFromAwareness = null;
  }

  intervalId = null;
  isRunning = false;
  onSignalComputed = null;
}

/**
 * Resets the engine's state.
 * (For testing purposes)
 */
export function _resetIntrospection(): void {
  stopIntrospection();
  aggregator = createEmptyAggregator();
}

// ------------------------------------------------------------------
// Test & compatibility exports
// ------------------------------------------------------------------

/**
 * Backwards-compatible name expected by tests.
 * startIntrospectionEngine(intervalOrOnComputed?) supports either
 * - nothing: uses internal interval and no onComputed
 * - a number: interval override
 * - a function: onComputed callback
 */
export function startIntrospectionEngine(arg?: any): void {
  // startIntrospectionEngine()
  if (arg === undefined) {
    startIntrospection();
    return;
  }

  // startIntrospectionEngine(interval: number)
  if (typeof arg === 'number') {
    startIntrospection(undefined, arg);
    return;
  }

  // startIntrospectionEngine(onComputed: function)
  if (typeof arg === 'function') {
    startIntrospection(arg as (s: Signal) => void);
    return;
  }

  // fallback
  startIntrospection();
}

export function stopIntrospectionEngine(): void {
  stopIntrospection();
}

/**
 * Manual computation helper for tests. Returns the computed signals array.
 */
export function computeSignalsNow(): Signal[] {
  const signals: Signal[] = [];
  const now = performance.now();
  const intervalInSeconds = INTROSPECTION_INTERVAL / 1000;

  const windowChurn =
    (aggregator.windowOpenCount + aggregator.windowCloseCount) / intervalInSeconds;
  signals.push({
    type: 'WINDOW_CHURN',
    value: windowChurn,
    timestamp: now,
    metadata: {
      opens: aggregator.windowOpenCount,
      closes: aggregator.windowCloseCount,
      interval: INTROSPECTION_INTERVAL,
    },
  });

  const focusStability = aggregator.focusChangeCount / intervalInSeconds;
  signals.push({
    type: 'FOCUS_STABILITY',
    value: focusStability,
    timestamp: now,
    metadata: { count: aggregator.focusChangeCount, interval: INTROSPECTION_INTERVAL },
  });

  // reset aggregator since this mimics the interval tick
  aggregator = createEmptyAggregator();
  return signals;
}

export function _resetIntrospectionForTests(): void {
  _resetIntrospection();
}
