'use strict';

var jsonld = require('jsonld');
var assert = require('assert');
var deepEqual = require('deep-equal');
var cid = require('multiformats/cid');
var rdfAndCd_common = require('./common.cjs');
var rdfAndCd_rdf = require('./rdf.cjs');
var rdfAndCd_cd = require('./cd.cjs');
var rdfAndCd_canonicalize = require('./canonicalize.cjs');
var rdfAndCd_claimgraph = require('./claimgraph.cjs');
require('../utils/assert.cjs');
require('../utils/error.cjs');
require('n3');
require('rify');
require('../vc/constants.cjs');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

var jsonld__default = /*#__PURE__*/_interopDefault(jsonld);
var assert__default = /*#__PURE__*/_interopDefault(assert);
var deepEqual__default = /*#__PURE__*/_interopDefault(deepEqual);

/*
eslint no-await-in-loop: "off",
no-constant-condition: ["error", { "checkLoops": false }]
*/


// Crawl the rdf dataset composed of DIDs and turtle documents on ipfs. Return the graph
// representing all knowledge obtained while crawling.
async function crawl(
  initialFacts,
  rules,
  curiosityQuery,
  resolveGraph,
  engine,
) {
  // namer is used to ensure blank node hygiene whenever adding new claimgraphs
  const namer = new rdfAndCd_claimgraph.Namer();

  let facts = rdfAndCd_common.deepClone(initialFacts);
  namer.reallocateNames(facts);

  const lookedup = new Set();
  const marknew = (term) => {
    // Add term to lookedup.
    // Return whether the term was already present in the set.
    // like https://doc.rust-lang.org/std/collections/btree_set/struct.BTreeSet.html#method.insert
    const str = rdfAndCd_canonicalize.canon(term);
    const isnew = !lookedup.has(str);
    lookedup.add(str);
    return isnew;
  };

  while (true) {
    // reason over what we have so far
    facts = facts.concat(rdfAndCd_cd.inferh(facts, rules));

    // lookup any interesting documents
    const interesting = await rdfAndCd_rdf.queryNextLookup(facts, curiosityQuery, engine);
    const novel = interesting.filter(marknew);
    const newfacts = [...(await Promise.all(novel.map(resolveGraph)))];
    for (const nf of newfacts) {
      namer.reallocateNames(nf);
    }
    facts = facts.concat(...newfacts);

    if (newfacts.length === 0) {
      break;
    }
  }

  return facts;
}

// construct a document fetcher for use in the crawler
function graphResolver(
  ipfsClient,
  documentLoader,
  onFailedLookup = (_term, err) => {
    throw err;
  },
) {
  async function resolve(term) {
    rdfAndCd_common.assertValidNode(term);
    if (!('Iri' in term)) {
      throw new Error(`attempted to lookup non-iri ${JSON.stringify(term)}`);
    }
    const iri = term.Iri;
    let triples;
    const ipfsPrefix = 'ipfs://';
    if (iri.startsWith(ipfsPrefix)) {
      const cid$1 = cid.CID.parse(iri.slice(ipfsPrefix.length));
      const body = await ipfsClient.get(cid$1);
      triples = await rdfAndCd_rdf.parseRDFDocument(body, { format: 'text/turtle' });
    } else {
      const jld = (await documentLoader(iri)).document;
      triples = await jsonldToCg(jld);
    }
    return triples.map(([s, p, o]) => [s, p, o, { Iri: iri }]);
  }

  async function resolveGraph(term) {
    try {
      return await resolve(term);
    } catch (err) {
      onFailedLookup(term, err);
      return [];
    }
  }

  return resolveGraph;
}

async function jsonldToCg(jld, documentLoader) {
  const exp = await jsonld__default.default.expand(jld, { documentLoader });
  const cg = rdfAndCd_claimgraph.fromJsonldjsCg(await jsonld__default.default.toRDF(exp));
  for (const claim of cg) {
    assert__default.default(
      deepEqual__default.default(claim[3], { DefaultGraph: true }),
      'illegal subgraph was specified',
    );
    claim.pop();
    assert__default.default(claim.length === 3);
  }
  return cg;
}

exports.crawl = crawl;
exports.graphResolver = graphResolver;
//# sourceMappingURL=crawl.cjs.map
