//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation
@_spi(SourceKitLSP) package import LanguageServerProtocol
@_spi(SourceKitLSP) import SKLogging
package import SKOptions
import SwiftExtensions
import TSCExtensions
import ToolchainRegistry
@_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions

import struct TSCBasic.AbsolutePath
import struct TSCBasic.RelativePath

private func searchForCompilationDatabaseConfig(
  in workspaceFolder: URL,
  options: SourceKitLSPOptions
) -> BuildServerSpec? {
  let searchPaths =
    (options.compilationDatabaseOrDefault.searchPaths ?? []).compactMap { searchPath in
      orLog("Compilation DB search path") {
        try RelativePath(validating: searchPath)
      }
    } + [
      // These default search paths match the behavior of `clangd`
      try! RelativePath(validating: "."),
      try! RelativePath(validating: "build"),
    ]

  return
    searchPaths
    .lazy
    .compactMap { searchPath in
      let path = workspaceFolder.appending(searchPath)

      let jsonPath = path.appending(component: JSONCompilationDatabaseBuildServer.dbName)
      if FileManager.default.isFile(at: jsonPath) {
        return BuildServerSpec(kind: .jsonCompilationDatabase, projectRoot: workspaceFolder, configPath: jsonPath)
      }

      let fixedPath = path.appending(component: FixedCompilationDatabaseBuildServer.dbName)
      if FileManager.default.isFile(at: fixedPath) {
        return BuildServerSpec(kind: .fixedCompilationDatabase, projectRoot: workspaceFolder, configPath: fixedPath)
      }

      return nil
    }
    .first
}

/// Determine which build server should be started to handle the given workspace folder and at which folder that build
/// servers's project root is (see `BuiltInBuildServer.projectRoot(for:options:)`). `onlyConsiderRoot` controls whether
/// paths outside the root should be considered (eg. configuration files in the user's home directory).
///
/// Returns `nil` if no build server can handle this workspace folder.
package func determineBuildServer(
  forWorkspaceFolder workspaceFolder: DocumentURI,
  onlyConsiderRoot: Bool,
  options: SourceKitLSPOptions,
  hooks: BuildServerHooks
) -> BuildServerSpec? {
  if let injectBuildServer = hooks.injectBuildServer {
    return BuildServerSpec(
      kind: .injected(injectBuildServer),
      projectRoot: workspaceFolder.arbitrarySchemeURL,
      configPath: workspaceFolder.arbitrarySchemeURL
    )
  }

  var buildServerPreference: [WorkspaceType] = [
    .buildServer, .swiftPM, .compilationDatabase,
  ]
  if let defaultBuildServer = options.defaultWorkspaceType {
    buildServerPreference.removeAll(where: { $0 == defaultBuildServer })
    buildServerPreference.insert(defaultBuildServer, at: 0)
  }
  guard let workspaceFolderUrl = workspaceFolder.fileURL else {
    return nil
  }
  for buildServerType in buildServerPreference {
    var spec: BuildServerSpec? = nil

    switch buildServerType {
    case .buildServer:
      spec = ExternalBuildServerAdapter.searchForConfig(
        in: workspaceFolderUrl,
        onlyConsiderRoot: onlyConsiderRoot,
        options: options
      )
    case .compilationDatabase:
      spec = searchForCompilationDatabaseConfig(in: workspaceFolderUrl, options: options)
    case .swiftPM:
      #if !NO_SWIFTPM_DEPENDENCY
      spec = SwiftPMBuildServer.searchForConfig(in: workspaceFolderUrl, options: options)
      #endif
    }

    if let spec {
      return spec
    }
  }

  return nil
}
