// 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.

// This file is generated, do not edit.
// File generated by pkgs/hooks/tool/generate_syntax.dart.
// Must be rerun when pkgs/hooks/doc/schema/ is modified.

// ignore_for_file: unused_element, public_member_api_docs

import 'dart:io';

class AssetSyntax extends JsonObjectSyntax {
  factory AssetSyntax.fromJson(
    Map<String, Object?> json, {
    List<Object> path = const [],
  }) {
    final result = AssetSyntax._fromJson(json, path: path);
    if (result.isHooksMetadataAsset) {
      return result.asHooksMetadataAsset;
    }
    return result;
  }

  AssetSyntax._fromJson(super.json, {super.path = const []}) : super.fromJson();

  AssetSyntax({
    required JsonObjectSyntax? encoding,
    required String type,
    super.path = const [],
  }) : super() {
    _encoding = encoding;
    _type = type;
    json.sortOnKey();
  }

  JsonObjectSyntax? get encoding {
    final jsonValue = _reader.optionalMap('encoding');
    if (jsonValue == null) return null;
    return JsonObjectSyntax.fromJson(jsonValue, path: [...path, 'encoding']);
  }

  set _encoding(JsonObjectSyntax? value) {
    json.setOrRemove('encoding', value?.json);
  }

  List<String> _validateEncoding() {
    final mapErrors = _reader.validate<Map<String, Object?>?>('encoding');
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    return encoding?.validate() ?? [];
  }

  String get type => _reader.get<String>('type');

  set _type(String value) {
    json.setOrRemove('type', value);
  }

  List<String> _validateType() => _reader.validate<String>('type');

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateEncoding(),
    ..._validateType(),
    ..._validateExtraRulesAsset(),
  ];

  List<String> _validateExtraRulesAsset() {
    final result = <String>[];
    if (_reader.tryTraverse(['type']) == 'hooks/metadata') {
      final objectErrors = _reader.validate<Map<String, Object?>?>('encoding');
      result.addAll(objectErrors);
      if (objectErrors.isEmpty) {
        final jsonValue = _reader.get<Map<String, Object?>?>('encoding');
        if (jsonValue != null) {
          final reader = _JsonReader(jsonValue, [...path, 'encoding']);
          result.addAll(reader.validate<Object>('key'));
        }
      }
    }
    return result;
  }

  @override
  String toString() => 'AssetSyntax($json)';
}

class BuildConfigSyntax extends ConfigSyntax {
  BuildConfigSyntax.fromJson(super.json, {super.path}) : super.fromJson();

  BuildConfigSyntax({
    required super.buildAssetTypes,
    required super.extensions,
    required bool linkingEnabled,
    super.path = const [],
  }) : super() {
    _linkingEnabled = linkingEnabled;
    json.sortOnKey();
  }

  /// Setup all fields for [BuildConfigSyntax] that are not in
  /// [ConfigSyntax].
  void setup({required bool linkingEnabled}) {
    _linkingEnabled = linkingEnabled;
    json.sortOnKey();
  }

  bool get linkingEnabled => _reader.get<bool>('linking_enabled');

  set _linkingEnabled(bool value) {
    json.setOrRemove('linking_enabled', value);
  }

  List<String> _validateLinkingEnabled() =>
      _reader.validate<bool>('linking_enabled');

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateLinkingEnabled(),
  ];

  @override
  String toString() => 'BuildConfigSyntax($json)';
}

class BuildInputSyntax extends HookInputSyntax {
  BuildInputSyntax.fromJson(super.json, {super.path}) : super.fromJson();

  BuildInputSyntax({
    required Map<String, List<AssetSyntax>>? assets,
    required BuildConfigSyntax config,
    required super.outDirShared,
    required super.outFile,
    required super.packageName,
    required super.packageRoot,
    required super.userDefines,
    super.path = const [],
  }) : super(config: config) {
    _assets = assets;
    json.sortOnKey();
  }

  /// Setup all fields for [BuildInputSyntax] that are not in
  /// [HookInputSyntax].
  void setup({required Map<String, List<AssetSyntax>>? assets}) {
    _assets = assets;
    json.sortOnKey();
  }

  Map<String, List<AssetSyntax>>? get assets {
    final jsonValue = _reader.optionalMap('assets');
    if (jsonValue == null) {
      return null;
    }
    final result = <String, List<AssetSyntax>>{};
    for (final MapEntry(:key, :value) in jsonValue.entries) {
      result[key] = [
        for (final (index, item) in (value as List<Object?>).indexed)
          AssetSyntax.fromJson(
            item as Map<String, Object?>,
            path: [...path, key, index],
          ),
      ];
    }
    return result;
  }

  set _assets(Map<String, List<AssetSyntax>>? value) {
    _checkArgumentMapKeys(value);
    if (value == null) {
      json.remove('assets');
    } else {
      json['assets'] = {
        for (final MapEntry(:key, :value) in value.entries)
          key: [for (final item in value) item.json],
      };
    }
  }

