'use strict';

var deepEqual = require('deep-equal');
var jsonld = require('jsonld');
var rify = require('rify');
var utils_assert = require('../utils/assert.cjs');
var vc_constants = require('../vc/constants.cjs');
var rdfAndCd_claimgraph = require('./claimgraph.cjs');
var rdfAndCd_canonicalize = require('./canonicalize.cjs');
var rdfAndCd_common = require('./common.cjs');
require('../utils/error.cjs');

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

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

// Claim deduction from verifiable presentations.


const expandedLogicProperty = 'https://www.dock.io/rdf2020#logicV1';
const expandedProofProperty = 'https://w3id.org/security#proof';
const expandedIssuerProperty = 'https://www.w3.org/2018/credentials#issuer';

/**
 * Returns a list of all RDF statements proven by the presentation. DOES NOT VERIFY THE
 * PRESENTATION. Verification must be performed for the results of this function to be trustworthy.
 * The return value may contaim duplicate claims.
 *
 * This function throws an error if:
 * a provided proof is not applicable given the ruleset,
 * a provided proof makes unverified assumptions,
 * a provided proof is malformed.
 *
 * @param expandedPresentation - a VCDM presentation as expanded json-ld
 * @param rules - ordered list of axioms which will be accepted within proofs of composite claims
 * @returns {Promise<Object[]>}
 */
async function acceptCompositeClaims(expandedPresentation, rules) {
  utils_assert.assert(
    rules !== undefined,
    'An axiom list must be provided. Hint: rules may be "[]" to reject all.',
  );

  const cg = await presentationToEEClaimGraph(expandedPresentation);
  const proof = extractProof(expandedPresentation);
  const implied = getImplications(cg, proof, rules);

  // return (claimgraph U implied)
  return [...cg, ...implied];
}

/**
 * Convert a list of expanded credentials which have already been verified into an RDF claim graph.
 * The resulting claimgraph is in Explicit Ethos form.
 *
 * @param expandedCredentials - A list of expanded credentials, each is expected to be a separate
 *                              @graph.
 * @returns {Promise<Object[]>}
 */
async function credsToEEClaimGraph(expandedCredentials) {
  const ees = await Promise.all(expandedCredentials.map(credToEECG));
  return rdfAndCd_claimgraph.merge(ees);
}

/**
 * Convert a single expanded credential which has already been verified into an RDF claim graph.
 *
 * @returns {Promise<Object[]>}
 */
async function credToEECG(expandedCredential) {
  utils_assert.assert(
    expandedCredential['@graph'] !== undefined,
    'Expected each credential to expand to its own @graph',
  );
  const cred = { ...unwrapE(expandedCredential['@graph']) };

  // This line relies on the assumption that if the credential passed verification then the
  // issuer property was not forged.
  utils_assert.assert(
    cred[expandedIssuerProperty] !== undefined,
    'encountered credential without an issuer',
  );
  const issuer = cred[expandedIssuerProperty][0]['@id'];
  rdfAndCd_common.assertType(issuer, 'string');
  utils_assert.assert(!issuer.startsWith('_:'), 'issuer is assumed not to be a blank node');

  // remove the proof
  delete cred[expandedProofProperty];

  // convert to claimgraph
  const cg = rdfAndCd_claimgraph.fromJsonldjsCg(await jsonld__default.default.toRDF(cred));

  for (const claim of cg) {
    // Credentials containing a "@graph" declaration will cause an assertion error here.
    // And rightfully so. Here's why:
    // Each statement in a cred is passed to the reasoner as a quad with `issuer` as the graph.
    // It would be potentially incorrect to overwrite a non-default graph.
    // Leaving a non-default graph as-is would be unsafe.
    // While it would be valid to simply delete quads with non-default graphs, it would be confusing
    // to users if the content of their credential was swallowed without warning.
    utils_assert.assert(
      deepEqual__default.default(claim[3], { DefaultGraph: true }),
      'subgraph found in credential',
    );
    claim[3] = { Iri: issuer };
  }

  return cg;
}

