import jsigs from 'jsonld-signatures';
import b58 from 'bs58';
import base64url from 'base64url';
import jsonld from 'jsonld';
import { possibleVerificationMethodRefs } from '../../../types/did/document/verification-method-ref.js';
import { createJws } from '../../jws.js';
import '../../../types/generic/typed-enum.js';
import '../../../utils/types/array.js';
import '../../../utils/inheritance.js';
import '../../../utils/types/object.js';
import '../../../utils/types/iterable.js';
import '../../../utils/types/set.js';
import 'crypto';
import '../../../utils/interfaces.js';
import '../../../utils/assert.js';
import '../../../utils/error.js';
import 'multiformats/bases/base58';
import 'varint';
import '../../../utils/encoding/ss-58.js';
import 'blake2b';
import '../../../utils/types/bytes.js';
import '../../../utils/types/string.js';
import '../../../types/generic/with-base.js';
import '../../../types/generic/with-catch-null.js';
import '../../../types/generic/with-eq.js';
import '../../../types/generic/any-of.js';
import '../../../types/generic/with-from.js';
import '../../../types/generic/with-from-json-bytes.js';
import '../../../types/generic/typed-null.js';
import '../../../types/generic/create-placeholder.js';
import '../../../types/generic/typed-bytes.js';
import '../../../types/generic/typed-string.js';
import '../../../types/generic/typed-array.js';
import '../../../types/generic/typed-tuple.js';
import '../../../types/generic/typed-number.js';
import '../../../types/generic/typed-set.js';
import '../../../types/generic/typed-map.js';
import '../../../types/generic/typed-struct.js';
import 'uuid';
import 'js-sha256';
import '../../../types/generic/with-qualifier.js';
import '../../../types/generic/utils.js';
import '../../../types/generic/typed-bytes-array.js';
import '../../../types/did/onchain/typed-did/index.js';
import '../../../types/did/onchain/typed-did/did-method-key/did-method-key-public-key.js';
import '../../../utils/encoding/base-58-btc.js';
import '../../../types/generic/sized.js';
import '../../../utils/types/number.js';
import '../../../types/public-keys/public-key.js';
import '../../../types/public-keys/public-key-x25519-value.js';
import '../../../types/public-keys/public-key-ed25519-value.js';
import '../constants.js';
import '../../../types/public-keys/public-key-secp256k1-value.js';
import '../../../types/public-keys/public-key-sr25519-value.js';
import '../../../types/did/onchain/constants.js';
import '../../../types/did/onchain/typed-did/signature.js';
import '../../../types/did/onchain/typed-did/did-method-key/did-method-key-signature.js';
import '../../../types/signatures/signature-value.js';
import '../../../types/signatures/signature-ed25519-value.js';
import '../../../types/signatures/signature-secp256k1-value.js';
import '../../../types/signatures/signature.js';
import '../../../types/signatures/signature-sr25519-value.js';
import '../../../types/did/onchain/typed-did/dock-did-value.js';
import '../../../types/generic/typed-uuid.js';
import '../../../types/did/onchain/typed-did/cheqd-did.js';

const MULTIBASE_BASE58BTC_HEADER = 'z';

function decodeBase64Url(string) {
  const buffer = base64url.toBuffer(string);
  return new Uint8Array(buffer);
}

function decodeBase64UrlToString(string) {
  return base64url.decode(string);
}

