import 'dart:async';
import 'dart:convert';
import 'dart:io';
import '../interfaces/i_activity_detector.dart';
import '../models/activity_event.dart';

/// Sway-specific activity detector that polls the focused window
class SwayActivityDetector implements IActivityDetector {
  final StreamController<ActivityEvent> _activityController = StreamController<ActivityEvent>.broadcast();
  Timer? _pollTimer;
  String? _lastActiveWindow;
  String? _lastActiveWindowTitle;
  DateTime? _lastActivityTime;
  bool _isRunning = false;

  @override
  Stream<ActivityEvent> get activityStream => _activityController.stream;

  @override
  Future<void> start() async {
    if (_isRunning) return;

    _isRunning = true;
    _pollTimer = Timer.periodic(Duration(seconds: 5), (_) => _pollActivity());
    print('🔍 Started Sway activity detection...');
  }

  @override
  void stop() {
    _isRunning = false;
    _pollTimer?.cancel();
    _pollTimer = null;

    // Flush any remaining activity
    if (_lastActiveWindow != null && _lastActivityTime != null) {
      final duration = DateTime.now().difference(_lastActivityTime!).inSeconds;
      if (duration >= 10) {
        _emitActivityEvent(_lastActiveWindow!, _lastActiveWindowTitle ?? '', duration);
      }
    }

    _activityController.close();
    print('🛑 Stopped Sway activity detection');
  }

  Future<void> _pollActivity() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_tree']);
      if (result.exitCode != 0) {
        print('Warning: swaymsg get_tree failed with exit code ${result.exitCode}');
        return;
      }

      final treeData = jsonDecode(result.stdout);
      final focusedNode = _findFocusedNode(treeData);
      
      if (focusedNode == null) {
        // No focused window found
        return;
      }

      final currentApp = _extractAppId(focusedNode);
      final currentWindowTitle = _extractWindowTitle(focusedNode);
      final now = DateTime.now();

      // If this is a different activity from the last one, emit the previous activity
      if (_lastActiveWindow != null &&
          (_lastActiveWindow != currentApp || _lastActiveWindowTitle != currentWindowTitle)) {
        final duration = now.difference(_lastActivityTime ?? now).inSeconds;
        if (duration >= 10) {
          _emitActivityEvent(_lastActiveWindow!, _lastActiveWindowTitle ?? '', duration);
        }
      }

      // Update current activity
      _lastActiveWindow = currentApp;
      _lastActiveWindowTitle = currentWindowTitle;
      _lastActivityTime = now;
    } catch (e) {
      print('Error polling Sway activity: $e');
    }
  }

  /// Recursively find the focused node in the Sway tree
  Map<String, dynamic>? _findFocusedNode(Map<String, dynamic> node) {
    // Check if this node is focused
    if (node['focused'] == true) {
      return node;
    }

    // Check child nodes
    final nodes = node['nodes'] as List<dynamic>? ?? [];
    for (final childNode in nodes) {
      if (childNode is Map<String, dynamic>) {
        final focused = _findFocusedNode(childNode);
        if (focused != null) {
          return focused;
        }
      }
    }

    // Check floating nodes
    final floatingNodes = node['floating_nodes'] as List<dynamic>? ?? [];
    for (final floatingNode in floatingNodes) {
      if (floatingNode is Map<String, dynamic>) {
        final focused = _findFocusedNode(floatingNode);
        if (focused != null) {
          return focused;
        }
      }
    }

    return null;
  }

  /// Extract application ID from a Sway node
  String _extractAppId(Map<String, dynamic> node) {
    // In Sway, the application identifier can be in different fields depending on the application type
    
    // For Wayland applications, use app_id
    final appId = node['app_id'] as String?;
    if (appId != null && appId.isNotEmpty) {
      return appId;
    }

    // For X11 applications, use window_properties.class
    final windowProperties = node['window_properties'] as Map<String, dynamic>?;
    if (windowProperties != null) {
      final windowClass = windowProperties['class'] as String?;
      if (windowClass != null && windowClass.isNotEmpty) {
        return windowClass;
      }
    }

    // Fallback to window type or node type
    final type = node['type'] as String?;
    if (type != null && type != 'con') {
      return type;
    }

    // Last resort fallback
    return 'unknown';
  }

  /// Extract window title from a Sway node
  String _extractWindowTitle(Map<String, dynamic> node) {
    // Try the name field first
    final name = node['name'] as String?;
    if (name != null && name.isNotEmpty) {
      return name;
    }

    // For X11 applications, try window_properties.title
    final windowProperties = node['window_properties'] as Map<String, dynamic>?;
    if (windowProperties != null) {
      final title = windowProperties['title'] as String?;
      if (title != null && title.isNotEmpty) {
        return title;
      }
    }

    // Fallback to app_id if name is empty
    final appId = node['app_id'] as String?;
    if (appId != null && appId.isNotEmpty) {
      return appId;
    }

    return '';
  }

  void _emitActivityEvent(String application, String title, int durationSeconds) {
    final event = ActivityEvent(
      type: ActivityEventType.categorize(applicationId: application, applicationTitle: title),
      application: application,
      metadata: title,
      timestamp: DateTime.now(),
    );

    _activityController.add(event);
  }

  @override
  Future<ActivityEvent> getCurrentActivity() {
    return _activityController.stream.single;
  }

  /// Check if swaymsg is available on the system
  static Future<bool> isAvailable() async {
    try {
      final result = await Process.run('which', ['swaymsg']);
      return result.exitCode == 0;
    } catch (e) {
      return false;
    }
  }

  /// Get Sway version information
  static Future<String?> getVersion() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_version']);
      if (result.exitCode == 0) {
        final versionData = jsonDecode(result.stdout);
        return versionData['human_readable'] as String? ?? 'Sway (version unknown)';
      }
      return null;
    } catch (e) {
      return null;
    }
  }

  /// Test the Sway connection and get basic info
  static Future<Map<String, dynamic>?> getSwayInfo() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_version']);
      if (result.exitCode == 0) {
        return jsonDecode(result.stdout) as Map<String, dynamic>;
      }
      return null;
    } catch (e) {
      return null;
    }
  }
}