package org.ajoberstar.gradle.git.publish;

import org.ajoberstar.gradle.git.publish.tasks.GitPublishCommit;
import org.ajoberstar.gradle.git.publish.tasks.GitPublishPush;
import org.ajoberstar.gradle.git.publish.tasks.GitPublishReset;
import org.ajoberstar.grgit.Grgit;
import org.ajoberstar.grgit.gradle.GrgitService;
import org.ajoberstar.grgit.gradle.GrgitServiceExtension;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Copy;
import org.gradle.api.tasks.TaskProvider;

public class GitPublishPlugin implements Plugin<Project> {
  @Override
  public void apply(Project project) {
    var extension = project.getExtensions().create("gitPublish", GitPublishExtension.class, project);

    // create the default
    extension.getPublications().create("main");

    // configure defaults and tasks for each publication
    extension.getPublications().configureEach(publication -> {
      configurePublicationDefaults(project, publication);

      var grgitService = project.getGradle().getSharedServices().registerIfAbsent(String.format("git-publish-%s-grgit", publication.getName()), GrgitService.class, spec -> {
        spec.parameters(parameters -> {
          parameters.getDirectory().set(publication.getRepoDir());
          parameters.getInitIfNotExists().set(true);
        });
        spec.getMaxParallelUsages().set(1);
      });

      var reset = createResetTask(project, publication, grgitService);
      var copy = createCopyTask(project, publication);
      var commit = createCommitTask(project, publication, grgitService);
      var push = createPushTask(project, publication, grgitService);

      push.configure(t -> t.dependsOn(commit));
      commit.configure(t -> t.dependsOn(copy));
      copy.configure(t -> t.dependsOn(reset));
    });

    // add helper task to push all publications
    project.getTasks().register("gitPublishPushAll", task -> {
      task.setGroup("publishing");
      task.setDescription("Pushes all publications to git");
      task.dependsOn(project.getTasks().withType(GitPublishPush.class));
    });
  }

  private void configurePublicationDefaults(Project project, GitPublication publication) {
    publication.getCommitMessage().set("Generated by gradle-git-publish.");

    // if using the grgit-service plugin, default to the repo's origin
    project.getPluginManager().withPlugin("org.ajoberstar.grgit.service", plugin -> {
      var grgitExt = project.getExtensions().getByType(GrgitServiceExtension.class);
      // TODO should this be based on tracking branch instead of assuming origin?
      publication.getRepoUri().set(grgitExt.getService().map(service -> getOriginUri(service.getGrgit())));
      publication.getReferenceRepoUri().set(grgitExt.getService().map(service -> service.getGrgit().getRepository().getRootDir().toURI().toString()));
    });

    publication.getRepoDir().set(project.getLayout().getBuildDirectory().dir("gitPublish/" + publication.getName()));
  }

  private TaskProvider<GitPublishReset> createResetTask(Project project, GitPublication publication, Provider<GrgitService> grgitService) {
    return project.getTasks().register(getTaskName(publication, "Reset"), GitPublishReset.class, task -> {
      task.setGroup("publishing");
      task.setDescription("Prepares a git repo for " + publication.getName() + " publication content to be generated.");
      task.getGrgitService().set(grgitService);
      task.getRepoUri().set(publication.getRepoUri());
      task.getReferenceRepoUri().set(publication.getReferenceRepoUri());
      task.getBranch().set(publication.getBranch());
      task.setPreserve(publication.getPreserve());
    });
  }

  private TaskProvider<Copy> createCopyTask(Project project, GitPublication publication) {
    return project.getTasks().register(getTaskName(publication, "Copy"), Copy.class, task -> {
      task.setGroup("publishing");
      task.setDescription("Copy " + publication.getName() + " publication contents to be published to git.");
      task.with(publication.getContents());
      task.into(publication.getRepoDir());
    });
  }

  private TaskProvider<GitPublishCommit> createCommitTask(Project project, GitPublication publication, Provider<GrgitService> grgitService) {
    return project.getTasks().register(getTaskName(publication, "Commit"), GitPublishCommit.class, task -> {
      task.setGroup("publishing");
      task.setDescription("Commits " + publication.getName() + " publication changes to be published to git.");
      task.getGrgitService().set(grgitService);
      task.getMessage().set(publication.getCommitMessage());
      task.getSign().set(publication.getSign());
    });
  }

  private TaskProvider<GitPublishPush> createPushTask(Project project, GitPublication publication, Provider<GrgitService> grgitService) {
    return project.getTasks().register(getTaskName(publication, "Push"), GitPublishPush.class, task -> {
      task.setGroup("publishing");
      task.setDescription("Pushes " + publication.getName() + " publication changes to git.");
      task.getGrgitService().set(grgitService);
      task.getBranch().set(publication.getBranch());
    });
  }

  private String getOriginUri(Grgit grgit) {
    return grgit.getRemote().list().stream()
        .filter(remote -> remote.getName().equals("origin"))
        .map(remote -> remote.getUrl())
        .findAny()
        .orElse(null);
  }

  private String getTaskName(GitPublication publication, String task) {
    if ("main".equals(publication.getName())) {
      return "gitPublish" + task;
    } else {
      var capitalizedName = publication.getName().substring(0, 1).toUpperCase() + publication.getName().substring(1);
      return "gitPublish" + capitalizedName + task;
    }
  }
}

@@ -548,16 +548,54 @@
 rovider;
+%0Aimport org.gradle.util.GradleVersion;
 %0A%0Apublic
@@ -4162,24 +4162,219 @@
 tRepoDir());
+%0A%0A      // do not track state added in Gradle 7.3%0A      if (GradleVersion.version(%227.3%22).compareTo(GradleVersion.current()) %3C= 0) %7B%0A        task.doNotTrackState(%22Tracked by Git instead%22);%0A      %7D
 %0A    %7D);%0A  %7D

Fix no such file or directory during commit