(function (root, factory) {
  "use strict";

  const comparison = typeof module === "object" && module.exports
    ? require("./comparison-workflow.js")
    : root?.MbnComparisonWorkflow;
  const api = Object.freeze(factory(comparison));
  if (typeof module === "object" && module.exports) module.exports = api;
  if (root) root.MbnComparisonImportWorkflow = api;
})(typeof globalThis === "object" ? globalThis : this, function (comparison) {
  "use strict";

  if (!comparison) throw new Error("Comparison workflow dependency is unavailable.");

  const IMPORT_SCHEMA_VERSION = 1;
  const STORAGE_ENVELOPE_SCHEMA_VERSION = 1;
  const MAXIMUM_PAIRS = 500;
  const requestIdPattern = /^[A-Za-z0-9_-]{1,128}$/;
  const digestPattern = /^[A-F0-9]{64}$/;
  const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,7})?(?:Z|[+-]\d{2}:\d{2})$/;
  const controlCharacterPattern = /[\u0000-\u001f\u007f]/u;
  const importedRecordIdPattern = /^legacy-compare-[a-f0-9]{12}-[1-9][0-9]{0,2}$/u;

  function hasOnlyKeys(value, expected) {
    if (!value || typeof value !== "object" || Array.isArray(value)) return false;
    const keys = Object.keys(value).sort();
    const wanted = [...expected].sort();
    return keys.length === wanted.length && keys.every((key, index) => key === wanted[index]);
  }

  function safeText(value, maximumLength) {
    return typeof value === "string" && value.length > 0 && value.length <= maximumLength &&
      value === value.trim() && !controlCharacterPattern.test(value)
      ? value
      : null;
  }

  function normalizeWireTarget(value) {
    if (!value || typeof value !== "object" || Array.isArray(value) ||
        typeof value.kind !== "string") return null;
    let candidate;
    switch (value.kind) {
      case "market-target":
        if (!hasOnlyKeys(value, ["kind", "target"]) || !safeText(value.target, 64)) return null;
        candidate = { kind: value.kind, target: value.target };
        break;
      case "krx-stock":
      case "nxt-stock":
        if (!hasOnlyKeys(value, ["kind", "market", "stockName", "stockCode"]) ||
            !safeText(value.market, 8) || !safeText(value.stockName, 200) ||
            !safeText(value.stockCode, 32)) return null;
        candidate = {
          kind: value.kind,
          market: value.market,
          stockName: value.stockName,
          stockCode: value.stockCode
        };
        break;
      case "world-stock":
        if (!hasOnlyKeys(value, ["kind", "inputName", "symbol", "nation"]) ||
            !safeText(value.inputName, 200) || !safeText(value.symbol, 64) ||
            !safeText(value.nation, 2)) return null;
        candidate = {
          kind: value.kind,
          inputName: value.inputName,
          displayName: value.inputName,
          symbol: value.symbol,
          nation: value.nation
        };
        break;
      default:
        return null;
    }

    return comparison.normalizeTarget(candidate);
  }

  function normalizeImportResponse(value, expectedRequestId) {
    if (!requestIdPattern.test(String(expectedRequestId || "")) ||
        !hasOnlyKeys(value, ["requestId", "sourceSha256", "sourceRowCount", "retrievedAt", "pairs"]) ||
        value.requestId !== expectedRequestId ||
        typeof value.sourceSha256 !== "string" || !digestPattern.test(value.sourceSha256) ||
        !Number.isInteger(value.sourceRowCount) || value.sourceRowCount < 1 ||
        value.sourceRowCount > MAXIMUM_PAIRS ||
        typeof value.retrievedAt !== "string" || !isoDatePattern.test(value.retrievedAt) ||
        !Number.isFinite(Date.parse(value.retrievedAt)) ||
        !Array.isArray(value.pairs) || value.pairs.length !== value.sourceRowCount) return null;

    const pairs = [];
    const signatures = new Set();
    for (let index = 0; index < value.pairs.length; index += 1) {
      const raw = value.pairs[index];
      if (!hasOnlyKeys(raw, ["rowNumber", "first", "second"]) ||
          raw.rowNumber !== index + 1) return null;
      const first = normalizeWireTarget(raw.first);
      const second = normalizeWireTarget(raw.second);
      const pair = comparison.normalizePair([first, second]);
      if (!pair) return null;
      const signature = pair.map(comparison.targetKey).join("\u001f");
      if (!signature || signatures.has(signature)) return null;
      signatures.add(signature);
      pairs.push(Object.freeze({ rowNumber: index + 1, pair }));
    }

    return Object.freeze({
      requestId: value.requestId,
      sourceSha256: value.sourceSha256,
      sourceRowCount: value.sourceRowCount,
      retrievedAt: value.retrievedAt,
      pairs: Object.freeze(pairs)
    });
  }

  function pairSignature(value) {
    const pair = comparison.normalizePair(value);
    return pair ? pair.map(comparison.targetKey).join("\u001f") : null;
  }

  function mergeImport(currentValue, importValue) {
    const current = comparison.normalizePairList(currentValue);
    if (!importValue || !Array.isArray(importValue.pairs) ||
        !digestPattern.test(String(importValue.sourceSha256 || "")) ||
        !Number.isInteger(importValue.sourceRowCount) ||
        importValue.sourceRowCount !== importValue.pairs.length ||
        importValue.sourceRowCount < 1 || importValue.sourceRowCount > MAXIMUM_PAIRS ||
        current.pairs.length + importValue.pairs.length > MAXIMUM_PAIRS) {
      throw new RangeError("The legacy comparison import cannot fit the saved-pair schema.");
    }

    const ids = new Set(current.pairs.map(record => record.id));
    const signatures = new Set(current.pairs.map(record => pairSignature(record.pair)));
    let merged = current;
    for (let index = 0; index < importValue.pairs.length; index += 1) {
      const imported = importValue.pairs[index];
      if (imported?.rowNumber !== index + 1) {
        throw new TypeError("The legacy comparison import row order is invalid.");
      }
      const signature = pairSignature(imported?.pair);
      if (!signature || signatures.has(signature)) {
        throw new RangeError("The legacy comparison import contains an existing pair.");
      }

      const id = `legacy-compare-${importValue.sourceSha256.slice(0, 12).toLocaleLowerCase("en-US")}-${imported.rowNumber}`;
      if (ids.has(id)) {
        throw new RangeError("The legacy comparison import id already exists.");
      }

      const record = comparison.createPairRecord(id, imported.pair);
      if (!record) throw new TypeError("The legacy comparison import pair is invalid.");
      merged = comparison.appendPair(merged, record);
      ids.add(id);
      signatures.add(signature);
    }

    return merged;
  }

  function createMarker(importValue, importedAt = new Date().toISOString()) {
    if (!importValue || !digestPattern.test(String(importValue.sourceSha256 || "")) ||
        !Number.isInteger(importValue.sourceRowCount) || importValue.sourceRowCount < 1 ||
        importValue.sourceRowCount > MAXIMUM_PAIRS ||
        typeof importedAt !== "string" || !isoDatePattern.test(importedAt) ||
        !Number.isFinite(Date.parse(importedAt))) return null;
    return Object.freeze({
      schemaVersion: IMPORT_SCHEMA_VERSION,
      sourceSha256: importValue.sourceSha256,
      importedPairCount: importValue.sourceRowCount,
      importedAt
    });
  }

  function normalizeMarker(value) {
    if (!hasOnlyKeys(value, ["schemaVersion", "sourceSha256", "importedPairCount", "importedAt"]) ||
        value.schemaVersion !== IMPORT_SCHEMA_VERSION ||
        !digestPattern.test(String(value.sourceSha256 || "")) ||
        !Number.isInteger(value.importedPairCount) || value.importedPairCount < 1 ||
        value.importedPairCount > MAXIMUM_PAIRS ||
        typeof value.importedAt !== "string" || !isoDatePattern.test(value.importedAt) ||
        !Number.isFinite(Date.parse(value.importedAt))) return null;
    return Object.freeze({
      schemaVersion: IMPORT_SCHEMA_VERSION,
      sourceSha256: value.sourceSha256,
      importedPairCount: value.importedPairCount,
      importedAt: value.importedAt
    });
  }

  function normalizeExactPairList(value) {
    if (!hasOnlyKeys(value, ["schemaVersion", "pairs"]) || !Array.isArray(value.pairs) ||
        value.pairs.length > MAXIMUM_PAIRS || value.pairs.some(record =>
          !hasOnlyKeys(record, ["id", "pair"]))) return null;
    const normalized = comparison.normalizePairList(value);
    if (normalized.pairs.length !== value.pairs.length ||
        normalized.pairs.some((record, index) => record.id !== value.pairs[index].id)) return null;
    return normalized;
  }

  function normalizeStorageEnvelope(value) {
    if (!hasOnlyKeys(value, ["schemaVersion", "pairList", "legacyImportMarker"]) ||
        value.schemaVersion !== STORAGE_ENVELOPE_SCHEMA_VERSION) return null;
    const pairList = normalizeExactPairList(value.pairList);
    const marker = value.legacyImportMarker === null
      ? null
      : normalizeMarker(value.legacyImportMarker);
    if (!pairList || (value.legacyImportMarker !== null && !marker) ||
        (!marker && pairList.pairs.some(record => importedRecordIdPattern.test(record.id)))) {
      return null;
    }
    return Object.freeze({
      schemaVersion: STORAGE_ENVELOPE_SCHEMA_VERSION,
      pairList,
      legacyImportMarker: marker
    });
  }

  function createStorageEnvelope(pairList, marker = null) {
    return normalizeStorageEnvelope({
      schemaVersion: STORAGE_ENVELOPE_SCHEMA_VERSION,
      pairList,
      legacyImportMarker: marker
    });
  }

  return {
    schemaVersion: IMPORT_SCHEMA_VERSION,
    storageEnvelopeSchemaVersion: STORAGE_ENVELOPE_SCHEMA_VERSION,
    maximumPairs: MAXIMUM_PAIRS,
    normalizeImportResponse,
    mergeImport,
    createMarker,
    normalizeMarker,
    createStorageEnvelope,
    normalizeStorageEnvelope
  };
});
