# typed: strict
# frozen_string_literal: true

module GoodJobHelper
  extend T::Sig
  extend T::Helpers
  abstract!

  class AnsiSegment < T::Struct
    include T::Struct::ActsAsComparable

    const :text, String
    const :class_names, T::Array[String]
    const :url, T.nilable(String)
  end

  # ANSI escape code pattern
  ANSI_PATTERN = /\e\[([0-9;]*)m/
  UUID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/

  sig { params(text: String).returns(T::Array[AnsiSegment]) }
  def parse_ansi(text)
    segments = []
    current_classes = T::Array[String].new

    # Split the text into parts based on ANSI codes
    parts = text.split(ANSI_PATTERN)

    # Process each part and its corresponding ANSI codes
    parts.each_with_index do |part, index|
      if index.even?
        # This is text content
        segments << AnsiSegment.new(
          text: part,
          class_names: current_classes.dup,
        )
      else
        # This is an ANSI code
        codes = part.split(";").map(&:to_i)
        if codes == [0]
          current_classes.clear
        else
          codes.each do |code|
            class_name = ansi_code_to_class(code)
            current_classes << class_name if class_name
          end
        end
      end
    end

    # go through segments and detect UUIDs, splitting the segment at the uuid
    # and adding them to the segments array. Should result in a <before>, <uuid>,
    # <after> tuple.
    segments.flat_map do |segment|
      if segment.text.match?(UUID_REGEX)
        idx = segment.text.index(UUID_REGEX)
        [
          AnsiSegment.new(
            text: segment.text[0...idx],
            class_names: segment.class_names,
          ),
          AnsiSegment.new(
            text: segment.text[idx...idx + 36],
            class_names: ["log-uuid"],
            url: "/jobs/jobs/#{segment.text[idx...idx + 36]}",
          ),
          AnsiSegment.new(
            text: segment.text[idx + 36..],
            class_names: segment.class_names,
          ),
        ]
      else
        [segment]
      end
    end
  end

  class JobArg < T::Struct
    const :key, Symbol
    const :value, T.untyped
    const :inferred, T::Boolean
  end

  sig { params(job: GoodJob::Job).returns(T::Array[JobArg]) }
  def arguments_for_job(job)
    deserialized =
      T.cast(
        ActiveJob::Arguments.deserialize(job.serialized_params).to_h,
        T::Hash[String, T.untyped],
      )
    args_hash =
      T.cast(deserialized["arguments"].first, T::Hash[Symbol, T.untyped])
    args =
      args_hash.map { |key, value| JobArg.new(key:, value:, inferred: false) }

    if args_hash[:fa_id] && !args_hash[:post] &&
         args << JobArg.new(
           key: :post,
           value:
             Domain::Post::FaPost.find_by(fa_id: args_hash[:fa_id]) ||
               "post not found",
           inferred: true,
         )
    end

    if args_hash[:url_name] && !args_hash[:user]
      args << JobArg.new(
        key: :user,
        value:
          Domain::User::FaUser.find_by(url_name: args_hash[:url_name]) ||
            "user not found",
        inferred: true,
      )
    end

    if job_id = args_hash[:caused_by_job_id]
      job = GoodJob::Job.find_by(id: job_id)
      if job
        args << JobArg.new(key: :caused_by_job, value: job, inferred: false)
        args.delete_if { |arg| arg.key == :caused_by_job_id }
      end
    end

    args.sort_by(&:key)
  end

  sig { params(state: String).returns(String) }
  def post_file_state_badge_class(state)
    case state
    when "ok"
      "bg-success"
    when "terminal_error"
      "bg-danger"
    when "retryable_error"
      "bg-warning text-dark"
    when "pending"
      "bg-info"
    else
      "bg-secondary"
    end
  end

  sig { params(state: String).returns(String) }
  def post_file_state_icon_class(state)
    base =
      case state
      when "ok"
        "fa-check"
      when "terminal_error"
        "fa-circle-xmark"
      when "retryable_error"
        "fa-triangle-exclamation"
      when "pending"
        "fa-clock"
      else
        "fa-question"
      end
    "fa-solid #{base}"
  end

  private

  sig { params(code: Integer).returns(T.nilable(String)) }
  def ansi_code_to_class(code)
    case code
    when 1
      "ansi-bold"
    when 30
      "ansi-black"
    when 31
      "ansi-red"
    when 32
      "ansi-green"
    when 33
      "ansi-yellow"
    when 34
      "ansi-blue"
    when 35
      "ansi-magenta"
    when 36
      "ansi-cyan"
    when 37
      "ansi-white"
    when 90
      "ansi-bright-black"
    when 91
      "ansi-bright-red"
    when 92
      "ansi-bright-green"
    when 93
      "ansi-bright-yellow"
    when 94
      "ansi-bright-blue"
    when 95
      "ansi-bright-magenta"
    when 96
      "ansi-bright-cyan"
    when 97
      "ansi-bright-white"
    else
      nil
    end
  end
end
