# iOS and macOS App Store submission lanes.
#
# The signed release binaries are produced in CI:
#   - iOS:   .github/workflows/build-ios.yml                       -> .ipa
#   - macOS: .github/workflows/build-publish-to-mac-store-on-release.yml -> MAS .pkg
#
# These lanes take the finished artifact, upload it to App Store Connect, set
# the "What's New" text, submit the version for review and flag it to be
# released automatically once Apple approves it. The only step that cannot be
# automated is Apple's human review itself.
#
# Authentication uses an App Store Connect API key (not an Apple ID +
# app-specific password), which is far more robust in CI.
#
# IMPORTANT: the API key must belong to a user with the "App Manager" role (or
# higher). A key with only the "Developer" role can upload/notarize but cannot
# create a version or submit it for review.
#
# SECURITY: never enable verbose mode (--verbose / FASTLANE_VERBOSE / verbose:
# true) in these lanes. Verbose output can dump the deliver options hash, which
# contains the App Store Connect API key material.
#
# Listing metadata (description, keywords, screenshots, ...) is managed manually
# in App Store Connect and is intentionally NOT overwritten here. METADATA_PATH
# is a dedicated dir that contains ONLY <locale>/release_notes.txt, so deliver's
# load_from_filesystem reads just the release notes (it skips fields with no
# local file: `next unless File.exist?`, and upload skips nil fields). There is
# no remote read-back, so other listing fields are left untouched. Do NOT set
# `skip_metadata` here: it makes deliver return early and upload no release notes
# at all. `skip_screenshots` is safe — screenshots live elsewhere.
#
# Required env vars (wired from GitHub secrets in the workflows):
#   ASC_KEY_ID       App Store Connect API key id      (secrets.mac_api_key_id)
#   ASC_ISSUER_ID    App Store Connect API issuer id   (secrets.mac_api_key_issuer_id)
#   ASC_KEY_CONTENT  Contents of the .p8 key file      (secrets.mac_api_key)
#   IPA_PATH         Path to the built .ipa            (ios lane)
#   PKG_PATH         Path to the built MAS .pkg        (mac lane)
# Optional:
#   METADATA_PATH      release-notes dir (default: fastlane/appstore_metadata)
#   SUBMIT_FOR_REVIEW  "false" to only upload the build without submitting

APP_IDENTIFIER = 'com.super-productivity.app'

def asc_api_key
  app_store_connect_api_key(
    key_id: ENV.fetch('ASC_KEY_ID'),
    issuer_id: ENV.fetch('ASC_ISSUER_ID'),
    key_content: ENV.fetch('ASC_KEY_CONTENT'),
    is_key_content_base64: false,
  )
end

def submit_for_review?
  ENV.fetch('SUBMIT_FOR_REVIEW', 'true') != 'false'
end

# Shared deliver options. `extra` carries the platform-specific binary path.
def deliver_options(extra)
  {
    api_key: asc_api_key,
    app_identifier: APP_IDENTIFIER,
    # Dir holding only <locale>/release_notes.txt -> deliver uploads just the
    # "What's New" text and leaves every other listing field as-is. (Do not add
    # skip_metadata; see the header comment.)
    metadata_path: ENV.fetch('METADATA_PATH', 'fastlane/appstore_metadata'),
    skip_screenshots: true,
    submit_for_review: submit_for_review?,
    automatic_release: true,
    # Precheck inspects listing metadata we don't manage from here.
    run_precheck_before_submit: false,
    submission_information: {
      add_id_info_uses_idfa: false,
      # The app only relies on exempt encryption (HTTPS/standard OS crypto), so
      # it does not use non-exempt encryption. Without this, App Store Connect
      # rejects the submission with "Export compliance is required to submit".
      # Mirrors ITSAppUsesNonExemptEncryption=false in ios/App/App/Info.plist.
      export_compliance_uses_encryption: false,
    },
    # Non-interactive: skip the HTML report confirmation prompt.
    force: true,
  }.merge(extra)
end