  List<String> _validateAssets() {
    final mapErrors = _reader.validateOptionalMap('assets');
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    final jsonValue = _reader.optionalMap('assets');
    if (jsonValue == null) {
      return [];
    }
    final result = <String>[];
    for (final list in assets!.values) {
      for (final element in list) {
        result.addAll(element.validate());
      }
    }
    return result;
  }

  @override
  BuildConfigSyntax get config {
    final jsonValue = _reader.map$('config');
    return BuildConfigSyntax.fromJson(jsonValue, path: [...path, 'config']);
  }

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateAssets(),
    ..._validateConfig(),
  ];

  @override
  String toString() => 'BuildInputSyntax($json)';
}

class BuildOutputSyntax extends HookOutputSyntax {
  BuildOutputSyntax.fromJson(super.json, {super.path}) : super.fromJson();

  BuildOutputSyntax({
    required super.assets,
    required List<AssetSyntax>? assetsForBuild,
    required super.assetsForLinking,
    required super.dependencies,
    required super.failureDetails,
    required super.status,
    required super.timestamp,
    super.path = const [],
  }) : super() {
    this.assetsForBuild = assetsForBuild;
    json.sortOnKey();
  }

  /// Setup all fields for [BuildOutputSyntax] that are not in
  /// [HookOutputSyntax].
  void setup({required List<AssetSyntax>? assetsForBuild}) {
    this.assetsForBuild = assetsForBuild;
    json.sortOnKey();
  }

  List<AssetSyntax>? get assetsForBuild {
    final jsonValue = _reader.optionalList('assets_for_build');
    if (jsonValue == null) return null;
    return [
      for (final (index, element) in jsonValue.indexed)
        AssetSyntax.fromJson(
          element as Map<String, Object?>,
          path: [...path, 'assets_for_build', index],
        ),
    ];
  }

  set assetsForBuild(List<AssetSyntax>? value) {
    if (value == null) {
      json.remove('assets_for_build');
    } else {
      json['assets_for_build'] = [for (final item in value) item.json];
    }
    json.sortOnKey();
  }

  List<String> _validateAssetsForBuild() {
    final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
      'assets_for_build',
    );
    if (listErrors.isNotEmpty) {
      return listErrors;
    }
    final elements = assetsForBuild;
    if (elements == null) {
      return [];
    }
    return [for (final element in elements) ...element.validate()];
  }

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateAssetsForBuild(),
    ..._validateExtraRulesBuildOutput(),
  ];

  List<String> _validateExtraRulesBuildOutput() {
    final result = <String>[];
    if (_reader.tryTraverse(['status']) == 'failure') {
      result.addAll(_reader.validate<Object>('failure_details'));
    }
    return result;
  }

  @override
  String toString() => 'BuildOutputSyntax($json)';
}

class ConfigSyntax extends JsonObjectSyntax {
  ConfigSyntax.fromJson(super.json, {super.path = const []}) : super.fromJson();

  ConfigSyntax({
    required List<String> buildAssetTypes,
    required JsonObjectSyntax? extensions,
    super.path = const [],
  }) : super() {
    this.buildAssetTypes = buildAssetTypes;
    this.extensions = extensions;
    json.sortOnKey();
  }

  List<String> get buildAssetTypes => _reader.stringList('build_asset_types');

  set buildAssetTypes(List<String> value) {
    json['build_asset_types'] = value;
    json.sortOnKey();
  }

  List<String> _validateBuildAssetTypes() =>
      _reader.validateStringList('build_asset_types');

  JsonObjectSyntax? get extensions {
    final jsonValue = _reader.optionalMap('extensions');
    if (jsonValue == null) return null;
    return JsonObjectSyntax.fromJson(jsonValue, path: [...path, 'extensions']);
  }

  set extensions(JsonObjectSyntax? value) {
    json.setOrRemove('extensions', value?.json);
    json.sortOnKey();
  }

  List<String> _validateExtensions() {
    final mapErrors = _reader.validate<Map<String, Object?>?>('extensions');
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    return extensions?.validate() ?? [];
  }

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateBuildAssetTypes(),
    ..._validateExtensions(),
  ];

  @override
  String toString() => 'ConfigSyntax($json)';
}

class FailureSyntax extends JsonObjectSyntax {
  FailureSyntax.fromJson(super.json, {super.path = const []})
    : super.fromJson();

  FailureSyntax({required FailureTypeSyntax type, super.path = const []})
    : super() {
    _type = type;
    json.sortOnKey();
  }

  FailureTypeSyntax get type {
    final jsonValue = _reader.get<String>('type');
    return FailureTypeSyntax.fromJson(jsonValue);
  }

  set _type(FailureTypeSyntax value) {
    json['type'] = value.name;
  }

  List<String> _validateType() => _reader.validate<String>('type');

