skip_docs
opt_out_usage
import("Configuration.rb")

public_repository_base = Configuration::PUBLIC_REPOSITORY_BASE
github_access_token = ENV["GITHUB_ACCESS_RW_TOKEN"]
basic_docs = ["README.md", "CHANGELOG.md", "LICENSE", ".gitignore", "MIGRATION.md"]
intermediate_path = Configuration::INTERMEDIATE_PATH
output_path = Configuration::OUTPUT_PATH
sample_app_source_path = Configuration::SAMPLE_PATH
sample_app_destination_name = "sample"
config_files = Configuration::SAMPLE_CONFIG_TEMPLATES
templates_path = Configuration::TEMPLATES_PATH
pre_release_templates_path = Configuration::PRE_RELEASE_CONFIG_TEMPLATES

desc "Copy the basic docs"
lane :copy_basic_docs do
  UI.message("Copying basic docs...")

  Dir.chdir("..") do
    # Loop through each document in the basic_docs array.
    basic_docs.each do |doc|
      source_path = doc
      destination_path = File.join(output_path, doc)

      # Check if the source file exists.
      if File.exist?(source_path)
        UI.message("Copying #{doc} to output directory...")

        # Create any necessary subdirectories for the destination path.
        FileUtils.mkdir_p(File.dirname(destination_path))

        # Copy the file.
        FileUtils.cp(source_path, destination_path)

        UI.success("Successfully copied #{doc}.")
      else
        UI.error("Source file not found: #{source_path}.")
      end
    end
  end

  UI.success("Basic docs copying complete!")
end

desc "Create a compressed archive"
lane :create_and_copy_archive do
  UI.message("Creating Flutter package archive...")

  Dir.chdir("..") do
    # Remove previous archives in output and intermediate paths.
    Dir.glob(File.join(output_path, "*.zip")).each { |f| FileUtils.rm_f(f) }
    Dir.glob(File.join(intermediate_path, "*.zip")).each { |f| FileUtils.rm_f(f) }

    UI.message("Regenerating sdk_version_generated.dart from pubspec.yaml...")
    sh("HOME=$PWD DART_SUPPRESS_ANALYTICS=true dart run tool/update_sdk_version.dart")

    # Get package name and version for better naming.
    pubspec_info = YAML.load(File.read("pubspec.yaml"))
    package_name = pubspec_info["name"]
    package_version = pubspec_info["version"]

    # Create the zip archive for Flutter packages.
    archive_name = "#{package_name}-#{package_version}.zip"
    archive_path = File.join(output_path, archive_name)
    intermediate_archive_path = File.join(intermediate_path, archive_name)

    # Make sure the output directory exists.
    FileUtils.mkdir_p(output_path)

    # Create zip archive excluding build artifacts and git files.
    sh("zip -r #{archive_path} . -x '.git/*' 'build/*' '.dart_tool/*' '.flutter-plugins' '.flutter-plugins-dependencies' '.packages' '*.lock' 'example/*'")

    # Copy to intermediate path as well.
    FileUtils.cp(archive_path, intermediate_archive_path)

    UI.success("Successfully created archive at: #{archive_path}.")
  end
end

desc "Copy sample directory as sample_app to output path"
lane :copy_sample_app do
  UI.message("Copying sample directory to output path as sample_app...")

  Dir.chdir("..") do
    # Define the source and destination paths.
    source_dir = sample_app_source_path
    dest_dir = File.join(output_path, sample_app_destination_name)

    # Check if the source directory exists.
    if Dir.exist?(source_dir)
      # Make sure the parent directory of the destination exists.
      FileUtils.mkdir_p(output_path)

      # Remove any existing sample_app directory in the output path.
      if Dir.exist?(dest_dir)
        UI.message("Removing existing sample_app directory...")
        FileUtils.rm_rf(dest_dir)
      end

      # Copy the directory excluding ios/, android/, and .gitignore patterns.
      UI.message("Copying #{source_dir} to #{dest_dir}...")
      FileUtils.cp_r("#{source_dir}/.", dest_dir)
      FileUtils.rm_rf(File.join(dest_dir, 'ios'))
      FileUtils.rm_rf(File.join(dest_dir, 'android'))

      gitignore = File.join(source_dir, '.gitignore')
      if File.exist?(gitignore)
        File.readlines(gitignore).each do |line|
          pattern = line.strip
          next if pattern.empty? || pattern.start_with?('#')

          clean = pattern.delete_prefix('/')
          Dir.glob(File.join(dest_dir, '**', clean)).each { |m| FileUtils.rm_rf(m) }
        end
      end

      UI.success("Successfully copied sample directory to #{dest_dir}.")
    else
      UI.error("Source directory not found: #{source_dir}.")
    end
  end
