// Copyright (c) 2017, 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 'dart:async';
import 'dart:convert';

import 'package:analyzer/dart/element/element.dart';
import 'package:meta/meta.dart';
import 'package:package_config/package_config_types.dart';

import '../analyzer/resolver.dart';
import '../asset/id.dart';
import '../asset/reader.dart';
import '../asset/writer.dart';
import '../resource/resource.dart';

/// A single step in a build process.
///
/// This represents a single [inputId], logic around resolving as a library,
/// and the ability to read and write assets as allowed by the underlying build
/// system.
@sealed
abstract class BuildStep implements AssetReader, AssetWriter {
  /// The primary for this build step.
  AssetId get inputId;

  /// Resolved library defined by [inputId].
  ///
  /// Throws [NonLibraryAssetException] if [inputId] is not a Dart library file.
  /// Throws [SyntaxErrorInAssetException] if [inputId] contains syntax errors.
  /// If you want to support libraries with syntax errors, resolve the library
  /// manually instead of using [inputLibrary]:
  /// ```dart
  /// Future<void> build(BuildStep step) async {
  ///   // Resolve the input library, allowing syntax errors
  ///   final inputLibrary =
  ///     await step.resolver.libraryFor(step.inputId, allowSyntaxErrors: true);
  /// }
  /// ```
  Future<LibraryElement> get inputLibrary;

  /// Gets an instance provided by [resource] which is guaranteed to be unique
  /// within a single build, and may be reused across build steps within a
  /// build if the implementation allows.
  ///
  /// It is also guaranteed that [resource] will be disposed before the next
  /// build starts (and the dispose callback will be invoked if provided).
  Future<T> fetchResource<T>(Resource<T> resource);

  /// Writes [bytes] to a binary file located at [id].
  ///
  /// Returns a [Future] that completes after writing the asset out.
  ///
  /// * Throws a `PackageNotFoundException` if `id.package` is not found.
  /// * Throws an `InvalidOutputException` if the output was not valid (that is,
  ///   [id] is not in [allowedOutputs])
  ///
  /// **NOTE**: Most `Builder` implementations should not need to `await` this
  /// Future since the runner will be responsible for waiting until all outputs
  /// are written.
  @override
  Future<void> writeAsBytes(AssetId id, FutureOr<List<int>> bytes);

  /// Writes [contents] to a text file located at [id] with [encoding].
  ///
  /// Returns a [Future] that completes after writing the asset out.
  ///
  /// * Throws a `PackageNotFoundException` if `id.package` is not found.
  /// * Throws an `InvalidOutputException` if the output was not valid (that is,
  ///   [id] is not in [allowedOutputs])
  ///
  /// **NOTE**: Most `Builder` implementations should not need to `await` this
  /// Future since the runner will be responsible for waiting until all outputs
  /// are written.
  @override
  Future<void> writeAsString(AssetId id, FutureOr<String> contents,
      {Encoding encoding = utf8});

  /// A [Resolver] for [inputId].
  Resolver get resolver;

  /// Tracks performance of [action] separately.
  ///
  /// If performance tracking is enabled, tracks [action] as separate stage
  /// identified by [label]. Otherwise just runs [action].
  ///
  /// You can specify [action] as [isExternal] (waiting for some external
  /// resource like network, process or file IO). In that case [action] will
  /// be tracked as single time slice from the beginning of the stage till
  /// completion of Future returned by [action].
  ///
  /// Otherwise all separate time slices of asynchronous execution will be
  /// tracked, but waiting for external resources will be a gap.
  ///
  /// Returns value returned by [action].
  /// [action] can be async function returning [Future].
  T trackStage<T>(String label, T Function() action, {bool isExternal = false});

  /// Indicates that [ids] were read but their content has no impact on the
  /// outputs of this step.
  ///
  /// **WARNING**: Using this introduces serious risk of non-hermetic builds.
  ///
  /// If these files change or become unreadable on the next build this build
  /// step may not run.
  ///
  /// **Note**: This is not guaranteed to have any effect and it should be
  /// assumed to be a no-op by default. Implementations must explicitly
  /// choose to support this feature.
  void reportUnusedAssets(Iterable<AssetId> ids);

  /// Returns assets that may be written in this build step.
  ///
  /// Allowed outputs are formed by matching the [inputId] against the builder's
  /// `buildExtensions`, which declares a list of output extensions for this
  /// input.
  ///
  /// The writing methods [writeAsBytes] and [writeAsString] will throw an
  /// `InvalidOutputException` when attempting to write an asset not part of
  /// the [allowedOutputs].
  Iterable<AssetId> get allowedOutputs;

  /// Returns a [PackageConfig] resolvable from this build step.
  ///
  /// The package config contains all packages involved in the build. Typically,
  /// this is the package config taken from the current isolate.
  ///
  /// The returned package config does not use `file:/`-based URIs and can't be
  /// used to access package contents with `dart:io`. Instead, packages resolve
  /// to `asset:/` URIs that can be parsed with [AssetId.resolve] and read with
  /// [readAsBytes] or [readAsString].
  Future<PackageConfig> get packageConfig;
}

abstract class StageTracker {
  T trackStage<T>(String label, T Function() action, {bool isExternal = false});
}

class NoOpStageTracker implements StageTracker {
  static const StageTracker instance = NoOpStageTracker._();

  @override
  T trackStage<T>(String label, T Function() action,
          {bool isExternal = false}) =>
      action();

  const NoOpStageTracker._();
}
