# frozen_string_literal: true

module Onebox
  module Engine
    def self.included(object)
      object.extend(ClassMethods)
    end

    def self.engines
      constants.select { |constant| constant.to_s =~ /Onebox\z/ }.sort.map(&method(:const_get))
    end

    def self.all_iframe_origins
      engines.flat_map { |e| e.iframe_origins }.uniq.compact
    end

    def self.origins_to_regexes(origins)
      return [/.*/] if origins.include?("*")

      origins.map do |origin|
        escaped_origin = Regexp.escape(origin)
        if origin.start_with?("*.", "https://*.", "http://*.")
          escaped_origin = escaped_origin.sub("\\*", '\S*')
        end

        Regexp.new("\\A#{escaped_origin}", "i")
      end
    end

    attr_reader :url, :uri, :options, :timeout
    attr :errors

    def options=(opt)
      return @options if opt.nil? # make sure options provided
      opt = opt.to_h if opt.instance_of?(OpenStruct)
      @options.merge!(opt)
    end

    def initialize(url, timeout = nil)
      @errors = {}
      @options = {}
      class_name = self.class.name.split("::").last.to_s

      # Set the engine options extracted from global options.
      self.options = Onebox.options[class_name] || {}

      @url = url
      @uri = URI(url)
      if always_https?
        @uri.scheme = "https"
        @url = @uri.to_s
      end
      @timeout = timeout || Onebox.options.timeout
    end

    # raises error if not defined in onebox engine.
    # This is the output method for an engine.
    def to_html
      fail NoMethodError, "Engines need to implement this method"
    end

    # Some oneboxes create iframes or other complicated controls. If you're using
    # a live editor with HTML preview, rendering those complicated controls can
    # be slow or cause flickering.
    #
    # This method allows engines to produce a placeholder such as static image
    # frame of a video.
    #
    # By default it just calls `to_html` unless implemented.
    def placeholder_html
      to_html
    end

    private

    # raises error if not defined in onebox engine
    # in each onebox, uses either Nokogiri or StandardEmbed to get raw HTML from url
    def raw
      fail NoMethodError, "Engines need to implement this method"
    end

    # raises error if not defined in onebox engine
    # in each onebox, returns hash of desired onebox content
    def data
      fail NoMethodError, "Engines need this method defined"
    end

    def link
      ::Onebox::Helpers.uri_encode(@url)
    end

    def always_https?
      self.class.always_https?
    end

    module ClassMethods
      def handles_content_type?(other)
        if other && class_variable_defined?(:@@matcher_content_type)
          !!(other.to_s =~ class_variable_get(:@@matcher_content_type))
        end
      end

      def ===(other)
        if other.kind_of?(URI)
          !!(other.to_s =~ class_variable_get(:@@matcher))
        else
          super
        end
      end

      def priority
        100
      end

      def matches_regexp(r)
        class_variable_set :@@matcher, r
      end

      def matches_content_type(ct)
        class_variable_set :@@matcher_content_type, ct
      end

      def requires_iframe_origins(*origins)
        class_variable_set :@@iframe_origins, origins
      end

      def iframe_origins
        class_variable_defined?(:@@iframe_origins) ? class_variable_get(:@@iframe_origins) : []
      end

      # calculates a name for onebox using the class name of engine
      def onebox_name
        name.split("::").last.downcase.gsub(/onebox/, "")
      end

      def always_https
        @https = true
      end

      def always_https?
        defined?(@https) ? @https : false
      end
    end
  end
end

require_relative "helpers"
require_relative "layout_support"
require_relative "file_type_finder"
require_relative "engine/standard_embed"
require_relative "engine/html"
require_relative "engine/json"
require_relative "engine/amazon_onebox"
require_relative "engine/github_issue_onebox"
require_relative "engine/github_blob_onebox"
require_relative "engine/github_commit_onebox"
require_relative "engine/github_folder_onebox"
require_relative "engine/github_gist_onebox"
require_relative "engine/github_pull_request_onebox"
require_relative "engine/google_calendar_onebox"
require_relative "engine/google_docs_onebox"
require_relative "engine/google_maps_onebox"
require_relative "engine/google_play_app_onebox"
require_relative "engine/image_onebox"
require_relative "engine/video_onebox"
require_relative "engine/audio_onebox"
require_relative "engine/threads_status_onebox"
require_relative "engine/stack_exchange_onebox"
require_relative "engine/twitter_status_onebox"
require_relative "engine/wikimedia_onebox"
require_relative "engine/wikipedia_onebox"
require_relative "engine/youtube_onebox"
require_relative "engine/youku_onebox"
require_relative "engine/allowlisted_generic_onebox"
require_relative "engine/pubmed_onebox"
require_relative "engine/sound_cloud_onebox"
require_relative "engine/imgur_onebox"
require_relative "engine/pastebin_onebox"
require_relative "engine/slides_onebox"
require_relative "engine/xkcd_onebox"
require_relative "engine/animated_image_onebox"
require_relative "engine/gfycat_onebox"
require_relative "engine/typeform_onebox"
require_relative "engine/vimeo_onebox"
require_relative "engine/steam_store_onebox"
require_relative "engine/sketch_fab_onebox"
require_relative "engine/audioboom_onebox"
require_relative "engine/replit_onebox"
require_relative "engine/asciinema_onebox"
require_relative "engine/mixcloud_onebox"
require_relative "engine/band_camp_onebox"
require_relative "engine/coub_onebox"
require_relative "engine/flickr_onebox"
require_relative "engine/flickr_shortened_onebox"
require_relative "engine/five_hundred_px_onebox"
require_relative "engine/pdf_onebox"
require_relative "engine/twitch_clips_onebox"
require_relative "engine/twitch_stream_onebox"
require_relative "engine/twitch_video_onebox"
require_relative "engine/trello_onebox"
require_relative "engine/cloud_app_onebox"
require_relative "engine/wistia_onebox"
require_relative "engine/simplecast_onebox"
require_relative "engine/instagram_onebox"
require_relative "engine/gitlab_blob_onebox"
require_relative "engine/google_photos_onebox"
require_relative "engine/kaltura_onebox"
require_relative "engine/reddit_media_onebox"
require_relative "engine/google_drive_onebox"
require_relative "engine/facebook_media_onebox"
require_relative "engine/hackernews_onebox"
require_relative "engine/motoko_onebox"
require_relative "engine/tiktok_onebox"
require_relative "engine/loom_onebox"