import { findFiles, loadJson } from "@embeddable.com/sdk-utils";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { getSDKVersions, hrtimeToISO8601 } from "./utils";
import { ResolvedEmbeddableConfig } from "./defineConfig";

export default async (ctx: ResolvedEmbeddableConfig) => {
  await extractBuild(ctx);

  await removeObsoleteDir(ctx.client.buildDir);

  await moveBuildTOBuildDir(ctx);
};

type ManifestArgs = {
  ctx: ResolvedEmbeddableConfig;
  typesFileName: string;
  metaFileName: string;
  editorsMetaFileName: string;
  stencilWrapperFileName: string;
};

export async function createManifest({
  ctx,
  typesFileName,
  metaFileName,
  editorsMetaFileName,
  stencilWrapperFileName,
}: ManifestArgs) {
  const sdkVersions = await getSDKVersions();
  // identify user's package manager and its version
  let packageManager = "npm";
  if (process.env.npm_config_user_agent?.includes("yarn")) {
    packageManager = "yarn";
  }
  if (process.env.npm_config_user_agent?.includes("pnpm")) {
    packageManager = "pnpm";
  }

  const packageManagerVersion =
    process.env.npm_config_user_agent?.match(/(\d+\.\d+\.\d+)/)?.[0] ||
    "unknown";

  const globalHooks = await loadJson(
    path.resolve(ctx.client.buildDir, "globalHooks.json"),
  );

  // write manifest file with files with hash
  const manifest = {
    entryFiles: {
      "embeddable-types.js": typesFileName,
      "embeddable-components-meta.js": metaFileName,
      "embeddable-editors-meta.js": editorsMetaFileName,
      "embeddable-wrapper.esm.js": stencilWrapperFileName,
    },
    metadata: {
      nodeVersion: process.version,
      platform: process.platform,
      bundleHash: ctx.client.bundleHash,
      sdkVersions,
      packageManager,
      packageManagerVersion,
      globalHooks,
      metrics: {
        buildTime: hrtimeToISO8601(ctx.buildTime),
      },
      origin: ctx.dev?.watch ? "dev" : "push",
    },
  };

  await fs.writeFile(
    path.join(ctx.client.tmpDir, "embeddable-manifest.json"),
    JSON.stringify(manifest),
  );
}

async function extractBuild(ctx: ResolvedEmbeddableConfig) {
  const stencilBuildFiles = await findFiles(
    ctx.client.stencilBuild,
    /embeddable-wrapper.esm-[a-z0-9]+\.js/,
  );

  const [[, stencilWrapperFilePath]] = stencilBuildFiles || [];

  const stencilWrapperFileName = path.basename(stencilWrapperFilePath);
  await fs.rename(
    path.resolve(ctx.client.buildDir, ctx.client.stencilBuild),
    ctx.client.tmpDir,
  );

  const typesBuildFiles = await findFiles(
    ctx.client.buildDir,
    /embeddable-types-[a-z0-9]+\.js/,
  );

  const [[, typesFilePath]] = typesBuildFiles || [];

  const typesFileName = path.basename(typesFilePath);
  await fs.rename(typesFilePath, path.join(ctx.client.tmpDir, typesFileName));

  const metaBuildFiles = await findFiles(
    ctx.client.buildDir,
    /embeddable-components-meta-[a-z0-9]+\.js/,
  );

  const [[, metaFilePath]] = metaBuildFiles || [];
  const metaFileName = path.basename(metaFilePath);
  await fs.rename(metaFilePath, path.join(ctx.client.tmpDir, metaFileName));

  const editorsMetaBuildFiles = await findFiles(
    ctx.client.buildDir,
    /embeddable-editors-meta-[a-z0-9]+\.js/,
  );

  const [[, editorsMetaFilePath]] = editorsMetaBuildFiles || [];
  const editorsMetaFileName = path.basename(editorsMetaFilePath);
  await fs.rename(
    editorsMetaFilePath,
    path.join(ctx.client.tmpDir, editorsMetaFileName),
  );

  const themeFile =
    (await findFiles(ctx.client.buildDir, /embeddable-theme-[0-9a-f]+\.js/)) ||
    [];

  for (const [, themeFilePath] of themeFile) {
    const themeFilePathFileName = path.basename(themeFilePath);
    await fs.rename(
      themeFilePath,
      path.join(ctx.client.tmpDir, themeFilePathFileName),
    );
  }

  const lifecycleFiles =
    (await findFiles(
      ctx.client.buildDir,
      /embeddable-lifecycle(?:-[0-9a-f]+)?\.js/,
    )) || [];

  for (const [, lifecycleFilePath] of lifecycleFiles) {
    const lifecycleFilePathFileName = path.basename(lifecycleFilePath);
    await fs.rename(
      lifecycleFilePath,
      path.join(ctx.client.tmpDir, lifecycleFilePathFileName),
    );
  }

  await createManifest({
    ctx,
    typesFileName,
    metaFileName,
    editorsMetaFileName,
    stencilWrapperFileName,
  });
}

async function removeObsoleteDir(dir: string) {
  await fs.rm(dir, { recursive: true });
}

async function moveBuildTOBuildDir(ctx: ResolvedEmbeddableConfig) {
  await fs.rename(ctx.client.tmpDir, ctx.client.buildDir);
}
