class Tag < ApplicationRecord
  has_many :post_tags, dependent: :delete_all, inverse_of: :tag
  has_many :active_post_tags, -> { kept }, class_name: 'PostTag', inverse_of: :tag
  has_many :post_tags_with_discarded, -> { with_discarded }, class_name: 'PostTag'
  has_many :posts, through: :active_post_tags
  has_many :tag_aliases, dependent: :destroy

  has_many :nico_tag_relations, foreign_key: :nico_tag_id, dependent: :destroy
  has_many :linked_tags, through: :nico_tag_relations, source: :tag

  has_many :reversed_nico_tag_relations, class_name: 'NicoTagRelation',
                                         foreign_key: :tag_id,
                                         dependent: :destroy
  has_many :linked_nico_tags, through: :reversed_nico_tag_relations, source: :nico_tag

  has_many :tag_implications, foreign_key: :parent_tag_id, dependent: :destroy
  has_many :children, through: :tag_implications, source: :tag

  has_many :reversed_tag_implications, class_name: 'TagImplication',
                                       foreign_key: :tag_id,
                                       dependent: :destroy
  has_many :parents, through: :reversed_tag_implications, source: :parent_tag

  enum :category, { deerjikist:  'deerjikist',
                    meme:        'meme',
                    character:   'character',
                    general:     'general',
                    material:    'material',
                    nico:        'nico',
                    meta:        'meta' }

  validates :name, presence: true, length: { maximum: 255 }
  validates :category, presence: true, inclusion: { in: Tag.categories.keys }

  validate :nico_tag_name_must_start_with_nico

  scope :nico_tags, -> { where(category: :nico) }

  CATEGORY_PREFIXES = {
      'gen:'  => 'general',
      'djk:'  => 'deerjikist',
      'meme:' => 'meme',
      'chr:'  => 'character',
      'mtr:'  => 'material',
      'meta:' => 'meta' }.freeze

  def self.tagme
    @tagme ||= Tag.find_or_create_by!(name: 'タグ希望') do |tag|
      tag.category = 'meta'
    end
  end

  def self.bot
    @bot ||= Tag.find_or_create_by!(name: 'bot操作') do |tag|
      tag.category = 'meta'
    end
  end

  def self.normalise_tags tag_names, with_tagme: true
    tags = tag_names.map do |name|
      pf, cat = CATEGORY_PREFIXES.find { |p, _| name.start_with?(p) } || ['', nil]
      name.delete_prefix!(pf)
      Tag.find_or_initialize_by(name:).tap do |tag|
        if cat && tag.category != cat
          tag.category = cat
          tag.save!
        end
      end
    end
    tags << Tag.tagme if with_tagme && tags.size < 10 && tags.none?(Tag.tagme)
    tags.uniq
  end

  def self.expand_parent_tags tags
    return [] if tags.blank?

    seen = Set.new
    result = []
    stack = tags.compact.dup

    until stack.empty?
      tag = stack.pop
      next unless tag

      tag.parents.each do |parent|
        next if seen.include?(parent.id)

        seen << parent.id
        result << parent
        stack << parent
      end
    end

    (result + tags).uniq { |t| t.id }
  end

  private

  def nico_tag_name_must_start_with_nico
    if ((category == 'nico' && !(name.start_with?('nico:'))) || 
        (category != 'nico' && name.start_with?('nico:')))
      errors.add :name, 'ニコニコ・タグの命名規則に反してゐます．'
    end
  end
end
