export interface UploadFileIdentity {
  name: string;
  size: number;
  lastModified: number;
}

export interface UploadCheckpoint {
  uploadId: string;
  expiresAt: string;
}

interface KeyValueBackend {
  get(key: string): Promise<unknown>;
  put(value: unknown, key: string): Promise<unknown>;
  delete(key: string): Promise<unknown>;
}

export function fileCheckpointKey(file: UploadFileIdentity): string {
  return `${file.name}::${file.size}::${file.lastModified}`;
}

function isCheckpoint(value: unknown): value is UploadCheckpoint {
  return Boolean(
    value &&
      typeof value === "object" &&
      "uploadId" in value &&
      typeof value.uploadId === "string" &&
      "expiresAt" in value &&
      typeof value.expiresAt === "string" &&
      Number.isFinite(Date.parse(value.expiresAt)),
  );
}

export class UploadCheckpointStore {
  private readonly backend: KeyValueBackend;
  private readonly now: () => Date;

  constructor(
    backend: KeyValueBackend,
    now: () => Date = () => new Date(),
  ) {
    this.backend = backend;
    this.now = now;
  }

  async save(file: UploadFileIdentity, checkpoint: UploadCheckpoint): Promise<void> {
    if (!isCheckpoint(checkpoint)) throw new Error("上传断点信息无效。");
    await this.backend.put(checkpoint, fileCheckpointKey(file));
  }

  async get(file: UploadFileIdentity): Promise<UploadCheckpoint | null> {
    const key = fileCheckpointKey(file);
    const value = await this.backend.get(key);
    if (!isCheckpoint(value)) return null;
    if (Date.parse(value.expiresAt) <= this.now().getTime()) {
      await this.backend.delete(key);
      return null;
    }
    return value;
  }

  async clear(file: UploadFileIdentity): Promise<void> {
    await this.backend.delete(fileCheckpointKey(file));
  }
}

export async function createBrowserUploadCheckpointStore(): Promise<UploadCheckpointStore> {
  const { openDB } = await import("idb");
  const database = await openDB("hearbooks-uploads", 1, {
    upgrade(db) {
      if (!db.objectStoreNames.contains("sessions")) db.createObjectStore("sessions");
    },
  });
  return new UploadCheckpointStore({
    get: (key) => database.get("sessions", key),
    put: (value, key) => database.put("sessions", value, key),
    delete: (key) => database.delete("sessions", key),
  });
}