  @override
  List<String> validate() => [...super.validate(), ..._validateType()];

  @override
  String toString() => 'FailureSyntax($json)';
}

class FailureTypeSyntax {
  final String name;

  const FailureTypeSyntax._(this.name);

  static const build = FailureTypeSyntax._('build');

  static const infra = FailureTypeSyntax._('infra');

  static const uncategorized = FailureTypeSyntax._('uncategorized');

  static const List<FailureTypeSyntax> values = [build, infra, uncategorized];

  static final Map<String, FailureTypeSyntax> _byName = {
    for (final value in values) value.name: value,
  };

  FailureTypeSyntax.unknown(this.name) : assert(!_byName.keys.contains(name));

  factory FailureTypeSyntax.fromJson(String name) {
    final knownValue = _byName[name];
    if (knownValue != null) {
      return knownValue;
    }
    return FailureTypeSyntax.unknown(name);
  }

  bool get isKnown => _byName[name] != null;

  @override
  String toString() => name;
}

class HookInputSyntax extends JsonObjectSyntax {
  HookInputSyntax.fromJson(super.json, {super.path = const []})
    : super.fromJson();

  HookInputSyntax({
    required ConfigSyntax config,
    required Uri outDirShared,
    required Uri outFile,
    required String packageName,
    required Uri packageRoot,
    required UserDefinesSyntax? userDefines,
    super.path = const [],
  }) : super() {
    this.config = config;
    this.outDirShared = outDirShared;
    this.outFile = outFile;
    this.packageName = packageName;
    this.packageRoot = packageRoot;
    this.userDefines = userDefines;
    json.sortOnKey();
  }

  ConfigSyntax get config {
    final jsonValue = _reader.map$('config');
    return ConfigSyntax.fromJson(jsonValue, path: [...path, 'config']);
  }

  set config(ConfigSyntax value) {
    json['config'] = value.json;
    json.sortOnKey();
  }

  List<String> _validateConfig() {
    final mapErrors = _reader.validate<Map<String, Object?>>('config');
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    return config.validate();
  }

  Uri get outDirShared => _reader.path$('out_dir_shared');

  set outDirShared(Uri value) {
    json['out_dir_shared'] = value.toFilePath();
    json.sortOnKey();
  }

  List<String> _validateOutDirShared() =>
      _reader.validatePath('out_dir_shared');

  Uri get outFile => _reader.path$('out_file');

  set outFile(Uri value) {
    json['out_file'] = value.toFilePath();
    json.sortOnKey();
  }

  List<String> _validateOutFile() => _reader.validatePath('out_file');

  String get packageName => _reader.get<String>('package_name');

  set packageName(String value) {
    json.setOrRemove('package_name', value);
    json.sortOnKey();
  }

  List<String> _validatePackageName() =>
      _reader.validate<String>('package_name');

  Uri get packageRoot => _reader.path$('package_root');

  set packageRoot(Uri value) {
    json['package_root'] = value.toFilePath();
    json.sortOnKey();
  }

  List<String> _validatePackageRoot() => _reader.validatePath('package_root');

  UserDefinesSyntax? get userDefines {
    final jsonValue = _reader.optionalMap('user_defines');
    if (jsonValue == null) return null;
    return UserDefinesSyntax.fromJson(
      jsonValue,
      path: [...path, 'user_defines'],
    );
  }

  set userDefines(UserDefinesSyntax? value) {
    json.setOrRemove('user_defines', value?.json);
    json.sortOnKey();
  }

  List<String> _validateUserDefines() {
    final mapErrors = _reader.validate<Map<String, Object?>?>('user_defines');
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    return userDefines?.validate() ?? [];
  }

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateConfig(),
    ..._validateOutDirShared(),
    ..._validateOutFile(),
    ..._validatePackageName(),
    ..._validatePackageRoot(),
    ..._validateUserDefines(),
  ];

  @override
  String toString() => 'HookInputSyntax($json)';
}

class HookOutputSyntax extends JsonObjectSyntax {
  HookOutputSyntax.fromJson(super.json, {super.path = const []})
    : super.fromJson();

  HookOutputSyntax({
    required List<AssetSyntax>? assets,
    required Map<String, List<AssetSyntax>>? assetsForLinking,
    required List<Uri>? dependencies,
    required FailureSyntax? failureDetails,
    required OutputStatusSyntax? status,
    required String timestamp,
    super.path = const [],
  }) : super() {
    this.assets = assets;
    this.assetsForLinking = assetsForLinking;
    this.dependencies = dependencies;
    this.failureDetails = failureDetails;
    this.status = status;
    this.timestamp = timestamp;
    json.sortOnKey();
  }

  List<AssetSyntax>? get assets {
    final jsonValue = _reader.optionalList('assets');
    if (jsonValue == null) return null;
    return [
      for (final (index, element) in jsonValue.indexed)
        AssetSyntax.fromJson(
          element as Map<String, Object?>,
          path: [...path, 'assets', index],
        ),
    ];
  }