class CustomLinkedDataSignature extends jsigs.suites
  .LinkedDataSignature {
  /**
   * Creates a new CustomLinkedDataSignature instance
   * @constructor
   * @param {object} config - Configuration options
   */
  constructor(config) {
    super(config);
    this.alg = config.alg;
    this.useProofValue = config.useProofValue || false;
  }

  /**
   * @param document {object} to be signed.
   * @param proof {object}
   * @param documentLoader {function}
   */
  static async getVerificationMethod({ proof, documentLoader }) {
    let { verificationMethod } = proof;
    if (typeof verificationMethod === 'object') {
      verificationMethod = verificationMethod.id;
    }
    if (!verificationMethod) {
      throw new Error('No "verificationMethod" found in proof.');
    }

    // Note: `expansionMap` is intentionally not passed; we can safely drop
    // properties here and must allow for it
    const result = await jsonld.frame(
      verificationMethod,
      {
        '@context': jsigs.SECURITY_CONTEXT_URL,
        '@embed': '@always',
        id: possibleVerificationMethodRefs(verificationMethod),
      },
      {
        documentLoader,
        compactToRelative: false,
        expandContext: jsigs.SECURITY_CONTEXT_URL,
      },
    );

    if (!result) {
      throw new Error(`Verification method ${verificationMethod} not found.`);
    }
    // ensure verification method has not been revoked
    if (result.revoked !== undefined) {
      throw new Error('The verification method has been revoked.');
    }
    return result;
  }

  /**
   * @param document {object} to be signed.
   * @param proof {object}
   * @param documentLoader {function}
   */
  async getVerificationMethod({ proof, documentLoader }) {
    return this.constructor.getVerificationMethod({
      proof,
      documentLoader,
    });
  }

  /**
   * Verifies the proof signature against the given data.
   *
   * @param {object} options - The options to use.
   * @param {Uint8Array} options.verifyData - Canonicalized hashed data.
   * @param {object} options.verificationMethod - Key object.
   * @param {object} options.proof - The proof to be verified.
   *
   * @returns {Promise<boolean>} Resolves with the verification result.
   */
  async verifySignature({ verifyData, verificationMethod, proof }) {
    let signatureBytes;
    let data = verifyData;

    const { proofValue, jws } = proof;
    if (proofValue && typeof proofValue === 'string') {
      signatureBytes = b58.decode(
        CustomLinkedDataSignature.fromJsigProofValue(proofValue),
      );
    } else if (jws && typeof jws === 'string') {
      // Fallback to older jsonld-signature implementations
      const [encodedHeader /* payload */, , encodedSignature] = jws.split('.');

      let header;
      try {
        header = JSON.parse(decodeBase64UrlToString(encodedHeader));
      } catch (e) {
        throw new Error(`Could not parse JWS header; ${e}`);
      }
      if (!(header && typeof header === 'object')) {
        throw new Error('Invalid JWS header.');
      }

      signatureBytes = decodeBase64Url(encodedSignature);
      data = createJws({ encodedHeader, verifyData });
    }

    let { verifier } = this;
    if (!verifier) {
      const key = await this.LDKeyClass.from(verificationMethod);
      verifier = key.verifier();
    }
    return verifier.verify({ data, signature: signatureBytes });
  }

  /**
   * Adds a signature (proofValue) field to the proof object. Called by
   * LinkedDataSignature.createProof().
   *
   * @param {object} options - The options to use.
   * @param {Uint8Array} options.verifyData - Data to be signed (extracted
   *   from document, according to the suite's spec).
   * @param {object} options.proof - Proof object (containing the proofPurpose,
   *   verificationMethod, etc).
   *
   * @returns {Promise<object>} Resolves with the proof containing the signature
   *   value.
   */
  async sign({ verifyData, proof }) {
    if (!(this.signer && typeof this.signer.sign === 'function')) {
      throw new Error('A signer API has not been specified.');
    }

    const getSigBytes = async (data) => {
      let signatureBytes;
      const signature = await this.signer.sign({ data });
      if (typeof signature === 'string') {
        // Some signers will return a string like: header..signature
        // split apart those strings to get the signature in bytes
        const signatureSplit = signature.split('.');
        const signatureEncoded = signatureSplit[signatureSplit.length - 1];
        signatureBytes = decodeBase64Url(signatureEncoded);
      } else {
        signatureBytes = signature;
      }
      return signatureBytes;
    };

    const finalProof = { ...proof };
    if (this.useProofValue) {
      const signatureBytes = await getSigBytes(verifyData);
      finalProof.proofValue = CustomLinkedDataSignature.encodeProofValue(signatureBytes);
    } else {
      if (!this.alg) {
        throw new Error("Suite doesn't contain required alg parameter");
      }

      const header = { alg: this.alg, b64: false, crit: ['b64'] };
      const encodedHeader = base64url.encode(JSON.stringify(header));
      const jwsData = createJws({ encodedHeader, verifyData });
      const signatureBytesJWS = await getSigBytes(jwsData);
      finalProof.jws = `${encodedHeader}..${base64url.encode(
        signatureBytesJWS,
      )}`;
    }

    return finalProof;
  }

  static encodeProofValue(signatureBytes) {
    return MULTIBASE_BASE58BTC_HEADER + b58.encode(signatureBytes);
  }

  /**
   * Json-ld signs prefix signatures with a specific character. Removes that character
   * @param proofValue
   * @returns {*|string}
   */
  static fromJsigProofValue(proofValue) {
    if (proofValue[0] !== MULTIBASE_BASE58BTC_HEADER) {
      throw new Error('Only base58btc multibase encoding is supported.');
    }
    return proofValue.substring(1);
  }
}

export { decodeBase64Url, decodeBase64UrlToString, CustomLinkedDataSignature as default };
//# sourceMappingURL=CustomLinkedDataSignature.js.map
