module Tapioca::ConfigHelper

def build_error(msg)

def build_error(msg)
  parts = msg.split(/(`[^`]+` ?)/)
  message_parts = parts.map do |part|
    match = part.match(/`([^`]+)`( ?)/)
    if match
      ConfigErrorMessagePart.new(
        message: "#{match[1]}#{match[2]}",
        colors: [:bold, :blue],
      )
    else
      ConfigErrorMessagePart.new(
        message: part,
        colors: [:yellow],
      )
    end
  end
  ConfigError.new(
    message_parts: message_parts,
  )
end

def build_error_message(config_file, errors)

def build_error_message(config_file, errors)
  error_messages = errors.map do |error|
    "- " + error.message_parts.map do |part|
      T.unsafe(self).set_color(part.message, *part.colors)
    end.join
  end.join("\n")
  <<~ERROR
    #{set_color("\nConfiguration file", :red)} #{set_color(config_file, :blue, :bold)} #{set_color("has the following errors:", :red)}
    #{error_messages}
  ERROR
end

def config_options(options)

def config_options(options)
  config_file = options[:config]
  config = {}
  if File.exist?(config_file)
    config = YAML.load_file(config_file, fallback: {})
  end
  validate_config!(config_file, config)
  Thor::CoreExt::HashWithIndifferentAccess.new(config[command_name] || {})
end

def filter_defaults(options)

def filter_defaults(options)
  options.each do |key, option|
    # Store the value of the current default in our defaults hash
    defaults[key] = option.default
    # Remove the default value from the option
    option.instance_variable_set(:@default, nil)
  end
end

def initialize(args = [], local_options = {}, config = {})

def initialize(args = [], local_options = {}, config = {})
  # Store current command
  command = config[:current_command]
  command_options = config[:command_options]
  @command_name = T.let(command.name, String)
  @merged_options = T.let(nil, T.nilable(Thor::CoreExt::HashWithIndifferentAccess))
  @defaults = T.let(Thor::CoreExt::HashWithIndifferentAccess.new, Thor::CoreExt::HashWithIndifferentAccess)
  # Filter command options unless we are handling the help command.
  # This is so that the defaults are printed
  filter_defaults(command_options) unless command_name == "help"
  super
end

def merge_options(*options)

def merge_options(*options)
  merged = options.each_with_object({}) do |option, result|
    result.merge!(option || {}) do |_, this_val, other_val|
      if this_val.is_a?(Hash) && other_val.is_a?(Hash)
        Thor::CoreExt::HashWithIndifferentAccess.new(this_val.merge(other_val))
      else
        other_val
      end
    end
  end
  Thor::CoreExt::HashWithIndifferentAccess.new(merged)
end

def options

def options
  @merged_options ||= begin
    original_options = super
    config_options = config_options(original_options)
    merge_options(defaults, config_options, original_options)
  end
end

def validate_config!(config_file, config)

def validate_config!(config_file, config)
  # To ensure that this is not re-entered, we mark during validation
  return if @validating_config
  @validating_config = T.let(true, T.nilable(T::Boolean))
  commands = T.cast(self, Thor).class.commands
  errors = config.flat_map do |config_key, config_options|
    command = commands[config_key.to_s]
    unless command
      next build_error("unknown key `#{config_key}`")
    end
    validate_config_options(command.options, config_key, config_options || {})
  end.compact
  unless errors.empty?
    raise Thor::Error, build_error_message(config_file, errors)
  end
ensure
  @validating_config = false
end

def validate_config_options(command_options, config_key, config_options)

def validate_config_options(command_options, config_key, config_options)
  config_options.filter_map do |config_option_key, config_option_value|
    command_option = command_options[config_option_key.to_sym]
    error_msg = "unknown option `#{config_option_key}` for key `#{config_key}`"
    next build_error(error_msg) unless command_option
    config_option_value_type = case config_option_value
    when FalseClass, TrueClass
      :boolean
    when Numeric
      :numeric
    when Hash
      :hash
    when Array
      :array
    when String
      :string
    else
      :object
    end
    error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
      "`#{command_option.type.capitalize}` but found #{config_option_value_type.capitalize}"
    next build_error(error_msg) unless config_option_value_type == command_option.type
    case config_option_value_type
    when :array
      error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
        "`Array[String]` but found `#{config_option_value}`"
      next build_error(error_msg) unless config_option_value.all? { |v| v.is_a?(String) }
    when :hash
      error_msg = "invalid value for option `#{config_option_key}` for key `#{config_key}` - expected " \
        "`Hash[String, String]` but found `#{config_option_value}`"
      values_to_validate = config_option_value.keys
      values_to_validate += config_option_value.values
      all_strings = values_to_validate.all? { |v| v.is_a?(String) }
      next build_error(error_msg) unless all_strings
    end
  end
end