  set assets(List<AssetSyntax>? value) {
    if (value == null) {
      json.remove('assets');
    } else {
      json['assets'] = [for (final item in value) item.json];
    }
    json.sortOnKey();
  }

  List<String> _validateAssets() {
    final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
      'assets',
    );
    if (listErrors.isNotEmpty) {
      return listErrors;
    }
    final elements = assets;
    if (elements == null) {
      return [];
    }
    return [for (final element in elements) ...element.validate()];
  }

  Map<String, List<AssetSyntax>>? get assetsForLinking {
    final jsonValue = _reader.optionalMap('assets_for_linking');
    if (jsonValue == null) {
      return null;
    }
    final result = <String, List<AssetSyntax>>{};
    for (final MapEntry(:key, :value) in jsonValue.entries) {
      result[key] = [
        for (final (index, item) in (value as List<Object?>).indexed)
          AssetSyntax.fromJson(
            item as Map<String, Object?>,
            path: [...path, key, index],
          ),
      ];
    }
    return result;
  }

  set assetsForLinking(Map<String, List<AssetSyntax>>? value) {
    _checkArgumentMapKeys(value);
    if (value == null) {
      json.remove('assets_for_linking');
    } else {
      json['assets_for_linking'] = {
        for (final MapEntry(:key, :value) in value.entries)
          key: [for (final item in value) item.json],
      };
    }
    json.sortOnKey();
  }

  List<String> _validateAssetsForLinking() {
    final mapErrors = _reader.validateOptionalMap('assets_for_linking');
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    final jsonValue = _reader.optionalMap('assets_for_linking');
    if (jsonValue == null) {
      return [];
    }
    final result = <String>[];
    for (final list in assetsForLinking!.values) {
      for (final element in list) {
        result.addAll(element.validate());
      }
    }
    return result;
  }

  List<Uri>? get dependencies => _reader.optionalPathList('dependencies');

  set dependencies(List<Uri>? value) {
    json.setOrRemove('dependencies', value?.toJson());
    json.sortOnKey();
  }

  List<String> _validateDependencies() =>
      _reader.validateOptionalPathList('dependencies');

  FailureSyntax? get failureDetails {
    final jsonValue = _reader.optionalMap('failure_details');
    if (jsonValue == null) return null;
    return FailureSyntax.fromJson(
      jsonValue,
      path: [...path, 'failure_details'],
    );
  }

  set failureDetails(FailureSyntax? value) {
    json.setOrRemove('failure_details', value?.json);
    json.sortOnKey();
  }

  List<String> _validateFailureDetails() {
    final mapErrors = _reader.validate<Map<String, Object?>?>(
      'failure_details',
    );
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    return failureDetails?.validate() ?? [];
  }

  OutputStatusSyntax? get status {
    final jsonValue = _reader.get<String?>('status');
    if (jsonValue == null) return null;
    return OutputStatusSyntax.fromJson(jsonValue);
  }

  set status(OutputStatusSyntax? value) {
    json.setOrRemove('status', value?.name);
    json.sortOnKey();
  }

  List<String> _validateStatus() => _reader.validate<String?>('status');

  String get timestamp => _reader.get<String>('timestamp');

  set timestamp(String value) {
    json.setOrRemove('timestamp', value);
    json.sortOnKey();
  }

  List<String> _validateTimestamp() => _reader.validate<String>('timestamp');

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateAssets(),
    ..._validateAssetsForLinking(),
    ..._validateDependencies(),
    ..._validateFailureDetails(),
    ..._validateStatus(),
    ..._validateTimestamp(),
    ..._validateExtraRulesHookOutput(),
  ];

  List<String> _validateExtraRulesHookOutput() {
    final result = <String>[];
    if (_reader.tryTraverse(['status']) == 'failure') {
      result.addAll(_reader.validate<Object>('failure_details'));
    }
    return result;
  }

  @override
  String toString() => 'HookOutputSyntax($json)';
}

class HooksMetadataAssetSyntax extends AssetSyntax {
  static const typeValue = 'hooks/metadata';

  HooksMetadataAssetSyntax.fromJson(super.json, {super.path})
    : super._fromJson();

  HooksMetadataAssetSyntax({
    required MetadataAssetEncodingSyntax encoding,
    super.path = const [],
  }) : super(type: 'hooks/metadata', encoding: encoding);

  /// Setup all fields for [HooksMetadataAssetSyntax] that are not in
  /// [AssetSyntax].
  void setup() {}

  @override
  MetadataAssetEncodingSyntax get encoding {
    final jsonValue = _reader.map$('encoding');
    return MetadataAssetEncodingSyntax.fromJson(
      jsonValue,
      path: [...path, 'encoding'],
    );
  }

  @override
  List<String> validate() => [...super.validate(), ..._validateEncoding()];

  @override
  String toString() => 'HooksMetadataAssetSyntax($json)';
}

