import type { RegexProjectV1 } from "./project.types";
import { assertProjectDocumentWithinLimit } from "./project-limits";
import { parseRegexProject } from "./project.validation";

const DATABASE_NAME = "de.add-ideas.regex-tools";
const STORE_NAME = "projects";
const UPDATED_AT_INDEX = "updatedAt";
const DATABASE_VERSION = 2;

function database(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
    request.onupgradeneeded = () => {
      const db = request.result;
      const store = db.objectStoreNames.contains(STORE_NAME)
        ? request.transaction!.objectStore(STORE_NAME)
        : db.createObjectStore(STORE_NAME, { keyPath: "id" });
      if (!store.indexNames.contains(UPDATED_AT_INDEX)) {
        store.createIndex(UPDATED_AT_INDEX, "updatedAt");
      }
    };
    request.onsuccess = () => resolve(request.result);
    request.onerror = () =>
      reject(request.error ?? new Error("IndexedDB could not be opened."));
  });
}

export async function saveProjectLocally(
  project: RegexProjectV1,
  includeTestText: boolean,
  includeUnitTestSubjects = false,
): Promise<void> {
  const persisted: RegexProjectV1 = {
    ...project,
    updatedAt: new Date().toISOString(),
    testText: includeTestText
      ? { included: true, value: project.testText?.value ?? "" }
      : { included: false },
    tests: includeUnitTestSubjects
      ? project.tests
      : project.tests.map((test) => ({ ...test, subject: "" })),
  };
  assertProjectDocumentWithinLimit(persisted);
  const db = await database();
  try {
    await new Promise<void>((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, "readwrite");
      transaction.objectStore(STORE_NAME).put(persisted);
      transaction.oncomplete = () => resolve();
      transaction.onerror = () =>
        reject(transaction.error ?? new Error("Project save failed."));
    });
  } finally {
    db.close();
  }
}

export async function loadProjectLocally(
  projectId: string,
): Promise<RegexProjectV1 | null> {
  const db = await database();
  try {
    const value = await new Promise<unknown>((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, "readonly");
      const request = transaction.objectStore(STORE_NAME).get(projectId);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () =>
        reject(request.error ?? new Error("Project load failed."));
    });
    if (value === undefined) return null;
    assertProjectDocumentWithinLimit(value);
    return parseRegexProject(value);
  } finally {
    db.close();
  }
}

export async function loadMostRecentProject(): Promise<RegexProjectV1 | null> {
  const db = await database();
  try {
    const value = await new Promise<unknown>((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, "readonly");
      const request = transaction
        .objectStore(STORE_NAME)
        .index(UPDATED_AT_INDEX)
        .openCursor(null, "prev");
      request.onsuccess = () => resolve(request.result?.value);
      request.onerror = () =>
        reject(request.error ?? new Error("Project load failed."));
    });
    if (value === undefined) return null;
    assertProjectDocumentWithinLimit(value);
    return parseRegexProject(value);
  } finally {
    db.close();
  }
}
