default_platform :ios

# No Xcode version pin: novaloop-mac-studio-001 carries a single, globally-selected Xcode, so
# whatever xcode-select points at is by definition the one we build with. Pinning bought nothing
# and risked everything — a version the runner lacks sends xcodes into a multi-GB authenticated
# download that no CI secret can satisfy, hanging the release. Change the runner's Xcode instead.
platform :ios do
  #### Public ####

  desc "Builds the App Store ipa and uploads it to TestFlight (internal testers only)"
  # Named release_testflight to avoid colliding with fastlane's built-in :testflight action.
  lane :release_testflight do |options|
    # Accept inputs via options (local) or env vars (CI — lets the workflow pass multi-line
    # release notes without shell-escaping them).
    marketing_version = options[:marketing_version] || ENV["HEALTHCHAT_MARKETING_VERSION"]
    release_notes     = options[:release_notes]     || ENV["HEALTHCHAT_RELEASE_NOTES"]

    UI.user_error!("'marketing_version' is required (pass the option or set HEALTHCHAT_MARKETING_VERSION)") if marketing_version.to_s.empty?

    # Apple caps CFBundleShortVersionString at three period-separated integers. The workflow's
    # regex is laxer and accepts markers like 26.07.3-rc1, so catch it here — otherwise the run
    # dies at the upload step after ~30 minutes of signing, looking exactly like a signing failure.
    unless marketing_version =~ /\A\d+(\.\d+){0,2}\z/
      UI.user_error!("'marketing_version' must be 1-3 dot-separated integers (got '#{marketing_version}'). " \
                     "Apple rejects anything else at upload. Put markers in the tag or release notes instead.")
    end

    build_number = options[:build_number] || generate_build_number

    build_release(marketing_version: marketing_version, build_number: build_number)

    upload_to_testflight(
      ipa: lane_context[SharedValues::IPA_OUTPUT_PATH],
      skip_submission: true,
      skip_waiting_for_build_processing: true,
      changelog: release_notes,
      distribute_external: false,
    )
  end

  #### Private ####

  desc "Builds the signed App Store ipa"
  private_lane :build_release do |options|
    marketing_version = options[:marketing_version]
    build_number = options[:build_number]
    UI.user_error!("'build_number' parameter is missing") if build_number.to_s.empty?

    if is_ci
      %w[APPSTORECONNECT_KEY_ID APPSTORECONNECT_KEY_ISSUER_ID APPSTORECONNECT_KEY_CONTENT].each do |key|
        UI.user_error!("'#{key}' environment variable should be set to use this lane") if ENV[key].to_s.empty?
      end

      app_store_connect_api_key(
        key_id: ENV["APPSTORECONNECT_KEY_ID"],
        issuer_id: ENV["APPSTORECONNECT_KEY_ISSUER_ID"],
        key_content: ENV["APPSTORECONNECT_KEY_CONTENT"]
      )
    else
      UI.user_error!("'APPLE_ID' environment variable should be set to use this lane") if ENV["APPLE_ID"].to_s.empty?
    end

    team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)
    UI.user_error!("Failed to fetch the team id from the Appfile") if team_id.to_s.empty?

    # Inject the version at build time rather than rewriting project.yml, so no tracked file ever
    # carries a release-only value. All three targets resolve CFBundleShortVersionString and
    # CFBundleVersion from these settings, which keeps the app and its extensions consistent —
    # App Store rejects an upload where they disagree.
    xcargs_hash = {
      "MARKETING_VERSION" => marketing_version,
      "CURRENT_PROJECT_VERSION" => build_number,
      "CODE_SIGN_STYLE" => "Manual",
    }
    xcargs = xcargs_hash.map { |k, v| "#{k}=#{v.to_s.shellescape}" }.join(" ")

    xcodegen(spec: "project.yml")
    clear_derived_data(derived_data_path: ENV["DERIVED_DATA_PATH"])

    download_provisioning_profiles
    disable_automatic_code_signing(path: ENV["PROJECT_PATH"])
    update_project_provisioning_profiles

    build_app(
      clean: true,
      scheme: ENV["SCHEME"],
      xcargs: xcargs,
      export_method: "app-store",
      derived_data_path: ENV["DERIVED_DATA_PATH"],
      archive_path: ENV["ARCHIVE_PATH"],
      output_directory: ENV["BUILD_OUTPUT_DIRECTORY"],
      output_name: "#{ENV['IPA_NAME']}.ipa",
      buildlog_path: ENV["BUILD_LOG_DIRECTORY"],
      codesigning_identity: ENV["APPSTORE_CODESIGNING_IDENTITY"],
      skip_profile_detection: true,
      export_options: {
        method: "app-store",
        signingStyle: "manual",
        teamID: team_id,
        signingCertificate: ENV["APPSTORE_SIGNING_CERTIFICATE"],
        provisioningProfiles: {
          ENV["MAIN_BUNDLE_ID"] => ENV["APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER"],
          ENV["NSE_BUNDLE_ID"] => ENV["APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER"],
          ENV["SHARE_EXTENSION_BUNDLE_ID"] => ENV["APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"],
        },
      },
    )
  end

  desc "Downloads the three App Store provisioning profiles"
  private_lane :download_provisioning_profiles do
    {
      ENV["MAIN_BUNDLE_ID"] => [ENV["APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER"], ENV["MAIN_PROVISIONING_PROFILE_FILENAME"]],
      ENV["NSE_BUNDLE_ID"] => [ENV["APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER"], ENV["NSE_PROVISIONING_PROFILE_FILENAME"]],
      ENV["SHARE_EXTENSION_BUNDLE_ID"] => [ENV["APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"], ENV["SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME"]],
    }.each do |bundle_id, (profile_name, filename)|
      get_provisioning_profile(
        app_identifier: bundle_id,
        provisioning_name: profile_name,
        ignore_profiles_with_different_name: true,
        adhoc: false,
        skip_certificate_verification: false,
        output_path: ENV["PROVISIONING_PROFILES_PATH"],
        filename: filename,
        readonly: true,
      )
    end
  end

  desc "Points each target at its downloaded provisioning profile"
  private_lane :update_project_provisioning_profiles do
    {
      ENV["MAIN_TARGET"] => ENV["MAIN_PROVISIONING_PROFILE_FILENAME"],
      ENV["NSE_TARGET"] => ENV["NSE_PROVISIONING_PROFILE_FILENAME"],
      ENV["SHARE_EXTENSION_TARGET"] => ENV["SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME"],
    }.each do |target, filename|
      update_project_provisioning(
        xcodeproj: ENV["PROJECT_PATH"],
        profile: "#{ENV['PROVISIONING_PROFILES_PATH']}#{filename}",
        # Anchored: target_filter is a regex, and an unanchored "NSE" or "ElementX" would also
        # match any future target containing that substring.
        target_filter: "^#{target}$",
        build_configuration: "Release",
      )
    end
  end
end

# Generates a build number from the current timestamp. Monotonic across runs, so a retry never
# collides with a build number App Store Connect has already seen.
def generate_build_number
  require 'date'
  DateTime.now.strftime("%Y%m%d%H%M%S")
end