end

desc "Copy the config templates"
lane :copy_config_templates do |options|
  UI.message("Copying config templates...")

  # Default options with required parameters.
  version = options[:version]

  # Create a binding that includes our current context.
  context = binding

  # If you need to add more variables to the context that aren't
  # parameters of this method, you can set them explicitly.
  context.local_variable_set(:version, version)

  config_files.each do |config_file, template_file|
    template_path = File.join(templates_path, template_file)

    if File.exist?(template_path)
      UI.message("Updating #{config_file} from template...")

      # Read the template content.
      template_content = ERB.new(File.read(template_path))

      # Render the template with our binding context.
      rendered_content = template_content.result(context)
      FileUtils.mkdir_p(File.dirname(File.join("..", output_path, sample_app_destination_name, config_file)))
      output_file = File.join("..", output_path, sample_app_destination_name, config_file)
      File.write(output_file, rendered_content)
    else
      UI.error("Template file not found: #{template_path}.")
    end
  end
  UI.success("Configuration files updated successfully.")
end

desc "Copy the pre-release config templates (in-place)"
lane :copy_pre_release_config_templates do |options|
  UI.message("Copying pre-release config templates...")

  version = options[:version]
  working_dir = options[:working_dir]

  context = binding
  context.local_variable_set(:version, version)

  template_base = File.join(working_dir, "Templates")

  pre_release_templates_path.each do |dest, template_file|
    template_path = File.join(template_base, template_file)

    if File.exist?(template_path)
      UI.message("Updating #{dest} from template...")
      template_content = ERB.new(File.read(template_path))
      rendered_content = template_content.result(context)
      FileUtils.mkdir_p(File.dirname(dest))
      File.write(dest, rendered_content)
    else
      UI.error("Template file not found: #{template_path}.")
    end
  end
  UI.success("Pre-release configuration files updated successfully.")
end

