import type { Artifact } from "../../../../types/artifacts.js";
import type { CompilationJob } from "../../../../types/solidity/compilation-job.js";
import type {
  CompilerOutput,
  CompilerOutputContract,
} from "../../../../types/solidity/compiler-io.js";
import type {
  SolidityBuildInfo,
  SolidityBuildInfoOutput,
} from "../../../../types/solidity/solidity-artifacts.js";

import { getPrefixedHexString } from "@nomicfoundation/hardhat-utils/hex";

export function getContractArtifact(
  buildInfoId: string,
  userSourceName: string,
  inputSourceName: string,
  contractName: string,
  contract: CompilerOutputContract,
): Artifact {
  const evmBytecode = contract.evm?.bytecode;
  const bytecode: string =
    evmBytecode?.object !== undefined
      ? getPrefixedHexString(evmBytecode.object)
      : "";

  const evmDeployedBytecode = contract.evm?.deployedBytecode;
  const deployedBytecode: string =
    evmDeployedBytecode?.object !== undefined
      ? getPrefixedHexString(evmDeployedBytecode.object)
      : "";

  const linkReferences = evmBytecode?.linkReferences ?? {};
  const deployedLinkReferences = evmDeployedBytecode?.linkReferences ?? {};

  const immutableReferences = evmDeployedBytecode?.immutableReferences ?? {};

  const artifact: Required<Artifact> = {
    _format: "hh3-artifact-1",
    contractName,
    sourceName: userSourceName,
    abi: contract.abi,
    bytecode,
    deployedBytecode,
    linkReferences,
    deployedLinkReferences,
    immutableReferences,
    inputSourceName,
    buildInfoId,
  };

  return artifact;
}

export function getArtifactsDeclarationFile(artifacts: Artifact[]): string {
  if (artifacts.length === 0) {
    return "";
  }

  const artifactTypes = artifacts.map(
    (artifact) =>
      `export interface ${artifact.contractName}$Type {
  ${Object.entries(artifact)
    .map(([name, value]) => `readonly ${name}: ${JSON.stringify(value)};`)
    .join("\n  ")}
};`,
  );

  return `// This file was autogenerated by Hardhat, do not edit it.
// prettier-ignore
// tslint:disable
// eslint-disable
// biome-ignore format: see above

${artifactTypes.join("\n\n")}

import "hardhat/types/artifacts";
declare module "hardhat/types/artifacts" {
  interface ArtifactMap {
    ${artifacts.map((artifact) => `["${artifact.contractName}"]: ${artifact.contractName}$Type`).join("\n    ")};
    ${artifacts.map((artifact) => `["${artifact.sourceName}:${artifact.contractName}"]: ${artifact.contractName}$Type`).join("\n    ")};
  }
}`;
}

export function getDuplicatedContractNamesDeclarationFile(
  duplicatedContractNames: string[],
): string {
  if (duplicatedContractNames.length === 0) {
    return "";
  }

  return `// This file was autogenerated by Hardhat, do not edit it.
// prettier-ignore
// tslint:disable
// eslint-disable
// biome-ignore format: see above

import "hardhat/types/artifacts";
declare module "hardhat/types/artifacts" {
  interface ArtifactMap {
    ${duplicatedContractNames.map((name) => `["${name}"]: never`).join("\n    ")};
  }
}`;
}

export async function getBuildInfo(
  compilationJob: CompilationJob,
): Promise<SolidityBuildInfo> {
  // Defaulting to "solc" is safe here: if it's already "solc" or undefined,
  // this doesn't alter the build info id.
  const compilerType = compilationJob.solcConfig.type ?? "solc";

  const buildInfo: SolidityBuildInfo = {
    _format: "hh3-sol-build-info-1",
    id: await compilationJob.getBuildId(),
    solcVersion: compilationJob.solcConfig.version,
    solcLongVersion: compilationJob.solcLongVersion,
    compilerType,
    ...(compilationJob.toolVersions !== undefined
      ? { toolVersions: compilationJob.toolVersions }
      : {}),
    userSourceNameMap:
      compilationJob.dependencyGraph.getRootsUserSourceNameMap(),
    input: await compilationJob.getSolcInput(),
  };

  return buildInfo;
}

export async function getBuildInfoOutput(
  compilationJob: CompilationJob,
  compilerOutput: CompilerOutput,
): Promise<SolidityBuildInfoOutput> {
  const buildInfoOutput: Required<SolidityBuildInfoOutput> = {
    _format: "hh3-sol-build-info-output-1",
    id: await compilationJob.getBuildId(),
    output: compilerOutput,
  };

  return buildInfoOutput;
}
