// Copyright (c) 2014, 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 'span.dart';

/// A class for exceptions that have source span information attached.
class SourceSpanException implements Exception {
  // This is a getter so that subclasses can override it.
  /// A message describing the exception.
  String get message => _message;
  final String _message;

  // This is a getter so that subclasses can override it.
  /// The span associated with this exception.
  ///
  /// This may be `null` if the source location can't be determined.
  SourceSpan? get span => _span;
  final SourceSpan? _span;

  SourceSpanException(this._message, this._span);

  /// Returns a string representation of `this`.
  ///
  /// [color] may either be a [String], a [bool], or `null`. If it's a string,
  /// it indicates an ANSI terminal color escape that should be used to
  /// highlight the span's text. If it's `true`, it indicates that the text
  /// should be highlighted using the default color. If it's `false` or `null`,
  /// it indicates that the text shouldn't be highlighted.
  @override
  String toString({Object? color}) {
    if (span == null) return message;
    return 'Error on ${span!.message(message, color: color)}';
  }
}

/// A [SourceSpanException] that's also a [FormatException].
class SourceSpanFormatException extends SourceSpanException
    implements FormatException {
  @override
  final dynamic source;

  @override
  int? get offset => span?.start.offset;

  SourceSpanFormatException(super.message, super.span, [this.source]);
}

/// A [SourceSpanException] that also highlights some secondary spans to provide
/// the user with extra context.
///
/// Each span has a label ([primaryLabel] for the primary, and the values of the
/// [secondarySpans] map for the secondary spans) that's used to indicate to the
/// user what that particular span represents.
class MultiSourceSpanException extends SourceSpanException {
  /// A label to attach to [span] that provides additional information and helps
  /// distinguish it from [secondarySpans].
  final String primaryLabel;

  /// A map whose keys are secondary spans that should be highlighted.
  ///
  /// Each span's value is a label to attach to that span that provides
  /// additional information and helps distinguish it from [secondarySpans].
  final Map<SourceSpan, String> secondarySpans;

  MultiSourceSpanException(super.message, super.span, this.primaryLabel,
      Map<SourceSpan, String> secondarySpans)
      : secondarySpans = Map.unmodifiable(secondarySpans);

  /// Returns a string representation of `this`.
  ///
  /// [color] may either be a [String], a [bool], or `null`. If it's a string,
  /// it indicates an ANSI terminal color escape that should be used to
  /// highlight the primary span's text. If it's `true`, it indicates that the
  /// text should be highlighted using the default color. If it's `false` or
  /// `null`, it indicates that the text shouldn't be highlighted.
  ///
  /// If [color] is `true` or a string, [secondaryColor] is used to highlight
  /// [secondarySpans].
  @override
  String toString({Object? color, String? secondaryColor}) {
    if (span == null) return message;

    var useColor = false;
    String? primaryColor;
    if (color is String) {
      useColor = true;
      primaryColor = color;
    } else if (color == true) {
      useColor = true;
    }

    final formatted = span!.messageMultiple(
        message, primaryLabel, secondarySpans,
        color: useColor,
        primaryColor: primaryColor,
        secondaryColor: secondaryColor);
    return 'Error on $formatted';
  }
}

/// A [MultiSourceSpanException] that's also a [FormatException].
class MultiSourceSpanFormatException extends MultiSourceSpanException
    implements FormatException {
  @override
  final dynamic source;

  @override
  int? get offset => span?.start.offset;

  MultiSourceSpanFormatException(
      super.message, super.span, super.primaryLabel, super.secondarySpans,
      [this.source]);
}
