# frozen_string_literal: true

module Alchemy
  class PagesController < Alchemy::BaseController
    SHOW_PAGE_PARAMS_KEYS = [
      "action",
      "controller",
      "urlname",
      "locale",
    ]

    include OnPageLayout::CallbacksRunner

    # Redirecting concerns. Order is important here!
    include SiteRedirects
    include LocaleRedirects

    before_action :load_index_page, only: [:index]
    before_action :load_page, only: [:show]

    # Legacy page redirects need to run after the page was loaded and before we render 404.
    include LegacyPageRedirects

    # From here on, we need a +@page+ to work with!
    before_action :page_not_found!, if: -> { @page.blank? }, only: [:index, :show]

    # Page redirects need to run after the page was loaded and we're sure to have a +@page+ set.
    include PageRedirects

    # We only need to set the +@root_page+ if we are sure that no more redirects happen.
    before_action :set_root_page, only: [:index, :show]

    # Page layout callbacks need to run after all other callbacks
    before_action :run_on_page_layout_callbacks,
      if: :run_on_page_layout_callbacks?,
      only: [:index, :show]

    before_action :set_expiration_headers, only: [:index, :show], if: -> { @page }

    rescue_from ActionController::UnknownFormat, with: :page_not_found!

    # == The index action gets invoked if one requests '/' or '/:locale'
    #
    # If the locale is the default locale, then it redirects to '/' without the locale.
    #
    # Loads the current language root page. The current language is either loaded via :locale
    # parameter or, if that's missing, the default language is used.
    #
    # If this page is not published then it redirects to the first published descendant it finds.
    #
    # If no public page can be found it renders a 404 error.
    #
    def index
      show
    end

    # == The show action gets invoked if one requests '/:urlname' or '/:locale/:urlname'
    #
    # If the locale is the default locale, then it redirects to '/' without the locale.
    #
    # Loads the page via it's urlname. If more than one language is published the
    # current language is either loaded via :locale parameter or, if that's missing,
    # the page language is used and a redirect to the page with prefixed locale happens.
    #
    # If the requested page is not published then it redirects to the first published
    # descendant it finds. If no public page can be found it renders a 404 error.
    #
    def show
      if redirect_url.present?
        redirect_permanently_to redirect_url
      else
        authorize! :show, @page
        render_page if render_fresh_page?
      end
    end

    # Renders a search engine compatible xml sitemap.
    def sitemap
      @pages = Page.sitemap
      respond_to do |format|
        format.xml { render layout: "alchemy/sitemap" }
      end
    end

    private

    # == Loads index page
    #
    # Loads the current public language root page.
    #
    # If the root page is not public it redirects to the first published child.
    # This can be configured via +redirect_to_public_child+ [default: true]
    #
    # If no index page and no admin users are present we show the "Welcome to Alchemy" page.
    #
    def load_index_page
      @page ||= Language.current_root_page
      render template: "alchemy/welcome", layout: false if signup_required?
    end

    # == Loads page by urlname
    #
    # If a locale is specified in the request parameters,
    # scope pages to it to make sure we can raise a 404 if the urlname
    # is not available in that language.
    #
    # @return Alchemy::Page
    # @return NilClass
    #
    def load_page
      page_not_found! unless Language.current

      @page ||= Language.current.pages.contentpages.find_by(
        urlname: params[:urlname],
        language_code: params[:locale] || Language.current.code,
      )
    end

    # Redirects to given url with 301 status
    def redirect_permanently_to(url)
      redirect_to url, status: :moved_permanently
    end

    # Returns url parameters that are not internal show page params.
    #
    # * action
    # * controller
    # * urlname
    # * locale
    #
    def additional_params
      params.to_unsafe_hash.delete_if do |key, _|
        SHOW_PAGE_PARAMS_KEYS.include?(key)
      end
    end

    # == Renders the page :show template
    #
    # Handles html and rss requests (for pages containing a feed)
    #
    # Omits the layout, if the request is a XHR request.
    #
    def render_page
      respond_to do |format|
        format.html do
          render action: :show, layout: !request.xhr?
        end

        format.rss do
          if @page.contains_feed?
            render action: :show, layout: false, handlers: [:builder]
          else
            render xml: { error: "Not found" }, status: 404
          end
        end
      end
    end

    def set_expiration_headers
      if must_not_cache?
        expires_now
      else
        expires_in @page.expiration_time, public: !@page.restricted, must_revalidate: true
      end
    end

    def set_root_page
      @root_page ||= Language.current_root_page
    end

    def signup_required?
      if Alchemy.user_class.respond_to?(:admins)
        Alchemy.user_class.admins.empty? && @page.nil?
      end
    end

    # Returns the etag used for response headers.
    #
    # If a user is logged in, we append theirs etag to prevent caching of user related content.
    #
    # IMPORTANT:
    #
    # If your user does not have a +cache_key+ method (i.e. it's not an ActiveRecord model),
    # you have to ensure to implement it and return a unique identifier for that particular user.
    # Otherwise all users will see the same cached page, regardless of user's state.
    #
    def page_etag
      @page.cache_key + current_alchemy_user.try(:cache_key).to_s
    end

    # We only render the page if either the cache is disabled for this page
    # or the cache is stale, because it's been republished by the user.
    #
    def render_fresh_page?
      must_not_cache? || stale?(etag: page_etag,
                                last_modified: @page.published_at,
                                public: !@page.restricted,
                                template: "pages/show")
    end

    # don't cache pages if we have flash message to display or the page has caching disabled
    def must_not_cache?
      flash.present? || !@page.cache_page?
    end

    def page_not_found!
      not_found_error!("Alchemy::Page not found \"#{request.fullpath}\"")
    end
  end
end
