/*
 * sbt
 * Copyright 2023, Scala center
 * Copyright 2011 - 2022, Lightbend, Inc.
 * Copyright 2008 - 2010, Mark Harrah
 * Licensed under Apache License 2.0 (see LICENSE)
 */

package sbt
package plugins

import java.io.File

import Keys.*
import sbt.internal.SysProp
import sbt.librarymanagement.syntax.*
import sbt.librarymanagement.{ Configuration, CrossVersion }
import sbt.librarymanagement.LibraryManagementCodec.given
import ProjectExtra.inConfig
import sbt.internal.inc.ScalaInstance
import sbt.ScopeFilter.Make.*
import sbt.util.CacheImplicits.given

object SemanticdbPlugin extends AutoPlugin {
  override def requires = JvmPlugin
  override def trigger = allRequirements

  override lazy val globalSettings: Seq[Def.Setting[?]] = Seq(
    semanticdbEnabled := SysProp.semanticdb,
    semanticdbIncludeInJar := false,
    semanticdbOptions := List(),
    semanticdbVersion := "4.15.2"
  )

  override lazy val projectSettings: Seq[Def.Setting[?]] = Seq(
    semanticdbCompilerPlugin := {
      val v = semanticdbVersion.value
      ("org.scalameta" % "semanticdb-scalac" % v).cross(CrossVersion.full)
    },
    allDependencies ++= {
      val sdb = semanticdbEnabled.value
      val m = semanticdbCompilerPlugin.value
      val sv = scalaVersion.value
      if (sdb && !ScalaInstance.isDotty(sv)) List(BuildExtra.compilerPlugin(m))
      else Nil
    },
    semanticdbOptions += {
      val sv = scalaVersion.value
      if (sv.startsWith("0.") || sv.startsWith("3.0.0-M1") || sv.startsWith("3.0.0-M2"))
        "-Ysemanticdb"
      else if (sv.startsWith("3.")) "-Xsemanticdb"
      else "-Yrangepos"
    }
  ) ++
    inConfig(Compile)(configurationSettings) ++
    inConfig(Test)(configurationSettings)

  lazy val configurationSettings: Seq[Def.Setting[?]] = List(
    compileIncremental := Def.taskIf {
      if (semanticdbIncludeInJar.value || !semanticdbEnabled.value) compileIncremental.value
      else compileIncAndCacheSemanticdbTargetRootTask.value
    }.value,
    semanticdbTargetRoot := {
      val converter = fileConverter.value
      val in = semanticdbIncludeInJar.value
      if in then converter.toPath(backendOutput.value).toFile()
      else semanticdbTargetRoot.value
    },
    semanticdbOptions --= Def.settingDyn {
      val scalaV = scalaVersion.value
      val config = configuration.value
      Def.setting {
        semanticdbTargetRoot.?.all(ancestorConfigs(config)).value.flatten
          .flatMap(targetRootOptions(scalaV, _))
      }
    }.value,
    semanticdbOptions ++=
      targetRootOptions(scalaVersion.value, semanticdbTargetRoot.value),
    scalacOptions := (Def.taskDyn {
      val orig = scalacOptions.value
      val config = configuration.value
      if semanticdbEnabled.value then
        Def.task {
          (orig diff semanticdbOptions.?.all(ancestorConfigs(config)).value.flatten.flatten) ++
            semanticdbOptions.value
        }
      else
        Def.task {
          orig
        }
    }).value,
  )

  def targetRootOptions(scalaVersion: String, targetRoot: File): Seq[String] = {
    if (ScalaInstance.isDotty(scalaVersion)) {
      Seq("-semanticdb-target", targetRoot.toString)
    } else {
      Seq(s"-P:semanticdb:targetroot:$targetRoot")
    }
  }

  private val compileIncAndCacheSemanticdbTargetRootTask = Def.cachedTask {
    val prev = compileIncremental.value
    val converter = fileConverter.value
    val targetRoot = semanticdbTargetRoot.value

    val vfTargetRoot = converter.toVirtualFile(targetRoot.toPath)
    Def.declareOutputDirectory(vfTargetRoot)
    prev
  }

  private def ancestorConfigs(config: Configuration) = {
    def ancestors(configs: Vector[Configuration]): Vector[Configuration] =
      configs ++ configs.flatMap(conf => ancestors(conf.extendsConfigs))

    ScopeFilter(configurations = inConfigurations(ancestors(config.extendsConfigs)*))
  }
}