extension HooksMetadataAssetSyntaxExtension on AssetSyntax {
  bool get isHooksMetadataAsset => type == 'hooks/metadata';

  HooksMetadataAssetSyntax get asHooksMetadataAsset =>
      HooksMetadataAssetSyntax.fromJson(json, path: path);
}

class LinkInputSyntax extends HookInputSyntax {
  LinkInputSyntax.fromJson(super.json, {super.path}) : super.fromJson();

  LinkInputSyntax({
    required List<AssetSyntax>? assets,
    required List<AssetSyntax>? assetsFromLinking,
    required super.config,
    required super.outDirShared,
    required super.outFile,
    required super.packageName,
    required super.packageRoot,
    required Uri? resourceIdentifiers,
    required super.userDefines,
    super.path = const [],
  }) : super() {
    _assets = assets;
    _assetsFromLinking = assetsFromLinking;
    _resourceIdentifiers = resourceIdentifiers;
    json.sortOnKey();
  }

  /// Setup all fields for [LinkInputSyntax] that are not in
  /// [HookInputSyntax].
  void setup({
    required List<AssetSyntax>? assets,
    required List<AssetSyntax>? assetsFromLinking,
    required Uri? resourceIdentifiers,
  }) {
    _assets = assets;
    _assetsFromLinking = assetsFromLinking;
    _resourceIdentifiers = resourceIdentifiers;
    json.sortOnKey();
  }

  List<AssetSyntax>? get assets {
    final jsonValue = _reader.optionalList('assets');
    if (jsonValue == null) return null;
    return [
      for (final (index, element) in jsonValue.indexed)
        AssetSyntax.fromJson(
          element as Map<String, Object?>,
          path: [...path, 'assets', index],
        ),
    ];
  }

  set _assets(List<AssetSyntax>? value) {
    if (value == null) {
      json.remove('assets');
    } else {
      json['assets'] = [for (final item in value) item.json];
    }
  }

  List<String> _validateAssets() {
    final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
      'assets',
    );
    if (listErrors.isNotEmpty) {
      return listErrors;
    }
    final elements = assets;
    if (elements == null) {
      return [];
    }
    return [for (final element in elements) ...element.validate()];
  }

  List<AssetSyntax>? get assetsFromLinking {
    final jsonValue = _reader.optionalList('assets_from_linking');
    if (jsonValue == null) return null;
    return [
      for (final (index, element) in jsonValue.indexed)
        AssetSyntax.fromJson(
          element as Map<String, Object?>,
          path: [...path, 'assets_from_linking', index],
        ),
    ];
  }

  set _assetsFromLinking(List<AssetSyntax>? value) {
    if (value == null) {
      json.remove('assets_from_linking');
    } else {
      json['assets_from_linking'] = [for (final item in value) item.json];
    }
  }

  List<String> _validateAssetsFromLinking() {
    final listErrors = _reader.validateOptionalList<Map<String, Object?>>(
      'assets_from_linking',
    );
    if (listErrors.isNotEmpty) {
      return listErrors;
    }
    final elements = assetsFromLinking;
    if (elements == null) {
      return [];
    }
    return [for (final element in elements) ...element.validate()];
  }

  Uri? get resourceIdentifiers => _reader.optionalPath('resource_identifiers');

  set _resourceIdentifiers(Uri? value) {
    json.setOrRemove('resource_identifiers', value?.toFilePath());
  }

  List<String> _validateResourceIdentifiers() =>
      _reader.validateOptionalPath('resource_identifiers');

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateAssets(),
    ..._validateAssetsFromLinking(),
    ..._validateResourceIdentifiers(),
  ];

  @override
  String toString() => 'LinkInputSyntax($json)';
}

class LinkOutputSyntax extends HookOutputSyntax {
  LinkOutputSyntax.fromJson(super.json, {super.path}) : super.fromJson();

  LinkOutputSyntax({
    required super.assets,
    required super.assetsForLinking,
    required super.dependencies,
    required super.failureDetails,
    required super.status,
    required super.timestamp,
    super.path = const [],
  }) : super();

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateExtraRulesLinkOutput(),
  ];

  List<String> _validateExtraRulesLinkOutput() {
    final result = <String>[];
    if (_reader.tryTraverse(['status']) == 'failure') {
      result.addAll(_reader.validate<Object>('failure_details'));
    }
    return result;
  }

  @override
  String toString() => 'LinkOutputSyntax($json)';
}

class MetadataAssetEncodingSyntax extends JsonObjectSyntax {
  MetadataAssetEncodingSyntax.fromJson(super.json, {super.path = const []})
    : super.fromJson();

  MetadataAssetEncodingSyntax({
    required String key,
    required Object? value,
    super.path = const [],
  }) : super() {
    _key = key;
    _value = value;
    json.sortOnKey();
  }

  String get key => _reader.get<String>('key');

