# Define app identifiers once for reuse across lanes
def app_identifiers
  [
    "io.bluewallet.bluewallet",
    "io.bluewallet.bluewallet.watch",
    "io.bluewallet.bluewallet.watch.extension",
    "io.bluewallet.bluewallet.Stickers",
    "io.bluewallet.bluewallet.MarketWidget"
  ]
end

def app_store_state_readable(state)
  states = {
    "DEVELOPER_REJECTED" => "Developer Rejected",
    "PREPARE_FOR_SUBMISSION" => "Prepare for Submission",
    "WAITING_FOR_REVIEW" => "Waiting for Review",
    "IN_REVIEW" => "In Review",
    "PENDING_DEVELOPER_RELEASE" => "Pending Developer Release",
    "READY_FOR_SALE" => "Ready for Sale",
    "REJECTED" => "Rejected",
    "METADATA_REJECTED" => "Metadata Rejected"
  }
  
  states[state] || state
end

require 'securerandom'

default_platform(:android)
PROJECT_ROOT = File.expand_path("..", __dir__)
project_root = PROJECT_ROOT

module AndroidHelpers
  module_function

  def require_env!(keys)
    missing = Array(keys).select { |key| ENV[key].nil? || ENV[key].empty? }
    UI.user_error!("Missing required env vars: #{missing.join(', ')}") unless missing.empty?
  end

  def project_root
    PROJECT_ROOT
  end

  def keystore_paths
    {
      hex: File.join(project_root, 'bluewallet-release-key.keystore.hex'),
      file: File.join(project_root, 'android', 'bluewallet-release-key.keystore'),
    }
  end

  def write_keystore_from_hex!(hex_value, paths)
    UI.user_error!("KEYSTORE_FILE_HEX environment variable is missing") if hex_value.nil? || hex_value.empty?
    File.write(paths[:hex], hex_value)
    Actions.sh("xxd -plain -revert #{paths[:hex]} > #{paths[:file]}") do |status|
      UI.user_error!("Error reverting hex to keystore") unless status.success?
    end
    File.delete(paths[:hex])
  end

  def version_name_and_update_code!(build_gradle_path, build_number)
    gradle_contents = File.read(build_gradle_path)
    File.write(build_gradle_path, gradle_contents.gsub(/versionCode\s+\d+/, "versionCode #{build_number}"))
    version_name = File.read(build_gradle_path)[/versionName\s+"([^"]+)"/, 1]
    UI.user_error!("Failed to extract versionName from #{build_gradle_path}") if version_name.nil? || version_name.empty?
    version_name
  end

  def branch_name
    raw = ENV['GITHUB_HEAD_REF'] || ENV['GITHUB_REF_NAME'] || `git rev-parse --abbrev-ref HEAD`.strip
    sanitized = raw.to_s.gsub(/[^a-zA-Z0-9_-]/, '_')
    sanitized.empty? ? 'master' : sanitized
  end

  def apk_name(version_name, build_number, branch)
    branch != 'master' ? "BlueWallet-#{version_name}-#{build_number}-#{branch}.apk" : "BlueWallet-#{version_name}-#{build_number}.apk"
  end

  def apksigner_path
    sdk_root = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT']
    Dir.glob(File.join(sdk_root.to_s, 'build-tools', '*', 'apksigner')).sort.last
  end

  def gradle_log_path
    path = File.join(project_root, 'fastlane', 'logs', 'gradle-build.log')
    FileUtils.mkdir_p(File.dirname(path))
    path
  end

  def assemble_release!(log_path:)
    Actions.sh("cd android && ./gradlew assembleRelease --no-daemon --stacktrace --console=plain | tee #{log_path}") do |status|
      UI.user_error!("Gradle assembleRelease failed") unless status.success?
    end
  end

  def resolve_apk_paths(version_name:, build_number:, branch_name:)
    apk_dir = File.join(project_root, 'android', 'app', 'build', 'outputs', 'apk', 'release')
    unsigned = File.join(apk_dir, 'app-release-unsigned.apk')
    fallback = File.join(apk_dir, 'app-release.apk')
    signed = File.join(apk_dir, apk_name(version_name, build_number, branch_name))
    { unsigned: unsigned, fallback: fallback, signed: signed }
  end

  def finalize_apk!(paths)
    candidate = File.exist?(paths[:unsigned]) ? paths[:unsigned] : paths[:fallback]
    UI.user_error!("Unsigned APK not found at path: #{paths[:unsigned]} or #{paths[:fallback]}") unless File.exist?(candidate)
    FileUtils.mv(candidate, paths[:signed])
    paths[:signed]
  end

  def sign_apk!(apk_path, keystore_path, keystore_password)
    signer = apksigner_path
    UI.user_error!("apksigner not found in Android build-tools") if signer.nil? || signer.empty?
    Actions.sh("#{signer} sign --ks #{keystore_path} --ks-pass=pass:#{keystore_password} #{apk_path}")
  end

  def write_github_output(hash)
    return unless ENV['GITHUB_OUTPUT'] && !ENV['GITHUB_OUTPUT'].empty?

    File.open(ENV['GITHUB_OUTPUT'], 'a') do |f|
      hash.each { |key, value| f.puts("#{key}=#{value}") }
    end
  end

  def ensure_temp_credentials!(auto: false)
    return unless auto

    temp_dir = Dir.mktmpdir('bw-temp-keystore')
    keystore_path = File.join(temp_dir, 'temp.keystore')
    password = "temp#{SecureRandom.hex(6)}"
    alias_name = "bluewallet-temp"

    Actions.sh(
      "keytool -genkeypair -v -keystore #{keystore_path} -storepass #{password} -keypass #{password} " \
      "-alias #{alias_name} -keyalg RSA -keysize 2048 -validity 10000 " \
      "-dname \"CN=Temp,O=BlueWallet,OU=CI,L=NY,ST=NY,C=US\""
    ) do |status|
      UI.user_error!("Failed to create temporary keystore with keytool") unless status.success?
    end

    keystore_hex = File.binread(keystore_path).unpack1('H*')
    ENV['KEYSTORE_FILE_HEX'] = keystore_hex
    ENV['KEYSTORE_PASSWORD'] = password
  end
end

# Add session caching for App Store Connect
def cached_app_store_connect_login
  # Skip if already authenticated and token is not expired
  if defined?(Spaceship::ConnectAPI) && Spaceship::ConnectAPI.token && !Spaceship::ConnectAPI.token.expired?
    UI.message("Using existing App Store Connect session")
    return true
  end
  
  UI.message("Logging in to App Store Connect...")
  
  # Try API key authentication first
  api_key_path = ENV["APPLE_API_KEY_PATH"] || "./appstore_api_key.json"
  
  if File.exist?(api_key_path) && ENV["APPLE_API_KEY_ID"] && ENV["APPLE_API_ISSUER_ID"]
    UI.message("Using API key authentication for App Store Connect")
    api_key = app_store_connect_api_key(
      key_id: ENV["APPLE_API_KEY_ID"],
      issuer_id: ENV["APPLE_API_ISSUER_ID"],
      key_filepath: api_key_path,
      duration: 1200, # 20 minute session
      in_house: false
    )
    
    # Store the API key in lane context for reuse
    ENV["SPACESHIP_CONNECT_API_KEY"] = api_key
    
    # Force App Store Connect API to use this key
    Spaceship::ConnectAPI.token = api_key
    
    UI.success("Successfully authenticated with App Store Connect API Key")
    return true
  elsif ENV["FASTLANE_USER"] && ENV["FASTLANE_PASSWORD"]
    UI.message("Using username/password from environment variables")
    # Use credentials from environment variables
    Spaceship::ConnectAPI.login(
      use_portal: true, 
      use_tunes: true,
      portal_team_id: ENV["TEAM_ID"],
      tunes_team_id: ENV["ITC_TEAM_ID"]
    )
    UI.success("Successfully authenticated with Apple ID")
    return true
  else
    UI.message("Using interactive username/password authentication")
    # Last resort - interactive login
    Spaceship::ConnectAPI.login(
      use_portal: true, 
      use_tunes: true
    )
    UI.success("Successfully authenticated with Apple ID")
    return true
  end
end