/**
 * Returns a list of all RDF statements in the presentation. DOES NOT VERIFY THE
 * PRESENTATION. Verification must be performed for the results of this function to be trustworthy.
 *
 * @param expandedPresentation - a VCDM presentation as expanded json-ld
 * @returns {Promise<Object[]>}
 */
async function presentationToEEClaimGraph(expandedPresentation) {
  const ep = unwrapE(expandedPresentation);

  // get ordered list of all credentials
  const creds = jsonld__default.default.getValues(ep, vc_constants.expandedCredentialProperty);

  // convert them to a single claimgraph
  return credsToEEClaimGraph(creds);
}

class UnverifiedAssumption extends Error {
  constructor(unverifiedAssumption) {
    super('Proof relies on assumption that are not in the input.');
    this.unverifiedAssumption = unverifiedAssumption;
  }
}

/**
 * Extracts any included proofs of composite claims from a presentation.
 * The presentation must be in expanded form.
 * Returns the proofs, concatenated together.
 *
 * @param expandedPresentation - a VCDM presentation as expanded json-ld
 * @returns {Promise<Object[]>}
 */
function extractProof(expandedPresentation) {
  return jsonld__default.default
    .getValues(unwrapE(expandedPresentation), expandedLogicProperty)
    .map(fromJSONLiteral)
    .flat(1);
}

/**
 * Given a claimgraph of true assumptions, and the allowed logical rules, return the claims that are
 * proven true by proof. If the proof is not applicable given the ruleset, or makes unverified
 * assumptions, throw an error. The return value may contain duplicate claims.
 *
 * @param claimgraph - known true assumptions (usually extracted from a presentation or a cred)
 * @param proof - proof of composite claims (usually comes from calling proveh())
 * @param rules - ordered list of axioms
 * @returns {Object[]}
 */
function getImplications(claimgraph, proof, rules) {
  const valid = validateh(rules, proof);

  // Check that all assumptions made by the proof were provided in the claimgraph of assumptions
  for (const assumption of valid.assumed) {
    if (!claimgraph.some((claim) => claimEq(claim, assumption))) {
      throw new UnverifiedAssumption([...assumption]);
    }
  }

  return valid.implied;
}

/**
 * Given the assumptions encoded in the provided presentation, prove a list of composite claims.
 * T return proof of composite claims as a jsonld json literal which can be attached directly to a
 * presentation as {expandedLogicProperty} before [signing and ]submitting.
 *
 * This function throws an error if the requested composite claims are unprovable.
 *
 * @param expandedPresentation - a VCDM presentation as expanded json-ld
 * @param compositeClaims - claims to prove, provide in claimgraph format
 * @param rules - ordered list of axioms which will be accepted within proofs of composite claims
 * @returns {Promise<Object>} - proof is returned as a json literal
 */
async function proveCompositeClaims(
  expandedPresentation,
  compositeClaims,
  rules,
) {
  utils_assert.assert(
    rules !== undefined,
    'An axiom list must be provided. Hint: rules may be "[]" to reject all.',
  );
  const cg = await presentationToEEClaimGraph(expandedPresentation);
  const prevProof = await extractProof(expandedPresentation);
  const newProof = proveh(cg, compositeClaims, rules);
  return toJsonLiteral(prevProof.concat(newProof));
}

// prove-high
// A higher level wrapper around prove that first converts rules, composite claims, and premises to
// the canonical representation as defined by `canon()` in `claimgraph.js`. This wrapper
// deserializes the returned values before passing them back to the caller.
function proveh(premises, toProve, rules) {
  foreachQuadInRules(rules, assertQuad);
  premises.forEach(assertQuad);
  toProve.forEach(assertQuad);
  const proof = rify.prove(
    rdfAndCd_canonicalize.canonClaimGraph(premises),
    rdfAndCd_canonicalize.canonClaimGraph(toProve),
    rdfAndCd_canonicalize.canonRules(rules),
  );
  return rdfAndCd_canonicalize.decanonProof(proof);
}