  set _key(String value) {
    json.setOrRemove('key', value);
  }

  List<String> _validateKey() => _reader.validate<String>('key');

  Object? get value => _reader.get<Object?>('value');

  set _value(Object? value) {
    json.setOrRemove('value', value);
  }

  List<String> _validateValue() => _reader.validate<Object?>('value');

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateKey(),
    ..._validateValue(),
  ];

  @override
  String toString() => 'MetadataAssetEncodingSyntax($json)';
}

class OutputStatusSyntax {
  final String name;

  const OutputStatusSyntax._(this.name);

  static const failure = OutputStatusSyntax._('failure');

  static const success = OutputStatusSyntax._('success');

  static const List<OutputStatusSyntax> values = [failure, success];

  static final Map<String, OutputStatusSyntax> _byName = {
    for (final value in values) value.name: value,
  };

  OutputStatusSyntax.unknown(this.name) : assert(!_byName.keys.contains(name));

  factory OutputStatusSyntax.fromJson(String name) {
    final knownValue = _byName[name];
    if (knownValue != null) {
      return knownValue;
    }
    return OutputStatusSyntax.unknown(name);
  }

  bool get isKnown => _byName[name] != null;

  @override
  String toString() => name;
}

class UserDefinesSyntax extends JsonObjectSyntax {
  UserDefinesSyntax.fromJson(super.json, {super.path = const []})
    : super.fromJson();

  UserDefinesSyntax({
    required UserDefinesSourceSyntax? workspacePubspec,
    super.path = const [],
  }) : super() {
    _workspacePubspec = workspacePubspec;
    json.sortOnKey();
  }

  UserDefinesSourceSyntax? get workspacePubspec {
    final jsonValue = _reader.optionalMap('workspace_pubspec');
    if (jsonValue == null) return null;
    return UserDefinesSourceSyntax.fromJson(
      jsonValue,
      path: [...path, 'workspace_pubspec'],
    );
  }

  set _workspacePubspec(UserDefinesSourceSyntax? value) {
    json.setOrRemove('workspace_pubspec', value?.json);
  }

  List<String> _validateWorkspacePubspec() {
    final mapErrors = _reader.validate<Map<String, Object?>?>(
      'workspace_pubspec',
    );
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    return workspacePubspec?.validate() ?? [];
  }

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateWorkspacePubspec(),
  ];

  @override
  String toString() => 'UserDefinesSyntax($json)';
}

class UserDefinesSourceSyntax extends JsonObjectSyntax {
  UserDefinesSourceSyntax.fromJson(super.json, {super.path = const []})
    : super.fromJson();

  UserDefinesSourceSyntax({
    required Uri basePath,
    required JsonObjectSyntax defines,
    super.path = const [],
  }) : super() {
    _basePath = basePath;
    _defines = defines;
    json.sortOnKey();
  }

  Uri get basePath => _reader.path$('base_path');

  set _basePath(Uri value) {
    json['base_path'] = value.toFilePath();
  }

  List<String> _validateBasePath() => _reader.validatePath('base_path');

  JsonObjectSyntax get defines {
    final jsonValue = _reader.map$('defines');
    return JsonObjectSyntax.fromJson(jsonValue, path: [...path, 'defines']);
  }

  set _defines(JsonObjectSyntax value) {
    json['defines'] = value.json;
  }

  List<String> _validateDefines() {
    final mapErrors = _reader.validate<Map<String, Object?>>('defines');
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    return defines.validate();
  }

  @override
  List<String> validate() => [
    ...super.validate(),
    ..._validateBasePath(),
    ..._validateDefines(),
  ];

  @override
  String toString() => 'UserDefinesSourceSyntax($json)';
}

class JsonObjectSyntax {
  final Map<String, Object?> json;

  final List<Object> path;

  _JsonReader get _reader => _JsonReader(json, path);

  JsonObjectSyntax({this.path = const []}) : json = {};

  JsonObjectSyntax.fromJson(this.json, {this.path = const []});

  List<String> validate() => [];
}

class _JsonReader {
  /// The JSON Object this reader is reading.
  final Map<String, Object?> json;

  /// The path traversed by readers of the surrounding JSON.
  ///
  /// Contains [String] property keys and [int] indices.
  ///
  /// This is used to give more precise error messages.
  final List<Object> path;

  _JsonReader(this.json, this.path);

  T get<T extends Object?>(String key) {
    final value = json[key];
    if (value is T) return value;
    throwFormatException(value, T, [key]);
  }

  List<String> validate<T extends Object?>(String key) {
    final value = json[key];
    if (value is T) return [];
    return [
      errorString(value, T, [key]),
    ];
  }

  List<T> list<T extends Object?>(String key) =>
      _castList<T>(get<List<Object?>>(key), key);

  List<String> validateList<T extends Object?>(String key) {
    final listErrors = validate<List<Object?>>(key);
    if (listErrors.isNotEmpty) {
      return listErrors;
    }
    return _validateListElements(get<List<Object?>>(key), key);
  }

