// Copyright (c) 2012, 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:source_span/source_span.dart';

import 'preprocessor_options.dart';

enum MessageLevel { info, warning, severe }

// TODO(#159): Remove the global messages, use some object that tracks
// compilation state.

/// The global [Messages] for tracking info/warnings/messages.
late Messages messages;

// Color constants used for generating messages.
const _greenColor = '\u001b[32m';
const _redColor = '\u001b[31m';
const _magentaColor = '\u001b[35m';
const _noColor = '\u001b[0m';

/// Map between error levels and their display color.
const Map<MessageLevel, String> _errorColors = {
  MessageLevel.severe: _redColor,
  MessageLevel.warning: _magentaColor,
  MessageLevel.info: _greenColor,
};

/// Map between error levels and their friendly name.
const Map<MessageLevel, String> _errorLabel = {
  MessageLevel.severe: 'error',
  MessageLevel.warning: 'warning',
  MessageLevel.info: 'info',
};

/// A single message from the compiler.
class Message {
  final MessageLevel level;
  final String message;
  final SourceSpan? span;
  final bool useColors;

  Message(this.level, this.message, {this.span, this.useColors = false});

  String get describe {
    var span = this.span;
    if (span == null) {
      return message;
    }

    var start = span.start;
    return '${start.line + 1}:${start.column + 1}:$message';
  }

  @override
  String toString() {
    var output = StringBuffer();
    var colors = useColors && _errorColors.containsKey(level);
    var levelColor = colors ? _errorColors[level] : null;
    if (colors) output.write(levelColor);
    output
      ..write(_errorLabel[level])
      ..write(' ');
    if (colors) output.write(_noColor);

    if (span == null) {
      output.write(message);
    } else {
      output.write('on ');
      output.write(span!.message(message, color: levelColor));
    }

    return output.toString();
  }
}

/// This class tracks and prints information, warnings, and errors emitted by
/// the compiler.
class Messages {
  /// Called on every error. Set to blank function to suppress printing.
  final void Function(Message obj) printHandler;

  final PreprocessorOptions options;

  final List<Message> messages = <Message>[];

  Messages({PreprocessorOptions? options, this.printHandler = print})
      : options = options ?? const PreprocessorOptions();

  /// Report a compile-time CSS error.
  void error(String message, SourceSpan? span) {
    var msg = Message(MessageLevel.severe, message,
        span: span, useColors: options.useColors);

    messages.add(msg);

    printHandler(msg);
  }

  /// Report a compile-time CSS warning.
  void warning(String message, SourceSpan? span) {
    if (options.warningsAsErrors) {
      error(message, span);
    } else {
      var msg = Message(MessageLevel.warning, message,
          span: span, useColors: options.useColors);

      messages.add(msg);
    }
  }

  /// Report and informational message about what the compiler is doing.
  void info(String message, SourceSpan span) {
    var msg = Message(MessageLevel.info, message,
        span: span, useColors: options.useColors);

    messages.add(msg);

    if (options.verbose) printHandler(msg);
  }

  /// Merge [newMessages] to this message list.
  void mergeMessages(Messages newMessages) {
    messages.addAll(newMessages.messages);
    newMessages.messages
        .where((message) =>
            message.level == MessageLevel.severe || options.verbose)
        .forEach(printHandler);
  }
}
