// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:yaml/yaml.dart';

/// An object used to locate the [YamlNode] associated with a source range.
/// More specifically, it will return the deepest [YamlNode] which completely
/// encompasses the specified range.
class YamlNodeLocator {
  /// The inclusive start offset of the range used to identify the node.
  final int _startOffset;

  /// The inclusive end offset of the range used to identify the node.
  final int _endOffset;

  /// Initialize a newly created locator to locate the deepest [YamlNode] for
  /// which `node.offset <= [start]` and `[end] < node.end`.
  ///
  /// If the [end] offset is not provided, then it is considered the same as the
  /// [start] offset.
  new({required int start, int? end})
    : _startOffset = start,
      _endOffset = end ?? start;

  /// Search within the given Yaml [node] and return the path to the most deeply
  /// nested node that includes the whole target range, or an empty list if no
  /// node was found. The path is represented by all of the elements from the
  /// starting [node] to the most deeply nested node, in reverse order.
  List<YamlNode> searchWithin(YamlNode node) {
    var path = <YamlNode>[];
    _searchWithin(path, node);
    return path;
  }

  void _searchWithin(List<YamlNode> path, YamlNode node) {
    var span = node.span;
    if (span.start.offset > _endOffset || span.end.offset < _startOffset) {
      return;
    }
    if (node is YamlList) {
      for (var element in node.nodes) {
        _searchWithin(path, element);
        if (path.isNotEmpty) {
          path.add(node);
          return;
        }
      }
    } else if (node is YamlMap) {
      var nodeMap = node.nodes;
      for (var entry in nodeMap.entries) {
        _searchWithin(path, entry.key as YamlNode);
        if (path.isNotEmpty) {
          path.add(node);
          return;
        }
        _searchWithin(path, entry.value);
        if (path.isNotEmpty) {
          path.add(node);
          return;
        }
      }
    }
    path.add(node);
  }
}
