"use strict";

async function normalizeGvlCatalogsFromSnapshot(snapshot) {
  const summary = {
    ok: false,
    vendorListVersion: snapshot?.vendorListVersion ?? null,
    snapshotSha256: snapshot?.sha256 ?? null,
    snapshotFetchedAt: snapshot?.fetchedAt ?? null,
    gvlLastUpdated: null,
    catalogsSeen: 0,
    catalogsWritten: 0,
    skipped: 0,
    error: null
  };

  try {
    if (!snapshot) {
      return warnAndReturnGvlCatalogSummary(summary, "missing_snapshot");
    }

    const gvl = parseGvlCatalogRawJson(snapshot.rawJson);

    if (!gvl) {
      return warnAndReturnGvlCatalogSummary(
        summary,
        "invalid_snapshot_raw_json"
      );
    }

    const vendorListVersion =
      gvl.vendorListVersion ?? snapshot.vendorListVersion ?? null;
    const normalizedAt = new Date().toISOString();

    summary.vendorListVersion = vendorListVersion;
    summary.gvlLastUpdated = gvl.lastUpdated ?? null;

    if (vendorListVersion === null || vendorListVersion === undefined) {
      return warnAndReturnGvlCatalogSummary(
        summary,
        "missing_vendor_list_version"
      );
    }

    const recordsByStoreName = buildGvlCatalogRecordsByStoreName({
      gvl: gvl,
      snapshot: snapshot,
      vendorListVersion: vendorListVersion,
      normalizedAt: normalizedAt,
      summary: summary
    });

    summary.catalogsWritten = await putGvlCatalogRecords(recordsByStoreName);
    summary.ok = true;

    return summary;
  } catch (error) {
    summary.error = error?.message ?? String(error);
    console.warn("GVL catalog normalization failed", {
      error: error,
      snapshotSha256: summary.snapshotSha256,
      vendorListVersion: summary.vendorListVersion
    });

    return summary;
  }
}

async function normalizeLatestGvlCatalogs() {
  const summary = {
    ok: false,
    vendorListVersion: null,
    snapshotSha256: null,
    snapshotFetchedAt: null,
    gvlLastUpdated: null,
    catalogsSeen: 0,
    catalogsWritten: 0,
    skipped: 0,
    error: null
  };

  try {
    const snapshot = await getLatestGvlCatalogSnapshot();

    if (!snapshot) {
      return warnAndReturnGvlCatalogSummary(
        summary,
        "no_gvl_snapshots_found"
      );
    }

    return normalizeGvlCatalogsFromSnapshot(snapshot);
  } catch (error) {
    summary.error = error?.message ?? String(error);
    console.warn("Latest GVL catalog normalization failed", error);

    return summary;
  }
}

function parseGvlCatalogRawJson(rawJson) {
  if (!rawJson) {
    return null;
  }

  if (typeof rawJson === "string") {
    try {
      return JSON.parse(rawJson);
    } catch (error) {
      console.warn("GVL snapshot rawJson is not valid JSON", error);
      return null;
    }
  }

  if (typeof rawJson === "object" && !Array.isArray(rawJson)) {
    return rawJson;
  }

  return null;
}

function buildGvlCatalogRecordsByStoreName({
  gvl,
  snapshot,
  vendorListVersion,
  normalizedAt,
  summary
}) {
  const recordsByStoreName = {};

  getGvlCatalogDefinitions().forEach((definition) => {
    const catalog = gvl[definition.gvlField];

    if (catalog === undefined || catalog === null) {
      return;
    }

    if (typeof catalog !== "object" || Array.isArray(catalog)) {
      summary.skipped += 1;
      return;
    }

    Object.entries(catalog).forEach(([catalogKey, catalogEntry]) => {
      summary.catalogsSeen += 1;

      if (
        !catalogEntry ||
        typeof catalogEntry !== "object" ||
        Array.isArray(catalogEntry)
      ) {
        summary.skipped += 1;
        return;
      }

      const catalogId =
        catalogEntry.id ?? parseGvlCatalogNumericId(catalogKey);

      if (catalogId === null || catalogId === undefined) {
        summary.skipped += 1;
        return;
      }

      if (!recordsByStoreName[definition.storeName]) {
        recordsByStoreName[definition.storeName] = [];
      }

      recordsByStoreName[definition.storeName].push(
        buildGvlCatalogRecord({
          definition: definition,
          catalogEntry: catalogEntry,
          catalogId: catalogId,
          vendorListVersion: vendorListVersion,
          snapshot: snapshot,
          gvl: gvl,
          normalizedAt: normalizedAt
        })
      );
    });
  });

  return recordsByStoreName;
}

