def call(Map args) {
    assert (args.awsRegions && args.ecrAccountId && args.imageName && args.imageTag): 'awsRegions, ecrAccountId, imageName, and imageTag are required arguments'
    assert args.awsRegions.getClass() in Collection: 'awsRegions must be a list or set'

    def dockerBuildSourceUrl = getDockerBuildSourceUrl(args)

    // Default build args
    def dockerBuildArgsMap = [
        GIT_COMMIT: env.GIT_COMMIT,
        VERSION: args.imageTag,
        REPOSITORY_URL: dockerBuildSourceUrl,
        version: args.imageTag // include lowercase "version" arg for backwards compatibility
    ]
    if (args.dockerBuildArgs) {
        assert args.dockerBuildArgs.getClass() in Map: 'dockerBuildArgs must be a map'
        dockerBuildArgsMap += args.dockerBuildArgs
    }

    def dockerBuildArgs = dockerBuildArgsMap.collect { key, value -> "--build-arg ${key}=${value}" }.join(' ')

    def additionalContexts = ''
    if (args.additionalContexts) {
        assert args.additionalContexts.getClass() in Map: 'additionalContexts must be a map'
        additionalContexts = args.additionalContexts.collect { key, value -> "--build-context ${key}=${value}" }.join(' ')
    }

    def cleanBuildDir = "dockerToEcr/${args.imageName}__${args.imageTag}"
    def dockerBuildCacheDisabled = args.dockerBuildCacheDisabled == null ? true : args.dockerBuildCacheDisabled
    def dockerBuildContext = args.dockerBuildContext ? "${env.WORKSPACE}/${cleanBuildDir}/${args.dockerBuildContext}" : "${env.WORKSPACE}/${cleanBuildDir}"
    def dockerBuildFile = args.dockerBuildFile ?: 'Dockerfile'
    def dockerBuildTarget = args.dockerBuildTarget ? "--target ${args.dockerBuildTarget}" : ''
    def pushLatest = args.pushLatest == null ? true: args.pushLatest

    // dockerProvenance: if true, enables provenance attestation; if false or not set, disables it (default: --provenance=false).
    def provenanceFlag = '--provenance=false'
    if (args.containsKey('dockerProvenance') && args.dockerProvenance == true) {
        provenanceFlag = '--provenance=true'
    }

    if (args.registryCredentials) {
        assert args.registryCredentials.getClass() in Collection: 'registryCredentials must be a list'
        args.registryCredentials.each { cred ->
            assert (cred['username'] && cred['password']): 'username and password are required for each registryCredentials entry'
        }
    }

    withEcr {
        if (args.registryCredentials) {
            dockerLogin(args.registryCredentials)
        }

        // Perform clean checkout to a subdirectory to ensure there are no workspace artifacts in the built image
        dir(cleanBuildDir) {
            checkout scm

            if (args.dockerBuildSecrets) {
                def dockerBuildSecrets = ''
                assert args.dockerBuildSecrets.getClass() in Collection: 'dockerBuildSecrets must be a list of maps'
                args.dockerBuildSecrets.each {
                    assert (it.size() == 2 && it.containsKey('id') && (it.containsKey('src') || it.containsKey('env'))): 'Each secret must be a map with an "id" key and either a "src" or an "env" key'
                    dockerBuildSecrets += "--secret id=${it.id},${it.src ? "src=${it.src}" : "env=${it.env}"} "
                }
                withEnv(["DOCKER_BUILDKIT=1"]) {
                    sh """
                        docker build ${dockerBuildTarget} ${dockerBuildArgs} ${additionalContexts} ${dockerBuildSecrets} ${provenanceFlag} --tag ${args.imageName}:${args.imageTag} --file ${dockerBuildFile} --no-cache=${dockerBuildCacheDisabled} --pull ${dockerBuildContext} --label org.opencontainers.image.revision=${args.imageTag} --label org.opencontainers.image.source=${dockerBuildSourceUrl}
                    """
                }
            } else {
                sh """
                    docker build ${dockerBuildTarget} ${dockerBuildArgs} ${additionalContexts} ${provenanceFlag} --tag ${args.imageName}:${args.imageTag} --file ${dockerBuildFile} --no-cache=${dockerBuildCacheDisabled} --pull ${dockerBuildContext} --label org.opencontainers.image.revision=${args.imageTag} --label org.opencontainers.image.source=${dockerBuildSourceUrl}
                """
            }

            for (def region in args.awsRegions) {
                sh """
                    aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${args.ecrAccountId}.dkr.ecr.${region}.amazonaws.com
                    docker tag ${args.imageName}:${args.imageTag} ${args.ecrAccountId}.dkr.ecr.${region}.amazonaws.com/${args.imageName}:${args.imageTag}
                    docker push ${args.ecrAccountId}.dkr.ecr.${region}.amazonaws.com/${args.imageName}:${args.imageTag}
                """
                if (pushLatest) {
                    sh """
                        docker tag ${args.imageName}:${args.imageTag} ${args.ecrAccountId}.dkr.ecr.${region}.amazonaws.com/${args.imageName}:latest
                        docker push ${args.ecrAccountId}.dkr.ecr.${region}.amazonaws.com/${args.imageName}:latest
                    """
                }
            }
        }
    }
}

private void dockerLogin(List credentials) {
    for (def cred in credentials) {
        def loginCmd = 'echo $REGISTRY_PASSWORD | docker login --username $REGISTRY_USERNAME --password-stdin'
        if (cred.url) {
            loginCmd += " ${cred.url}"
        }
        withEnv(["REGISTRY_USERNAME=${cred.username}", "REGISTRY_PASSWORD=${cred.password}"]) {
            sh loginCmd
        }
    }
}

def getDockerBuildSourceUrl(Map args) {
    // If explicitly set, use that
    if (args.dockerBuildSourceUrl) {
        return args.dockerBuildSourceUrl
    }

    // Try and determine the source URL from the GIT_URL environment variable that is set by Jenkins
    if (env.GIT_URL) {
        def matcher = (env.GIT_URL =~ "(?:https://github\\.com/|git@github.com:)(.*)\\.git")
        if (matcher.matches()) {
            return "github.com/${matcher[0][1]}"
        }
    }

    // Fallback to defaulting from the image name
    return "github.com/theorchard/${args.imageName}"
}
