// 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:collection/collection.dart';

import 'config.dart';
import 'encoded_asset.dart';
import 'hooks/syntax.g.dart';

/// An asset that contains metadata.
///
/// Should only be used with [ToLinkHook] and [ToBuildHooks].
//
// Note: not exported to public API. The public API only contains a way to read
// and write metadata, it doesn't expose the underlying mechanism.
final class MetadataAsset {
  /// The key for the metadata.
  final String key;

  /// The value of the metadata.
  final Object? value;

  /// Creates a [MetadataAsset].
  MetadataAsset({required this.key, required this.value});

  /// Creates a [MetadataAsset] from an [EncodedAsset].
  factory MetadataAsset.fromEncoded(EncodedAsset asset) {
    assert(asset.type == _type);
    final syntaxNode = MetadataAssetEncodingSyntax.fromJson(
      asset.encoding,
      path: asset.encodingJsonPath ?? [],
    );
    return MetadataAsset(key: syntaxNode.key, value: syntaxNode.value);
  }

  @override
  bool operator ==(Object other) {
    if (other is! MetadataAsset) {
      return false;
    }
    return other.key == key &&
        const DeepCollectionEquality().equals(value, other.value);
  }

  @override
  int get hashCode =>
      Object.hash(key, const DeepCollectionEquality().hash(value));

  /// Encodes this [MetadataAsset] into an [EncodedAsset].
  EncodedAsset encode() => EncodedAsset(_type, {'key': key, 'value': value});

  @override
  String toString() => 'MetadataAsset(${encode().encoding})';

  static const _type = 'hooks/metadata';
}

/// Extension methods for [EncodedAsset] to handle metadata.
extension EncodedMetadataAsset on EncodedAsset {
  /// Whether this asset is a metadata asset.
  bool get isMetadataAsset => type == MetadataAsset._type;

  /// Converts this asset to a [MetadataAsset].
  ///
  /// Requires [isMetadataAsset] to be true.
  MetadataAsset get asMetadataAsset => MetadataAsset.fromEncoded(this);
}