desc "Commit, push and create GitHub release"
lane :publish do |options|
  version = options[:version]
  is_release = options[:release].to_s == 'true'

  if version.nil? || version.empty?
    UI.important "The version is not provided, will try to extract it from the current branch name."
    current_branch = sh("git rev-parse --abbrev-ref HEAD").strip
    if current_branch =~ /^(release|hotfix)\/(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)$/
      version = $2
      UI.important "Using version #{version} from branch name #{current_branch}."
    end
  end

  if version.nil? || version.empty?
    UI.abort_with_message! "The version (`version`) parameter is missing!"
  end

  if github_access_token.nil? || github_access_token.empty?
    UI.abort_with_message! "The GitHub access token (`GITHUB_ACCESS_RW_TOKEN`) is missing!"
  end

  if is_release
    tag_name = "v#{version}"
    target_repository_name = Configuration::PUBLIC_REPOSITORY

    target_repository_url = "git@github.com:#{target_repository_name}.git"

    # Clean and prepare directories.
    Dir.chdir("..") do
      FileUtils.rm_rf(output_path)
      sh("git clone --branch main --depth 1 --single-branch #{target_repository_url} #{output_path}")
    end

    # Extract the latest changelog for the release description.
    # Defined here so it's accessible inside the Dir.chdir block below.
    changelog = "Release version #{version}"
    Dir.chdir("..") do
      if File.exist?("CHANGELOG.md")
        full_changelog = File.read("CHANGELOG.md")
        changelog = extract_latest_changelog(full_changelog, version)
      end
    end

    # Package and copy artifacts.
    create_and_copy_archive
    copy_basic_docs
    copy_sample_app
    copy_config_templates(version: version)

    # Commit and push changes to the repository.
    Dir.chdir("../#{output_path}") do
      sh("git fetch origin main")
      sh("git switch main")
      sh("git pull origin main")

      release_branch = "release/v#{version}"
      sh("git checkout -b #{release_branch}")

      sh("git add .")
      changes = sh("git status --porcelain", log: false)

      if changes.empty?
        UI.important "No changes to commit."
      else
        sh("git", "commit", "-m", "[skip ci] Release v#{version}\n\nThis PR contains the automated release updates for version #{version}.\n\nChanges:\n- Updated sample app\n- Updated documentation\n- Updated README, CHANGELOG, and MIGRATION guide")

        sh("git push origin #{release_branch}")

        require 'uri'
        encoded_title = URI.encode_www_form_component("Release v#{version}")
        pr_url = "https://github.com/#{target_repository_name}/compare/main...#{release_branch}?expand=1&title=#{encoded_title}"

        UI.important("==============================================")
        UI.important("  Create the release PR manually:")
        UI.important("  #{pr_url}")
        UI.important("==============================================")

        ENV['PUBLIC_REPO_PR_URL'] = pr_url

        UI.important("Creating GitHub release...")
        begin
          release_name = "Version #{version}"
          assets = Dir.glob("*.zip")

          github_release = set_github_release(
            repository_name: target_repository_name,
            name: release_name,
            tag_name: tag_name,
            api_token: github_access_token,
            description: changelog,
            commitish: release_branch,
            upload_assets: assets
          )

          UI.success "Release created successfully at: #{github_release['html_url']}."
        rescue => e
          UI.error "The GitHub release could not be created! Error: #{e.message}."
        end

        UI.success("Public repo PR created at: #{pr_url}")
        UI.important("Don't forget to review and merge the PR: #{pr_url}")

        if ENV['CI'] || ENV['CIRCLECI'] || ENV['CONTINUOUS_INTEGRATION']
          pr_url_file = "/tmp/public_repo_pr_url.txt"
          File.write(pr_url_file, pr_url)
          UI.important("PR URL written to #{pr_url_file} for CircleCI")
        end
      end
    end
  else
    # Pre-release path: publish to test repository.
    target_repository_name = Configuration::TEST_REPOSITORY
    target_repository_url = "https://github.com/#{target_repository_name}.git"

    # Randomize version for pre-release builds.
    if options[:randomize_version]
      sanitized_timestamp = Time.now.to_i
      version = "#{version}-#{sanitized_timestamp}"
      UI.important "Will use a randomized version for testing purposes: #{version}"
    end

    UI.important "Publishing pre-release version #{version} to test repository: #{target_repository_name}"

    temp_dir = Dir.mktmpdir
    working_dir = Dir.pwd

    begin
      Dir.chdir("..") do
        sh("git clone --branch main --depth 1 --single-branch #{target_repository_url} #{temp_dir}")
      end

      Dir.chdir(temp_dir) do
        Dir.glob("*").each { |f| FileUtils.rm_rf(f) unless f == ".git" }

        Dir.chdir("#{working_dir}/..") do
          UI.message("Regenerating sdk_version_generated.dart from pubspec.yaml...")
          sh("HOME=$PWD DART_SUPPRESS_ANALYTICS=true dart run tool/update_sdk_version.dart")
          FileUtils.cp_r(Dir.glob("*") - [".circleci", ".github", ".git", ".gitignore"], temp_dir)
        end

        if File.exist?("CHANGELOG.md")
          content = File.read("CHANGELOG.md")
          updated_content = "## #{version}\n\n#{content}"
          File.write("CHANGELOG.md", updated_content)
        end

        copy_pre_release_config_templates(version: version, working_dir: working_dir)

        sh("git add .")
        sh("git commit -m 'Pre-release build: #{version}' || echo 'No changes to commit'")
        sh("git tag -a 'v#{version}' -m 'Pre-release version #{version}'")
        sh("git push origin --tags")
        sh("git push origin 'v#{version}'")
        sh("git push origin main")
      end

      UI.success "Pre-release #{version} published successfully to #{target_repository_name}"
    ensure
      FileUtils.rm_rf(temp_dir) if temp_dir && Dir.exist?(temp_dir)
    end
  end
end

def extract_latest_changelog(changelog, version)
  # Ensure the changelog is provided.
  if changelog.nil? || changelog.empty?
    UI.abort_with_message! "The changelog is missing!"
  end

  # Ensure version is provided.
  if version.empty?
    UI.abort_with_message! "The version (`version`) parameter is missing!"
  end

  UI.message "Extracting the changelog for version #{version}."

  # Split the changelog into sections.
  sections = changelog.split(/^## \[/)

  # Find the section for the specified version.
  target_section = sections.find { |s| s.start_with?("#{version}]") }

  if target_section
    # Remove the version heading and any leading/trailing whitespace.
    latest_changelog = target_section.sub(/^#{version}\].*$\n/, "").strip
    # Return the changelog.
    latest_changelog
  else
    UI.message "No specific changelog found for version #{version}, using generic message."
    "Release version #{version}"
  end
end