import path from "path";
import Crate from "./crate";
import Target from "./target";
import BuildSettings from "./build-settings";
import log from "./log";
import { execFile, spawn } from "./async/child_process";
import * as rust from "./rust";

export type ProjectOptions = {
  crate: string;
  targetDirectory: string;
};

/** A Neon project and its directory tree. */
export default class Project {
  readonly root: string;
  readonly targetDirectory: string;
  readonly crate: Crate;

  private constructor(root: string, options: ProjectOptions) {
    let { crate, targetDirectory } = options;
    this.root = root;
    this.targetDirectory = targetDirectory;
    this.crate = new Crate(this, { subdirectory: crate });
  }

  static async create(
    root: string,
    options: Partial<ProjectOptions> = {}
  ): Promise<Project> {
    let { crate = "native" } = options;
    const { stdout } = await execFile(
      "cargo",
      ["metadata", "--format-version=1", "--no-deps"],
      {
        cwd: path.join(root, crate),
      }
    );
    const targetDirectory: string = JSON.parse(stdout).target_directory;

    return new Project(root, {
      targetDirectory,
      crate,
      ...options,
    });
  }

  async build(toolchain: rust.Toolchain, release: boolean, args: string[]) {
    let target = new Target(this.crate, { release: release });
    let settings = BuildSettings.current(toolchain);

    // 1. Force a rebuild if build settings have changed.
    if (!target.inState(settings)) {
      log("forcing rebuild for new build settings");
      await target.clean();
    }

    // 2. Build the dylib.
    log("running cargo");
    await target.build(toolchain, settings, args);

    // 3. Copy the dylib as the main addon file.
    log(
      "generating " + path.join(this.crate.subdirectory, this.crate.nodefile)
    );
    await this.crate.finish(target.dylib);
  }

  async clean() {
    // 1. Do a `cargo clean` to delete the `target` directory.
    log("cargo clean");
    await spawn("cargo", ["clean"], { cwd: this.crate.root, stdio: "inherit" });

    // 2. Remove the main addon file.
    log("remove " + path.join(this.crate.subdirectory, this.crate.nodefile));
    await this.crate.removeAddon();

    // 3. Clear the artifacts file.
    this.crate.resetArtifacts();
    this.crate.saveArtifacts();
  }
}
