import * as FS from "fs";
import * as FSE from "fs-extra";
import * as Path from "path";
import {
   EnumDefinition,
   IR,
   ServiceDefinition,
   Step,
   TypeDefinition,
} from "./ir";

export abstract class CompileTarget<T = any> {
   abstract name: string;
   constructor(
      private outputFolder: string,
      protected options: T & { allow_bytes: boolean }
   ) {
      if (!FS.existsSync(outputFolder)) {
         FS.mkdirSync(outputFolder, {
            recursive: true,
         });
      }
   }

   abstract start(): void;

   abstract generateType(definition: TypeDefinition): void;

   abstract generateEnum(definition: EnumDefinition): void;

   abstract generateService(definition: ServiceDefinition): void;

   abstract finalize(steps: Step[]): void;

   protected writeFile(name: string, content: string | Promise<string>) {
      let resPath = Path.join(this.outputFolder, name);
      let resDir = Path.dirname(resPath);
      if (!FS.existsSync(resDir)) FS.mkdirSync(resDir, { recursive: true });
      if (content instanceof Promise) {
         content.then((res) => FS.writeFileSync(resPath, res));
      } else {
         FS.writeFileSync(resPath, content);
      }
   }

   protected getTemplate(name: string): string {
      let path = Path.join(__dirname, "../templates/" + name);
      let file = FS.readFileSync(path, "utf-8");

      const splitted = file.split("\n");
      let res = [];
      let ignore = false;
      for (const line of splitted) {
         if (ignore) {
            ignore = false;
         } else if (line.trim().startsWith("//@template-ignore")) {
            ignore = true;
         } else {
            res.push(line);
         }
      }

      return res.join("\n");
   }

   protected loadTemplateFolder(name: string) {
      let root = Path.join(__dirname, "../templates/", name);

      FSE.copySync(root, this.outputFolder, {});
   }
}

export default function compile(ir: IR, target: CompileTarget) {
   // Types are verified. They are now ready to be compiled to targets

   // setState("Building for target: " + target.name);
   target.start();
   ir.steps.forEach((step) => {
      const [type, def] = step;
      if (type == "type") target.generateType(def as TypeDefinition);
      else if (type == "enum") target.generateEnum(def as EnumDefinition);
      else if (type == "service")
         target.generateService(def as ServiceDefinition);
   });
   if (target.finalize) target.finalize(ir.steps);
}
