def renameConfig = [
        // DO NOT CHANGE THESE
        templateName                : "template",
        templateAppId               : "template.app.id",
        templateMaterialThemeName   : "TemplateTheme",
        templateApplicationClassName: "TemplateApp",
        templateIOSName             : "templateIOS",
        // MODIFY THE BELOW PROPERTIES
        newPackage                  : "aaa.yourname.app",
        newProjectName              : "Your Project",
        newMaterialThemeName        : "MyMaterialTheme",
        newApplicationClassName     : "MyApp",
        newIOSName                  : "myIOS",
        useDataStore                : true,
        useRenovate                 : true,
        useSqldelight               : true,
        useMultiplatformSettings    : true,
        useKtor                     : true,
        useKoin                     : true,
        useApollo                   : true,
]

tasks.register('renameIOSProject', Copy) {
    into(rootDir)

    from("iosApp/${renameConfig.templateIOSName}") {
        into("iosApp/${renameConfig.newIOSName}")
    }

    from("iosApp/${renameConfig.templateIOSName}.xcodeproj") {
        into("iosApp/${renameConfig.newIOSName}.xcodeproj")
    }

    from("iosApp/${renameConfig.templateIOSName}Tests") {
        into("iosApp/${renameConfig.newIOSName}Tests")
    }

    from("iosApp/${renameConfig.templateIOSName}UITests") {
        into("iosApp/${renameConfig.newIOSName}UITests")
    }

    filter { line ->
        line.replaceAll(
                renameConfig.templateIOSName,
                renameConfig.newIOSName
        )
    }

    rename { filename ->
        filename.replace renameConfig.templateIOSName, renameConfig.newIOSName
    }

    doLast {
        delete("$rootDir/iosApp/${renameConfig.templateIOSName}")
        delete("$rootDir/iosApp/${renameConfig.templateIOSName}Tests")
        delete("$rootDir/iosApp/${renameConfig.templateIOSName}UITests")
    }
}

project('androidApp').tasks.named {
    // startsWith is used because this applies to multiple tasks,
    // like formatKotlinMain, formatKotlinTest, etc.
    // Similar to `convertXmlValueResourcesFor[...]` call.
    it.startsWith("formatKotlin") || it.startsWith("convertXmlValueResourcesFor")
}.configureEach {
    mustRunAfter(rootProject.tasks.named("renameTemplate"))
}

project('desktopApp').tasks.named {
    // startsWith is used because this applies to multiple tasks,
    // like formatKotlinMain, formatKotlinTest, etc.
    // Similar to `copyNonXmlValueResourcesFor[...]` call.
    it.startsWith("formatKotlin")
            || it.startsWith("copyNonXmlValueResources")
            || it.startsWith("convertXmlValueResourcesFor")
}.configureEach {
    mustRunAfter(rootProject.tasks.named("renameTemplate"))
}

project('shared').tasks.named {
    // startsWith is used because this applies to multiple tasks,
    // like formatKotlinMain, formatKotlinTest, etc.
    // Similar to `convertXmlValueResourcesFor[...]` call.
    it.startsWith("formatKotlin") || it.startsWith("convertXmlValueResourcesFor")
}.configureEach {
    mustRunAfter(rootProject.tasks.named("renameTemplate"))
}

// The following tasks appear to be added to the task graph
// for the rename template flow but we don't actually want them to run when
// we're doing the rename process. I haven't figured out why they're included,
// but this ensures they only run when we're NOT calling renameTemplate.
def tasksToExclude = [
        "copyNonXmlValueResources",
        "generateActualResourceCollectors",
        "generateCommonMainAppDatabaseInterface",
        "generateResourceAccessors",
        "generateServiceApolloSources",
        "prepareComposeResourcesTask",
]

project('shared').tasks.named { taskName ->
    tasksToExclude.any { excludeName ->
        taskName.startsWith(excludeName)
    }
}.configureEach {
    onlyIf {
        !project.gradle.startParameter.taskNames.contains("renameTemplate")
    }
}


