content: import 'dart:async';
  import 'dart:convert';
  import 'dart:io';
  
  import 'package:flutter/material.dart';
  import 'package:flutter/services.dart';
  import 'package:flutter_pty/flutter_pty.dart';
  import 'package:hooks_riverpod/hooks_riverpod.dart';
  import 'package:scroll_to_index/scroll_to_index.dart';
  import 'package:wives/providers/preferences_provider.dart';
  import 'package:wives/routes.dart';
  import 'package:wives/services/native.dart';
  import 'package:xterm/core.dart';
  import 'package:xterm/ui.dart';
  import 'package:xterm/src/ui/input_map.dart';
  
  enum CursorSelectorType {
    selectLeft,
    selectRight,
    clearLeft,
    clearRight,
  }
  
  abstract class TerminalAxis {
    static const row = Axis.vertical;
    static const column = Axis.horizontal;
  }
  
  /// Infinite Root Tree (IRT) Algorithm
  /// for holding all the terminal instance
  class TerminalNode {
    final Set<TerminalNode> _children;
  
    final Terminal terminal;
    final TerminalController controller;
    final FocusNode focusNode;
  
    /// Axis/Direction of the Node
    Axis axis;
  
    List<TerminalNode> get children => _children.toList();
  
    bool get isLeaf => _children.isEmpty;
  
    /// Indication of whether the terminal is a root in the tree
    /// It's basically a root node in a General Tree
    bool get isRoot => parent == null;
  
    /// Custom property to separate all the children that are "not in" the group of the parent
    List<TerminalNode> get obedientChildren =>
        children.where((child) => child.parentAxis == axis).toList();
  
    /// Custom property to separate all the children that are "in" the group of the parent
    List<TerminalNode> get disobedientChildren =>
        children.where((child) => child.parentAxis != axis).toList();
  
    TerminalNode? parent;
  
    /// When Terminal is split in Grouper, it's assigned to allow deciding the axis of the child
    Axis? parentAxis;
  
    final Ref ref;
    final TerminalTree tree;
  
    TerminalNode(
      this.ref, {
      required this.tree,
      required this.terminal,
      this.parent,
      this.parentAxis,
      this.axis = TerminalAxis.row,
      List<TerminalNode>? children,
      TerminalController? controller,
      FocusNode? focusNode,
    })  : controller = controller ?? TerminalController(),
          _children = children?.toSet() ?? {},
          focusNode = focusNode ?? FocusNode(),
          assert((parent == null && parentAxis == null) ||
              (parent != null && parentAxis != null));
  
    void updateTree() {
      tree.notifyListeners();
    }
  
    void addChild(TerminalNode child) {
      if (child.parent != this) {
        throw Exception("Can't add other parent's child to this node");
      }
      _children.add(child);
      updateTree();
    }
  
    void removeChild(TerminalNode child) {
      if (child.parent != this) {
        throw Exception("Can't remove other parent's child from this node");
      }
      if (!child.isLeaf) {
        for (var d2Child in child.children) {
          d2Child.parent = this;
          d2Child.parentAxis = axis;
          addChild(d2Child);
        }
      }
      child.dispose();
      _children.remove(child);
  
      updateTree();
    }
  
    void setDefaultAxis(Axis axis) {
      this.axis = axis;
      updateTree();
    }
  
    void split(Axis axis, [String? shell]) {
      if (isLeaf) {
        this.axis = axis;
      }
      final node = TerminalNode(
        ref,
        parentAxis: axis,
        tree: tree,
        terminal: tree.getTerminal(ref, shell),
        parent: this,
      );
      addChild(node);
      updateTree();
      node.focusNode.requestFocus();
    }
  
    void dispose() {
      controller.dispose();
      focusNode.dispose();
    }
  }
  
  class TerminalTree with ChangeNotifier {
    final ChangeNotifierProviderRef<Object?> ref;
  
    TerminalTree(this.ref) : _nodes = {};
  
    static final provider = ChangeNotifierProvider((ref) => TerminalTree(ref));
  
    Set<TerminalNode> _nodes;
  
    List<TerminalNode> get nodes => _nodes.toList();
  
    TerminalNode? focused;
  
    TerminalNode? active;
    int? get activeIndex => active != null ? nodes.indexOf(active!) : null;
    PreferencesProvider get preferences => ref.read(preferencesProvider);
  
    Terminal getTerminal(Ref ref, [String? shell, String? workingDirectory]) {
      shell ??= ref.read(preferencesProvider).defaultShell;
      workingDirectory ??= ref.read(preferencesProvider).defaultWorkingDirectory;
      final terminal = Terminal();
      final pty = Pty.start(
        shell ?? NativeUtils.getShells().last,
        workingDirectory: workingDirectory,
        columns: terminal.viewWidth,
        rows: terminal.viewHeight,
        environment: {
          ...Platform.environment,
          'TERM': 'xterm-256color',
        },
      );
  
      pty.output
          .cast<List<int>>()
          .transform(const Utf8Decoder())
          .listen(terminal.write);
  
      pty.exitCode.then((code) {
        final tab = nodes.firstWhere((s) => s.terminal == terminal);
        if (_nodes.length <= 1) {
          exit(0);
        } else {
          closeTerminalTab(tab);
        }
      });
  
      terminal.onOutput = (data) {
        pty.write(const Utf8Encoder().convert(data));
      };
  
      terminal.onResize = (w, h, pw, ph) {
        pty.resize(h, w);
      };
  
      return terminal;
    }
  
    void addNode(TerminalNode node) {
      if (node.isRoot) {
        _nodes.add(node);
        notifyListeners();
      }
    }
  
    void removeNode(TerminalNode node) {
      if (node == active) {
        active = null;
      }
      node.dispose();
      _nodes.remove(node);
      notifyListeners();
    }
  
    TerminalNode createNewTerminalTab([String? shell, String? workingDirectory]) {
      final node = TerminalNode(
        ref,
        tree: this,
        terminal: getTerminal(
          ref,
          shell,
          workingDirectory,
        ),
      );
      addNode(node);
      active = node;
      node.focusNode.requestFocus();
      ref.read(tabScrollControllerProvider).scrollToIndex(
            nodes.indexOf(node),
            preferPosition: AutoScrollPosition.begin,
          );
      return node;
    }
  
    Future<void> closeTerminalTab([TerminalNode? node]) async {
      if (!_nodes.contains(node ?? active)) return;
      if (_nodes.length <= 1) {
        final result = await showDialog<bool>(
          context: routerKey.currentContext!,
          builder: (context) {
            return AlertDialog(
              title: const Text("Close Terminal"),
              content: const Text("Do you want to close the terminal?"),
              actions: [
                TextButton(
                  onPressed: () {
                    Navigator.of(context).pop(false);
                  },
                  child: const Text('No'),
                ),
                TextButton(
                  onPressed: () {
                    Navigator.of(context).pop(true);
                  },
                  child: const Text('Yes'),
                ),
              ],
            );
          },
        );
        if (result == true) {
          exit(0);
        } else {
          return;
        }
      }
      // closes the Tab/Removes the tab
      removeNode(
        node ?? active!,
      );
      setActiveRoot(_nodes.last);
      active?.focusNode.requestFocus();
    }
  
    int cycleForwardTerminalTab() {
      int index = nodes.length - 1 == activeIndex ? 0 : activeIndex! + 1;
      setActiveRoot(nodes.elementAt(index));
      active?.focusNode.requestFocus();
      ref.read(tabScrollControllerProvider).scrollToIndex(
            index,
            preferPosition: AutoScrollPosition.end,
          );
      return index;
    }
  
    int cycleBackwardTerminalTab() {
      int index = activeIndex == 0 ? nodes.length - 1 : activeIndex! - 1;
      setActiveRoot(nodes.elementAt(index));
      active?.focusNode.requestFocus();
      ref.read(tabScrollControllerProvider).scrollToIndex(
            index,
            preferPosition: AutoScrollPosition.end,
          );
      return index;
    }
  
    void reorderTerminalTabs(int oldIndex, int newIndex) {
      final nodes = List<TerminalNode>.from(this.nodes);
      TerminalNode node;
      if (newIndex > nodes.length - 1) {
        newIndex = nodes.length - 1;
        node = nodes.removeAt(oldIndex);
        nodes.insert(newIndex, node);
      } else {
        node = nodes.removeAt(oldIndex);
        nodes.insert(newIndex <= oldIndex ? newIndex : newIndex - 1, node);
      }
  
      _nodes = nodes.toSet();
      active = node;
      node.focusNode.requestFocus();
      notifyListeners();
    }
  
    void setActiveRoot(TerminalNode node) {
      if (_nodes.contains(node)) {
        active = node;
        notifyListeners();
      }
    }
  
    void setFocused(TerminalNode node) {
      focused = node;
      notifyListeners();
    }
  
    void textSelection(CursorSelectorType type) {
      final terminal = focused ?? active;
      if (terminal == null) return;
      final cursorX = terminal.terminal.buffer.cursorX;
      final cursorY = terminal.terminal.buffer.cursorY;
      final selectionBegin = terminal.controller.selection?.begin;
      final selectionEnd = terminal.controller.selection?.end;
      final content = terminal.terminal.mainBuffer.currentLine.getText().trim();
      final contentCursorX = content.trim().length + 1;
  
      switch (type) {
        case CursorSelectorType.selectRight:
          {
            if (cursorX + 2 > contentCursorX) break;
            terminal.controller.setSelection(
              BufferRangeBlock(
                CellOffset(selectionBegin?.x ?? cursorX, cursorY),
                CellOffset(
                  cursorX + 1,
                  cursorY,
                ),
              ),
            );
            terminal.terminal.moveCursorX(1);
            break;
          }
        case CursorSelectorType.selectLeft:
          {
            terminal.controller.setSelection(
              BufferRangeBlock(
                CellOffset(selectionEnd?.x ?? cursorX, cursorY),
                CellOffset(
                  cursorX > 0 ? cursorX - 1 : cursorX,
                  cursorY,
                ),
              ),
            );
            terminal.terminal.moveCursorX(-1);
            break;
          }
        case CursorSelectorType.clearLeft:
          {
            terminal.controller.clearSelection();
  
            if (cursorX > contentCursorX) {
              terminal.terminal.setCursorX(contentCursorX);
            }
            terminal.terminal.keyInput(
              keyToTerminalKey(LogicalKeyboardKey.arrowLeft)!,
            );
            break;
          }
        case CursorSelectorType.clearRight:
          {
            terminal.controller.clearSelection();
            if (cursorX > contentCursorX) {
              terminal.terminal.setCursorX(contentCursorX);
            }
            terminal.terminal.keyInput(
              keyToTerminalKey(LogicalKeyboardKey.arrowRight)!,
            );
            break;
          }
      }
    }
  }
  
  final tabScrollControllerProvider = Provider((ref) => AutoScrollController());
  