import type { GameVersion } from "~/prisma/client";
import prisma from "../db/database";

export type DropChunk = {
  permissions: number;
  ids: string[];
  checksums: string[];
  lengths: string[];
};

export type DropManifest = {
  [key: string]: DropChunk;
};

export type DropManifestMetadata = {
  manifest: DropManifest;
  versionName: string;
};

export type DropGeneratedManifest = DropManifest & {
  [key: string]: { versionName: string };
};

class ManifestGenerator {
  private static generateManifestFromMetadata(
    rootManifest: DropManifestMetadata,
    ...overlays: DropManifestMetadata[]
  ): DropGeneratedManifest {
    if (overlays.length == 0) {
      return Object.fromEntries(
        Object.entries(rootManifest.manifest).map(([key, value]) => {
          return [
            key,
            Object.assign({}, value, { versionName: rootManifest.versionName }),
          ];
        }),
      );
    }

    // Recurse in verse order through versions, skipping files that already exist.
    const versions = [...overlays.reverse(), rootManifest];
    const manifest: DropGeneratedManifest = {};
    for (const version of versions) {
      for (const [filename, chunk] of Object.entries(version.manifest)) {
        if (manifest[filename]) continue;
        manifest[filename] = Object.assign({}, chunk, {
          versionName: version.versionName,
        });
      }
    }

    return manifest;
  }

  // Local function because eventual caching
  async generateManifest(gameId: string, versionName: string) {
    const versions: GameVersion[] = [];

    const baseVersion = await prisma.gameVersion.findUnique({
      where: {
        gameId_versionName: {
          gameId: gameId,
          versionName: versionName,
        },
      },
    });
    if (!baseVersion) return undefined;
    versions.push(baseVersion);

    // Collect other versions if this is a delta
    if (baseVersion.delta) {
      // Start at the same index minus one, and keep grabbing them
      // until we run out or we hit something that isn't a delta
      // eslint-disable-next-line no-constant-condition
      for (let i = baseVersion.versionIndex - 1; true; i--) {
        const currentVersion = await prisma.gameVersion.findFirst({
          where: {
            gameId: gameId,
            versionIndex: i,
            platform: baseVersion.platform,
          },
        });
        if (!currentVersion) return undefined;
        versions.push(currentVersion);
        if (!currentVersion.delta) break;
      }
    }
    const leastToMost = versions.reverse();
    const metadata: DropManifestMetadata[] = leastToMost.map((e) => {
      return {
        manifest: JSON.parse(
          e.dropletManifest?.toString() ?? "{}",
        ) as DropManifest,
        versionName: e.versionName,
      };
    });

    const manifest = ManifestGenerator.generateManifestFromMetadata(
      metadata[0],
      ...metadata.slice(1),
    );

    return manifest;
  }
}

export const manifestGenerator = new ManifestGenerator();
export default manifestGenerator;