tasks.register('renameTemplate', Copy) {
    dependsOn(renameIOSProject)

    into(rootDir)

    def newPackageAsDirectory = renameConfig.newPackage.replaceAll("\\.", "/")
    def templateName = renameConfig.templateName

    // Android App Package
    from("androidApp/src/main/kotlin/$templateName") {
        into("androidApp/src/main/kotlin/$newPackageAsDirectory")
    }

    // Shared Android Package
    from("shared/src/androidMain/kotlin/$templateName/shared") {
        into("shared/src/androidMain/kotlin/$newPackageAsDirectory/shared")
    }

    // Shared iOS Package
    from("shared/src/iosMain/kotlin/$templateName/shared") {
        into("shared/src/iosMain/kotlin/$newPackageAsDirectory/shared")
    }

    // Shared Desktop Package
    from("shared/src/desktopMain/kotlin/$templateName/shared") {
        into("shared/src/desktopMain/kotlin/$newPackageAsDirectory/shared")
    }

    // Shared Common Package
    from("shared/src/commonMain/kotlin/$templateName/shared") {
        into("shared/src/commonMain/kotlin/$newPackageAsDirectory/shared")
    }

    // Shared Sqldelight Directory
    from("shared/src/commonMain/sqldelight/$templateName/shared") {
        into("shared/src/commonMain/sqldelight/$newPackageAsDirectory/shared")
    }

    // Shared Graphql Directory
    from("shared/src/commonMain/graphql/$templateName/shared") {
        into("shared/src/commonMain/graphql/$newPackageAsDirectory/shared")
    }

    // Desktop App Package
    from("desktopApp/src/jvmMain/kotlin/$templateName") {
        into("desktopApp/src/jvmMain/kotlin/$newPackageAsDirectory")
    }

    // Replace package statements
    filter { line ->
        line.replaceAll(
                "package ${renameConfig.templateName}",
                "package ${renameConfig.newPackage}"
        )
    }

    // Replace import statements
    filter { line ->
        line.replaceAll(
                "import ${renameConfig.templateName}",
                "import ${renameConfig.newPackage}"
        )
    }

    // Replace Theme references. We can just replace on name,
    // which covers both imports and function calls.
    filter { line ->
        line.replaceAll(
                "${renameConfig.templateMaterialThemeName}",
                "${renameConfig.newMaterialThemeName}"
        )
    }

    // Replace application class references
    filter { line ->
        line.replaceAll(
                "${renameConfig.templateApplicationClassName}",
                "${renameConfig.newApplicationClassName}",
        )
    }

    rename { fileName ->
        if (fileName.contains("${renameConfig.templateApplicationClassName}")) {
            fileName.replace(
                    "${renameConfig.templateApplicationClassName}",
                    "${renameConfig.newApplicationClassName}",
            )
        } else {
            fileName
        }
    }

    doFirst {
        keepOrRemoveDependencies(project, renameConfig)
    }

    doLast {
        replaceTemplateReferences(rootDir, renameConfig)
        deleteSetupCode(project)
        delete("$rootDir/androidApp/src/main/kotlin/$templateName")
        delete("$rootDir/desktopApp/src/jvmMain/kotlin/$templateName")
        delete("$rootDir/shared/src/androidMain/kotlin/$templateName")
        delete("$rootDir/shared/src/iosMain/kotlin/$templateName")
        delete("$rootDir/shared/src/desktopMain/kotlin/$templateName")
        delete("$rootDir/shared/src/commonMain/kotlin/$templateName")
        delete("$rootDir/shared/src/commonMain/sqldelight/$templateName")
        delete("$rootDir/shared/src/commonMain/graphql/$templateName")

        def execOps = services.get(ExecOperations.class)

        execOps.exec {
            workingDir rootDir
            // After all setup changes happen, run a `git add` so
            // folks can just immediately commit and push if they wish.
            commandLine "git", "add", "." // Using relative path "." as workingDir is set
        }
    }

    finalizedBy(
            "androidApp:formatKotlin",
            "desktopApp:formatKotlin",
            "shared:formatKotlin",
    )
}

