import * as fs from "fs";
import { cosmiconfig } from "cosmiconfig";
import config from ".";
import type { ConfigFileType } from "./fileAndContent";
import { join as pathJoin, dirname as pathDirname } from "path";

const guardAsOptionalString = (string: unknown) => (typeof string === "string" ? string : undefined);

const guardAsString = (param: { string: unknown; errorMessage: string }) => {
    if (typeof param.string === "string") {
        return param.string;
    }
    throw new Error(param.errorMessage);
};

export type DenoifyParams = {
    replacer?: string;
    ports?: {
        [portName: string]: string;
    };
    out?: string;
    index?: string;
    includes?: (
        | string
        | {
              src: string;
              destDir?: string;
              destBasename?: string;
          }
    )[];
};

export function parseAsDenoifyParams(denoifyParams: any): DenoifyParams | undefined {
    if (denoifyParams === undefined) {
        return undefined;
    }

    const generateErrorMessage = ({ name, type, param }: { name: string; type: string; param: unknown }) =>
        [
            "Denoify configuration Error",
            `Expect ${name} to be ${type}, got ${param} instead`,
            "See: https://github.com/garronej/my_dummy_npm_and_deno_module"
        ].join("\n");

    const { includes } = denoifyParams;
    return {
        "replacer": guardAsOptionalString(denoifyParams.replacer),
        "out": guardAsOptionalString(denoifyParams.out),
        "index": guardAsOptionalString(denoifyParams.index),
        "includes": !Array.isArray(includes)
            ? undefined
            : includes.map(element => {
                  const result = guardAsOptionalString(element);
                  if (result !== undefined) {
                      return result;
                  }
                  if (typeof element !== "object") {
                      throw new Error(
                          generateErrorMessage({
                              "param": includes,
                              "name": "element in includes",
                              "type": "object with type: { destDir: string?, destBasename: string?, src: string }"
                          })
                      );
                  }
                  return {
                      "destDir": guardAsOptionalString(element.destDir),
                      "destBasename": guardAsOptionalString(element.destBasename),
                      "src": guardAsString({
                          string: element.src,
                          errorMessage: generateErrorMessage({
                              "type": "string",
                              "param": element.src,
                              "name": "src in includes array"
                          })
                      })
                  };
              }),
        "ports":
            denoifyParams.ports === undefined || denoifyParams.ports === null
                ? undefined
                : Object.entries(denoifyParams.ports).reduce(
                      (prev, [portName, string]) => ({
                          ...prev,
                          [portName]: guardAsString({
                              string,
                              errorMessage: generateErrorMessage({
                                  "param": string,
                                  "type": "string",
                                  "name": "value of ports object"
                              })
                          })
                      }),
                      {}
                  )
    };
}

export function parseAsDenoifyConfig({ configFileType }: { configFileType: ConfigFileType }) {
    switch (configFileType.type) {
        case "absent":
            return undefined;
        case "json": {
            const parsed = JSON.parse(configFileType.configFileRawContent);
            return parseAsDenoifyParams(configFileType.configFileBasename !== config.packageJson ? parsed : parsed.denoify);
        }
        case "js": {
            const cacheFilePath = pathJoin(process.cwd(), "node_modules", ".cache", "denoify", "cacheDirPath", "config.js");

            {
                const dirPath = pathDirname(cacheFilePath);

                if (!fs.existsSync(dirPath)) {
                    fs.mkdirSync(dirPath, {
                        "recursive": true
                    });
                }
            }
            fs.writeFileSync(cacheFilePath, configFileType.configFileRawContent);
            // cosmiconfig internally uses import-fresh to parse JS config
            // import-fresh only support commonjs export, so we can use require
            const denoifyParams = parseAsDenoifyParams(require(cacheFilePath));

            fs.rmSync(cacheFilePath, { "force": true });

            return denoifyParams;
        }
    }
}

export async function getDenoifyParamsWithCosmiconfig() {
    const explorer = cosmiconfig("denoify");
    const search = await explorer.search();
    if (search) {
        console.log(`Configurations from ${search.filepath} are used`);
    }
    return parseAsDenoifyParams(search?.config ?? undefined);
}
