// Copyright 2019 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'dart:collection';

import '../../../visitor/interface/modifiable_css.dart';
import '../node.dart';

/// A modifiable version of [CssNode].
///
/// Almost all CSS nodes are the modifiable classes under the covers. However,
/// modification should only be done within the evaluation step, so the
/// unmodifiable types are used elsewhere to enforce that constraint.
abstract base class ModifiableCssNode extends CssNode {
  ModifiableCssParentNode? get parent => _parent;
  ModifiableCssParentNode? _parent;

  /// The index of `this` in `parent.children`.
  ///
  /// This makes [remove] more efficient.
  int? _indexInParent;

  var isGroupEnd = false;

  /// Whether this node has a visible sibling after it.
  bool get hasFollowingSibling =>
      _parent?.children
          .skip(_indexInParent! + 1)
          .any((sibling) => !sibling.isInvisible) ??
      false;

  T accept<T>(ModifiableCssVisitor<T> visitor);

  /// Removes `this` from [parent]'s child list.
  ///
  /// Throws a [StateError] if [parent] is `null`.
  void remove() {
    var parent = _parent;
    if (parent == null) {
      throw StateError("Can't remove a node without a parent.");
    }

    parent._children.removeAt(_indexInParent!);
    for (var child in parent._children.skip(_indexInParent!)) {
      child._indexInParent = child._indexInParent! - 1;
    }
    _parent = null;
  }
}

/// A modifiable version of [CssParentNode] for use in the evaluation step.
abstract base class ModifiableCssParentNode extends ModifiableCssNode
    implements CssParentNode {
  final List<ModifiableCssNode> children;
  final List<ModifiableCssNode> _children;
  bool get isChildless => false;

  ModifiableCssParentNode() : this._([]);

  /// A dummy constructor so that [_children] can be passed to the constructor
  /// for [this.children].
  ModifiableCssParentNode._(List<ModifiableCssNode> children)
      : _children = children,
        children = UnmodifiableListView(children);

  /// Returns whether `this` is equal to [other], ignoring their child nodes.
  bool equalsIgnoringChildren(ModifiableCssNode other);

  /// Returns a copy of `this` with an empty [children] list.
  ///
  /// This is *not* a deep copy. If other parts of this node are modifiable,
  /// they are shared between the new and old nodes.
  ModifiableCssParentNode copyWithoutChildren();

  /// Adds [child] as a child of this statement.
  void addChild(ModifiableCssNode child) {
    child._parent = this;
    child._indexInParent = _children.length;
    _children.add(child);
  }

  /// Destructively removes all elements from [children].
  void clearChildren() {
    for (var child in _children) {
      child._parent = null;
      child._indexInParent = null;
    }
    _children.clear();
  }
}
