// Copyright (c) 2019, 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:analyzer/file_system/file_system.dart';
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/generator/generator.dart';
import 'package:dartdoc/src/generator/generator_utils.dart' as generator_util;
import 'package:dartdoc/src/generator/template_data.dart';
import 'package:dartdoc/src/generator/templates.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/runtime_stats.dart';
import 'package:dartdoc/src/version.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as p show Context;

/// Configuration options for Dartdoc's default backend.
class DartdocGeneratorBackendOptions implements TemplateOptions {
  @override
  final String? relCanonicalPrefix;

  @override
  final String toolVersion;

  final String? favicon;

  final bool prettyIndexJson;

  @override
  final bool useBaseHref;

  @override
  final String customHeaderContent;

  @override
  final String customFooterContent;

  @override
  final String customInnerFooterText;

  final String? resourcesDir;

  final List<String> packageOrder;

  DartdocGeneratorBackendOptions.fromContext(
      DartdocGeneratorOptionContext context)
      : relCanonicalPrefix = context.relCanonicalPrefix,
        toolVersion = packageVersion,
        favicon = context.favicon,
        prettyIndexJson = context.prettyIndexJson,
        useBaseHref = context.useBaseHref,
        customHeaderContent = context.header,
        customFooterContent = context.footer,
        customInnerFooterText = context.footerText,
        resourcesDir = context.resourcesDir,
        packageOrder = context.packageOrder;
}

/// Outputs generated documentation.
abstract class GeneratorBackend {
  final DartdocGeneratorBackendOptions options;
  final Templates templates;

  final FileWriter writer;
  final ResourceProvider resourceProvider;
  final p.Context _pathContext;

  GeneratorBackend(
      this.options, this.templates, this.writer, this.resourceProvider)
      : _pathContext = resourceProvider.pathContext;

  /// Binds template data and emits the content to the [writer].
  void write(
    FileWriter writer,
    String filename,
    TemplateData data,
    String content, {
    bool isSidebar = false,
  }) {
    if (!options.useBaseHref) {
      content = content.replaceAll(
        htmlBasePlaceholder,
        // URLs in sidebars are tweaked in the front-end; other URLs use
        // `htmlBase`.
        isSidebar ? '' : data.htmlBase,
      );
    }
    var e = data.self;
    writer.write(filename, content, element: e is Warnable ? e : null);
  }

  /// Emits JSON describing the [categorizedElements] defined by the package.
  void generateCategoryJson(List<ModelElement> categorizedElements) {
    var json = '[]';
    if (categorizedElements.isNotEmpty) {
      json = generator_util.generateCategoryJson(
          categorizedElements, options.prettyIndexJson);
      if (!options.useBaseHref) {
        json = json.replaceAll(htmlBasePlaceholder, '');
      }
    }

    writer.write(_pathContext.join('categories.json'), '$json\n');
  }

  /// Emits a JSON catalog of [indexedElements] for use with a search index.
  void generateSearchIndex(List<Documentable> indexedElements) {
    var json = generator_util.generateSearchIndexJson(
      indexedElements,
      packageOrder: options.packageOrder,
      pretty: options.prettyIndexJson,
    );
    if (!options.useBaseHref) {
      json = json.replaceAll(htmlBasePlaceholder, '');
    }
    writer.write(_pathContext.join('index.json'), '$json\n');
  }

  /// Emits documentation content for the [category].
  void generateCategory(PackageGraph packageGraph, Category category) {
    var data = CategoryTemplateData(options, packageGraph, category);
    var content = templates.renderCategory(data);
    write(writer, category.filePath, data, content);
    if (category.filePath != category.redirectFilePath) {
      var redirectContent = templates.renderCategoryRedirect(data);
      write(writer, category.redirectFilePath, data, redirectContent);
    }

    runtimeStats.incrementAccumulator('writtenCategoryFileCount');
  }