static def replaceTemplateReferences(rootDir, renameConfig) {
    replaceTextInFile(
            "$rootDir/androidApp/src/main/AndroidManifest.xml",
            "${renameConfig.templateName}.MainActivity",
            "${renameConfig.newPackage}.MainActivity",
    )

    replaceTextInFile(
            "$rootDir/androidApp/src/main/AndroidManifest.xml",
            ".${renameConfig.templateApplicationClassName}",
            ".${renameConfig.newApplicationClassName}",
    )

    replaceTextInFile(
            "$rootDir/androidApp/build.gradle.kts",
            "namespace = \"${renameConfig.templateName}\"",
            "namespace = \"${renameConfig.newPackage}\"",
    )

    replaceTextInFile(
            "$rootDir/androidApp/build.gradle.kts",
            "applicationId = \"${renameConfig.templateAppId}\"",
            "applicationId = \"${renameConfig.newPackage}\"",
    )

    replaceTextInFile(
            "$rootDir/settings.gradle.kts",
            "rootProject.name = \"${renameConfig.templateName}\"",
            "rootProject.name = \"${renameConfig.newProjectName}\"",
    )

    replaceTextInFile(
            "$rootDir/androidApp/src/main/res/values/strings.xml",
            "<string name=\"app_name\">${renameConfig.templateName}</string>",
            "<string name=\"app_name\">${renameConfig.newProjectName}</string>",
    )

    replaceTextInFile(
            "$rootDir/shared/build.gradle.kts",
            "namespace = \"${renameConfig.templateName}.shared\"",
            "namespace = \"${renameConfig.newPackage}.shared\"",
    )

    replaceTextInFile(
            "$rootDir/shared/build.gradle.kts",
            "packageOfResClass = \"${renameConfig.templateName}.shared\"",
            "packageOfResClass = \"${renameConfig.newPackage}.shared\"",
    )

    replaceTextInFile(
            "$rootDir/shared/build.gradle.kts",
            "packageName.set\\(\"${renameConfig.templateName}.shared\"\\)",
            "packageName.set\\(\"${renameConfig.newPackage}.shared\"\\)"
    )

    replaceTextInFile(
            "$rootDir/.github/workflows/ios_build.yml",
            renameConfig.templateIOSName,
            renameConfig.newIOSName
    )

    replaceTextInFile(
            "$rootDir/desktopApp/build.gradle.kts",
            "mainClass = \"${renameConfig.templateName}.MainKt\"",
            "mainClass = \"${renameConfig.newPackage}.MainKt\"",
    )

    replaceTextInFile(
            "$rootDir/desktopApp/build.gradle.kts",
            "package = \"${renameConfig.templateName}\"",
            "package = \"${renameConfig.newPackage}\"",
    )

    replaceTextInFile(
            "$rootDir/shared/build.gradle.kts",
            "=${renameConfig.templateName}.shared.Parcelize",
            "=${renameConfig.newPackage}.shared.Parcelize",
    )
}