// validate-high
// A higher level wrapper around validate that first converts rules and proof to the canonical
// representation as defined by `canon()` in `claimgraph.js`. This wrapper deserializes the
// returned values before passing them back to the caller.
function validateh(rules, proof) {
  foreachQuadInRules(rules, assertQuad);
  const { assumed, implied } = rify.validate(rdfAndCd_canonicalize.canonRules(rules), rdfAndCd_canonicalize.canonProof(proof));
  return {
    assumed: rdfAndCd_canonicalize.decanonClaimGraph(assumed),
    implied: rdfAndCd_canonicalize.decanonClaimGraph(implied),
  };
}

// infer-high
// A higher level wrapper around infer that first converts premises and rules to the canonical
// representation as defined by `canon()` in `claimgraph.js`. This wrapper deserializes the
// returned values before passing them back to the caller.
function inferh(premises, rules) {
  premises.forEach(assertQuad);
  foreachQuadInRules(rules, assertQuad);
  const inferred = rify.infer(rdfAndCd_canonicalize.canonClaimGraph(premises), rdfAndCd_canonicalize.canonRules(rules));
  return rdfAndCd_canonicalize.decanonClaimGraph(inferred);
}

function foreachQuadInRules(rules, f) {
  for (const rule of rules) {
    for (const quad of rule.if_all) {
      f(quad);
    }
    for (const quad of rule.then) {
      f(quad);
    }
  }
}

function assertQuad(quad) {
  utils_assert.assert(
    quad.length === 4,
    () => `Quads must have length 4, got: ${JSON.stringify(quad)}`,
  );
}

/// check two claims for equality
function claimEq(a, b) {
  for (const claim of [a, b]) {
    if (!(claim instanceof Array) || claim.length !== 4) {
      throw new TypeError();
    }
    for (const node of claim) {
      rdfAndCd_common.assertValidNode(node);
    }
  }
  return deepEqual__default.default(a, b);
}

// https://w3c.github.io/json-ld-syntax/#json-literals
function fromJSONLiteral(literal) {
  if (literal['@type'] !== '@json') {
    throw new TypeError(`not a json literal: ${literal}`);
  }
  if (literal['@value'] === undefined) {
    throw new TypeError(`json literal has no "@value": ${literal}`);
  }
  return literal['@value'];
}

// https://w3c.github.io/json-ld-syntax/#json-literals
function toJsonLiteral(json) {
  return {
    '@type': '@json',
    '@value': JSON.parse(JSON.stringify(json)),
  };
}

// Unwrap Expanded jsonld
//
// jsonld expansion by jsonld-js results in an array. This module operates on expanded jsonld so
// getting the first and only element from that expanded array is a common task.
//
// We assume that a root level expanded jsonld object will always be an array with exactly one
// element.
function unwrapE(expanded) {
  utils_assert.assert(
    Array.isArray(expanded) && expanded.length === 1,
    'expected expanded jsonld as an array of one element',
  );
  return expanded[0];
}

exports.UnverifiedAssumption = UnverifiedAssumption;
exports.acceptCompositeClaims = acceptCompositeClaims;
exports.expandedIssuerProperty = expandedIssuerProperty;
exports.expandedLogicProperty = expandedLogicProperty;
exports.expandedProofProperty = expandedProofProperty;
exports.extractProof = extractProof;
exports.getImplications = getImplications;
exports.inferh = inferh;
exports.presentationToEEClaimGraph = presentationToEEClaimGraph;
exports.proveCompositeClaims = proveCompositeClaims;
exports.proveh = proveh;
exports.validateh = validateh;
//# sourceMappingURL=cd.cjs.map
