'use strict';

var ContractPublisherAbi = require('@thirdweb-dev/contracts-js/dist/abis/ContractPublisher.json');
var ethers = require('ethers');
var invariant = require('tiny-invariant');
var index = require('./index-7ac94121.cjs.dev.js');
var fetchSourceFilesFromMetadata = require('./fetchSourceFilesFromMetadata-0140dd2b.cjs.dev.js');
var transactions = require('./transactions-6f587a37.cjs.dev.js');

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

var ContractPublisherAbi__default = /*#__PURE__*/_interopDefault(ContractPublisherAbi);
var invariant__default = /*#__PURE__*/_interopDefault(invariant);

/**
 * @internal
 */
async function extractConstructorParams(predeployMetadataUri, storage) {
  const meta = await index.fetchPreDeployMetadata(predeployMetadataUri, storage);
  return index.extractConstructorParamsFromAbi(meta.abi);
}

/**
 * @internal
 * @param predeployMetadataUri - The predeploy metadata URI to fetch
 * @param storage - The storage to use
 */
async function extractFunctions(predeployMetadataUri, storage) {
  const metadata = await index.fetchPreDeployMetadata(predeployMetadataUri, storage);
  return index.extractFunctionsFromAbi(metadata.abi, metadata.metadata);
}

/**
 * Handles publishing contracts (EXPERIMENTAL)
 * @internal
 */
class ContractPublisher extends index.RPCConnectionHandler {
  constructor(network, options, storage) {
    super(network, options);
    this.storage = storage;
    this.publisher = new index.ContractWrapper(network, index.getContractPublisherAddress(), ContractPublisherAbi__default["default"], options, storage);
  }
  updateSignerOrProvider(network) {
    super.updateSignerOrProvider(network);
    this.publisher.updateSignerOrProvider(network);
  }

  /**
   * @internal
   * @param metadataUri - URI of the contract metadata
   */
  async extractConstructorParams(metadataUri) {
    return extractConstructorParams(metadataUri, this.storage);
  }

  /**
   * @internal
   * @param predeployMetadataUri - URI of the predeploy metadata
   */
  async extractFunctions(predeployMetadataUri) {
    return extractFunctions(predeployMetadataUri, this.storage);
  }

  /**
   * @internal
   * @param predeployUri - URI of the predeploy metadata
   */
  async fetchCompilerMetadataFromPredeployURI(predeployUri) {
    return index.fetchPreDeployMetadata(predeployUri, this.storage);
  }

  /**
   * @internal
   * @param prepublishUri - URI of the prepublish metadata
   * @param publisherAddress - Address of the publisher
   */
  async fetchPrePublishMetadata(prepublishUri, publisherAddress) {
    const preDeployMetadataFetched = await index.fetchPreDeployMetadata(prepublishUri, this.storage);
    const latestPublishedContract = publisherAddress ? await this.getLatest(publisherAddress, preDeployMetadataFetched.name) : undefined;
    const latestPublishedContractMetadata = latestPublishedContract ? await this.fetchPublishedContractInfo(latestPublishedContract) : undefined;
    return {
      preDeployMetadata: preDeployMetadataFetched,
      latestPublishedContractMetadata
    };
  }

  /**
   * @param address - Address of the contract
   */
  async fetchCompilerMetadataFromAddress(address) {
    const resolvedAddress = await index.resolveAddress(address);
    return index.fetchContractMetadataFromAddress(resolvedAddress, this.getProvider(), this.storage, this.options);
  }

  /**
   * @internal
   * Get the full information about a published contract
   * @param contract - Published contract
   */
  async fetchPublishedContractInfo(contract) {
    return {
      name: contract.id,
      publishedTimestamp: contract.timestamp,
      publishedMetadata: await this.fetchFullPublishMetadata(contract.metadataUri)
    };
  }

  /**
   * @internal
   * @param publishedMetadataUri - URI of the published metadata
   */
  async fetchFullPublishMetadata(publishedMetadataUri) {
    return index.fetchExtendedReleaseMetadata(publishedMetadataUri, this.storage);
  }

  /**
   * @internal
   * // TODO expose a resolvePublishMetadata(contractAddress, chainId) that handles the dual chain case
   * // TODO will be easy to do with the multichain pattern of 3.0
   * @param compilerMetadataUri - URI of the compiler metadata
   */
  async resolvePublishMetadataFromCompilerMetadata(compilerMetadataUri) {
    const publishedMetadataUri = await this.publisher.read("getPublishedUriFromCompilerUri", [compilerMetadataUri]);
    if (publishedMetadataUri.length === 0) {
      throw Error(`Could not resolve published metadata URI from ${compilerMetadataUri}`);
    }
    return await Promise.all(publishedMetadataUri.filter(uri => uri.length > 0).map(uri => this.fetchFullPublishMetadata(uri)));
  }

