# frozen_string_literal: true

class Anime < ApplicationRecord
  SEASONS = %w[winter spring summer fall].freeze

  include Media
  include AgeRatings
  include Episodic

  enum subtype: { TV: 0, special: 1, OVA: 2, ONA: 3, movie: 4, music: 5 }
  has_many :streaming_links, as: 'media', dependent: :destroy, inverse_of: :media
  has_many :producers, through: :anime_productions
  has_many :anime_productions, dependent: :destroy
  has_many :anime_characters, dependent: :destroy
  has_many :anime_staff, dependent: :destroy
  has_many :anime_media_attributes
  has_many :media_attributes, through: :anime_media_attributes
  accepts_nested_attributes_for :streaming_links
  alias_attribute :show_type, :subtype

  rails_admin { fields :episode_count }

  update_index('media#anime') { self }

  def slug_candidates
    # Prefer the canonical title or romaji title before anything else
    candidates = [
      -> { canonical_title } # attack-on-titan
    ]
    if subtype == :TV
      # If it's a TV show with a name collision, common practice is to
      # specify the year (ex: kanon-2006)
      candidates << -> { [canonical_title, year] }
    else
      # If it's not TV and it's having a name collision, it's probably the
      # movie or OVA for a series (ex: attack-on-titan-movie)
      candidates << -> { [canonical_title, subtype] }
      candidates << -> { [canonical_title, subtype, year] }
    end
    candidates
  end

  def season
    case start_date.try(:month)
    when 12, 1, 2 then :winter
    when 3, 4, 5 then :spring
    when 6, 7, 8 then :summer
    when 9, 10, 11 then :fall
    end
  end

  def season_changed?
    start_date_changed?
  end

  # Season year is the year, adjusted so that December is part of the next year
  def season_year
    if start_date.try(:month) == 12
      year + 1
    else
      year
    end
  end
  alias_method :season_year_changed?, :season_changed?
  alias_method :year_changed?, :season_changed?

  def update_unit_count_guess(guess)
    return if episode_count || (episode_count_guess && episode_count_guess > guess)
    update(episode_count_guess: guess)
  end

  def self.unit_class
    Episode
  end

  def self.fuzzy_find(title)
    MediaIndex::Anime.query(multi_match: {
      fields: %w[titles.* abbreviated_titles description actors characters],
      query: title,
      fuzziness: 2,
      max_expansions: 15,
      prefix_length: 2
    }).preload.first
  end

  def self.rails_admin_search(keyword)
    where(id: TypesenseAnimeIndex.search(
      query: keyword,
      query_by: {
        'canonical_title' => 100,
        'titles.*' => 90,
        'alternative_titles' => 90
      }
    ).sort({
      '_text_match(buckets: 6)' => 'desc',
      'user_count' => 'desc'
    }).include_fields(:id).load.hits.map { |res| res.document['id'] })
  end

  def self.typesense_index
    TypesenseAnimeIndex
  end
  delegate :typesense_index, to: :class

  before_save do
    self.episode_count_guess = nil if episode_count

    if episode_count == 1
      self.start_date = end_date if start_date.nil? && !end_date.nil?
      self.end_date = start_date if end_date.nil? && !start_date.nil?
    end
  end

  after_commit(on: %i[create update]) do
    typesense_index.index_one(id) if typesense_index.should_sync?(saved_changes)
  end

  after_commit(on: :destroy) do
    typesense_index.remove_one(id)
  end

  after_save do
    if (saved_change_to_episode_count_guess? && !episode_count_guess.nil?) &&
       episodes.length != episode_count_guess
      episodes.create_defaults(episode_count_guess || 0)
    elsif saved_change_to_episode_count? && episodes.length != episode_count
      episodes.create_defaults(episode_count || 0, status: :validated)
    end
  end
end
