// src/core/CheatDetector.ts
//
// Detects architectural violations ("cheats") at runtime.
// Enable via: CheatDetector.enable() or MATRIX_STRICT=1 env var
//
// Cheats detected:
// - P1: Accessing runtime._components directly
// - P2: Storing component references instead of mount paths
// - P3: Using __parent chain
// - P4: Accessing children map directly
// - P5: Bypassing transport with _localServices
// - P6: Casting to get internal component from shell

type CheatType = 'P1' | 'P2' | 'P3' | 'P4' | 'P5' | 'P6';

interface CheatViolation {
  type: CheatType;
  message: string;
  stack?: string;
  timestamp: number;
}

type CheatHandler = (violation: CheatViolation) => void;

class CheatDetectorImpl {
  private enabled = false;
  private violations: CheatViolation[] = [];
  private handlers: CheatHandler[] = [];
  private mode: 'warn' | 'throw' | 'collect' = 'warn';

  /**
   * Enable cheat detection.
   * @param mode - 'warn' logs to console, 'throw' throws error, 'collect' silently collects
   */
  enable(mode: 'warn' | 'throw' | 'collect' = 'warn'): void {
    this.enabled = true;
    this.mode = mode;
    // eslint-disable-next-line no-console -- developer-facing cheat detector mode announcement in kernel code
    console.log(`[CheatDetector] Enabled in '${mode}' mode`);
  }

  disable(): void {
    this.enabled = false;
  }

  isEnabled(): boolean {
    return this.enabled;
  }

  /**
   * Report a cheat violation.
   */
  report(type: CheatType, message: string): void {
    if (!this.enabled) return;

    const violation: CheatViolation = {
      type,
      message,
      stack: new Error().stack,
      timestamp: Date.now(),
    };

    this.violations.push(violation);

    // Notify handlers
    for (const handler of this.handlers) {
      try {
        handler(violation);
      } catch {}
    }

    // Handle based on mode
    switch (this.mode) {
      case 'throw':
        throw new Error(`[CHEAT DETECTED] ${type}: ${message}`);
      case 'warn':
        // eslint-disable-next-line no-console -- cheat detector violations must surface without actor context
        console.warn(`[CHEAT DETECTED] ${type}: ${message}`);
        break;
      case 'collect':
        // Silent collection
        break;
    }
  }

  /**
   * Get all collected violations.
   */
  getViolations(): CheatViolation[] {
    return [...this.violations];
  }

  /**
   * Clear collected violations.
   */
  clear(): void {
    this.violations = [];
  }

  /**
   * Add a violation handler.
   */
  onViolation(handler: CheatHandler): () => void {
    this.handlers.push(handler);
    return () => {
      const idx = this.handlers.indexOf(handler);
      if (idx >= 0) this.handlers.splice(idx, 1);
    };
  }

  /**
   * Assert no violations occurred (for tests).
   */
  assertNoViolations(): void {
    if (this.violations.length > 0) {
      const summary = this.violations
        .map(v => `  ${v.type}: ${v.message}`)
        .join('\n');
      throw new Error(`CheatDetector found ${this.violations.length} violation(s):\n${summary}`);
    }
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Specific Cheat Detectors
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * P1: Wrap runtime to detect _components access.
   */
  wrapRuntime<T extends object>(runtime: T): T {
    if (!this.enabled) return runtime;

    return new Proxy(runtime, {
      get: (target, prop, receiver) => {
        if (prop === '_components') {
          this.report('P1', 'Direct access to runtime._components');
        }
        return Reflect.get(target, prop, receiver);
      },
    });
  }

  /**
   * P2: Check if a value is a component reference (should be mount string).
   */
  checkNotComponentRef(value: unknown, context: string): void {
    if (!this.enabled) return;

    if (value && typeof value === 'object') {
      // Check for MatrixActor-like shape
      const obj = value as Record<string, unknown>;
      if (
        typeof obj.initialize === 'function' ||
        typeof obj.dispose === 'function' ||
        typeof obj.onIntrospect === 'function' ||
        obj._context !== undefined
      ) {
        this.report('P2', `Storing component reference instead of mount path: ${context}`);
      }
    }
  }

  /**
   * P5: Detect _localServices usage.
   */
  reportLocalServiceBypass(mount: string): void {
    this.report('P5', `Bypassing transport with _localServices for: ${mount}`);
  }

  /**
   * P6: Detect ghost casting.
   */
  reportGhostCasting(shellTag: string): void {
    this.report('P6', `Casting shell to get internal component: ${shellTag}`);
  }
}

// Singleton instance
export const CheatDetector = new CheatDetectorImpl();

// Auto-enable if env var set
if (typeof process !== 'undefined' && process.env?.MATRIX_STRICT === '1') {
  CheatDetector.enable('throw');
}
