// Copyright (c) 2023, 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:dap/dap.dart';
import 'package:vm_service/vm_service.dart';

/// A wrapper around variables for use in `variablesRequest` that can hold
/// additional data, such as a formatting information supplied in an evaluation
/// request.
class VariableData {
  final Object data;
  final VariableFormat? format;

  VariableData(this.data, this.format);
}

/// Data used to lazily evaluate a getter in a Variables request.
class VariableGetter {
  final Instance instance;
  final String getterName;
  final String? parentEvaluateName;
  final bool allowCallingToString;

  VariableGetter({
    required this.instance,
    required this.getterName,
    required this.parentEvaluateName,
    required this.allowCallingToString,
  });
}

/// A wrapper around variables for use in `variablesRequest` that can hold
/// additional data, such as a formatting information supplied in an evaluation
/// request.
class FrameScopeData {
  final Frame frame;
  final FrameScopeDataKind kind;

  FrameScopeData(this.frame, this.kind);
}

enum FrameScopeDataKind {
  locals,
  globals,
}

/// A wrapper around a variable for use in `variablesRequest` that holds
/// an instance sent for inspection.
class InspectData {
  final InstanceRef? instance;

  InspectData(this.instance);
}

/// A wrapper around an Instance ID that will result in a variable with a single
/// field `value` that can be used by DAP-over-DDS clients wanting to use
/// variables requests for variable display.
class WrappedInstanceVariable {
  final String instanceId;

  WrappedInstanceVariable(this.instanceId);
}

/// Formatting preferences for a variable.
class VariableFormat {
  /// Whether to supress quotes around [String]s.
  final bool noQuotes;

  /// Whether to render integers as hex.
  final bool hex;

  /// Whether to force rendering integers as decimal (base 10).
  final bool decimal;

  const VariableFormat({
    this.noQuotes = false,
    this.hex = false,
    this.decimal = false,
  });

  factory VariableFormat.from(
    VariableFormat base, {
    bool? noQuotes,
    bool? hex,
    bool? decimal,
  }) {
    return VariableFormat(
      noQuotes: noQuotes ?? base.noQuotes,
      hex: hex ?? base.hex,
      decimal: decimal ?? base.decimal,
    );
  }

  const VariableFormat.noQuotes() : this(noQuotes: true);
  const VariableFormat.hex() : this(hex: true);

  String formatInt(int? value) {
    return value != null && hex
        ? '0x${value.toRadixString(16)}'
        : value.toString();
  }

  String formatString(String value) {
    return noQuotes ? value : '"$value"';
  }

  String format(Object? value) {
    return switch (value) {
      int() => formatInt(value),
      String() => formatString(value),
      _ => value.toString(),
    };
  }

  /// Converts a DAP ValueFormat into our own formatting class used by our
  /// debug adapters.
  ///
  /// Returns `null` if the desired format does not require special handling
  /// (or is something we don't support).
  static VariableFormat? fromDapValueFormat(ValueFormat? format) {
    return (format?.hex ?? false) ? VariableFormat.hex() : null;
  }
}

/// An evaluation expression and optional formatting preferences that were
/// supplied as a trailing format specifier in expression evaluations.
class EvaluationExpression {
  final String expression;
  final VariableFormat? format;

  EvaluationExpression._(this.expression, {this.format});

  /// A regular expression that extracts format specifiers from an evaluation
  /// expression.
  static final _expressionWithFormatSpecifierRegex =
      RegExp(r'(.*?)(?:,([\w,]+))$');

  /// Parse an expression that may end with a format specifier to dictate the
  /// format a value should be presented in.
  factory EvaluationExpression.parse(String expression) {
    final match = _expressionWithFormatSpecifierRegex.firstMatch(expression);
    expression = match?.group(1) ?? expression;
    final formatSpecifiers = match?.group(2)?.split(',').toSet() ?? const {};
    final format = formatSpecifiers.isEmpty
        ? null
        : VariableFormat(
            noQuotes: formatSpecifiers.contains('nq'),
            hex: formatSpecifiers.contains('h'),
            decimal: formatSpecifiers.contains('d'),
          );

    return EvaluationExpression._(expression, format: format);
  }
}
