package com.github.sormuras.bach.internal;

import com.sun.source.tree.ModuleTree;
import com.sun.source.tree.RequiresTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.module.ModuleDescriptor;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.spi.ToolProvider;

public class ModuleDescriptors {

  private static final Map<Path, ModuleDescriptor> DESCRIPTORS = new ConcurrentHashMap<>();

  public static ModuleDescriptor parse(Path info) {
    if (!Path.of("module-info.java").equals(info.getFileName()))
      throw new IllegalArgumentException("Path must end with 'module-info.java': " + info);

    var javac = ToolProvider.findFirst("javac").orElseThrow();
    var text = new StringWriter();
    var writer = new PrintWriter(text);
    javac.run(writer, writer, "-proc:only", "-Xplugin:" + Parser.NAME, info.toString());
    var descriptor = DESCRIPTORS.get(info);
    if (descriptor == null) throw new IllegalStateException("Descriptor not found for: " + info + "\n" + text);
    return descriptor;
  }

  public record Parser() implements Plugin, TaskListener {

    public static final String NAME = "ModuleDescriptors.Parser";

    @Override
    public String getName() {
      return NAME;
    }

    @Override
    public void init(JavacTask task, String... args) {
      task.addTaskListener(this);
    }

    @Override
    public void finished(TaskEvent event) {
      if (event.getKind() != TaskEvent.Kind.PARSE) return;
      var unit = event.getCompilationUnit();
      if (unit == null) return;
      var path = Path.of(unit.getSourceFile().getName());
      for (var declaration : unit.getTypeDecls()) {
        if (declaration instanceof ModuleTree module) DESCRIPTORS.put(path, describe(module));
      }
    }

    private ModuleDescriptor describe(ModuleTree module) {
      var moduleName = module.getName().toString();
      var builder = ModuleDescriptor.newModule(moduleName);
      for (var directive : module.getDirectives()) {
        if (directive instanceof RequiresTree requires) {
          var requiresModuleName = requires.getModuleName().toString();
          builder.requires(requiresModuleName);
        }
      }
      return builder.build();
    }
  }

  /** Hidden default constructor. */
  private ModuleDescriptors() {}
}

@@ -318,32 +318,90 @@
 o.StringWriter;%0A
+import java.lang.module.InvalidModuleDescriptorException;%0A
 import java.lang
@@ -558,16 +558,67 @@
 vider;%0A%0A
+/** %7B@link ModuleDescriptor%7D-related utilities. */%0A
 public c
@@ -736,16 +736,351 @@
 p%3C%3E();%0A%0A
+  /**%0A   * Reads the source form of a module declaration from a file as a module descriptor.%0A   *%0A   * @param info the path to a %7B@code module-info.java%7D file to parse%0A   * @return the module descriptor%0A   * @implNote For the time being, only the %7B@code name%7D of a module and its %7B@code requires%7D%0A   *     directives are parsed.%0A   */%0A
   public
@@ -1579,16 +1579,22 @@
 == null)
+%0A     
  throw n
@@ -1601,19 +1601,30 @@
 ew I
-llegalState
+nvalidModuleDescriptor
 Exce
@@ -1645,21 +1645,15 @@
 tor 
-not found for
+is null
 : %22 
@@ -1703,16 +1703,108 @@
 r;%0A  %7D%0A%0A
+  /** A %7B@code javac%7D plug-in that transforms module tree nodes into module descriptors. */%0A
   public

Document `ModuleDescriptor`-related utilities