before_all do |lane, options|
  if ENV['SKIP_APP_STORE_CONNECT_AUTH'] == '1'
    UI.message('Skipping App Store Connect authentication (SKIP_APP_STORE_CONNECT_AUTH=1)')
    next
  end

  skip_auth_lanes = ['register_devices_from_txt', 'build_catalyst_app_lane', 'install_pods', 'clear_derived_data_lane']
  lane_name = lane.to_s
  should_skip_auth = skip_auth_lanes.any? { |skip_lane| lane_name == skip_lane || lane_name.end_with?(" #{skip_lane}") }
  
  # Check if we need App Store Connect for this lane
  unless should_skip_auth
    begin
      # Try to authenticate once at the beginning
      require 'spaceship'
      cached_app_store_connect_login
    rescue => ex
      UI.error("Authentication failed: #{ex.message}")
      # Continue anyway as some lanes might not need authentication
    end
  end
end

# ===========================
#       Android Lanes
# ===========================

platform :android do

  desc "Prepare the keystore file"
  lane :prepare_keystore do
    Dir.chdir(PROJECT_ROOT) do
      paths = AndroidHelpers.keystore_paths
      AndroidHelpers.write_keystore_from_hex!(ENV['KEYSTORE_FILE_HEX'], paths)
      UI.message("Keystore created successfully.")
    end
  end

  desc "Update version, build number, and sign APK"
  lane :update_version_build_and_sign_apk do |options|
    Dir.chdir(PROJECT_ROOT) do
      AndroidHelpers.ensure_temp_credentials!(auto: options[:auto_credentials])
      AndroidHelpers.require_env!(%w[BUILD_NUMBER KEYSTORE_PASSWORD KEYSTORE_FILE_HEX])

      keystore_paths = AndroidHelpers.keystore_paths
      AndroidHelpers.write_keystore_from_hex!(ENV['KEYSTORE_FILE_HEX'], keystore_paths)

      build_gradle_path = File.join('android', 'app', 'build.gradle')
      version_name = AndroidHelpers.version_name_and_update_code!(build_gradle_path, ENV['BUILD_NUMBER'])
      branch = AndroidHelpers.branch_name
      apk_paths = AndroidHelpers.resolve_apk_paths(version_name: version_name, build_number: ENV['BUILD_NUMBER'], branch_name: branch)

      UI.message("Building APK...")
      AndroidHelpers.assemble_release!(log_path: AndroidHelpers.gradle_log_path)
      UI.message("APK build completed.")

      signed_apk_path = AndroidHelpers.finalize_apk!(apk_paths)
      ENV['APK_OUTPUT_PATH'] = File.expand_path(signed_apk_path)

      UI.message("Signing APK with apksigner...")
      AndroidHelpers.sign_apk!(signed_apk_path, keystore_paths[:file], ENV['KEYSTORE_PASSWORD'])
      UI.message("APK signed successfully: #{signed_apk_path}")

      FileUtils.rm_f(keystore_paths[:file])
    end
  end

  desc "Build and sign release APK"
  lane :build_release_apk do |options|
    Dir.chdir(PROJECT_ROOT) do
      # Allow caller to pass a build_number; otherwise fall back to env or timestamp
      build_number = options[:build_number] || ENV['BUILD_NUMBER'] || Time.now.to_i.to_s
      ENV['BUILD_NUMBER'] = build_number

      AndroidHelpers.ensure_temp_credentials!(auto: options[:auto_credentials])
      AndroidHelpers.require_env!(%w[KEYSTORE_FILE_HEX KEYSTORE_PASSWORD])

      keystore_paths = AndroidHelpers.keystore_paths
      AndroidHelpers.write_keystore_from_hex!(ENV['KEYSTORE_FILE_HEX'], keystore_paths)

      build_gradle_path = File.join('android', 'app', 'build.gradle')
      version_name = AndroidHelpers.version_name_and_update_code!(build_gradle_path, build_number)
      branch = AndroidHelpers.branch_name

      UI.message("Building release APK...")
      AndroidHelpers.assemble_release!(log_path: AndroidHelpers.gradle_log_path)

      apk_paths = AndroidHelpers.resolve_apk_paths(version_name: version_name, build_number: build_number, branch_name: branch)
      signed_apk_path = AndroidHelpers.finalize_apk!(apk_paths)

      UI.message("Signing APK with apksigner...")
      AndroidHelpers.sign_apk!(signed_apk_path, keystore_paths[:file], ENV['KEYSTORE_PASSWORD'])
      UI.success("APK signed successfully: #{signed_apk_path}")

      FileUtils.rm_f(keystore_paths[:file])

      apk_absolute_path = File.expand_path(signed_apk_path)
      ENV['APK_OUTPUT_PATH'] = apk_absolute_path
      ENV['APK_VERSION_NAME'] = version_name
      ENV['APK_VERSION_CODE'] = build_number

      AndroidHelpers.write_github_output(
        apk_output_path: apk_absolute_path,
        apk_version_name: version_name,
        apk_version_code: build_number,
      )
    end
  end

  desc "Upload APK to BrowserStack and post result as PR comment"
  lane :upload_to_browserstack_and_comment do
    Dir.chdir(PROJECT_ROOT) do
      apk_path = ENV['APK_PATH']
      if apk_path.nil? || apk_path.empty?
        UI.message("No APK path provided, searching for APK...")
        apk_path = `find ./ -name "*.apk"`.strip
        UI.user_error!("No APK file found") if apk_path.nil? || apk_path.empty?
      end

      UI.message("Uploading APK to BrowserStack: #{apk_path}...")
      upload_to_browserstack_app_live(
        file_path: apk_path,
        browserstack_username: ENV['BROWSERSTACK_USERNAME'],
        browserstack_access_key: ENV['BROWSERSTACK_ACCESS_KEY']
      )

      app_url = ENV['BROWSERSTACK_LIVE_APP_ID']
      UI.user_error!("BrowserStack upload failed, no app URL returned") if app_url.nil? || app_url.empty?

      apk_filename = File.basename(apk_path)
      apk_download_url = ENV['APK_OUTPUT_PATH']
      browserstack_hashed_id = app_url.gsub('bs://', '')
      pr_number = ENV['GITHUB_PR_NUMBER']

      comment_identifier = '### APK Successfully Uploaded to BrowserStack'

      comment = <<~COMMENT
        #{comment_identifier}

        You can test it on the following devices:
        
        - [Google Pixel 9 (Android 15)](https://app-live.browserstack.com/dashboard#os=android&os_version=15.0&device=Google+Pixel+8&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        - [Google Pixel 8 (Android 14)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Google+Pixel+8&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        - [Google Pixel 7 (Android 13)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Google+Pixel+7&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        - [Google Pixel 5 (Android 12)](https://app-live.browserstack.com/dashboard#os=android&os_version=12.0&device=Google+Pixel+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        - [Google Pixel 3a (Android 9)](https://app-live.browserstack.com/dashboard#os=android&os_version=9.0&device=Google+Pixel+3a&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        
        - [Samsung Galaxy Z Fold 6 (Android 14)](https://app-live.browserstack.com/dashboard#os=android&os_version=14.0&device=Samsung+Galaxy+Z+Fold+6&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        - [Samsung Galaxy Z Fold 5 (Android 13)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Z+Fold+5&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        - [Samsung Galaxy Tab S9 (Android 13)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=Samsung+Galaxy+Tab+S9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        - [Samsung Galaxy Note 9 (Android 8.1)](https://app-live.browserstack.com/dashboard#os=android&os_version=8.1&device=Samsung+Galaxy+Note+9&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)

        - [OnePlus 11R (Android 13)](https://app-live.browserstack.com/dashboard#os=android&os_version=13.0&device=OnePlus+11R&app_hashed_id=#{browserstack_hashed_id}&scale_to_fit=true&speed=1&start=true&browser=chrome)
        **Filename**: [#{apk_filename}](#{apk_download_url})
        **BrowserStack App URL**: #{app_url}
      COMMENT

      if pr_number
        begin
          repo = ENV['GITHUB_REPOSITORY']
          repo_owner, repo_name = repo.split('/')

          UI.message("Fetching existing comments for PR ##{pr_number}...")

          comments_json = `gh api -X GET /repos/#{repo_owner}/#{repo_name}/issues/#{pr_number}/comments`
          comments = JSON.parse(comments_json)

          comments.each do |comment|
            if comment['body'].start_with?(comment_identifier)
              comment_id = comment['id']
              UI.message("Deleting previous comment ID: #{comment_id}...")
              `gh api -X DELETE /repos/#{repo_owner}/#{repo_name}/issues/comments/#{comment_id}`
              UI.success("Deleted comment ID: #{comment_id}")
            end
          end

        rescue => e
          UI.error("Failed to delete previous comments: #{e.message}")
        end
      else
        UI.important("No PR number found. Skipping deletion of previous comments.")
      end

      if pr_number
        begin
          escaped_comment = comment.gsub("'", "'\\''")
          sh("GH_TOKEN=#{ENV['GH_TOKEN']} gh pr comment #{pr_number} --body '#{escaped_comment}'")
          UI.success("Posted new comment to PR ##{pr_number}")
        rescue => e
          UI.error("Failed to post comment to PR: #{e.message}")
        end
      else
        UI.important("No PR number found. Skipping PR comment.")
      end
    end
  end
end


# ===========================
#       iOS Lanes
# ===========================

platform :ios do
  # Add helper methods for error handling and retries
  def ensure_env_vars(vars)
    vars.each do |var|
      UI.user_error!("#{var} environment variable is missing") if ENV[var].nil? || ENV[var].empty?
    end
  end
  
  def log_success(message)
    UI.success("✅ #{message}")
  end
  
  def log_error(message)
    UI.error("❌ #{message}")
  end
  
  # Method to safely call actions with retry logic
  def with_retry(max_attempts = 3, action_name = "")
    attempts = 0
    begin
      attempts += 1
      yield
    rescue => e
      if attempts < max_attempts
        wait_time = 10 * attempts
        log_error("Attempt #{attempts}/#{max_attempts} for #{action_name} failed: #{e.message}")
        UI.message("Retrying in #{wait_time} seconds...")
        sleep(wait_time)
        retry
      else
        log_error("#{action_name} failed after #{max_attempts} attempts: #{e.message}")
        raise e
      end
    end
  end

  desc "Register new devices from a file"
  lane :register_devices_from_txt do
    UI.message("Registering new devices from file...")

    csv_path = "../../devices.txt" # Update this with the actual path to your file

    # Register devices using the devices_file parameter
    register_devices(
      devices_file: csv_path
    )

    UI.message("Devices registered successfully.")

    # Update provisioning profiles for all app identifiers
    app_identifiers.each do |app_identifier|
      match(
        type: "development", 
        app_identifier: app_identifier,
        readonly: false, # Regenerate provisioning profile if needed
        force_for_new_devices: true,
        clone_branch_directly: true
      )
    end

    UI.message("Development provisioning profiles updated.")
  end  

  desc "Create a temporary keychain"
  lane :create_temp_keychain do
    UI.message("Creating a temporary keychain...")

    create_keychain(
      name: "temp_keychain",
      password: ENV["KEYCHAIN_PASSWORD"],
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      lock_when_sleeps: true
    )

    UI.message("Temporary keychain created successfully.")
  end

  desc "Synchronize certificates and provisioning profiles"
  lane :setup_provisioning_profiles do
    required_vars = ["GIT_ACCESS_TOKEN", "GIT_URL", "ITC_TEAM_ID", "ITC_TEAM_NAME", "KEYCHAIN_PASSWORD"]
    ensure_env_vars(required_vars)
    
    UI.message("Setting up provisioning profiles...")
    
    # Iterate over app identifiers to fetch provisioning profiles
    app_identifiers.each do |app_identifier|
      with_retry(3, "Fetching provisioning profile for #{app_identifier}") do
        UI.message("Fetching provisioning profile for #{app_identifier}...")
        match(
          git_basic_authorization: ENV["GIT_ACCESS_TOKEN"],
          git_url: ENV["GIT_URL"],
          type: "appstore",
          clone_branch_directly: true,
          platform: "ios",
          app_identifier: app_identifier,
          team_id: ENV["ITC_TEAM_ID"],
          team_name: ENV["ITC_TEAM_NAME"],
          readonly: true,
          keychain_name: "temp_keychain",
          keychain_password: ENV["KEYCHAIN_PASSWORD"]
        )
        log_success("Successfully fetched provisioning profile for #{app_identifier}")
      end
    end
    
    log_success("All provisioning profiles set up")
  end

  # Only these targets support Mac Catalyst (watch/stickers do not)
  def catalyst_app_identifiers
    [
      "io.bluewallet.bluewallet",
      "io.bluewallet.bluewallet.MarketWidget"
    ]
  end

  desc "Fetch development certificates and provisioning profiles for Mac Catalyst"
  lane :fetch_dev_profiles_catalyst do
    match(
      type: "development",
      platform: "catalyst",
      app_identifier: catalyst_app_identifiers,
      readonly: true,
      clone_branch_directly: true
    )
  end

  desc "Fetch App Store certificates and provisioning profiles for Mac Catalyst"
  lane :fetch_appstore_profiles_catalyst do
    match(
      type: "appstore",
      platform: "catalyst",
      app_identifier: catalyst_app_identifiers,
      readonly: true,
      clone_branch_directly: true
    )
  end

  desc "Create provisioning profiles for Mac Catalyst (first-time setup)"
  lane :setup_catalyst_provisioning_profiles do
    catalyst_app_identifiers.each do |app_identifier|
      match(
        type: "development",
        platform: "catalyst",
        app_identifier: app_identifier,
        readonly: false,
        force_for_new_devices: true,
        clone_branch_directly: true
      )

      match(
        type: "appstore",
        platform: "catalyst",
        app_identifier: app_identifier,
        readonly: false,
        clone_branch_directly: true
      )
    end
  end

  desc "Clear derived data"
  lane :clear_derived_data_lane do
    UI.message("Clearing derived data...")
    clear_derived_data
  end

  desc "Increment build number"
  lane :increment_build_number_lane do
    UI.message("Incrementing build number to current timestamp...")
    
    # Set the new build number
    increment_build_number(
      xcodeproj: "ios/BlueWallet.xcodeproj", 
      build_number: ENV["NEW_BUILD_NUMBER"]
    )
  
    UI.message("Build number set to: #{ENV['NEW_BUILD_NUMBER']}")
  end

  desc "Install CocoaPods dependencies"
  lane :install_pods do
    UI.message("Installing CocoaPods dependencies...")
    cocoapods(podfile: "ios/Podfile",
      try_repo_update_on_error: true,
      repo_update: true,
      
      clean_install: true)
  end

  desc "Build Mac Catalyst app, handle code signing, create DMG with multilingual README, and set GitHub outputs"
  lane :build_catalyst_app_lane do
    Dir.chdir(project_root) do
      UI.message("Building Mac Catalyst application from: #{Dir.pwd}")

      workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
      derived_data_path = File.join(project_root, "ios", "build", "catalyst-derived-data")
      output_dir = File.join(project_root, "ios", "build", "catalyst-output")

      if ENV['SKIP_CLEAR_DERIVED_DATA'] == '1'
        UI.message('Skipping clear_derived_data_lane (SKIP_CLEAR_DERIVED_DATA=1)')
      else
        clear_derived_data_lane
      end
      FileUtils.mkdir_p(derived_data_path)
      FileUtils.mkdir_p(output_dir)

      # Only these targets support Mac Catalyst
      catalyst_identifiers = [
        "io.bluewallet.bluewallet",
        "io.bluewallet.bluewallet.MarketWidget"
      ]

      has_signing_creds = ENV['CATALYST_SIGNING_IDENTITY'] && !ENV['CATALYST_SIGNING_IDENTITY'].empty? &&
                          ENV['CATALYST_TEAM_ID'] && !ENV['CATALYST_TEAM_ID'].empty?
      has_match_creds = ENV['GIT_URL'] && !ENV['GIT_URL'].empty? &&
                        ENV['GIT_ACCESS_TOKEN'] && !ENV['GIT_ACCESS_TOKEN'].empty?
      should_sign = has_signing_creds && has_match_creds && ENV['CATALYST_SKIP_CODESIGNING'] != '1'
      signing_identity = ENV['CATALYST_SIGNING_IDENTITY'] || "Apple Distribution"

      xcargs_str = "ARCHS=arm64 ONLY_ACTIVE_ARCH=YES"
      if should_sign
        UI.message("Setting up Mac Catalyst provisioning profiles via match...")
        team_id = ENV['CATALYST_TEAM_ID']
        match_readonly = ENV['MATCH_READONLY'] != 'false'

        # Create/fetch provisioning profiles for catalyst targets
        catalyst_identifiers.each do |app_id|
          match(
            type: "appstore",
            platform: "catalyst",
            app_identifier: app_id,
            team_id: team_id,
            git_url: ENV['GIT_URL'],
            git_basic_authorization: ENV['GIT_ACCESS_TOKEN'],
            readonly: match_readonly,
            clone_branch_directly: true,
            keychain_name: ENV['KEYCHAIN_NAME'] || "login",
            keychain_password: ENV['KEYCHAIN_PASSWORD'] || ""
          )
        end

        xcargs_str += " DEVELOPMENT_TEAM=#{team_id}"
        xcargs_str += " CODE_SIGN_IDENTITY=\"#{signing_identity}\""
        xcargs_str += " CODE_SIGN_STYLE=Manual"

        # Set provisioning profile specifiers per catalyst target
        catalyst_identifiers.each do |app_id|
          profile_name = "match AppStore #{app_id} catalyst"
          # Convert bundle ID to xcodebuild target setting key
          # e.g., io.bluewallet.bluewallet -> PROVISIONING_PROFILE_SPECIFIER for that target
          xcargs_str += " PROVISIONING_PROFILE_SPECIFIER_#{app_id.gsub('.', '_')}=\"#{profile_name}\""
        end

        UI.success("Provisioning profiles configured for Mac Catalyst")
      else
        # Disable code signing entirely so xcodebuild doesn't look for provisioning profiles
        xcargs_str += " CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO"
        UI.message("No signing credentials provided — building without code signing")
      end

      build_app(
        scheme: "BlueWallet",
        workspace: workspace_path,
        configuration: "Release",
        destination: "generic/platform=macOS,variant=Mac Catalyst",
        xcargs: xcargs_str,
        clean: true,
        skip_codesigning: !should_sign,
        skip_package_ipa: true,
        derived_data_path: derived_data_path,
        buildlog_path: File.join(project_root, "ios", "build_logs")
      )

      archive_path = lane_context[SharedValues::XCODEBUILD_ARCHIVE]
      if archive_path.nil? || archive_path.empty?
        archive_path = Dir.glob(File.join(Dir.home, "Library/Developer/Xcode/Archives/**/*.xcarchive")).max_by { |path| File.mtime(path) }
      end

      candidate_paths = []
      candidate_paths << File.join(archive_path, "Products/Applications/BlueWallet.app") if archive_path
      candidate_paths << Dir.glob(File.join(derived_data_path, "Build/Products/Release-maccatalyst/*.app")).first
      candidate_paths << Dir.glob(File.join(derived_data_path, "Build/Intermediates.noindex/ArchiveIntermediates/BlueWallet/BuildProductsPath/Release-maccatalyst/*.app")).first

      catalyst_app_path = candidate_paths.compact.find { |path| File.exist?(path) }
      UI.user_error!("Mac Catalyst app was not found after build") if catalyst_app_path.nil?
      UI.success("Mac Catalyst app found at: #{catalyst_app_path}")

      if should_sign && ENV['CATALYST_SIGNING_IDENTITY'] && !ENV['CATALYST_SIGNING_IDENTITY'].empty?
        UI.message("Re-signing app with: #{signing_identity}")
        sh("codesign",
           "--deep",
           "--force",
           "--options", "runtime",
           "--sign", signing_identity,
           catalyst_app_path)
        sh("codesign",
           "--verify",
           "--deep",
           "--strict",
           catalyst_app_path)
        UI.success("App signed and verified")
      else
        # Ad-hoc sign so macOS doesn't treat the app as "damaged"
        UI.message("Ad-hoc signing app to avoid Gatekeeper quarantine issues...")
        sh("codesign",
           "--deep",
           "--force",
           "--sign", "-",
           catalyst_app_path)
        UI.success("App ad-hoc signed")
      end

      dmg_path = File.join(output_dir, "BlueWallet-Mac-Catalyst.dmg")
      UI.message("Creating DMG at: #{dmg_path}")

      dmg_staging = File.join(output_dir, "dmg-staging")
      FileUtils.rm_rf(dmg_staging)
      FileUtils.mkdir_p(dmg_staging)
      FileUtils.cp_r(catalyst_app_path, dmg_staging)

      sh("ln", "-s", "/Applications", "#{dmg_staging}/Applications")

      # Add README with first-launch instructions
      readme_path = File.join(dmg_staging, "README - Read Before Opening.txt")
      File.write(readme_path, <<~README)
        ╔══════════════════════════════════════════════════════════════╗
        ║                     BlueWallet for macOS                     ║
        ╚══════════════════════════════════════════════════════════════╝

        ═══ ENGLISH ═══════════════════════════════════════════════════

        INSTALLATION: Drag "BlueWallet.app" into "Applications".

        FIRST LAUNCH — macOS may show a warning. To open:
          • Right-click the app → Open → click "Open" in the dialog.
          • Or: System Settings → Privacy & Security → Open Anyway.
          • Or (Terminal): xattr -cr /Applications/BlueWallet.app
        You only need to do this once.

        ═══ ESPAÑOL ════════════════════════════════════════════════════

        INSTALACIÓN: Arrastra "BlueWallet.app" a "Aplicaciones".

        PRIMER INICIO — macOS puede mostrar una advertencia. Para abrir:
          • Haz clic derecho en la app → Abrir → clic en "Abrir".
          • O: Ajustes del Sistema → Privacidad y Seguridad → Abrir igualmente.
          • O (Terminal): xattr -cr /Applications/BlueWallet.app
        Solo necesitas hacerlo una vez.

        ═══ 中文 ═══════════════════════════════════════════════════════

        安装：将 "BlueWallet.app" 拖入 "应用程序" 文件夹。

        首次启动 — macOS 可能会显示警告。打开方法：
          • 右键点击应用 → 打开 → 在对话框中点击"打开"。
          • 或：系统设置 → 隐私与安全性 → 仍要打开。
          • 或（终端）：xattr -cr /Applications/BlueWallet.app
        只需操作一次。

        ═══ PORTUGUÊS ══════════════════════════════════════════════════

        INSTALAÇÃO: Arraste "BlueWallet.app" para "Aplicativos".

        PRIMEIRA ABERTURA — o macOS pode exibir um aviso. Para abrir:
          • Clique com o botão direito → Abrir → clique em "Abrir".
          • Ou: Ajustes do Sistema → Privacidade e Segurança → Abrir Mesmo Assim.
          • Ou (Terminal): xattr -cr /Applications/BlueWallet.app
        Você só precisa fazer isso uma vez.

        ═══ РУССКИЙ ════════════════════════════════════════════════════

        УСТАНОВКА: Перетащите "BlueWallet.app" в "Программы".

        ПЕРВЫЙ ЗАПУСК — macOS может показать предупреждение. Чтобы открыть:
          • Нажмите правой кнопкой → Открыть → нажмите «Открыть».
          • Или: Системные настройки → Конфиденциальность → Подтвердить открытие.
          • Или (Терминал): xattr -cr /Applications/BlueWallet.app
        Это нужно сделать только один раз.

        ═══ 日本語 ═════════════════════════════════════════════════════

        インストール：「BlueWallet.app」を「アプリケーション」にドラッグ。

        初回起動 — macOS が警告を表示する場合があります。開くには：
          • アプリを右クリック →「開く」→ ダイアログで「開く」をクリック。
          • または：システム設定 → プライバシーとセキュリティ →「このまま開く」。
          • または（ターミナル）：xattr -cr /Applications/BlueWallet.app
        この操作は一度だけ必要です。

        ═══ DEUTSCH ════════════════════════════════════════════════════

        INSTALLATION: Ziehe „BlueWallet.app" in den Ordner „Programme".

        ERSTER START — macOS zeigt möglicherweise eine Warnung. Zum Öffnen:
          • Rechtsklick auf die App → Öffnen → „Öffnen" klicken.
          • Oder: Systemeinstellungen → Datenschutz & Sicherheit → Dennoch öffnen.
          • Oder (Terminal): xattr -cr /Applications/BlueWallet.app
        Dies ist nur beim ersten Mal nötig.

        ═══ FRANÇAIS ═══════════════════════════════════════════════════

        INSTALLATION : Glissez « BlueWallet.app » dans « Applications ».

        PREMIER LANCEMENT — macOS peut afficher un avertissement. Pour ouvrir :
          • Clic droit sur l'app → Ouvrir → cliquez sur « Ouvrir ».
          • Ou : Réglages Système → Confidentialité et sécurité → Ouvrir quand même.
          • Ou (Terminal) : xattr -cr /Applications/BlueWallet.app
        Cette opération n'est nécessaire qu'une seule fois.

        ═══ العربية ════════════════════════════════════════════════════

        التثبيت: اسحب "BlueWallet.app" إلى مجلد "التطبيقات".

        التشغيل الأول — قد يعرض macOS تحذيرًا. لفتح التطبيق:
          • انقر بزر الماوس الأيمن → افتح → انقر "فتح" في مربع الحوار.
          • أو: إعدادات النظام → الخصوصية والأمان → فتح على أي حال.
          • أو (الطرفية): xattr -cr /Applications/BlueWallet.app
        تحتاج للقيام بذلك مرة واحدة فقط.

        ────────────────────────────────────────────────────────────────
        https://bluewallet.io
      README
      UI.message("Added multilingual README with first-launch instructions to DMG")

      FileUtils.rm_f(dmg_path)
      sh("hdiutil", "create", "-volname", "BlueWallet", "-srcfolder", dmg_staging, "-ov", "-format", "UDZO", dmg_path)
      UI.user_error!("DMG was not created at #{dmg_path}") unless File.exist?(dmg_path)
      UI.success("DMG created at: #{dmg_path}")

      FileUtils.rm_rf(dmg_staging)

      ENV['CATALYST_APP_PATH'] = catalyst_app_path
      ENV['CATALYST_DMG_PATH'] = dmg_path
      if ENV['GITHUB_OUTPUT']
        File.open(ENV['GITHUB_OUTPUT'], 'a') do |f|
          f.puts "catalyst_app_path=#{catalyst_app_path}"
          f.puts "catalyst_dmg_path=#{dmg_path}"
        end
      end

      UI.success("macOS app built at: #{catalyst_app_path}")
      UI.success("macOS DMG at: #{dmg_path}")
    end
  end

  desc "Upload Mac Catalyst app to TestFlight"
  lane :upload_catalyst_to_testflight do
    Dir.chdir(project_root) do
      # Locate the xcarchive
      archive_path = lane_context[SharedValues::XCODEBUILD_ARCHIVE]
      if archive_path.nil? || archive_path.empty?
        archive_path = Dir.glob(File.join(Dir.home, "Library/Developer/Xcode/Archives/**/*.xcarchive")).max_by { |path| File.mtime(path) }
      end
      UI.user_error!("No xcarchive found for TestFlight upload") if archive_path.nil? || !File.exist?(archive_path)
      UI.message("Using archive: #{archive_path}")

      output_dir = File.join(project_root, "ios", "build", "catalyst-output")
      FileUtils.mkdir_p(output_dir)

      # Export the archive as a .pkg for Mac Catalyst
      team_id = ENV['CATALYST_TEAM_ID'] || ENV['TEAM_ID']
      export_plist_path = File.join(output_dir, "ExportOptions.plist")

      # Build provisioning profiles mapping for manual signing
      profiles_xml = catalyst_app_identifiers.map do |app_id|
        profile_name = "match AppStore #{app_id} catalyst"
        "\t\t\t<key>#{app_id}</key>\n\t\t\t<string>#{profile_name}</string>"
      end.join("\n")

      File.write(export_plist_path, <<~PLIST)
        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
        <plist version="1.0">
        <dict>
          <key>method</key>
          <string>app-store</string>
          <key>destination</key>
          <string>upload</string>
          <key>teamID</key>
          <string>#{team_id}</string>
          <key>signingStyle</key>
          <string>manual</string>
          <key>provisioningProfiles</key>
          <dict>
#{profiles_xml}
          </dict>
        </dict>
        </plist>
      PLIST

      pkg_path = File.join(output_dir, "BlueWallet.pkg")
      sh("xcodebuild",
         "-exportArchive",
         "-archivePath", archive_path,
         "-exportOptionsPlist", export_plist_path,
         "-exportPath", output_dir)

      # Find the exported pkg
      exported_pkg = Dir.glob(File.join(output_dir, "*.pkg")).first
      UI.user_error!("No .pkg found after export") if exported_pkg.nil? || !File.exist?(exported_pkg)
      UI.success("Exported pkg: #{exported_pkg}")

      # Build changelog
      branch_name = ENV['BRANCH_NAME'] || "unknown-branch"
      last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found"
      changelog = "Build Information:\n"
      changelog += "- Branch: #{branch_name}\n" if branch_name != 'master'
      changelog += "- Commit: #{last_commit_message}\n"

      # Upload to TestFlight
      upload_to_testflight(
        api_key_path: "./appstore_api_key.json",
        pkg: exported_pkg,
        skip_waiting_for_build_processing: true,
        changelog: changelog
      )

      UI.success("Successfully uploaded Mac Catalyst app to TestFlight!")
    end
  end


  desc "Upload IPA to TestFlight"
  lane :upload_to_testflight_lane do

  branch_name = ENV['BRANCH_NAME'] || "unknown-branch"
  last_commit_message = ENV['LATEST_COMMIT_MESSAGE'] || "No commit message found"


  changelog = <<~CHANGELOG
    Build Information:
  CHANGELOG

  # Include the branch name only if it is not 'master'
  if branch_name != 'master'
    changelog += <<~CHANGELOG
      - Branch: #{branch_name}
    CHANGELOG
  end

  changelog += <<~CHANGELOG
    - Commit: #{last_commit_message}
  CHANGELOG

  ipa_path = ENV['IPA_OUTPUT_PATH']
  
  if ipa_path.nil? || ipa_path.empty? || !File.exist?(ipa_path)
    UI.user_error!("IPA file not found at path: #{ipa_path}")
  end

  UI.message("Uploading IPA to TestFlight from path: #{ipa_path}")
  UI.message("Changelog:\n#{changelog}")


  upload_to_testflight(
    api_key_path: "./appstore_api_key.json",
    ipa: ipa_path,
    skip_waiting_for_build_processing: true,
    changelog: changelog
  )

  UI.success("Successfully uploaded IPA to TestFlight!")
end

desc "Upload iOS source maps to Bugsnag"
lane :upload_bugsnag_sourcemaps do
  bugsnag_api_key = ENV['BUGSNAG_API_KEY']
  bugsnag_release_stage = ENV['BUGSNAG_RELEASE_STAGE'] || "production"
  version = ENV['PROJECT_VERSION']
  build_number = ENV['NEW_BUILD_NUMBER']

  UI.user_error!("BUGSNAG_API_KEY environment variable is missing") if bugsnag_api_key.nil?
  UI.user_error!("PROJECT_VERSION environment variable is missing") if version.nil?
  UI.user_error!("NEW_BUILD_NUMBER environment variable is missing") if build_number.nil?

  ios_sourcemap = "./ios/build/Build/Products/Release-iphonesimulator/main.jsbundle.map"

  if File.exist?(ios_sourcemap)
    UI.message("Uploading iOS source map to Bugsnag...")
    bugsnag_sourcemaps_upload(
      api_key: bugsnag_api_key,
      source_map: ios_sourcemap,
      minified_file: "./ios/main.jsbundle",
      code_bundle_id: "#{version}-#{build_number}",
      release_stage: bugsnag_release_stage,
      app_version: version
    )
    UI.success("iOS source map uploaded successfully.")
  else
    UI.error("iOS source map not found at #{ios_sourcemap}")
  end
end

  desc "Build the iOS app"
  lane :build_app_lane do
    Dir.chdir(project_root) do
      UI.message("Building the application from: #{Dir.pwd}")

      workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
      export_options_path = File.join(project_root, "ios", "export_options.plist")

      clear_derived_data_lane
      
      UI.message("\033[1;34m==================== FASTLANE BUILD DEBUG ====================\033[0m")
      
      # Comprehensive environment check
      UI.message("\033[1;36mEnvironment Analysis:\033[0m")
      UI.message("  Project Root: #{project_root}")
      UI.message("  Current Directory: #{Dir.pwd}")
      UI.message("  Ruby Version: #{RUBY_VERSION}")
      UI.message("  Fastlane Version: #{Fastlane::VERSION}")
      UI.message("  Build Mode: Release (App Store)")
      
      # Ensure we're using the correct Xcode installation
      UI.message("\033[1;36mXcode Configuration:\033[0m")
      begin
        sh("sudo xcode-select -s /Applications/Xcode.app") rescue nil
        xcode_path = sh('xcode-select -p', log: false).strip
        xcode_version = sh('xcodebuild -version', log: false).strip
        UI.message("  Active Xcode Path: #{xcode_path}")
        UI.message("  Xcode Version: #{xcode_version}")
        
        # Check if Xcode path is valid
        if File.exist?(File.join(xcode_path, "usr/bin/xcodebuild"))
          UI.success("  \033[1;32mXcode installation is valid\033[0m")
        else
          UI.error("  \033[1;31mXcode installation appears invalid\033[0m")
        end
      rescue => e
        UI.error("  \033[1;31mError checking Xcode: #{e.message}\033[0m")
      end
      
      # Project structure analysis
      UI.message("\033[1;36mProject Structure Analysis:\033[0m")
      %w[
        ios/BlueWallet.xcworkspace
        ios/BlueWallet.xcodeproj
        ios/export_options.plist
        ios/Podfile
        ios/Podfile.lock
      ].each do |file_path|
        full_path = File.join(project_root, file_path)
        if File.exist?(full_path)
          UI.message("  \033[1;32m#{file_path} exists\033[0m")
          if file_path.end_with?('.plist')
            UI.message("    Content preview:")
            content = File.read(full_path).lines.first(10).join.strip
            UI.message("    #{content[0..200]}...")
          end
        else
          UI.error("  \033[1;31m#{file_path} missing\033[0m")
        end
      end
      
      # Environment variables check
      UI.message("\033[1;36mEnvironment Variables:\033[0m")
      %w[PROJECT_VERSION NEW_BUILD_NUMBER].each do |var|
        value = ENV[var]
        if value && !value.empty?
          UI.message("  \033[1;32m#{var}: #{value}\033[0m")
        else
          UI.error("  \033[1;31m#{var}: Not set or empty\033[0m")
        end
      end
      
      # Determine which iOS version to use
      UI.message("\033[1;36miOS Version Analysis:\033[0m")
      ios_version = determine_ios_version
      UI.message("  Selected iOS version: #{ios_version}")

      UI.message("\033[1;36mBuild Configuration:\033[0m")
      UI.message("  Workspace: #{workspace_path}")
      UI.message("  Export options: #{export_options_path}")
      UI.message("  Output directory: #{File.join(project_root, 'ios', 'build')}")
      UI.message("  Build type: Release (generic/platform=iOS)")
      
      # Comprehensive destination analysis
      UI.message("\033[1;33mBuild Destinations Analysis:\033[0m")
      begin
        destinations_output = sh("xcodebuild -workspace '#{workspace_path}' -scheme BlueWallet -showdestinations", log: false)
        UI.message("  Available destinations:")
        destinations_output.lines.each_with_index do |line, index|
          UI.message("    #{index + 1}. #{line.strip}") if line.strip.length > 0
        end
        
        # Analyze destination types
        ios_destinations = destinations_output.scan(/platform:iOS[^}]*/).length
        catalyst_destinations = destinations_output.scan(/Mac Catalyst/).length
        
        UI.message("  \033[1;36mDestination Summary:\033[0m")
        UI.message("    iOS destinations: #{ios_destinations}")
        UI.message("    Mac Catalyst destinations: #{catalyst_destinations}")
        
        if ios_destinations > 0
          UI.success("  \033[1;32miOS destinations available for release build\033[0m")
        else
          UI.important("  \033[1;33mNo iOS destinations found - may need to create simulators\033[0m")
        end
      rescue => e
        UI.error("  \033[1;31mFailed to get destinations: #{e.message}\033[0m")
        destinations_output = "Failed to get destinations: #{e.message}"
      end
      
      # Simulator runtime check for development/debugging
      UI.message("\033[1;33mSimulator Runtime Check (for debugging):\033[0m")
      begin
        runtimes = sh("xcrun simctl list runtimes", log: false)
        ios_runtimes = runtimes.scan(/iOS ([0-9.]+)/).flatten
        UI.message("  Available iOS runtimes: #{ios_runtimes.join(', ')}")
        
        devices = sh("xcrun simctl list devices iOS", log: false)
        iphone_count = devices.scan(/iPhone/).length
        UI.message("  Available iPhone simulators: #{iphone_count}")
      rescue => e
        UI.error("  \033[1;31mError checking simulators: #{e.message}\033[0m")
      end
      
      # Define the IPA output path before building
      ipa_directory = File.join(project_root, "ios", "build")
      ipa_name = "BlueWallet_#{ENV['PROJECT_VERSION']}_#{ENV['NEW_BUILD_NUMBER']}.ipa"
      ipa_path = File.join(ipa_directory, ipa_name)
      
      UI.message("\033[1;36mBuild Output Configuration:\033[0m")
      UI.message("  IPA Directory: #{ipa_directory}")
      UI.message("  IPA Name: #{ipa_name}")
      UI.message("  Full IPA Path: #{ipa_path}")
      
      # Ensure build directory exists
      FileUtils.mkdir_p(ipa_directory) unless Dir.exist?(ipa_directory)
      
      begin
        UI.message("🚀 Starting iOS Build Process...")
        UI.message("  Build Parameters:")
        UI.message("    Scheme: BlueWallet")
        UI.message("    Workspace: #{workspace_path}")
        UI.message("    Export Method: app-store")
        UI.message("    Export Options: #{export_options_path}")
        UI.message("    Output Directory: #{ipa_directory}")
        UI.message("    Output Name: #{ipa_name}")
        UI.message("    Build Logs: #{File.join(project_root, 'ios', 'build_logs')}")
        UI.message("    Destination: generic/platform=iOS")
        
        # Pre-build validation
        UI.message("🔍 Pre-Build Validation:")
        
        # Check workspace
        if File.exist?(workspace_path)
          UI.success("  ✅ Workspace exists")
        else
          UI.user_error!("  ❌ Workspace not found: #{workspace_path}")
        end
        
        # Check export options
        if File.exist?(export_options_path)
          UI.success("  ✅ Export options exist")
          # Read and validate export options
          begin
            export_content = File.read(export_options_path)
            UI.message("    Export options preview:")
            export_content.lines.first(5).each { |line| UI.message("      #{line.strip}") }
          rescue => e
            UI.error("    ⚠️ Could not read export options: #{e.message}")
          end
        else
          UI.user_error!("  ❌ Export options not found: #{export_options_path}")
        end
        
        build_ios_app(
          scheme: "BlueWallet",
          workspace: workspace_path,
          export_method: "app-store",
          export_options: export_options_path,
          output_directory: ipa_directory,
          output_name: ipa_name,
          buildlog_path: File.join(project_root, "ios", "build_logs")
          # Removed explicit destination - let Xcode determine the best destination for release builds
        )
        
        UI.success("\033[1;32miOS release build completed successfully!\033[0m")
        
      rescue => build_error
        UI.error("\033[1;31miOS release build failed!\033[0m")
        UI.error("  Error Class: #{build_error.class}")
        UI.error("  Error Message: #{build_error.message}")
        UI.error("  \033[1;31mError Backtrace:\033[0m")
        build_error.backtrace.first(5).each { |line| UI.error("    #{line}") }
        
        UI.message("\033[1;33mPost-Build Debugging:\033[0m")
        
        # Check for partial build artifacts
        build_logs_dir = File.join(project_root, "ios", "build_logs")
        if Dir.exist?(build_logs_dir)
          UI.message("  \033[1;36mBuild logs directory contents:\033[0m")
          Dir.entries(build_logs_dir).each do |file|
            next if file.start_with?('.')
            file_path = File.join(build_logs_dir, file)
            size = File.size(file_path) rescue 0
            UI.message("    #{file} (#{size} bytes)")
          end
        end
        
        # Check for xcarchive files
        archives = Dir.glob(File.join(Dir.home, "Library/Developer/Xcode/Archives/**/*.xcarchive"))
        if archives.any?
          UI.message("  \033[1;36mRecent archives found:\033[0m")
          archives.last(3).each { |archive| UI.message("    #{archive}") }
        end
        
        # If iOS build fails, check if we can build using available destinations
        if destinations_output.include?("Any iOS Device")
          UI.message("Retrying build without explicit destination...")
          build_ios_app(
            scheme: "BlueWallet",
            workspace: workspace_path,
            export_method: "app-store",
            export_options: export_options_path,
            output_directory: ipa_directory,
            output_name: ipa_name,
            buildlog_path: File.join(project_root, "ios", "build_logs")
          )
        else
          UI.user_error!("build_ios_app failed: #{build_error.message}")
        end
      end

      # Check for IPA path from both our defined path and fastlane's context
      ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH] || ipa_path
      
      # Ensure the directory exists
      FileUtils.mkdir_p(File.dirname(ipa_path)) unless Dir.exist?(File.dirname(ipa_path))
      
      if ipa_path && File.exist?(ipa_path)
        UI.message("IPA successfully found at: #{ipa_path}")
      else
        # Try to find any IPA file as fallback
        Dir.chdir(project_root) do
          fallback_ipa = Dir.glob("**/*.ipa").first
          if fallback_ipa
            ipa_path = File.join(project_root, fallback_ipa)
            UI.message("Found fallback IPA at: #{ipa_path}")
          else
            UI.user_error!("No IPA file found after build")
          end
        end
      end
      
      # Set both environment variable and GitHub Actions output
      ENV['IPA_OUTPUT_PATH'] = ipa_path
      # Set both standard output format and the newer GITHUB_OUTPUT format
      sh("echo 'ipa_output_path=#{ipa_path}' >> $GITHUB_OUTPUT") if ENV['GITHUB_OUTPUT']
      sh("echo ::set-output name=ipa_output_path::#{ipa_path}")
      
      # Also write path to a file that can be read by subsequent steps
      ipa_path_file = "#{ipa_directory}/ipa_path.txt"
      File.write(ipa_path_file, ipa_path)
      UI.success("Saved IPA path to: #{ipa_path_file}")
    end
  end

  desc "Delete temporary keychain"
  lane :delete_temp_keychain do
    UI.message("Deleting temporary keychain...")
    
    delete_keychain(
      name: "temp_keychain"
    ) if File.exist?(File.expand_path("~/Library/Keychains/temp_keychain-db"))
    
    UI.message("Temporary keychain deleted successfully.")
  end

  # Helper method to determine which iOS version to use
  # Updated for macOS-15 compatibility (defaults to iOS 17.5 for broader compatibility)
  private_lane :determine_ios_version do
    UI.message("\033[1;33mDetermining iOS Version for Release Build:\033[0m")
    
    begin
      runtimes_output = sh("xcrun simctl list runtimes 2>&1", log: false)
      UI.message("  \033[1;32mSuccessfully retrieved simulator runtimes\033[0m")
      
      # Debug: Show all runtimes
      UI.message("  \033[1;36mAll available runtimes:\033[0m")
      runtimes_output.lines.first(10).each_with_index do |line, index|
        UI.message("    #{index + 1}. #{line.strip}")
      end
      
    rescue => e
      UI.error("  \033[1;31mFailed to get simulator runtimes: #{e.message}\033[0m")
      runtimes_output = ""
    end
    
    if runtimes_output.include?("iOS")
      UI.message("  \033[1;36miOS runtimes detected\033[0m")
      
      begin
        ios_versions = runtimes_output.scan(/iOS ([0-9.]+)/)
                                     .flatten
                                     .map { |v| Gem::Version.new(v) }
                                     .sort
                                     .reverse
        
        UI.message("  \033[1;36mParsed iOS versions:\033[0m")
        ios_versions.each_with_index do |version, index|
          UI.message("    #{index + 1}. iOS #{version}")
        end
        
        if ios_versions.any?
          latest_version = ios_versions.first.to_s
          UI.success("  \033[1;32mSelected iOS version for release: #{latest_version}\033[0m")
          
          # Additional validation
          if Gem::Version.new(latest_version) >= Gem::Version.new("17.0")
            UI.success("    \033[1;32mVersion is compatible for release builds (17.0+)\033[0m")
          else
            UI.important("    \033[1;33mVersion is older than 17.0, may have compatibility issues\033[0m")
          end
          
          latest_version
        else
          UI.important("  \033[1;33mNo iOS versions could be parsed from runtime output\033[0m")
          UI.message("  \033[1;36mUsing fallback version for release: 17.5\033[0m")
          "17.5"
        end
      rescue => e
        UI.error("  \033[1;31mError parsing iOS versions: #{e.message}\033[0m")
        UI.message("  \033[1;36mUsing fallback version for release: 17.5\033[0m")
        "17.5"
      end
    else
      UI.important("  \033[1;33mNo iOS runtimes found in simulator list\033[0m")
      UI.message("  \033[1;36mRuntime output preview:\033[0m")
      runtimes_output.lines.first(5).each { |line| UI.message("    #{line.strip}") }
      UI.message("  \033[1;36mUsing fallback version for release: 17.5\033[0m")
      "17.5"
    end
  end
end

# ===========================
#       Global Lanes
# ===========================

desc "Deploy to TestFlight"
lane :deploy do |options|
  UI.message("Starting deployment process...")

  update_wwdr_certificate
  setup_app_store_connect_api_key
  setup_provisioning_profiles
  clear_derived_data_lane
  increment_build_number_lane

  unless File.directory?("Pods")
    install_pods
  end

  build_app_lane
  upload_to_testflight_lane

  delete_keychain(name: "temp_keychain")

  last_commit = last_git_commit
  already_built_flag = ".already_built_#{last_commit[:sha]}"
  File.write(already_built_flag, Time.now.to_s)
end

desc "Update release notes for App Store versions (iOS or Mac Catalyst)"
lane :release_notes do |options|
  require 'spaceship'

  app = Spaceship::ConnectAPI::App.find(app_identifiers.first)

  UI.user_error!("Could not find the app with identifier: #{app_identifiers.first}") unless app

  platform_option = options[:platform]
  
  if platform_option
    platform = case platform_option.to_s.downcase
      when "ios"
        UI.message("Using platform from options: iOS")
        Spaceship::ConnectAPI::Platform::IOS
      when "catalyst", "mac_catalyst", "mac-catalyst"
        UI.message("Using platform from options: Mac Catalyst")
        UI.message("Using Spaceship::ConnectAPI::Platform::MAC_OS for Mac Catalyst")
        Spaceship::ConnectAPI::Platform::MAC_OS
      else
        UI.user_error!("Invalid platform option: #{platform_option}")
      end
  else
    platform_selection = UI.select("Select platform for release notes:", ["iOS", "Mac Catalyst"])
    
    platform = case platform_selection
      when "iOS"
        UI.message("Selected platform: iOS")
        Spaceship::ConnectAPI::Platform::IOS
      when "Mac Catalyst"
        UI.message("Selected platform: Mac Catalyst")
        UI.message("Using Spaceship::ConnectAPI::Platform::MAC_OS for Mac Catalyst")
        Spaceship::ConnectAPI::Platform::MAC_OS
      else
        UI.user_error!("Invalid platform selection")
      end
  end

  retries = 5
  begin
    rejected_version = nil
    UI.message("Checking for Developer Rejected version for platform: #{platform}")
    
    begin
      filter = {
        appStoreState: "DEVELOPER_REJECTED",
        platformString: platform.to_s
      }
      
      app.get_app_store_versions(filter: filter).each do |version|
        rejected_version = version
        UI.message("Found rejected version: #{version.version_string}")
        break
      end
    rescue => e
      UI.error("Error fetching Developer Rejected versions: #{e.message}")
      UI.message("Debug info: Platform type: #{platform.class}, Value: #{platform}")
    end

    if rejected_version
      UI.success("Found 'Developer Rejected' version: #{rejected_version.version_string}. This will be the target for updates.")
      prepare_version = rejected_version
    else
      UI.message("No Developer Rejected version found. Checking for version in edit mode or waiting for review...")
      
      begin
        prepare_version = app.get_edit_app_store_version(platform: platform)
        if prepare_version
          UI.message("Found version in edit mode: #{prepare_version.version_string}")
        else
          UI.message("No version in edit mode found")
        end
      rescue => e
        UI.error("Error fetching edit app store version: #{e.message}")
        prepare_version = nil
      end
      
      if prepare_version.nil?
        UI.message("Checking for version in Waiting for Review status...")
        begin
          waiting_filter = {
            platformString: platform.to_s,
            appStoreState: "WAITING_FOR_REVIEW"
          }
          
          waiting_versions = app.get_app_store_versions(filter: waiting_filter)
          if waiting_versions && !waiting_versions.empty?
            prepare_version = waiting_versions.first
            UI.success("Found version in Waiting for Review status: #{prepare_version.version_string}")
          else
            UI.message("No version in Waiting for Review status found")
          end
        rescue => e
          UI.error("Error fetching Waiting for Review versions: #{e.message}")
        end
      end
      
      if prepare_version.nil?
        UI.message("Looking for any in-flight version...")
        begin
          all_versions = app.get_app_store_versions(filter: { platformString: platform.to_s })
          UI.message("Found #{all_versions.count} versions for platform #{platform}:")
          
          all_versions.each do |version|
            state = app_store_state_readable(version.app_store_state)
            UI.message("  - Version: #{version.version_string}, State: #{state}")
          end
          
          editable_states = ["PREPARE_FOR_SUBMISSION", "WAITING_FOR_REVIEW", "REJECTED", "METADATA_REJECTED", "DEVELOPER_REJECTED"]
          editable_version = all_versions.find { |v| editable_states.include?(v.app_store_state) }
          
          if editable_version
            prepare_version = editable_version
            UI.success("Using editable version: #{prepare_version.version_string} (#{app_store_state_readable(prepare_version.app_store_state)})")
          elsif all_versions.count > 0
            latest_version = all_versions.sort_by { |v| Gem::Version.new(v.version_string) }.last
            UI.message("Latest version: #{latest_version.version_string} (#{app_store_state_readable(latest_version.app_store_state)})")
          end
        rescue => e
          UI.error("Error listing versions: #{e.message}")
        end
      end
      
      if prepare_version.nil?
        UI.message("No editable version found.")
        
        create_new_version = UI.confirm("Would you like to create a new version?")
        
        if create_new_version
          begin
            UI.message("Fetching latest version for platform: #{platform}")
            latest_version = app.get_latest_version(platform: platform)
            if latest_version
              UI.message("Latest version: #{latest_version.version_string}")
              new_version_number = (latest_version.version_string.to_f + 0.1).round(1).to_s
            else
              UI.message("No latest version found. Using 1.0 as base")
              new_version_number = "1.0"
            end
            
            UI.message("Creating new version: #{new_version_number} for platform: #{platform_selection || platform_option}")
            prepare_version = app.create_version!(platform: platform, version_string: new_version_number)
            UI.message("Created new version: #{new_version_number}")
          rescue => e
            UI.error("Failed to create new version: #{e.message}")
            UI.user_error!("Failed to create version. Make sure your app is configured for Mac Catalyst in App Store Connect.")
          end
        else
          UI.user_error!("No editable version found and user chose not to create one. Aborting.")
        end
      else
        UI.message("Using version #{prepare_version.version_string} in state: #{app_store_state_readable(prepare_version.app_store_state)}")
      end
    end
  rescue => e
    retries -= 1
    if retries > 0
      delay = 20
      UI.message("Cannot find app version info... Retrying after #{delay} seconds (remaining: #{retries})")
      UI.error("Error details: #{e.message}")
      sleep(delay)
      retry
    else
      UI.user_error!("Failed to fetch or create the app version: #{e.message}")
    end
  end

  localized_metadata = prepare_version.get_app_store_version_localizations
  enabled_locales = localized_metadata.map(&:locale)
  release_notes_text = options[:release_notes]
  
  if release_notes_text.nil? || release_notes_text.strip.empty?
    existing_release_notes = nil
    
    en_us_localization = localized_metadata.find { |loc| loc.locale == 'en-US' }
    if en_us_localization && en_us_localization.whats_new && !en_us_localization.whats_new.strip.empty?
      existing_release_notes = en_us_localization.whats_new
      UI.success("Found existing release notes in App Store Connect!")
    else
      localized_metadata.each do |loc|
        if loc.whats_new && !loc.whats_new.strip.empty?
          existing_release_notes = loc.whats_new
          UI.success("Found existing release notes in App Store Connect for locale: #{loc.locale}")
          break
        end
      end
    end
    
    ios_release_notes_path = "metadata/ios/en-US/release_notes.txt"
    project_release_notes_path = "../release-notes.txt"
    
    ios_notes_exist = File.exist?(ios_release_notes_path)
    project_notes_exist = File.exist?(project_release_notes_path)
    
    options_list = []
    
    if existing_release_notes
      options_list << "View/Edit existing App Store notes"
    end
    
    options_list += [
      "Enter manually",
      "Use clipboard content"
    ]
    
    if project_notes_exist
      options_list << "Use release-notes.txt file"
    end
    
    if ios_notes_exist
      options_list << "Use iOS metadata release notes"
    end
    
    selection = UI.select("Select a source for release notes:", options_list)
    
    case selection
    when "View/Edit existing App Store notes"
      UI.message("Existing release notes:")
      UI.message("-" * 50)
      UI.message(existing_release_notes)
      UI.message("-" * 50)
      
      edit_choice = UI.select("Do you want to edit these notes or use as-is?:", [
        "Use as-is",
        "Edit notes"
      ])
      
      if edit_choice == "Edit notes"
        require 'tempfile'
        temp_file = Tempfile.new('release_notes')
        temp_file.write(existing_release_notes)
        temp_file.close
        
        editor = ENV['EDITOR'] || 'nano'
        system("#{editor} #{temp_file.path}")
        
        release_notes_text = File.read(temp_file.path)
        temp_file.unlink
        
        UI.message("Edited release notes:")
        UI.message("-" * 50)
        UI.message(release_notes_text.length > 500 ? "#{release_notes_text[0..500]}..." : release_notes_text)
        UI.message("-" * 50)
        
        unless UI.confirm("Use these edited notes?")
          UI.user_error!("User canceled edited notes. Aborting.")
        end
      else
        release_notes_text = existing_release_notes
      end
    
    when "Enter manually"
      release_notes_text = UI.input("Enter the release notes:")
      if release_notes_text.nil? || release_notes_text.strip.empty?
        UI.user_error!("No release notes provided. Aborting.")
      end
      
    when "Use clipboard content"
      require 'open3'
      stdout, stderr, status = Open3.capture3("pbpaste")
      
      if !status.success? || stdout.strip.empty?
        UI.user_error!("Failed to get clipboard content or clipboard is empty")
      end
      
      UI.message("Clipboard content preview:")
      UI.message("-" * 50)
      UI.message(stdout.length > 500 ? "#{stdout[0..500]}..." : stdout)
      UI.message("-" * 50)
      
      unless UI.confirm("Use this clipboard content for release notes?")
        UI.user_error!("User canceled clipboard content usage. Aborting.")
      end
      
      release_notes_text = stdout
      
    when "Use iOS metadata release notes"
      release_notes_text = File.read(ios_release_notes_path)
      
      UI.message("iOS metadata release notes preview:")
      UI.message("-" * 50)
      UI.message(release_notes_text.length > 500 ? "#{release_notes_text[0..500]}..." : release_notes_text)
      UI.message("-" * 50)
      
      unless UI.confirm("Use this content from iOS metadata release notes?")
        UI.user_error!("User canceled file content usage. Aborting.")
      end
      
    when "Use release-notes.txt file"
      release_notes_path = "../release-notes.txt"
      
      unless File.exist?(release_notes_path)
        UI.error("Release notes file does not exist at path: #{release_notes_path}")
        UI.user_error!("No release-notes.txt file found. Aborting.")
      end
      
      release_notes_text = File.read(release_notes_path)
      
      UI.message("release-notes.txt content preview:")
      UI.message("-" * 50)
      UI.message(release_notes_text.length > 500 ? "#{release_notes_text[0..500]}..." : release_notes_text)
      UI.message("-" * 50)
      
      unless UI.confirm("Use this content from release-notes.txt?")
        UI.user_error!("User canceled file content usage. Aborting.")
      end
    end
  end
  
  if release_notes_text.nil? || release_notes_text.strip.empty?
    UI.user_error!("No release notes content available. Aborting.")
  end

  localized_release_notes = {
    'en-US' => release_notes_text,
    'ar-SA' => release_notes_text,
    'zh-Hans' => release_notes_text,
    'hr' => release_notes_text,
    'da' => release_notes_text,
    'nl-NL' => release_notes_text,
    'fi' => release_notes_text,
    'fr-FR' => release_notes_text,
    'de-DE' => release_notes_text,
    'el' => release_notes_text,
    'he' => release_notes_text,
    'hu' => release_notes_text,
    'it' => release_notes_text,
    'ja' => release_notes_text,
    'ms' => release_notes_text,
    'nb' => release_notes_text,
    'no' => release_notes_text,
    'pl' => release_notes_text,
    'pt-BR' => release_notes_text,
    'pt-PT' => release_notes_text,
    'ro' => release_notes_text,
    'ru' => release_notes_text,
    'es-MX' => release_notes_text,
    'es-ES' => release_notes_text,
    'sv' => release_notes_text,
    'th' => release_notes_text,
  }

  if platform == Spaceship::ConnectAPI::Platform::MAC_OS
    UI.message("Mac Catalyst selected - using only en-US localization")
    localized_release_notes = { 'en-US' => release_notes_text }
  end

  localized_release_notes = localized_release_notes.select { |locale, _| enabled_locales.include?(locale) }

  UI.message("Review the following release notes updates:")
  localized_release_notes.each do |locale, notes|
    UI.message("Locale: #{locale} - Notes: #{notes}")
  end

  force_yes = options && options.is_a?(Hash) && options[:force_yes] == true
  
  unless force_yes
    confirm = UI.confirm("Do you want to proceed with these release notes updates?")
    UI.user_error!("User aborted the lane.") unless confirm
  end

  localized_release_notes.each do |locale, notes|
    app_store_version_localization = localized_metadata.find { |loc| loc.locale == locale }
    if app_store_version_localization
      app_store_version_localization.update(attributes: { "whats_new" => notes })
    else
      UI.error("No localization found for locale #{locale}")
    end
  end
end
