# frozen_string_literal: true

DATE_REGEX ||= /\A\d{4}-\d{2}-\d{2}/

CHANGE_TYPES ||= [
  { pattern: /\AFEATURE:/, heading: "New Features" },
  { pattern: /\AFIX:/, heading: "Bug Fixes" },
  { pattern: /\AUX:/, heading: "UX Changes" },
  { pattern: /\ASECURITY:/, heading: "Security Changes" },
  { pattern: /\APERF:/, heading: "Performance" },
  { pattern: /\AA11Y:/, heading: "Accessibility" },
]

desc "generate a release note from the important commits"
task "release_note:generate", :from, :to, :repo do |t, args|
  repo = args[:repo] || "."
  changes = find_changes(repo, args[:from], args[:to])

  CHANGE_TYPES.each { |ct| print_changes(ct[:heading], changes[ct], "###") }

  puts "(no changes)", "" if changes.values.all?(&:empty?)
end

# To use with all-the-plugins:
#  1. Make sure you have a local, up-to-date clone of https://github.com/discourse/all-the-plugins
#  2. In all-the-plugins, `git submodule update --init --recursive --remote`
#  3. Change back to your discourse directory
#  4. rake "release_note:plugins:generate[ 2021-06-01 , 2021-07-01 , /path/to/all-the-plugins/plugins/* , discourse ]"
desc "generate release notes for all official plugins in a directory"
task "release_note:plugins:generate", :from, :to, :plugin_glob, :org do |t, args|
  from = args[:from]
  to = args[:to]
  plugin_glob = args[:plugin_glob] || "./plugins/*"
  git_org = args[:org]

  all_repos = Dir.glob(plugin_glob).filter { |f| File.directory?(f) && File.exist?("#{f}/.git") }

  if git_org
    all_repos =
      all_repos.filter do |dir|
        `git -C #{dir} remote get-url origin`.match?(%r{github.com[/:]#{git_org}/})
      end
  end

  no_changes_repos = []

  all_repos.each do |dir|
    name = File.basename(dir)
    changes = find_changes(dir, from, to)

    if changes.values.all?(&:empty?)
      no_changes_repos << name
      next
    end

    puts "### #{name}\n\n"
    CHANGE_TYPES.each { |ct| print_changes_plugin(ct[:heading], changes[ct]) }
  end

  puts "(No changes found in #{no_changes_repos.join(", ")})"
end

def find_changes(repo, from, to)
  dates = from&.match?(DATE_REGEX) || to&.match?(DATE_REGEX)

  if !dates
    from ||= `git -C #{repo} describe --tags --abbrev=0`.strip
    to ||= "HEAD"
  end

  cmd = "git -C #{repo} log --pretty='tformat:%s' "
  if dates
    cmd += "--after '#{from}' " if from
    cmd += "--before '#{to}' " if to
  else
    cmd += "#{from}..#{to}"
  end

  out = `#{cmd}`
  raise "Status #{$?.exitstatus} running git log\n#{out}" if !$?.success?

  changes = {}
  CHANGE_TYPES.each { |ct| changes[ct] = Set.new }

  out.each_line do |comment|
    next if comment =~ /\A\s*Revert/
    split_comments(comment).each do |line|
      ct = CHANGE_TYPES.find { |t| line =~ t[:pattern] }
      changes[ct] << better(line) if ct
    end
  end

  changes
end

def print_changes(heading, changes, importance)
  return if changes.length == 0

  puts "#{importance} #{heading}", ""
  puts changes.to_a, ""
end

def print_changes_plugin(heading, changes)
  return if changes.length == 0

  puts "[details=\"#{heading}\"]\n", ""
  puts changes.to_a, ""
  puts "[/details]\n", ""
end

def better(line)
  line = remove_prefix(line)
  line = escape_brackets(line)
  line = remove_pull_request(line)
  line[0] = '\#' if line[0] == "#"
  if line[0]
    line[0] = line[0].capitalize
    "- " + line
  else
    nil
  end
end

def remove_prefix(line)
  line.gsub(/\A(FIX|FEATURE|UX|SECURITY|PERF|A11Y):/, "").strip
end

def escape_brackets(line)
  line.gsub("<", "`<").gsub(">", ">`").gsub("[", "`[").gsub("]", "]`")
end

def remove_pull_request(line)
  line.gsub(/ \(\#\d+\)\z/, "")
end

def split_comments(text)
  text = normalize_terms(text)
  terms = %w[FIX: FEATURE: UX: SECURITY: PERF: A11Y:]
  terms.each do |term|
    text = text.gsub(/(#{term})+/i, term)
    text = newlines_at_term(text, term)
  end
  text.split("\n")
end

def normalize_terms(text)
  text = text.gsub(/(BUGFIX|FIX|BUG):/i, "FIX:")
  text = text.gsub(/FEATURE:/i, "FEATURE:")
  text = text.gsub(/(UX|UI):/i, "UX:")
  text = text.gsub(/(SECURITY):/i, "SECURITY:")
  text = text.gsub(/(PERF):/i, "PERF:")
  text = text.gsub(/(A11Y):/i, "A11Y:")
end

def newlines_at_term(text, term)
  text = text.split(term).map { |l| l.strip }.join("\n#{term} ") if text.include?(term)
  text
end