  /**
   * @internal
   * TODO clean this up (see method above, too)
   */
  async resolveContractUriFromAddress(address) {
    const resolvedAddress = await index.resolveAddress(address);
    const contractUri = await index.resolveContractUriFromAddress(resolvedAddress, this.getProvider());
    invariant__default["default"](contractUri, "Could not resolve contract URI from address");
    return contractUri;
  }

  /**
   * Fetch all sources for a contract from its address
   * @param address - Address of the contract
   */
  async fetchContractSourcesFromAddress(address) {
    const resolvedAddress = await index.resolveAddress(address);
    const metadata = await this.fetchCompilerMetadataFromAddress(resolvedAddress);
    return await fetchSourceFilesFromMetadata.fetchSourceFilesFromMetadata(metadata, this.storage);
  }

  /**
   * Fetch ABI from a contract, or undefined if not found
   * @param address - Address of the contract
   */
  async fetchContractAbiFromAddress(address) {
    const resolvedAddress = await index.resolveAddress(address);
    const meta = await index.fetchContractMetadataFromAddress(resolvedAddress, this.getProvider(), this.storage);
    return meta.abi;
  }

  /**
   * @internal
   * @param profileMetadata - Profile metadata
   */
  updatePublisherProfile = /* @__PURE__ */transactions.buildTransactionFunction(async profileMetadata => {
    const signer = this.getSigner();
    invariant__default["default"](signer, "A signer is required");
    const publisher = await signer.getAddress();
    const profileUri = await this.storage.upload(profileMetadata);
    return transactions.Transaction.fromContractWrapper({
      contractWrapper: this.publisher,
      method: "setPublisherProfileUri",
      args: [publisher, profileUri]
    });
  });

  /**
   * @internal
   * @param publisherAddress - Address of the publisher
   */
  async getPublisherProfile(publisherAddress) {
    const resolvedPublisherAddress = await index.resolveAddress(publisherAddress);
    const profileUri = await this.publisher.read("getPublisherProfileUri", [resolvedPublisherAddress]);
    if (!profileUri || profileUri.length === 0) {
      return {};
    }
    return index.ProfileSchemaOutput.parse(await this.storage.downloadJSON(profileUri));
  }

  /**
   * @internal
   * @param publisherAddress - Address of the publisher
   */
  async getAll(publisherAddress) {
    const resolvedPublisherAddress = await index.resolveAddress(publisherAddress);
    const data = await this.publisher.read("getAllPublishedContracts", [resolvedPublisherAddress]);
    // since we can fetch from multiple publisher contracts, just keep the latest one in the list
    const map = data.reduce((acc, curr) => {
      // replaces the previous contract with the latest one
      acc[curr.contractId] = curr;
      return acc;
    }, {});
    return Object.entries(map).map(_ref => {
      let [, struct] = _ref;
      return this.toPublishedContract(struct);
    });
  }