  /// Emits documentation content for the [class_].
  void generateClass(PackageGraph packageGraph, Library library, Class class_) {
    var data = ClassTemplateData(options, packageGraph, library, class_);
    var content = templates.renderClass(data);
    write(writer, class_.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenClassFileCount');
  }

  /// Emits documentation content for the [constructor].
  void generateConstructor(PackageGraph packageGraph, Library library,
      Constructable constructable, Constructor constructor) {
    var data = ConstructorTemplateData(
        options, packageGraph, library, constructable, constructor);
    var content = templates.renderConstructor(data);
    write(writer, constructor.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenConstructorFileCount');
  }

  /// Emits documentation content for the [enum_].
  void generateEnum(PackageGraph packageGraph, Library library, Enum enum_) {
    var data = EnumTemplateData(options, packageGraph, library, enum_);
    var content = templates.renderEnum(data);
    write(writer, enum_.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenEnumFileCount');
  }

  /// Emits documentation content for the [extension].
  void generateExtension(
      PackageGraph packageGraph, Library library, Extension extension) {
    var data = ExtensionTemplateData(options, packageGraph, library, extension);
    var content = templates.renderExtension(data);
    write(writer, extension.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenExtensionFileCount');
  }

  /// Emits documentation content for the [extensionType].
  void generateExtensionType(
      PackageGraph packageGraph, Library library, ExtensionType extensionType) {
    var data = ExtensionTypeTemplateData(
        options, packageGraph, library, extensionType);
    var content = templates.renderExtensionType(data);
    write(writer, extensionType.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenExtensionTypeFileCount');
  }

  /// Emits documentation content for the [function].
  void generateFunction(
      PackageGraph packageGraph, Library library, ModelFunction function) {
    var data = FunctionTemplateData(options, packageGraph, library, function);
    var content = templates.renderFunction(data);
    write(writer, function.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenFunctionFileCount');
  }

  /// Emits documentation content for the [library], and the content for the
  /// library's previous location (which just redirects to the new location).
  void generateLibrary(PackageGraph packageGraph, Library library) {
    var data = LibraryTemplateData(options, packageGraph, library);
    var content = templates.renderLibrary(data);
    var redirectContent = templates.renderLibraryRedirect(data);
    write(writer, library.filePath, data, content);
    write(writer, library.redirectingPath, data, redirectContent);
    runtimeStats.incrementAccumulator('writtenLibraryFileCount');
  }

  /// Emits documentation content for the [method].
  void generateMethod(PackageGraph packageGraph, Library library,
      Container container, Method method) {
    var data =
        MethodTemplateData(options, packageGraph, library, container, method);
    var content = templates.renderMethod(data);
    write(writer, method.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenMethodFileCount');
  }

  /// Emits documentation content for the [mixin].
  void generateMixin(PackageGraph packageGraph, Library library, Mixin mixin) {
    var data = MixinTemplateData(options, packageGraph, library, mixin);
    var content = templates.renderMixin(data);
    write(writer, mixin.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenMixinFileCount');
  }

  /// Emits documentation content for the [package].
  void generatePackage(PackageGraph packageGraph, Package package) {
    var data = PackageTemplateData(options, packageGraph, package);
    var content = templates.renderIndex(data);
    write(writer, package.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenPackageFileCount');
  }

  /// Emits documentation content for the [field].
  void generateProperty(PackageGraph packageGraph, Library library,
      Container container, Field field) {
    var data =
        PropertyTemplateData(options, packageGraph, library, container, field);
    var content = templates.renderProperty(data);
    write(writer, field.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenPropertyFileCount');
  }

  /// Emits documentation content for the [property].
  void generateTopLevelProperty(
      PackageGraph packageGraph, Library library, TopLevelVariable property) {
    var data =
        TopLevelPropertyTemplateData(options, packageGraph, library, property);
    var content = templates.renderTopLevelProperty(data);
    write(writer, property.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenTopLevelPropertyFileCount');
  }

  /// Emits documentation content for the [typedef].
  void generateTypeDef(
      PackageGraph packageGraph, Library library, Typedef typedef) {
    var data = TypedefTemplateData(options, packageGraph, library, typedef);
    var content = templates.renderTypedef(data);
    write(writer, typedef.filePath, data, content);
    runtimeStats.incrementAccumulator('writtenTypedefFileCount');
  }

  /// Emits files not specific to a Dart language element (like a favicon, etc).
  Future<void> generateAdditionalFiles();
}
