defmodule Mix.Tasks.PopulateEnglish do
  @shortdoc "Populates English .po files with msgstr = msgid"

  @moduledoc """
  Populates English translation files by setting msgstr = msgid.

  For English-only applications using Gettext, this task automatically
  fills in translation strings so that the original English text is used.

  ## Usage

      mix populate_english

  This task will:
  1. Find all .po files in priv/gettext/en/LC_MESSAGES/
  2. For each msgid without a msgstr, set msgstr to equal msgid
  3. Preserve existing non-empty msgstr values

  Run this after extracting translations with `mix gettext.extract --merge`.
  """

  use Mix.Task

  @impl Mix.Task
  def run(_args) do
    po_dir = Path.join(["priv", "gettext", "en", "LC_MESSAGES"])

    if !File.dir?(po_dir) do
      Mix.raise("Directory not found: #{po_dir}")
    end

    po_files = Path.wildcard(Path.join(po_dir, "*.po"))

    if Enum.empty?(po_files) do
      Mix.shell().info("No .po files found in #{po_dir}")
      :ok
    else
      Enum.each(po_files, &process_po_file/1)
      Mix.shell().info("✓ Populated #{length(po_files)} English translation file(s)")
    end
  end

  defp process_po_file(file_path) do
    content = File.read!(file_path)
    updated_content = populate_translations(content)

    if content == updated_content do
      Mix.shell().info("  No changes: #{Path.basename(file_path)}")
    else
      File.write!(file_path, updated_content)
      Mix.shell().info("  Updated: #{Path.basename(file_path)}")
    end
  end

  defp populate_translations(content) do
    # Match msgid followed by msgstr
    # Handle both single-line and multi-line strings
    content
    |> String.split("\n")
    |> process_lines([])
    |> Enum.reverse()
    |> Enum.join("\n")
  end

  defp process_lines([], acc), do: acc

  defp process_lines([line | rest], acc) do
    if String.starts_with?(line, "msgid ") do
      process_msgid_line([line | rest], acc)
    else
      # Regular line (comments, metadata, blank lines)
      process_lines(rest, [line | acc])
    end
  end

  defp process_msgid_line(lines, acc) do
    {msgid_lines, remaining} = collect_string_lines(lines, [])

    # Check if this is a plural form (msgid_plural follows)
    if plural_form?(remaining) do
      process_plural_form(msgid_lines, remaining, acc)
    else
      process_singular_form(msgid_lines, remaining, acc)
    end
  end

  defp process_plural_form(msgid_lines, remaining, acc) do
    {plural_lines, remaining2} = skip_plural_translation(remaining, [])
    new_acc = Enum.reverse(plural_lines, Enum.reverse(msgid_lines, acc))
    process_lines(remaining2, new_acc)
  end

  defp process_singular_form(msgid_lines, remaining, acc) do
    {msgstr_lines, remaining2} = collect_msgstr_lines(remaining, [])
    msgid_value = extract_string_value(msgid_lines)

    updated_msgstr =
      if should_populate?(msgid_value, msgstr_lines) do
        populate_msgstr(msgid_lines, msgid_value)
      else
        msgstr_lines
      end

    new_acc = Enum.reverse(updated_msgstr, Enum.reverse(msgid_lines, acc))
    process_lines(remaining2, new_acc)
  end

  # Collect lines that are part of a msgid string (including continuation lines)
  defp collect_string_lines([], acc), do: {Enum.reverse(acc), []}

  defp collect_string_lines([line | rest] = lines, acc) do
    cond do
      String.starts_with?(line, "msgid ") ->
        collect_string_lines(rest, [line | acc])

      String.starts_with?(String.trim_leading(line), "\"") and acc != [] ->
        # Continuation line
        collect_string_lines(rest, [line | acc])

      true ->
        {Enum.reverse(acc), lines}
    end
  end

  # Collect msgstr lines
  defp collect_msgstr_lines([], acc), do: {Enum.reverse(acc), []}

  defp collect_msgstr_lines([line | rest] = lines, acc) do
    cond do
      String.starts_with?(line, "msgstr ") ->
        collect_msgstr_lines(rest, [line | acc])

      String.starts_with?(String.trim_leading(line), "\"") and acc != [] ->
        # Continuation line
        collect_msgstr_lines(rest, [line | acc])

      true ->
        {Enum.reverse(acc), lines}
    end
  end

  # Extract the actual string value from msgid/msgstr lines
  defp extract_string_value(lines) do
    Enum.map_join(lines, &extract_line_value/1)
  end

  defp extract_line_value(line) do
    # Remove msgid/msgstr prefix and extract quoted content
    line
    |> String.replace(~r/^msgid\s+/, "")
    |> String.replace(~r/^msgstr\s+/, "")
    |> String.trim()
    |> extract_quoted_string()
  end

  defp extract_quoted_string(s) do
    if String.starts_with?(s, "\"") and String.ends_with?(s, "\"") do
      s
      |> String.slice(1..-2//1)
      |> unescape_string()
    else
      ""
    end
  end

  defp unescape_string(s) do
    s
    |> String.replace("\\n", "\n")
    |> String.replace("\\t", "\t")
    |> String.replace("\\\"", "\"")
    |> String.replace("\\\\", "\\")
  end

  defp format_string_value(value) do
    escaped =
      value
      |> String.replace("\\", "\\\\")
      |> String.replace("\"", "\\\"")
      |> String.replace("\n", "\\n")
      |> String.replace("\t", "\\t")

    "\"#{escaped}\""
  end

  defp msgstr_empty?([]), do: true

  defp msgstr_empty?(lines) do
    value = extract_string_value(lines)
    String.trim(value) == ""
  end

  defp should_populate?(msgid_value, msgstr_lines) do
    String.trim(msgid_value) != "" and msgstr_empty?(msgstr_lines)
  end

  defp populate_msgstr(msgid_lines, msgid_value) do
    if length(msgid_lines) > 1 do
      create_multiline_msgstr(msgid_lines)
    else
      ["msgstr #{format_string_value(msgid_value)}"]
    end
  end

  defp create_multiline_msgstr(msgid_lines) do
    msgid_lines
    |> Enum.with_index()
    |> Enum.map(fn {line, index} ->
      if index == 0 do
        String.replace(line, ~r/^msgid\s+/, "msgstr ")
      else
        line
      end
    end)
  end

  defp plural_form?([]), do: false

  defp plural_form?([line | _rest]) do
    String.starts_with?(line, "msgid_plural ")
  end

  defp skip_plural_translation([], acc), do: {Enum.reverse(acc), []}

  defp skip_plural_translation([line | rest] = lines, acc) do
    # Collect msgid_plural and msgstr[N] lines
    if String.starts_with?(line, "msgid_plural ") or
         String.starts_with?(line, "msgstr[") or
         (String.starts_with?(String.trim_leading(line), "\"") and acc != []) do
      skip_plural_translation(rest, [line | acc])
    else
      {Enum.reverse(acc), lines}
    end
  end
end