# Both release lanes run on the same `v*` tag push (see the two release
# workflows) and can race on App Store Connect's per-app review-submission
# state. When they collide, the lane that submits second gets "Cannot submit
# for review - A review submission is already in progress" -- but only AFTER its
# binary has already uploaded and processed successfully, so the build is safe
# on App Store Connect. Treat that one collision as a soft success and emit a CI
# warning: the build just needs to be added to the already-open submission for
# THIS version in the App Store Connect UI (otherwise this platform skips this
# version and users get it at the next release). Every other failure -- and any
# failure before the binary uploads, e.g. the version-creation race -- still
# aborts the lane loudly.
#
# No stable error code is surfaced through deliver, so this is message-matched
# (verified against fastlane deliver 2.225.0). If Apple/fastlane rephrase it,
# the match stops firing and the lane falls back to hard-failing -- the safe
# direction.
REVIEW_SUBMISSION_RACE = 'review submission is already in progress'

# App Store Connect also serializes review state per app across releases: while
# one version is still in review, the next version can neither be created nor
# have a build attached. So a release tagged before the previous one clears
# review fails either at version creation ("You cannot create a new version of
# the App in the current state") or at build-attach ("The specified pre-release
# build could not be added"). Unlike the race above this is NOT softened -- the
# version genuinely did not go out, and a green run would falsely read as
# "released". We only trade the raw spaceship stacktrace for a one-line,
# actionable explanation; the lane still fails. Message-matched against fastlane
# deliver 2.225.0 -- if the phrasing drifts it simply falls back to the raw
# error, which is fine (the job still fails).
VERSION_IN_REVIEW_BLOCKS = [
  'cannot create a new version',
  'pre-release build could not be added',
].freeze

def submit_to_app_store(extra)
  upload_to_app_store(deliver_options(extra))
rescue => e
  msg = e.message.to_s.downcase

  # Soft success: the other platform's release lane already opened the shared
  # per-app review submission. The binary is uploaded and safe; it just needs to
  # be added to the open submission by hand. Surface as a GitHub Actions run
  # annotation, not just a buried log line, so a green job still flags the
  # required manual step. Static text only -- never interpolate e/options here
  # (they can carry API key material; see header).
  if submit_for_review? && msg.include?(REVIEW_SUBMISSION_RACE)
    if ENV['GITHUB_ACTIONS']
      puts '::warning title=App Store build uploaded but NOT submitted::A review ' \
           'submission is already open for this app. Add this build to it in App ' \
           'Store Connect, or this platform skips this version.'
    end
    UI.important('Build uploaded, but a review submission is already open for this app')
    UI.important("(the other platform's release lane created it first). Add this build to")
    UI.important('the open submission in App Store Connect for this version, or this platform')
    UI.important('will skip this version. Not failing the lane: the binary is uploaded.')
    return
  end

  # Known "a previous version is still in review" block. Still a hard failure --
  # we only replace the raw stacktrace with a clear, actionable one-liner (the
  # raw error still prints after the re-raise). Static text only, same
  # key-material reason as above.
  if submit_for_review? && VERSION_IN_REVIEW_BLOCKS.any? { |m| msg.include?(m) }
    if ENV['GITHUB_ACTIONS']
      puts '::error title=App Store submit blocked: previous version still in review::' \
           'App Store Connect will not create or submit this version while the ' \
           'previous version is still in review. Let it finish review (or remove ' \
           'it from review in App Store Connect), then re-run this release.'
    end
    UI.error('App Store submit blocked: a previous version is still in review.')
    UI.error('App Store Connect will not create or submit this version until the')
    UI.error('previous one clears review. Let it finish (or pull it from review in')
    UI.error('App Store Connect), then re-run this release.')
  end

  raise
end

platform :ios do
  desc 'Upload the prebuilt iOS .ipa to App Store Connect and submit for review'
  lane :release do
    submit_to_app_store(platform: 'ios', ipa: ENV.fetch('IPA_PATH'))
  end
end

platform :mac do
  desc 'Upload the prebuilt macOS .pkg to App Store Connect and submit for review'
  lane :release do
    submit_to_app_store(platform: 'osx', pkg: ENV.fetch('PKG_PATH'))
  end
end
