// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import 'package:meta/meta.dart';

import '../visitor/interface/value.dart';
import '../value.dart';
import '../utils.dart';
import '../util/map.dart';

/// A SassScript map.
///
/// {@category Value}
@sealed
class SassMap extends Value {
  // We don't use public fields because they'd be overridden by the getters of
  // the same name in the JS API.

  // TODO(nweiz): Use persistent data structures rather than copying here. We
  // need to preserve the order, which can be done by tracking an RRB vector of
  // keys along with the hash-mapped array trie representing the map.
  //
  // We may also want to fall back to a plain unmodifiable Map for small maps
  // (<32 items?).
  /// The contents of the map.
  Map<Value, Value> get contents => _contents;
  final Map<Value, Value> _contents;

  ListSeparator get separator =>
      contents.isEmpty ? ListSeparator.undecided : ListSeparator.comma;

  List<Value> get asList => [
        for (var (key, value) in contents.pairs)
          SassList([key, value], ListSeparator.space),
      ];

  /// @nodoc
  @internal
  int get lengthAsList => contents.length;

  /// Returns an empty map.
  const SassMap.empty() : _contents = const {};

  SassMap(Map<Value, Value> contents) : _contents = Map.unmodifiable(contents);

  /// @nodoc
  @internal
  T accept<T>(ValueVisitor<T> visitor) => visitor.visitMap(this);

  SassMap assertMap([String? name]) => this;

  SassMap tryMap() => this;

  bool operator ==(Object other) =>
      (other is SassMap && mapEquals(other.contents, contents)) ||
      (contents.isEmpty && other is SassList && other.asList.isEmpty);

  int get hashCode =>
      contents.isEmpty ? const SassList.empty().hashCode : mapHash(contents);
}
