// Copyright (c) 2020, 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:meta/meta.dart';
import 'package:yaml/yaml.dart';

/// Error thrown when a function is passed an invalid path.
@sealed
class PathError extends ArgumentError {
  /// The full path that caused the error
  final Iterable<Object?> path;

  /// The subpath that caused the error
  final Iterable<Object?> subPath;

  /// The last element of [path] that could be traversed.
  YamlNode? parent;

  PathError(this.path, this.subPath, this.parent, [String? message])
      : super.value(subPath, 'path', message);

  PathError.unexpected(this.path, String message)
      : subPath = path,
        super(message);

  @override
  String toString() {
    if (message == null) {
      var errorMessage = 'Failed to traverse to subpath $subPath!';

      if (subPath.isNotEmpty) {
        errorMessage +=
            ' Parent $parent does not contain key or index ${subPath.last}';
      }

      return 'Invalid path: $path. $errorMessage.';
    }

    return 'Invalid path: $path. $message';
  }
}

/// Exception thrown when the path contains an alias along the way.
///
/// When a path contains an aliased node, the behavior becomes less well-defined
/// because we cannot be certain if the user wishes for the change to propagate
/// throughout all the other aliased nodes, or if the user wishes for only that
/// particular node to be modified. As such, [AliasException] reflects the
/// detection that our change will impact an alias, and we do not intend on
/// supporting such changes for the foreseeable future.
@sealed
class AliasException extends FormatException {
  /// The path that caused the error
  final Iterable<Object?> path;

  /// The anchor node of the alias
  final YamlNode anchor;

  AliasException(this.path, this.anchor)
      : super('Encountered an alias node along $path! '
            'Alias nodes are nodes that refer to a previously serialized '
            'nodes, and are denoted by either the "*" or the "&" indicators in '
            'the original YAML. As the resulting behavior of mutations on '
            'these nodes is not well-defined, the operation will not be '
            'supported by this library.\n\n'
            '${anchor.span.message('The alias was first defined here.')}');
}

/// Error thrown when an assertion about the YAML fails. Extends
/// [AssertionError] to override the [toString] method for pretty printing.
class _YamlAssertionError extends AssertionError {
  _YamlAssertionError(super.message);

  @override
  String toString() {
    if (message != null) {
      return 'Assertion failed: $message';
    }
    return 'Assertion failed';
  }
}

/// Throws an [AssertionError] with the given [message], and format
/// [oldYaml] and [newYaml] for information.
Error createAssertionError(String message, String oldYaml, String newYaml) {
  return _YamlAssertionError('''
(package:yaml_edit) $message

# YAML before edit:
> ${oldYaml.replaceAll('\n', '\n> ')}

# YAML after edit:
> ${newYaml.replaceAll('\n', '\n> ')}

Please file an issue at:
https://github.com/dart-lang/yaml_edit/issues/new?labels=bug
''');
}
