# frozen_string_literal: true
require "addressable/uri"

class ::CustomWizard::UpdateValidator
  attr_reader :updater

  def initialize(updater)
    @updater = updater
  end

  def perform
    updater.step.fields.each { |field| validate_field(field) }
  end

  def validate_field(field)
    return if field.type == "text_only"

    field_id = field.id.to_s
    value = @updater.submission[field_id]
    min_length = false
    label = field.raw[:label]
    type = field.type
    required = field.required
    min_length = field.min_length if is_text_type(field)
    max_length = field.max_length if is_text_type(field)
    file_types = field.file_types
    format = field.format

    if required && !value
      @updater.errors.add(field_id, I18n.t("wizard.field.required", label: label))
    end

    if value.is_a?(String) && (stripped_length = value.strip.length) > 0
      if min_length.present? && stripped_length < min_length.to_i
        @updater.errors.add(
          field_id,
          I18n.t("wizard.field.too_short", label: label, min: min_length.to_i),
        )
      end

      if max_length.present? && stripped_length > max_length.to_i
        @updater.errors.add(
          field_id,
          I18n.t("wizard.field.too_long", label: label, max: max_length.to_i),
        )
      end
    end

    if is_url_type(field) && value.present? && !check_if_url(value)
      @updater.errors.add(field_id, I18n.t("wizard.field.not_url", label: label))
    end

    @updater.submission[field_id] = standardise_boolean(value) if type === "checkbox"

    if type === "upload" && value.present? && !validate_file_type(value, file_types)
      @updater.errors.add(
        field_id,
        I18n.t("wizard.field.invalid_file", label: label, types: file_types),
      )
    end

    if %w[date date_time].include?(type) && value.present? && !validate_date(value, format)
      @updater.errors.add(field_id, I18n.t("wizard.field.invalid_date"))
    end

    if type === "time" && value.present? && !validate_time(value)
      @updater.errors.add(field_id, I18n.t("wizard.field.invalid_time"))
    end

    self.class.field_validators.each do |validator|
      validator[:block].call(field, value, @updater) if type === validator[:type]
    end
  end

  def self.sorted_field_validators
    @sorted_field_validators ||= []
  end

  def self.field_validators
    sorted_field_validators.map { |h| { type: h[:type], block: h[:block] } }
  end

  def self.add_field_validator(priority = 0, type, &block)
    sorted_field_validators << { priority: priority, type: type, block: block }
    @sorted_field_validators.sort_by! { |h| -h[:priority] }
  end

  private

  def validate_file_type(value, file_types)
    file_types
      .split(",")
      .map { |t| t.gsub(".", "") }
      .include?(File.extname(value["original_filename"])[1..-1])
  end

  def validate_date(value, format)
    v8.eval("moment('#{value}', '#{format}', true).isValid()")
  end

  def validate_time(value)
    begin
      Time.parse(value)
      true
    rescue ArgumentError
      false
    end
  end

  def is_text_type(field)
    %w[text textarea composer].include? field.type
  end

  def is_url_type(field)
    ["url"].include? field.type
  end

  SCHEMES ||= %w[http https]

  def check_if_url(url)
    parsed = Addressable::URI.parse(url) or return false
    SCHEMES.include?(parsed.scheme)
  rescue Addressable::URI::InvalidURIError
    false
  end

  def standardise_boolean(value)
    ActiveRecord::Type::Boolean.new.cast(value)
  end

  def v8
    return @ctx if @ctx

    @ctx = PrettyText.v8
    @ctx.load("#{Rails.root}/app/assets/javascripts/discourse/node_modules/moment/moment.js")
    @ctx
  end
end
