app/controllers/lookbook/previews_controller.rb



module Lookbook
  class PreviewsController < ApplicationController
    def self.controller_path
      "lookbook/previews"
    end

    before_action :lookup_entities, only: [:preview, :show]
    before_action :set_title

    def preview
      if @example
        set_params
        begin
          render html: render_examples(examples_data)
        rescue => exception
          render_in_layout "lookbook/error",
            layout: "lookbook/basic",
            error: prettify_error(exception),
            disable_header: true
        end
      else
        render_in_layout "not_found"
      end
    end

    def show
      if @example
        begin
          set_params
          @examples = examples_data
          @drawer_panels = drawer_panels.filter { |name, panel| panel[:show] }
          @preview_panels = preview_panels.filter { |name, panel| panel[:show] }
        rescue => exception
          render_in_layout "lookbook/error", error: prettify_error(exception)
        end
      else
        render_in_layout "not_found"
      end
    end

    private

    def lookup_entities
      @example = Lookbook.previews.find_example(params[:path])
      if @example
        @preview = @example.preview
        if params[:path] == @preview&.lookup_path
          redirect_to show_path "#{params[:path]}/#{@preview.default_example.name}"
        end
      else
        first_example = Lookbook.previews.find(params[:path])&.examples&.first
        redirect_to show_path(first_example.lookup_path) if first_example
      end
    end

    def set_title
      @title = @example.present? ? [@example&.label, @preview&.label].compact.join(" :: ") : "Not found"
    end

    def examples_data
      @examples_data ||= (@example.type == :group ? @example.examples : [@example]).map do |example|
        example_data(example)
      end
    end

    def example_data(example)
      render_args = @preview.render_args(example.name, params: preview_controller.params.permit!)
      has_template = render_args[:template] != "view_components/preview"
      {
        label: example.label,
        notes: example.notes,
        html: preview_controller.process(:render_example_to_string, @preview, example.name),
        source: has_template ? example.template_source(render_args[:template]) : example.method_source,
        source_lang: has_template ? example.template_lang(render_args[:template]) : example.source_lang,
        params: example.params
      }
    end

    def render_examples(examples)
      preview_controller.process(:render_in_layout_to_string, "layouts/lookbook/preview", {examples: examples}, @preview.layout)
    end

    def set_params
      # cast known params to type
      @example.params.each do |param|
        if preview_controller.params.key?(param[:name])
          preview_controller.params[param[:name]] = Lookbook::Params.cast(preview_controller.params[param[:name]], param[:type])
        end
      end

      # set display params
      preview_controller.params.merge!({
        lookbook: {
          display: @example.display_params
        }
      })
    end

    def preview_panels
      {
        preview: {
          label: "Preview",
          template: "lookbook/previews/panels/preview",
          srcdoc: Lookbook.config.preview_srcdoc ? render_examples(examples_data).gsub("\"", "&quot;") : nil,
          hotkey: "v",
          show: true,
          disabled: false,
          copy: false
        },
        output: {
          label: "HTML",
          template: "lookbook/previews/panels/output",
          hotkey: "o",
          show: true,
          disabled: false,
          copy: true
        }
      }
    end

    def drawer_panels
      {
        source: {
          label: "Source",
          template: "lookbook/previews/panels/source",
          hotkey: "s",
          show: true,
          disabled: false,
          copy: true
        },
        notes: {
          label: "Notes",
          template: "lookbook/previews/panels/notes",
          hotkey: "n",
          show: true,
          disabled: @examples.filter { |e| e[:notes].present? }.none?
        },
        params: {
          label: "Params",
          template: "lookbook/previews/panels/params",
          hotkey: "p",
          show: true,
          disabled: @example.type == :group || @example.params.none?
        }
      }
    end

    def preview_controller
      return @preview_controller if @preview_controller
      controller = Lookbook::Engine.preview_controller.new
      controller.request = request
      controller.response = response
      @preview_controller ||= controller
    end

    def render_in_layout(path, layout: nil, **locals)
      render path, layout: layout.presence || (params[:lookbook_embed] ? "lookbook/basic" : "lookbook/application"), locals: locals
    end

    def prettify_error(exception)
      error_params = if exception.is_a?(ViewComponent::PreviewTemplateError)
        {
          file_path: @preview&.full_path,
          line_number: 0,
          source_code: @example&.source
        }
      elsif exception.is_a?(ActionView::Template::Error) & exception.message.include?("implements a reserved method")
        message_parts = exception.message.split("\n").first.split
        component_class = message_parts.first.constantize
        naughty_method = message_parts.last.delete("#").delete("`").delete(".")
        method = component_class.instance_method(naughty_method.to_sym)
        if method
          {
            file_path: method.source_location.first,
            line_number: method.source_location[1]
          }
        end
      end
      Lookbook::Error.new(exception, **(error_params || {}))
    end
  end
end