static def keepOrRemoveDependencies(project, renameConfig) {
    def rootDir = project.rootDir

    def filesWithDependencies = [
            "$rootDir/build.gradle.kts",
            "$rootDir/androidApp/build.gradle.kts",
            "$rootDir/shared/build.gradle.kts",
            "$rootDir/gradle/libs.versions.toml",
    ]

    filesWithDependencies.each { fileName ->
        if (renameConfig.useSqldelight != true) {
            println("Removing sqldelight dependencies")

            def pluginSetupRegex = /sqldelight\s*\{\s*databases\s*\{\s*create\("AppDatabase"\)\s*\{\s*packageName\.set\("template\.shared"\)\s*}\s*}\s*}\s*/
            replaceTextInFile(
                    fileName,
                    pluginSetupRegex,
                    ''
            )

            removeTextFromFile(fileName, "sqldelight")

            project.delete("$rootDir/shared/src/commonMain/kotlin/template/shared/data/local/DatabaseDriverFactory.kt")
            project.delete("$rootDir/shared/src/androidMain/kotlin/template/shared/data/local/DatabaseDriverFactory.android.kt")
            project.delete("$rootDir/shared/src/iosMain/kotlin/template/shared/data/local/DatabaseDriverFactory.ios.kt")
            project.delete("$rootDir/shared/src/commonMain/sqldelight/template/shared/AppDatabase.sq")
        }

        if (renameConfig.useKtor != true) {
            println("Removing ktor dependencies")
            removeTextFromFile(fileName, "ktor")
            removeTextFromFile(fileName, "kotlinx-serialization")
            removeTextFromFile(fileName, "kotlinx.serialization")

            project.delete("$rootDir/shared/src/commonMain/kotlin/template/shared/data/remote/BaseKtorClient.kt")
        }

        if (renameConfig.useKoin != true) {
            println("Removing koin dependencies")
            removeTextFromFile(fileName, "koin")
        }

        if (renameConfig.useApollo != true) {
            println("Removing apollo dependencies")

            def pluginSetupRegex = /apollo\s*\{\s*service\("service"\)\s*\{\s*packageName\.set\("template\.shared"\)\s*\}\s*\}\s*/
            replaceTextInFile(
                    fileName,
                    pluginSetupRegex,
                    ''
            )

            removeTextFromFile(fileName, "apollo")

            project.delete("$rootDir/shared/src/commonMain/graphql")
        }

        if (renameConfig.useDataStore != true) {
            removeTextFromFile(fileName, "datastore")

            project.delete("$rootDir/shared/src/commonMain/kotlin/template/shared/data/preferences/CreateDataStore.kt")
            project.delete("$rootDir/shared/src/androidMain/kotlin/template/shared/data/preferences/CreateDataStore.android.kt")
            project.delete("$rootDir/shared/src/iosMain/kotlin/template/shared/data/preferences/CreateDataStore.ios.kt")
            project.delete("$rootDir/shared/src/desktopMain/kotlin/template/shared/data/preferences/CreateDataStore.desktop.kt")
        }
    }
}

static def deleteSetupCode(project) {
    def rootDir = project.rootDir

    def workflowsFolder = "$rootDir/.github/workflows"
    def buildscriptsFolder = "$rootDir/buildscripts"
    def templateChangeWorkflowFile = "$workflowsFolder/template_change_test.yml"
    def macosHooksWorkflowFile = "$workflowsFolder/install_git_hooks_macos.yml"
    def windowsHooksWorkflowFile = "$workflowsFolder/install_git_hooks_windows.yml"
    def setupGradle = "$buildscriptsFolder/setup.gradle"

    removeTextFromFile("$rootDir/build.gradle.kts", "setup.gradle")
    project.delete(templateChangeWorkflowFile)
    project.delete(macosHooksWorkflowFile)
    project.delete(windowsHooksWorkflowFile)
    project.delete(setupGradle)
}

/**
 * Replaces all instances of [text] in a given [fileName].
 */
static def replaceTextInFile(fileName, originalText, newText) {
    def file = new File(fileName)

    file.text = file.text.replaceAll(originalText, newText)
}

/**
 * Removes all lines from the given fileName that contain some supplied text.
 */
static def removeTextFromFile(fileName, text) {
    def file = new File(fileName)
    List fileLines = file.readLines()
    file.text = ""
    fileLines.each { line ->
        if (!line.contains(text)) {
            file.append(line)
            file.append("\n")
        }
    }
}