  List<T>? optionalList<T extends Object?>(String key) =>
      switch (get<List<Object?>?>(key)?.cast<T>()) {
        null => null,
        final l => _castList<T>(l, key),
      };

  List<String> validateOptionalList<T extends Object?>(String key) {
    final listErrors = validate<List<Object?>?>(key);
    if (listErrors.isNotEmpty) {
      return listErrors;
    }
    final list = get<List<Object?>?>(key);
    if (list == null) {
      return [];
    }
    return _validateListElements(list, key);
  }

  /// [List.cast] but with [FormatException]s.
  List<T> _castList<T extends Object?>(List<Object?> list, String key) {
    for (final (index, value) in list.indexed) {
      if (value is! T) {
        throwFormatException(value, T, [key, index]);
      }
    }
    return list.cast();
  }

  List<String> _validateListElements<T extends Object?>(
    List<Object?> list,
    String key,
  ) {
    final result = <String>[];
    for (final (index, value) in list.indexed) {
      if (value is! T) {
        result.add(errorString(value, T, [key, index]));
      }
    }
    return result;
  }

  Map<String, T> map$<T extends Object?>(String key, {RegExp? keyPattern}) {
    final map = get<Map<String, Object?>>(key);
    final keyErrors = _validateMapKeys(map, key, keyPattern: keyPattern);
    if (keyErrors.isNotEmpty) {
      throw FormatException(keyErrors.join('\n'));
    }
    return _castMap<T>(map, key);
  }

  List<String> validateMap<T extends Object?>(
    String key, {
    RegExp? keyPattern,
  }) {
    final mapErrors = validate<Map<String, Object?>>(key);
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    final map = get<Map<String, Object?>>(key);
    return [
      ..._validateMapKeys(map, key, keyPattern: keyPattern),
      ..._validateMapElements<T>(map, key),
    ];
  }

  Map<String, T>? optionalMap<T extends Object?>(
    String key, {
    RegExp? keyPattern,
  }) {
    final map = get<Map<String, Object?>?>(key);
    if (map == null) return null;
    final keyErrors = _validateMapKeys(map, key, keyPattern: keyPattern);
    if (keyErrors.isNotEmpty) {
      throw FormatException(keyErrors.join('\n'));
    }
    return _castMap<T>(map, key);
  }

  List<String> validateOptionalMap<T extends Object?>(
    String key, {
    RegExp? keyPattern,
  }) {
    final mapErrors = validate<Map<String, Object?>?>(key);
    if (mapErrors.isNotEmpty) {
      return mapErrors;
    }
    final map = get<Map<String, Object?>?>(key);
    if (map == null) {
      return [];
    }
    return [
      ..._validateMapKeys(map, key, keyPattern: keyPattern),
      ..._validateMapElements<T>(map, key),
    ];
  }

  /// [Map.cast] but with [FormatException]s.
  Map<String, T> _castMap<T extends Object?>(
    Map<String, Object?> map_,
    String parentKey,
  ) {
    for (final MapEntry(:key, :value) in map_.entries) {
      if (value is! T) {
        throwFormatException(value, T, [parentKey, key]);
      }
    }
    return map_.cast();
  }

  List<String> _validateMapKeys(
    Map<String, Object?> map_,
    String parentKey, {
    required RegExp? keyPattern,
  }) {
    if (keyPattern == null) return [];
    final result = <String>[];
    for (final key in map_.keys) {
      if (!keyPattern.hasMatch(key)) {
        result.add(
          keyErrorString(key, pattern: keyPattern, pathExtension: [parentKey]),
        );
      }
    }
    return result;
  }

  List<String> _validateMapElements<T extends Object?>(
    Map<String, Object?> map_,
    String parentKey,
  ) {
    final result = <String>[];
    for (final MapEntry(:key, :value) in map_.entries) {
      if (value is! T) {
        result.add(errorString(value, T, [parentKey, key]));
      }
    }
    return result;
  }

  List<String> validateMapStringElements<T extends Object?>(
    Map<String, String?> map_,
    String parentKey, {
    RegExp? valuePattern,
  }) {
    final result = <String>[];
    for (final MapEntry(:key, :value) in map_.entries) {
      if (value != null &&
          valuePattern != null &&
          !valuePattern.hasMatch(value)) {
        result.add(
          errorString(value, T, [parentKey, key], pattern: valuePattern),
        );
      }
    }
    return result;
  }

  String string(String key, RegExp? pattern) {
    final value = get<String>(key);
    if (pattern != null && !pattern.hasMatch(value)) {
      throwFormatException(value, String, [key], pattern: pattern);
    }
    return value;
  }

  String? optionalString(String key, RegExp? pattern) {
    final value = get<String?>(key);
    if (value == null) return null;
    if (pattern != null && !pattern.hasMatch(value)) {
      throwFormatException(value, String, [key], pattern: pattern);
    }
    return value;
  }