  /**
   * @internal
   * @param publisherAddress - Address of the publisher
   * @param contractId - ID of the contract
   */
  async getAllVersions(publisherAddress, contractId) {
    const resolvedPublisherAddress = await index.resolveAddress(publisherAddress);
    const contractStructs = await this.publisher.read("getPublishedContractVersions", [resolvedPublisherAddress, contractId]);
    if (contractStructs.length === 0) {
      throw Error("Not found");
    }
    return contractStructs.map(d => this.toPublishedContract(d));
  }
  async getVersion(publisherAddress, contractId) {
    let version = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "latest";
    const resolvedPublisherAddress = await index.resolveAddress(publisherAddress);
    if (version === "latest") {
      return this.getLatest(resolvedPublisherAddress, contractId);
    }
    const allVersions = await this.getAllVersions(resolvedPublisherAddress, contractId);
    // get the metadata for each version
    const versionMetadata = await Promise.all(allVersions.map(contract => this.fetchPublishedContractInfo(contract)));
    // find the version that matches the version string
    const versionMatch = versionMetadata.find(metadata => metadata.publishedMetadata.version === version);
    invariant__default["default"](versionMatch, "Contract version not found");
    // match the version back to the contract based on the published timestamp
    return allVersions.find(contract => contract.timestamp === versionMatch.publishedTimestamp);
  }
  async getLatest(publisherAddress, contractId) {
    const resolvedPublisherAddress = await index.resolveAddress(publisherAddress);
    const model = await this.publisher.read("getPublishedContract", [resolvedPublisherAddress, contractId]);
    if (model && model.publishMetadataUri) {
      return this.toPublishedContract(model);
    }
    return undefined;
  }
  publish = /* @__PURE__ */transactions.buildTransactionFunction(async (predeployUri, extraMetadata) => {
    const extraMetadataCleaned = this.cleanupOldPublishFlowData(extraMetadata);
    const signer = this.getSigner();
    invariant__default["default"](signer, "A signer is required");
    const publisher = await signer.getAddress();
    const predeployMetadata = await index.fetchRawPredeployMetadata(predeployUri, this.storage);
    const compilerMetadata = await index.fetchContractMetadata(predeployMetadata.metadataUri, this.storage);
    const features = index.detectFeatures(compilerMetadata.abi);
    const isPlugin = index.isFeatureEnabled(compilerMetadata.abi, "PluginRouter", features);
    const isDynamic = index.isFeatureEnabled(compilerMetadata.abi, "DynamicContract", features);
    const isModular = index.isFeatureEnabled(compilerMetadata.abi, "ModularCore", features);
    extraMetadataCleaned.routerType = isPlugin ? "plugin" : isDynamic ? "dynamic" : isModular ? "modular" : "none";

    // For a dynamic contract Router, try to fetch plugin/extension metadata
    if (isDynamic || isPlugin) {
      const defaultExtensions = extraMetadataCleaned.defaultExtensions;
      if (defaultExtensions && defaultExtensions.length > 0) {
        try {
          const publishedExtensions = await Promise.all(defaultExtensions.map(e => {
            return index.fetchPublishedContractFromPolygon(e.publisherAddress, e.extensionName, e.extensionVersion, this.storage, this.options.clientId, this.options.secretKey);
          }));
          const publishedExtensionUris = publishedExtensions.map(ext => ext.metadataUri);
          const extensionABIs = (await Promise.all(publishedExtensionUris.map(async uri => {
            return index.fetchAndCacheDeployMetadata(uri, this.storage);
          }))).map(fetchedMetadata => fetchedMetadata.compilerMetadata.abi);
          const composite = index.joinABIs([compilerMetadata.abi, ...extensionABIs]);
          extraMetadataCleaned.compositeAbi = index.AbiSchema.parse(composite);
        } catch {}
      }
    }

    // ensure version is incremental
    const latestContract = await this.getLatest(publisher, predeployMetadata.name);
    if (latestContract && latestContract.metadataUri) {
      const latestMetadata = await this.fetchPublishedContractInfo(latestContract);
      const latestVersion = latestMetadata.publishedMetadata.version;
      if (!index.isIncrementalVersion(latestVersion, extraMetadataCleaned.version)) {
        throw Error(`Version ${extraMetadataCleaned.version} is not greater than ${latestVersion}`);
      }
    }
    const fetchedBytecode = await (await this.storage.download(predeployMetadata.bytecodeUri)).text();
    const bytecode = fetchedBytecode.startsWith("0x") ? fetchedBytecode : `0x${fetchedBytecode}`;
    const bytecodeHash = ethers.utils.solidityKeccak256(["bytes"], [bytecode]);
    const contractId = predeployMetadata.name;
    const fullMetadata = await index.FullPublishMetadataSchemaInput.parseAsync({
      ...extraMetadataCleaned,
      metadataUri: predeployMetadata.metadataUri,
      bytecodeUri: predeployMetadata.bytecodeUri,
      name: predeployMetadata.name,
      analytics: predeployMetadata.analytics,
      compilers: predeployMetadata.compilers,
      publisher
    });
    const fullMetadataUri = await this.storage.upload(fullMetadata);
    return transactions.Transaction.fromContractWrapper({
      contractWrapper: this.publisher,
      method: "publishContract",
      args: [publisher, contractId, fullMetadataUri, predeployMetadata.metadataUri, bytecodeHash, ethers.constants.AddressZero],
      parse: receipt => {
        const events = this.publisher.parseLogs("ContractPublished", receipt.logs);
        if (events.length < 1) {
          throw new Error("No ContractPublished event found");
        }
        const contract = events[0].args.publishedContract;
        return {
          receipt,
          data: async () => this.toPublishedContract(contract)
        };
      }
    });
  });
  unpublish = /* @__PURE__ */transactions.buildTransactionFunction(async (publisher, contractId) => {
    const resolvedPublisher = await index.resolveAddress(publisher);
    return transactions.Transaction.fromContractWrapper({
      contractWrapper: this.publisher,
      method: "unpublishContract",
      args: [resolvedPublisher, contractId]
    });
  });
  toPublishedContract(contractModel) {
    return index.PublishedContractSchema.parse({
      id: contractModel.contractId,
      timestamp: contractModel.publishTimestamp,
      metadataUri: contractModel.publishMetadataUri
    });
  }
  cleanupOldPublishFlowData(extraMetadata) {
    if (extraMetadata.compositeAbi) {
      delete extraMetadata.compositeAbi;
    }
    return {
      ...extraMetadata,
      isDeployableViaFactory: false,
      isDeployableViaProxy: false,
      factoryDeploymentData: {
        ...extraMetadata.factoryDeploymentData,
        implementationAddresses: {},
        factoryAddresses: {}
      }
    };
  }
}

var contractPublisher = /*#__PURE__*/Object.freeze({
  __proto__: null,
  ContractPublisher: ContractPublisher
});

exports.ContractPublisher = ContractPublisher;
exports.contractPublisher = contractPublisher;
exports.extractConstructorParams = extractConstructorParams;
exports.extractFunctions = extractFunctions;
