import { ArkErrors, type } from "arktype";
import type { LibraryProvider } from "../provider";
import { VersionNotFoundError } from "../provider";
import { LibraryBackend } from "~/prisma/client/enums";
import fs from "fs";
import path from "path";
import { fsStats } from "~/server/internal/utils/files";
import { dropletInterface } from "../../services/torrential/droplet-interface";

export const FlatFilesystemProviderConfig = type({
  baseDir: "string",
});

export class FlatFilesystemProvider implements LibraryProvider<
  typeof FlatFilesystemProviderConfig.infer
> {
  private config: typeof FlatFilesystemProviderConfig.infer;
  private myId: string;

  constructor(rawConfig: unknown, id: string) {
    const config = FlatFilesystemProviderConfig(rawConfig);
    if (config instanceof ArkErrors) {
      throw new Error(
        `Failed to create filesystem provider: ${config.summary}`,
      );
    }

    this.myId = id;
    this.config = config;

    if (!fs.existsSync(this.config.baseDir))
      throw "Base directory does not exist.";
  }

  type() {
    return LibraryBackend.FlatFilesystem;
  }
  id() {
    return this.myId;
  }

  /**
   * These are basically our versions, but also our games.
   * @returns list of valid games
   */
  async listGames() {
    const versionDirs = fs.readdirSync(this.config.baseDir);
    const validVersionDirs = [];

    for (const versionDir of versionDirs) {
      const fullDir = path.join(this.config.baseDir, versionDir);
      const valid = await dropletInterface.hasBackend(fullDir);
      if (!valid) continue;

      validVersionDirs.push(versionDir);
    }
    return validVersionDirs;
  }

  /**
   * Doesn't do anything, just returns "default"
   * @param _game Ignored
   * @returns
   */
  async listVersions(_game: string) {
    return ["default"];
  }

  async versionReaddir(game: string, _version: string) {
    const versionDir = path.join(this.config.baseDir, game);
    if (!fs.existsSync(versionDir)) throw new VersionNotFoundError();
    return await dropletInterface.listFiles(versionDir);
  }

  async generateDropletManifest(
    game: string,
    _version: string,
    progress: (v: number) => void,
    log: (v: string) => void,
  ) {
    const versionDir = path.join(this.config.baseDir, game);
    if (!fs.existsSync(versionDir)) throw new VersionNotFoundError();
    const manifest = await dropletInterface.generateDropletManifest(
      versionDir,
      progress,
      log,
    );
    return manifest;
  }

  async peekFile(game: string, _version: string, filename: string) {
    const filepath = path.join(this.config.baseDir, game);
    if (!fs.existsSync(filepath)) return undefined;
    const stat = await dropletInterface.peekFile(filepath, filename);
    return { size: Number(stat) };
  }

  fsStats() {
    return fsStats(this.config.baseDir);
  }
}
