// Copyright (c) 2025, 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:analysis_server/src/session_logger/entry_keys.dart' as key;
import 'package:analysis_server/src/session_logger/entry_kind.dart';
import 'package:analysis_server/src/session_logger/process_id.dart';
import 'package:language_server_protocol/protocol_special.dart' show Either2;

/// A representation of an entry in a log.
///
/// Every entry has a time and a [kind]. Other properties are dependent on the
/// [kind]. See [EntryKind] for a description of the properties associated with
/// each [kind].
extension type LogEntry(JsonMap map) {
  /// Returns the command-line arguments used to start the server.
  List<String> get argList =>
      (map[key.argList] as List<dynamic>).cast<String>();

  /// Whether this entry is a command line entry.
  bool get isCommandLine => kind == EntryKind.commandLine;

  /// Whether this entry is a message entry.
  bool get isMessage => kind == EntryKind.message;

  /// Returns the kind of this log entry.
  EntryKind get kind => EntryKind.forName(map[key.kind] as String);

  /// Returns the message for this log entry.
  Message get message => Message(map[key.message] as JsonMap);

  /// Returns the receiver of the message.
  ProcessId get receiver => ProcessId.forName(map[key.receiver] as String);

  /// Returns the sender of the message.
  ProcessId get sender => ProcessId.forName(map[key.sender] as String);
}

/// A representation of a message sent from one process to another.
extension type Message(JsonMap map) {
  /// The ID of the message. All request messages have IDs, but notifications
  /// do not.
  Either2<int, String>? get id {
    // The id in the JSON could be either an int or String (LSP is JSON-RPC 2
    // which allows either).
    return switch (map['id']) {
      int i => Either2<int, String>.t1(i),
      String s => Either2<int, String>.t2(s),
      null => null,
      _ => throw Exception(
        'Message ID was unexpected type ${map['id'].runtimeType}',
      ),
    };
  }

  /// Whether this message is a notification that a file has changed.
  bool get isDidChange => method == 'textDocument/didChange';

  /// Whether this message is a notification that a file has been closed.
  bool get isDidClose => method == 'textDocument/didClose';

  /// Whether this message is a notification that a file has been opened.
  bool get isDidOpen => method == 'textDocument/didOpen';

  /// Whether this message is a request for the server to exit.
  bool get isExit => method == 'exit';

  /// Whether this message is a notification to the server indicating that the
  /// client is initialized.
  bool get isInitialized => method == 'initialized';

  /// Whether this message is a request for the server to initialize itself.
  bool get isInitializeRequest => method == 'initialize';

  /// Whether this message is a request from the server to log a message.
  bool get isLogMessage => method == 'window/logMessage';

  /// Whether this message is a request for the server to connect with DTD.
  bool get isRequestToConnectWithDtd => method == 'dart/connectToDtd';

  /// Whether this is a response to a request.
  bool get isResponse => method == null;

  /// Whether this message is a request from the server to show a document to
  /// the user.
  bool get isShowDocument => method == 'window/showDocument';

  /// Whether this message is a request from the server to show a message to the
  /// user.
  bool get isShowMessage => method == 'window/showMessage';

  /// Whether this message is a request from the server to show a message to the
  /// user.
  bool get isShowMessageRequest => method == 'window/showMessageRequest';

  /// Whether this message is a request for the server to shut down.
  bool get isShutdown => method == 'shutdown';

  /// Whether this message is a request for the server to set the configuration
  /// of the workspace.
  bool get isWorkspaceConfiguration => method == 'workspace/configuration';

  /// The method being sent in this message, or `null` if this message isn't an
  /// LSP request
  String? get method => map['method'] as String?;

  /// Only present if [method] is non-null, and still optional in that case.
  JsonMap? get params => map['params'] as Map<String, Object?>?;

  /// If [method] is null, this should be non-null.
  ///
  /// Could be any valid type for a result, which is dependent on the [method].
  Object? get result => map['result'];

  /// The URI of the text document associated with this message.
  ///
  /// Return `null` if this message is not associated with a text document.
  String? get textDocument =>
      (params?['textDocument'] as Map<String, Object?>?)?['uri'] as String?;

  /// Whether this message is a response to the request with the [requestId].
  bool isResponseTo(Either2<int, String> requestId) =>
      isResponse && id == requestId;
}