function getGvlCatalogDefinitions() {
  return [
    {
      entityType: "purpose",
      gvlField: "purposes",
      storeName: VENDORGET_STORE_NAMES.gvlPurposes,
      keySegment: "purpose"
    },
    {
      entityType: "specialPurpose",
      gvlField: "specialPurposes",
      storeName: VENDORGET_STORE_NAMES.gvlSpecialPurposes,
      keySegment: "specialPurpose"
    },
    {
      entityType: "feature",
      gvlField: "features",
      storeName: VENDORGET_STORE_NAMES.gvlFeatures,
      keySegment: "feature"
    },
    {
      entityType: "specialFeature",
      gvlField: "specialFeatures",
      storeName: VENDORGET_STORE_NAMES.gvlSpecialFeatures,
      keySegment: "specialFeature"
    }
  ];
}

function buildGvlCatalogRecord({
  definition,
  catalogEntry,
  catalogId,
  vendorListVersion,
  snapshot,
  gvl,
  normalizedAt
}) {
  return {
    id: `${vendorListVersion}:${definition.keySegment}:${catalogId}`,
    entityType: definition.entityType,
    vendorListVersion: vendorListVersion,
    catalogId: catalogId,
    name: catalogEntry.name ?? null,
    description: catalogEntry.description ?? null,
    descriptionLegal: catalogEntry.descriptionLegal ?? null,
    illustrations: catalogEntry.illustrations ?? null,
    snapshotSha256: snapshot.sha256,
    snapshotFetchedAt: snapshot.fetchedAt ?? null,
    gvlLastUpdated: gvl.lastUpdated ?? null,
    normalizedAt: normalizedAt,
    rawCatalog: catalogEntry
  };
}

function parseGvlCatalogNumericId(value) {
  const numericId = Number(value);

  if (!Number.isFinite(numericId)) {
    return null;
  }

  return numericId;
}

async function putGvlCatalogRecords(recordsByStoreName) {
  const storeNames = Object.keys(recordsByStoreName);

  if (!storeNames.length) {
    return 0;
  }

  const db = await openVendorGetDb();

  storeNames.forEach((storeName) => {
    if (!db.objectStoreNames.contains(storeName)) {
      throw new Error(`missing_gvl_catalog_store:${storeName}`);
    }
  });

  return new Promise((resolve, reject) => {
    const tx = db.transaction(storeNames, "readwrite");
    let written = 0;

    storeNames.forEach((storeName) => {
      const objectStore = tx.objectStore(storeName);

      recordsByStoreName[storeName].forEach((record) => {
        const putRequest = objectStore.put(record);

        putRequest.onsuccess = () => {
          written += 1;
        };
      });
    });

    tx.onerror = () => reject(tx.error);
    tx.onabort = () => reject(tx.error);
    tx.oncomplete = () => resolve(written);
  });
}

async function getLatestGvlCatalogSnapshot() {
  const db = await openVendorGetDb();

  if (!db.objectStoreNames.contains(VENDORGET_STORE_NAMES.gvlSnapshots)) {
    throw new Error("missing_gvl_snapshots_store");
  }

  return new Promise((resolve, reject) => {
    const tx = db.transaction([VENDORGET_STORE_NAMES.gvlSnapshots], "readonly");
    const snapshotsStore = tx.objectStore(VENDORGET_STORE_NAMES.gvlSnapshots);
    const getAllRequest = snapshotsStore.getAll();

    getAllRequest.onerror = () => reject(getAllRequest.error);
    getAllRequest.onsuccess = () => {
      const snapshots = getAllRequest.result ?? [];

      resolve(sortGvlCatalogSnapshotsNewestFirst(snapshots)[0] ?? null);
    };
  });
}

function sortGvlCatalogSnapshotsNewestFirst(snapshots) {
  return snapshots.slice().sort((left, right) => {
    const fetchedAtComparison =
      toGvlCatalogComparableTime(right.fetchedAt) -
      toGvlCatalogComparableTime(left.fetchedAt);

    if (fetchedAtComparison !== 0) {
      return fetchedAtComparison;
    }

    return (
      toGvlCatalogComparableNumber(right.vendorListVersion) -
      toGvlCatalogComparableNumber(left.vendorListVersion)
    );
  });
}

function toGvlCatalogComparableTime(value) {
  const timestamp = Date.parse(value ?? "");

  return Number.isNaN(timestamp) ? 0 : timestamp;
}

function toGvlCatalogComparableNumber(value) {
  const numberValue = Number(value);

  return Number.isFinite(numberValue) ? numberValue : 0;
}

function warnAndReturnGvlCatalogSummary(summary, error) {
  summary.error = error;

  console.warn("GVL catalog normalization skipped", {
    error: error,
    snapshotSha256: summary.snapshotSha256,
    vendorListVersion: summary.vendorListVersion
  });

  return summary;
}

globalThis.normalizeGvlCatalogsFromSnapshot = normalizeGvlCatalogsFromSnapshot;
globalThis.normalizeLatestGvlCatalogs = normalizeLatestGvlCatalogs;
