#!/usr/bin/env ruby

begin
  require "cldr"
rescue LoadError
  warn <<~MSG
    Error: the ruby-cldr gem is not installed
    Install it with 'gem install ruby-cldr' and run this script again
  MSG
  exit 1
end
require "pathname"
require "yaml"

class App
  class << self
    def locales
      @locales ||= rails_root.glob("config/locales/**/*.yml")
          .map { |f| f.basename.to_s.split(".", 2).first }
          .reject! { |l| l.start_with?("js-") || l == "lol" }
          .uniq
          .map(&:to_sym)
    end

    def rails_root
      @rails_root ||=
        Pathname.new(__dir__)
          .ascend
          .find { |dir| dir.join("Gemfile").exist? }
          .tap { |dir| raise "Unable to find Rails root directory (looking up from #{__dir__})" if dir.nil? }
    end
  end
end

class CldrTranslations
  CLDR_VERSION = 44

  def initialize
    ensure_cldr_database_is_downloaded
    Cldr::Export::Data.dir = cldr_db_location.join("common").to_s
  end

  def language_name(locale)
    locale = cldr_supported(locale)
    variations = variations(locale)
    language_names = merged_language_names(variations)
    language_name = variations.filter_map { |l| language_names[l] }.first
    language_name.capitalize
  end

  private

  def merged_language_names(variations)
    variations.reduce({}) do |merged, locale|
      merged.merge(language_names(locale))
    end
  end

  def language_names(locale)
    @language_names ||= {}
    @language_names[locale] ||= Cldr::Export.data(:Languages, locale)[:languages] || {}
  rescue Errno::ENOENT => e
    warn "ERROR: cannot load Languages CLDR component file for locale #{locale}: #{e}"
    warn e.backtrace.join("\n")
    exit 1
  end

  # Maps OpenProject locale to a locale recognized by CLDR.
  #
  # According to https://sites.psu.edu/symbolcodes/languages/asia/chinese/
  #   zh-CN <=> zh-Hans (zh-Hans is preferred, but zh-CN may be found on older sites)
  #   zh-TW <=> zh-Hant (zh-Hant or zh-Hant-TW (Taiwan) is preferred zh-TW)
  def cldr_supported(locale)
    case locale
    when :"zh-CN" then :"zh-Hans-CN"
    when :"zh-TW" then :"zh-Hant-TW"
    else locale
    end
  end

  # From :'zh-Hans-CN', returns [:'zh-Hans-CN', :'zh-Hans', :zh]
  #   :'zh-ZH' is not included as it's not available in CLDR
  # From :de, returns [:de]
  # From :'pt-BR', returns [:'pt-BR', :'pt-PT', :pt]
  # From :'pt-PT', returns [:'pt-PT', :pt]
  def variations(locale)
    locale.to_s
      .split("-")
      .reduce([]) { |variations, part| variations.unshift([variations.first, part].compact.join("-")) }
      .then { |variations| insert_self_variation(variations) }
      .select { |variation| available_locale?(variation) }
      .map(&:to_sym)
  end

  def insert_self_variation(variations)
    return variations if variations.length == 1

    self_variation = "#{variations.last}-#{variations.last.upcase}"
    return variations if variations.include?(self_variation)

    variations.insert(-2, self_variation)
  end

  def available_locale?(locale)
    available_locales.include?(locale)
  end

  def available_locales
    @available_locales ||= Set.new(Cldr::Export::Data.locales.map { _1.tr("_", "-") })
  end

  def ensure_cldr_database_is_downloaded
    return if cldr_db_location.exist?

    require "cldr/download"
    target = cldr_db_location.to_s
    source = "https://unicode.org/Public/cldr/#{CLDR_VERSION}/core.zip"
    puts <<~MSG
      CLDR database version #{CLDR_VERSION} missing
      Downloading from #{source}
                    to #{target}
    MSG
    Cldr.download(source, target)
    puts "Downloaded!"
  end

  def cldr_db_location
    App.rails_root.join("tmp/cldr-v#{CLDR_VERSION}")
  end
end

class GenerateTranslationFiles
  COMMENT = <<~COMMENT.freeze
    # This file has been generated by #{Pathname.new($0).cleanpath}.
    # Please do not edit directly.
    #
    # To update this file, run #{Pathname.new($0).cleanpath}.
    #
    # The translations come from version #{CldrTranslations::CLDR_VERSION} of the Unicode CLDR project.
    #
    # The Unicode Common Locale Data Repository (CLDR) provides key building
    # blocks for software to support the world's languages, with the largest
    # and most extensive standard repository of locale data available.
  COMMENT

  class << self
    def call
      puts "Generate #{App.locales.count} translation files in #{translations_directory} using CLDR"
      App.locales.each do |locale|
        create_translation_file(locale)
      end
      puts "Done"
    end

    def create_translation_file(locale)
      translations_directory.join("#{locale}.yml").open("w") do |f|
        f.puts COMMENT
        YAML.dump(yaml_content(locale), f)
      end
    end

    def yaml_content(locale)
      {
        locale.to_s => {
          "cldr" => {
            "language_name" => cldr_translations.language_name(locale)
          }
        }
      }
    end

    def cldr_translations
      @cldr_translations ||= CldrTranslations.new
    end

    def translations_directory
      @translations_directory ||= App.rails_root.join("config/locales/generated").tap do |dir|
        dir.mkdir unless dir.exist?
      end
    end
  end
end

GenerateTranslationFiles.call
