import 'dart:convert';
import 'dart:io';
import '../interfaces/i_window_manager.dart';

/// Sway window manager implementation
class SwayManager implements IWindowManager {
  @override
  String get managerType => 'Sway';

  @override
  Future<Map<String, dynamic>?> getCurrentWindow() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_tree']);
      if (result.exitCode != 0) {
        return null;
      }

      final treeData = jsonDecode(result.stdout) as Map<String, dynamic>;
      final focusedNode = _findFocusedNode(treeData);
      
      if (focusedNode == null) {
        return null;
      }

      return {
        'application': _extractAppId(focusedNode),
        'title': _extractWindowTitle(focusedNode),
        'pid': focusedNode['pid'] as int? ?? -1,
        'workspace': _extractWorkspaceNumber(focusedNode),
        'id': focusedNode['id'] as int? ?? -1,
        'floating': _isFloating(focusedNode),
        'fullscreen_mode': focusedNode['fullscreen_mode'] as int? ?? 0,
        'rect': focusedNode['rect'] != null 
            ? Map<String, dynamic>.from(focusedNode['rect'] as Map)
            : null,
        'window_rect': focusedNode['window_rect'] != null
            ? Map<String, dynamic>.from(focusedNode['window_rect'] as Map) 
            : null,
        'type': focusedNode['type'] as String? ?? 'unknown',
      };
    } catch (e) {
      print('Error getting current window from Sway: $e');
      return null;
    }
  }

  @override
  Future<String> getActiveApplication() async {
    final window = await getCurrentWindow();
    return window?['application'] as String? ?? 'unknown';
  }

  @override
  Future<String> getWindowTitle() async {
    final window = await getCurrentWindow();
    return window?['title'] as String? ?? '';
  }

  @override
  Future<Map<String, dynamic>?> getCurrentWorkspace() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_workspaces']);
      if (result.exitCode != 0) {
        return null;
      }

      final workspacesData = jsonDecode(result.stdout) as List<dynamic>;
      
      // Find the focused workspace
      for (final workspace in workspacesData) {
        if (workspace is Map<String, dynamic> && workspace['focused'] == true) {
          return {
            'id': workspace['num'] as int? ?? -1,
            'name': workspace['name'] as String? ?? '',
            'output': workspace['output'] as String? ?? '',
            'focused': workspace['focused'] as bool? ?? false,
            'urgent': workspace['urgent'] as bool? ?? false,
            'visible': workspace['visible'] as bool? ?? false,
            'rect': workspace['rect'] != null
                ? Map<String, dynamic>.from(workspace['rect'] as Map)
                : null,
          };
        }
      }
      
      return null;
    } catch (e) {
      print('Error getting current workspace from Sway: $e');
      return null;
    }
  }

  @override
  Future<bool> isAvailable() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_version']);
      return result.exitCode == 0;
    } catch (e) {
      return false;
    }
  }

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

  /// 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) {
    // 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;
    }

    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;
      }
    }

    return '';
  }

  /// Extract workspace number from a Sway node by traversing up the tree
  int _extractWorkspaceNumber(Map<String, dynamic> node) {
    // If this node has a workspace number, return it
    final num = node['num'] as int?;
    if (num != null && num > 0) {
      return num;
    }

    // This is a simplified approach - in practice, you might need to 
    // maintain the parent relationship while traversing the tree
    return -1;
  }

  /// Check if a node represents a floating window
  bool _isFloating(Map<String, dynamic> node) {
    final type = node['type'] as String?;
    return type == 'floating_con';
  }

  /// Get all workspaces from Sway
  Future<List<Map<String, dynamic>>> getAllWorkspaces() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_workspaces']);
      if (result.exitCode != 0) {
        return [];
      }

      final workspacesData = jsonDecode(result.stdout) as List<dynamic>;
      return workspacesData.cast<Map<String, dynamic>>();
    } catch (e) {
      print('Error getting all workspaces from Sway: $e');
      return [];
    }
  }

  /// Get outputs (monitors) from Sway
  Future<List<Map<String, dynamic>>> getOutputs() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_outputs']);
      if (result.exitCode != 0) {
        return [];
      }

      final outputsData = jsonDecode(result.stdout) as List<dynamic>;
      return outputsData.cast<Map<String, dynamic>>();
    } catch (e) {
      print('Error getting outputs from Sway: $e');
      return [];
    }
  }

  /// Get the full window tree from Sway
  Future<Map<String, dynamic>?> getWindowTree() async {
    try {
      final result = await Process.run('swaymsg', ['-t', 'get_tree']);
      if (result.exitCode != 0) {
        return null;
      }

      return jsonDecode(result.stdout) as Map<String, dynamic>;
    } catch (e) {
      print('Error getting window tree from Sway: $e');
      return null;
    }
  }

  /// Execute a Sway command
  Future<bool> executeCommand(String command) async {
    try {
      final result = await Process.run('swaymsg', [command]);
      return result.exitCode == 0;
    } catch (e) {
      print('Error executing Sway command: $e');
      return false;
    }
  }

  @override
  Future<Map<String, int>?> getCursorPosition() async {
    try {
      // Sway doesn't have a direct cursor position query command
      // As a fallback, we'll return the center of the focused window
      final window = await getCurrentWindow();
      if (window == null) {
        return null;
      }

      final rect = window['rect'] as Map<String, dynamic>?;
      if (rect == null) {
        return null;
      }

      final x = (rect['x'] as int? ?? 0) + ((rect['width'] as int? ?? 0) ~/ 2);
      final y = (rect['y'] as int? ?? 0) + ((rect['height'] as int? ?? 0) ~/ 2);

      return {'x': x, 'y': y};
    } catch (e) {
      print('Error getting cursor position from Sway: $e');
      return null;
    }
  }
}