# STAGE: validate

event :validate_name, :validate, on: :save, changed: :name, when: :no_autoname? do
  validate_legality_of_name
  return if errors.any?

  Card.write_to_temp_cache self
  validate_uniqueness_of_name
end

# called by validate_name
event :validate_uniqueness_of_name, skip: :allowed do
  return unless (existing_id = Lexicon.id key) && existing_id != id
  # The above is a fast check but cannot detect if card is in trash

  # TODO: perform the following as a remote-only fetch (not yet supported)
  return unless (existing_card = Card.where(id: existing_id, trash: false).take)

  errors.add :name, t(:core_error_name_exists, name: existing_card.name)
end

# called by validate_name
event :validate_legality_of_name do
  if name.length > 255
    errors.add :name, t(:core_error_too_long, length: name.length)
  elsif name.blank?
    errors.add :name, t(:core_error_blank_name)
  elsif name_incomplete?
    errors.add :name, t(:core_is_incomplete)
  elsif !name.valid?
    errors.add :name, t(:core_error_banned_characters,
                        banned: Card::Name.banned_array * " ")
  elsif changing_existing_tag_to_compound?
    errors.add :name, t(:core_error_name_tag, name: name)
  end
end

event :validate_key, after: :validate_name, on: :save, when: :no_autoname? do
  if key.empty?
    errors.add :key, t(:core_error_blank_key) if errors.empty?
  elsif key != name.key
    errors.add :key, t(:core_error_wrong_key, key: key, name: name)
  end
end

event :validate_renaming, :validate, on: :update, changed: :name, skip: :allowed do
  return if name_before_act&.to_name == name # just changing to new variant

  errors.add :content, t(:core_cannot_change_content) if content_is_changing?
  errors.add :type, t(:core_cannot_change_type) if type_is_changing?
  detect_illegal_compound_names
end

# STAGE: store

event :expire_old_name, :store, changed: :name, on: :update do
  Director.expirees << name_before_act
end

event :rename_in_trash, after: :expire_old_name, on: :update do
  existing_card = Card.find_by_key_and_trash name.key, true
  return if !existing_card || existing_card == self

  existing_card.rename_as_trash_obstacle
end

event :prepare_left_and_right, :store, changed: :name, on: :save do
  return if name.simple?

  prepare_side :left
  prepare_side :right
end

# as soon as the name has an id, we have to update the lexicon.
# (the after_store callbacks are called _right_ after the storage)
event :update_lexicon, :store, changed: :name do
  lexicon_action = @action == :create ? :add : @action
  director.after_store { |card| Lexicon.send lexicon_action, card }
end

protected

def rename_as_trash_obstacle
  self.name = "#{name}*trash"
  rename_in_trash_without_callbacks
  save!
end

private

def name_incomplete?
  name.parts.include?("") && !superleft&.autoname?
end

def changed_from_simple_to_compound?
  name.compound? && name_before_act.to_name.simple?
end

def detect_illegal_compound_names
  return unless changed_from_simple_to_compound? && child_ids(:right).present?

  errors.add :name, "illegal name change; existing names end in +#{name_before_act}"
end

def changing_existing_tag_to_compound?
  changing_name_to_compound? && name_in_use_as_tag?
end

def name_in_use_as_tag?
  !Card.where(right_id: id, trash: false).take.nil?
end

def changing_name_to_compound?
  name.compound? && simple?
end

def old_name_in_way? sidecard
  real? && sidecard&.simple? && id == sidecard&.id
end

def clear_name name
  # move the current card out of the way, in case the new name will require
  # re-creating a card with the current name, ie.  A -> A+B
  Card.where(id: id).update_all(name: nil, key: nil, left_id: nil, right_id: nil)
  Card.expire name
  Card::Lexicon.cache.reset # probably overkill, but this for an edge case...
  # Card::Lexicon.delete id, key
end

def prepare_side side
  side_id = send "#{side}_id"
  sidename = name.send "#{side}_name"
  prepare_obstructed_side(side, side_id, sidename) ||
    prepare_new_side(side, side_id, sidename)
end

def prepare_new_side side, side_id, sidename
  return unless side_id == -1 || !Card[side_id]&.real?

  sidecard = Director.card(sidename) || subcard(sidename)
  send "#{side}_id=", sidecard
end

def prepare_obstructed_side side, side_id, sidename
  return unless side_id && side_id == id

  clear_name sidename
  send "#{side}_id=", subcard(sidename)
  true
end