  List<String> validateString(String key, RegExp? pattern) {
    final errors = validate<String>(key);
    if (errors.isNotEmpty) {
      return errors;
    }
    final value = get<String>(key);
    if (pattern != null && !pattern.hasMatch(value)) {
      return [
        errorString(value, String, [key], pattern: pattern),
      ];
    }
    return [];
  }

  List<String> validateOptionalString(String key, RegExp? pattern) {
    final errors = validate<String?>(key);
    if (errors.isNotEmpty) {
      return errors;
    }
    final value = get<String?>(key);
    if (value == null) return [];
    if (pattern != null && !pattern.hasMatch(value)) {
      return [
        errorString(value, String, [key], pattern: pattern),
      ];
    }
    return [];
  }

  List<String>? optionalStringList(String key) => optionalList<String>(key);

  List<String> validateOptionalStringList(String key) =>
      validateOptionalList<String>(key);

  List<String> stringList(String key) => list<String>(key);

  List<String> validateStringList(String key) => validateList<String>(key);

  Uri path$(String key) => _fileSystemPathToUri(get<String>(key));

  List<String> validatePath(String key) => validate<String>(key);

  Uri? optionalPath(String key) {
    final value = get<String?>(key);
    if (value == null) return null;
    return _fileSystemPathToUri(value);
  }

  List<String> validateOptionalPath(String key) => validate<String?>(key);

  List<Uri>? optionalPathList(String key) {
    final strings = optionalStringList(key);
    if (strings == null) {
      return null;
    }
    return [for (final string in strings) _fileSystemPathToUri(string)];
  }

  List<String> validateOptionalPathList(String key) =>
      validateOptionalStringList(key);

  static Uri _fileSystemPathToUri(String path) {
    if (path.endsWith(Platform.pathSeparator)) {
      return Uri.directory(path);
    }
    return Uri.file(path);
  }

  String _jsonPathToString(List<Object> pathEnding) =>
      [...path, ...pathEnding].join('.');

  Never throwFormatException(
    Object? value,
    Type expectedType,
    List<Object> pathExtension, {
    RegExp? pattern,
  }) {
    throw FormatException(
      errorString(value, expectedType, pathExtension, pattern: pattern),
    );
  }

  String errorString(
    Object? value,
    Type expectedType,
    List<Object> pathExtension, {
    RegExp? pattern,
  }) {
    final pathString = _jsonPathToString(pathExtension);
    if (value == null) {
      return "No value was provided for '$pathString'."
          ' Expected a $expectedType.';
    }
    final satisfying = pattern == null ? '' : ' satisfying ${pattern.pattern}';
    return "Unexpected value '$value' (${value.runtimeType}) for '$pathString'."
        ' Expected a $expectedType$satisfying.';
  }

  String keyErrorString(
    String key, {
    required RegExp pattern,
    List<Object> pathExtension = const [],
  }) {
    final pathString = _jsonPathToString(pathExtension);
    return "Unexpected key '$key' in '$pathString'."
        ' Expected a key satisfying ${pattern.pattern}.';
  }

  /// Traverses a JSON path, returns `null` if the path cannot be traversed.
  Object? tryTraverse(List<String> path) {
    Object? json = this.json;
    for (final key in path) {
      if (json is! Map<String, Object?>) {
        return null;
      }
      json = json[key];
    }
    return json;
  }
}

extension on Map<String, Object?> {
  void setOrRemove(String key, Object? value) {
    if (value == null) {
      remove(key);
    } else {
      this[key] = value;
    }
  }
}

extension on List<Uri> {
  List<String> toJson() => [for (final uri in this) uri.toFilePath()];
}

extension<K extends Comparable<K>, V extends Object?> on Map<K, V> {
  void sortOnKey() {
    final result = <K, V>{};
    final keysSorted = keys.toList()..sort();
    for (final key in keysSorted) {
      result[key] = this[key] as V;
    }
    clear();
    addAll(result);
  }
}

void _checkArgumentMapKeys(Map<String, Object?>? map, {RegExp? keyPattern}) {
  if (map == null) return;
  if (keyPattern == null) return;
  for (final key in map.keys) {
    if (!keyPattern.hasMatch(key)) {
      throw ArgumentError.value(
        map,
        "Unexpected key '$key'."
        ' Expected a key satisfying ${keyPattern.pattern}.',
      );
    }
  }
}

void _checkArgumentMapStringElements(
  Map<String, String?>? map, {
  RegExp? valuePattern,
}) {
  if (map == null) return;
  if (valuePattern == null) return;
  for (final entry in map.entries) {
    final value = entry.value;
    if (value != null && !valuePattern.hasMatch(value)) {
      throw ArgumentError.value(
        map,
        "Unexpected value '$value' under key '${entry.key}'."
        ' Expected a value satisfying ${valuePattern.pattern}.',
      );
    }
  }
}
