# frozen_string_literal: true

require "pathname"
require "zeitwerk"
require_relative "hanami/constants"

# A complete web framework for Ruby
#
# @since 0.1.0
#
# @see http://hanamirb.org
module Hanami
  @_mutex = Mutex.new
  @_bundled = {}

  # @api private
  # @since 2.0.0
  def self.loader
    @loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
      loader.inflector.inflect "db" => "DB"
      loader.inflector.inflect "db_logging" => "DBLogging"
      loader.inflector.inflect "slice_configured_db_operation" => "SliceConfiguredDBOperation"
      loader.inflector.inflect "sql_adapter" => "SQLAdapter"
      loader.inflector.inflect "sql_logger" => "SQLLogger"
      loader.inflector.inflect "sql_formatter" => "SQLFormatter"

      gem_lib = loader.dirs.first
      loader.ignore(
        "#{gem_lib}/hanami/{constants,boot,errors,extensions/router/errors,prepare,rake_tasks,setup}.rb",
        # Ignore conditionally-loaded classes dependent on gems that may not be included in the
        # user's Gemfile
        "#{gem_lib}/hanami/config/{assets,router,views}.rb",
        "#{gem_lib}/hanami/slice/router.rb",
        "#{gem_lib}/hanami/slice/routing/resolver.rb",
        "#{gem_lib}/hanami/slice/routing/middleware/stack.rb",
        "#{gem_lib}/hanami/extensions/**/*"
      )

      unless Hanami.bundled?("hanami-router")
        loader.ignore("#{gem_lib}/hanami/routes.rb")
      end
    end
  end

  # Finds and loads the Hanami app file (`config/app.rb`).
  #
  # Raises an exception if the app file cannot be found.
  #
  # @return [app] the loaded app class
  #
  # @api public
  # @since 2.0.0
  def self.setup(raise_exception: true)
    return app if app?

    app_path = self.app_path

    if app_path
      prepare_load_path
      require(app_path.to_s)
      app
    elsif raise_exception
      raise(
        AppLoadError,
        "Could not locate your Hanami app file.\n\n" \
        "Your app file should be at `config/app.rb` in your project's root directory."
      )
    end
  end

  # Prepare the load path as early as possible (based on the default root inferred from the location
  # of `config/app.rb`), so `require` can work at the top of `config/app.rb`. This may be useful
  # when external classes are needed for configuring certain aspects of the app.
  #
  # @api private
  # @since 2.0.0
  private_class_method def self.prepare_load_path
    lib_path = app_path&.join("..", "..", LIB_DIR)

    if lib_path&.directory?
      path = lib_path.realpath.to_s
      $LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
    end

    lib_path
  end

  # Returns the Hamami app class.
  #
  # To ensure your Hanami app is loaded, run {.setup} (or `require "hanami/setup"`) first.
  #
  # @return [Hanami::App] the app class
  #
  # @raise [AppLoadError] if the app has not been loaded
  #
  # @see .setup
  #
  # @api public
  # @since 2.0.0
  def self.app
    @_mutex.synchronize do
      unless defined?(@_app)
        raise AppLoadError,
              "Hanami.app is not yet configured. " \
              "You may need to `require \"hanami/setup\"` to load your config/app.rb file."
      end

      @_app
    end
  end

  # Returns true if the Hanami app class has been loaded.
  #
  # @return [Boolean]
  #
  # @api public
  # @since 2.0.0
  def self.app?
    instance_variable_defined?(:@_app)
  end

  # @api private
  # @since 2.0.0
  def self.app=(klass)
    @_mutex.synchronize do
      if instance_variable_defined?(:@_app)
        raise AppLoadError, "Hanami.app is already configured."
      end

      @_app = klass unless klass.name.nil?
    end
  end

  # Finds and returns the absolute path for the Hanami app file (`config/app.rb`).
  #
  # Searches within the given directory, then searches upwards through parent directories until the
  # app file can be found.
  #
  # @param dir [String, Pathname] The directory from which to start searching. Defaults to the
  #   current directory.
  #
  # @return [Pathname, nil] the app file path, or nil if not found.
  #
  # @api public
  # @since 2.0.0
  def self.app_path(dir = Dir.pwd)
    dir = Pathname(dir).expand_path
    path = dir.join(APP_PATH)

    if path.file?
      path
    elsif !dir.root?
      app_path(dir.parent)
    end
  end

  # Returns the Hanami app environment as determined from the environment.
  #
  # Checks the following environment variables in order:
  #
  # - `HANAMI_ENV`
  # - `APP_ENV`
  # - `RACK_ENV`
  #
  # Defaults to `:development` if no environment variable is set.
  #
  # @example
  #   Hanami.env # => :development
  #
  # @return [Symbol] the environment name
  #
  # @api public
  # @since 2.0.0
  def self.env(e: ENV)
    (e["HANAMI_ENV"] || e["APP_ENV"] || e["RACK_ENV"] || :development).to_sym
  end

  # Returns true if {.env} matches any of the given names
  #
  # @example
  #   Hanami.env # => :development
  #   Hanami.env?(:development, :test) # => true
  #
  # @param names [Array<Symbol>] the environment names to check
  #
  # @return [Boolean]
  #
  # @api public
  # @since 2.0.0
  def self.env?(*names)
    names.map(&:to_sym).include?(env)
  end

  # Returns the app's logger.
  #
  # Direct global access to the logger via this method is not recommended. Instead, consider
  # accessing the logger via the app or slice container, in most cases as an dependency using the
  # `Deps` mixin.
  #
  # @example
  #   # app/my_component.rb
  #
  #   module MyApp
  #     class MyComponent
  #       include Deps["logger"]
  #
  #       def some_method
  #         logger.info("hello")
  #       end
  #     end
  #   end
  #
  # @return [Dry::Logger::Dispatcher]
  #
  # @api public
  # @since 1.0.0
  def self.logger
    app[:logger]
  end

  # Prepares the Hanami app.
  #
  # @see App::ClassMethods#prepare
  #
  # @api public
  # @since 2.0.0
  def self.prepare
    app.prepare
  end

  # Boots the Hanami app.
  #
  # @see App::ClassMethods#boot
  #
  # @api public
  # @since 2.0.0
  def self.boot
    app.boot
  end

  # Shuts down the Hanami app.
  #
  # @see App::ClassMethods#shutdown
  #
  # @api public
  # @since 2.0.0
  def self.shutdown
    app.shutdown
  end

  # @api private
  # @since 2.0.0
  def self.bundled?(gem_name)
    @_mutex.synchronize do
      @_bundled[gem_name] ||= begin
        gem(gem_name)
      rescue Gem::LoadError
        false
      end
    end
  end

  # Returns an array of bundler group names to be eagerly loaded by hanami-cli and other CLI
  # extensions.
  #
  # @api private
  # @since 2.0.0
  def self.bundler_groups
    [:plugins]
  end

  loader.setup

  require_relative "hanami/errors"
  require_relative "hanami/extensions"
end


def self.setup(raise_exception: true)
    return app if app?

    app_path = self.app_path

    if app_path
      prepare_load_path
      require(app_path.to_s)
      app
    elsif raise_exception
      raise(
        AppLoadError,
        "Could not locate your Hanami app file.\n\n" \
        "Your app file should be at `config/app.rb` in your project's root directory."
      )
    end
  end


def self.app
    @_mutex.synchronize do
      unless defined?(@_app)
        raise AppLoadError,
              "Hanami.app is not yet configured. " \
              "You may need to `require \"hanami/setup\"` to load your config/app.rb file."
      end

      @_app
    end
  end


# frozen_string_literal: true

require_relative "constants"

module Hanami
  # @api private
  class SliceRegistrar
    VALID_SLICE_NAME_RE = /^[a-z][a-z0-9_]*$/
    SLICE_DELIMITER = CONTAINER_KEY_DELIMITER

    attr_reader :parent, :slices
    private :parent, :slices

    def initialize(parent)
      @parent = parent
      @slices = {}
    end

    def register(name, slice_class = nil, &block)
      unless name.to_s =~ VALID_SLICE_NAME_RE
        raise ArgumentError, "slice name #{name.inspect} must be lowercase alphanumeric text and underscores only"
      end

      return unless filter_slice_names([name]).any?

      if slices.key?(name.to_sym)
        raise SliceLoadError, "Slice '#{name}' is already registered"
      end

      slice = slice_class || build_slice(name, &block)

      configure_slice(name, slice)

      slices[name.to_sym] = slice
    end

    def [](name)
      slices.fetch(name) do
        raise SliceLoadError, "Slice '#{name}' not found"
      end
    end

    def freeze
      slices.freeze
      super
    end

    def load_slices
      slice_configs = root.join(CONFIG_DIR, SLICES_DIR).glob("*#{RB_EXT}")
        .map { _1.basename(RB_EXT) }

      slice_dirs = root.join(SLICES_DIR).glob("*")
        .select(&:directory?)
        .map { _1.basename }

      (slice_dirs + slice_configs).uniq.sort
        .then { filter_slice_names(_1) }
        .each(&method(:load_slice))

      self
    end

    def each(&block)
      slices.each_value(&block)
    end

    def keys
      slices.keys
    end

    def to_a
      slices.values
    end

    def with_nested
      to_a.flat_map { |slice|
        # Return nested slices first so that their more specific namespaces may be picked up first
        # by SliceConfigurable#slice_for
        slice.slices.with_nested + [slice]
      }
    end

    private

    def root
      parent.root
    end

    def inflector
      parent.inflector
    end

    def parent_slice_namespace
      parent.eql?(parent.app) ? Object : parent.namespace
    end

    # Runs when a slice file has been found inside the app at `config/slices/[slice_name].rb`,
    # or when a slice directory exists at `slices/[slice_name]`.
    #
    # If a slice definition file is found by `find_slice_require_path`, then `load_slice` will
    # require the file before registering the slice class.
    #
    # If a slice class is not found, registering the slice will generate the slice class.
    def load_slice(slice_name)
      slice_require_path = find_slice_require_path(slice_name)
      require slice_require_path if slice_require_path

      slice_class =
        begin
          inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice")
        rescue NameError => exception
          raise exception unless exception.name.to_s == inflector.camelize(slice_name) || exception.name.to_s == :Slice
        end

      register(slice_name, slice_class)
    end

    # Finds the path to the slice's definition file, if it exists, in the following order:
    #
    # 1. `config/slices/[slice_name].rb`
    # 2. `slices/[parent_slice_name]/config/[slice_name].rb` (unless parent is the app)
    # 3. `slices/[slice_name]/config/slice.rb`
    #
    # If the slice is nested under another slice then it will look in the following order:
    #
    # 1. `config/slices/[parent_slice_name]/[slice_name].rb`
    # 2. `slices/[parent_slice_name]/config/[slice_name].rb`
    # 3. `slices/[parent_slice_name]/[slice_name]/config/slice.rb`
    def find_slice_require_path(slice_name)
      app_slice_file_path = [slice_name]
      app_slice_file_path.prepend(parent.slice_name) unless parent.eql?(parent.app)
      ancestors = [
        parent.app.root.join(CONFIG_DIR, SLICES_DIR, app_slice_file_path.join(File::SEPARATOR)),
        parent.root.join(CONFIG_DIR, SLICES_DIR, slice_name),
        root.join(SLICES_DIR, slice_name, CONFIG_DIR, "slice")
      ]

      ancestors
        .uniq
        .find { _1.sub_ext(RB_EXT).file? }
        &.to_s
    end

    def build_slice(slice_name, &block)
      slice_module =
        begin
          inflector.constantize(slice_module_name(slice_name))
        rescue NameError
          parent_slice_namespace.const_set(inflector.camelize(slice_name), Module.new)
        end

      slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block))
    end

    def slice_module_name(slice_name)
      inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
    end

    def configure_slice(slice_name, slice)
      slice.instance_variable_set(:@parent, parent)

      # Slices require a root, so provide a sensible default based on the slice's parent
      slice.config.root ||= root.join(SLICES_DIR, slice_name.to_s)

      slice.config.slices = child_slice_names(slice_name, parent.config.slices)
    end

    # Returns a filtered array of slice names based on the parent's `config.slices`
    #
    # This works with both singular slice names (e.g. `"admin"`) as well as dot-delimited nested
    # slice names (e.g. `"admin.shop"`).
    #
    # It will consider only the base names of the slices (since in this case, a parent slice must be
    # loaded in order for its children to be loaded).
    #
    # @example
    #   parent.config.slices # => ["admin.shop"]
    #   filter_slice_names(["admin", "main"]) # => ["admin"]
    #
    #   parent.config.slices # => ["admin"]
    #   filter_slice_names(["admin", "main"]) # => ["admin"]
    def filter_slice_names(slice_names)
      slice_names = slice_names.map(&:to_s)

      if parent.config.slices
        slice_names & parent.config.slices.map { base_slice_name(_1) }
      else
        slice_names
      end
    end

    # Returns the base slice name from an (optionally) dot-delimited nested slice name.
    #
    # @example
    #   base_slice_name("admin") # => "admin"
    #   base_slice_name("admin.users") # => "admin"
    def base_slice_name(name)
      name.to_s.split(SLICE_DELIMITER).first
    end

    # Returns an array of slice names specific to the given child slice.
    #
    # @example
    #   child_local_slice_names("admin", ["main", "admin.users"]) # => ["users"]
    def child_slice_names(parent_slice_name, slice_names)
      slice_names
        &.select { |name|
          name.include?(SLICE_DELIMITER) && name.split(SLICE_DELIMITER)[0] == parent_slice_name.to_s
        }
        &.map { |name|
          name.split(SLICE_DELIMITER)[1..].join(SLICE_DELIMITER) # which version of Ruby supports this?
        }
    end
  end
end

register

name.to_s =~ VALID_SLICE_NAME_RE
!(name.to_s =~ VALID_SLICE_NAME_RE)


def load_slices
      slice_configs = root.join(CONFIG_DIR, SLICES_DIR).glob("*#{RB_EXT}")
        .map { _1.basename(RB_EXT) }

      slice_dirs = root.join(SLICES_DIR).glob("*")
        .select(&:directory?)
        .map { _1.basename }

      (slice_dirs + slice_configs).uniq.sort
        .then { filter_slice_names(_1) }
        .each(&method(:load_slice))

      self
    end


def load_slice(slice_name)
      slice_require_path = find_slice_require_path(slice_name)
      require slice_require_path if slice_require_path

      slice_class =
        begin
          inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice")
        rescue NameError => exception
          raise exception unless exception.name.to_s == inflector.camelize(slice_name) || exception.name.to_s == :Slice
        end

      register(slice_name, slice_class)
    end

    def find_slice_require_path(slice_name)
      app_slice_file_path = [slice_name]
      app_slice_file_path.prepend(parent.slice_name) unless parent.eql?(parent.app)
      ancestors = [
        parent.app.root.join(CONFIG_DIR, SLICES_DIR, app_slice_file_path.join(File::SEPARATOR)),
        parent.root.join(CONFIG_DIR, SLICES_DIR, slice_name),
        root.join(SLICES_DIR, slice_name, CONFIG_DIR, "slice")
      ]

      ancestors
        .uniq
        .find { _1.sub_ext(RB_EXT).file? }
        &.to_s
    end

    def slice_module_name(slice_name)
      inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
    end

register

    def register(name, slice_class = nil, &block)
      unless name.to_s =~ VALID_SLICE_NAME_RE
        raise ArgumentError, "slice name #{name.inspect} must be lowercase alphanumeric text and underscores only"
      end

      return unless filter_slice_names([name]).any?

      if slices.key?(name.to_sym)
        raise SliceLoadError, "Slice '#{name}' is already registered"
      end

      slice = slice_class || build_slice(name, &block)

      configure_slice(name, slice)

      slices[name.to_sym] = slice
    end


def find_slice_require_path(slice_name)
      app_slice_file_path = [slice_name]
      app_slice_file_path.prepend(parent.slice_name) unless parent.eql?(parent.app)
      ancestors = [
        parent.app.root.join(CONFIG_DIR, SLICES_DIR, app_slice_file_path.join(File::SEPARATOR)),
        parent.root.join(CONFIG_DIR, SLICES_DIR, slice_name),
        root.join(SLICES_DIR, slice_name, CONFIG_DIR, "slice")
      ]

      ancestors
        .uniq
        .find { _1.sub_ext(RB_EXT).file? }
        &.to_s
    end


# frozen_string_literal: true

require_relative "constants"
require_relative "errors"
require_relative "slice/router"

module Hanami
  # App routes
  #
  # Users are expected to inherit from this class to define their app
  # routes.
  #
  # @example
  #   # config/routes.rb
  #   # frozen_string_literal: true
  #
  #   require "hanami/routes"
  #
  #   module MyApp
  #     class Routes < Hanami::Routes
  #       root to: "home.show"
  #     end
  #   end
  #
  #   See {Hanami::Slice::Router} for the syntax allowed within the `define` block.
  #
  # @see Hanami::Slice::Router
  # @since 2.0.0
  class Routes
    # Error raised when no action could be found in an app or slice container for the key given in a
    # routes file.
    #
    # @api public
    # @since 2.0.0
    class MissingActionError < Hanami::Error
      # @api private
      def initialize(action_key, slice)
        action_path = action_key.gsub(CONTAINER_KEY_DELIMITER, PATH_DELIMITER)
        action_constant = slice.inflector.camelize(
          slice.inflector.underscore(slice.namespace.to_s) + PATH_DELIMITER + action_path
        )
        action_file_path = slice.relative_source_path.join(action_path).to_s.concat(RB_EXT)

        super(<<~MSG)
          Could not find action with key #{action_key.inspect} in #{slice}

          To fix this, define the action class #{action_constant} in #{action_file_path}
        MSG
      end
    end

    # Error raised when a given routes endpoint does not implement the `#call` interface required
    # for Rack.
    #
    # @api public
    # @since 2.0.0
    class NotCallableEndpointError < Hanami::Error
      # @api private
      def initialize(endpoint)
        super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
      end
    end

    # Wrapper class for the (otherwise opaque) proc returned from {.routes}, adding an `#empty?`
    # method that returns true if no routes were defined.
    #
    # This is useful when needing to determine behaviour based on the presence of user-defined
    # routes, such as determining whether to show the Hanami welcome page in {Slice#load_router}.
    #
    # @api private
    # @since 2.1.0
    class RoutesProc < DelegateClass(Proc)
      # @api private
      # @since 2.1.0
      def self.empty
        new(proc {}, empty: true)
      end

      # @api private
      # @since 2.1.0
      def initialize(proc, empty: false)
        @empty = empty
        super(proc)
      end

      # @api private
      # @since 2.1.0
      def empty?
        !!@empty
      end
    end

    # @api private
    def self.routes
      @routes ||= build_routes
    end

    class << self
      # @api private
      def build_routes(definitions = self.definitions)
        return RoutesProc.empty if definitions.empty?

        routes_proc = proc do
          definitions.each do |(name, args, kwargs, block)|
            if block
              public_send(name, *args, **kwargs, &block)
            else
              public_send(name, *args, **kwargs)
            end
          end
        end

        RoutesProc.new(routes_proc)
      end

      # @api private
      def definitions
        @definitions ||= []
      end

      private

      # @api private
      def supported_methods
        @supported_methods ||= Slice::Router.public_instance_methods
      end

      # @api private
      def respond_to_missing?(name, include_private = false)
        supported_methods.include?(name) || super
      end

      # Capture all method calls that are supported by the router DSL
      # so that it can be evaluated lazily during configuration/boot
      # process
      #
      # @api private
      def method_missing(name, *args, **kwargs, &block)
        return super unless respond_to?(name)

        definitions << [name, args, kwargs, block]
        self
      end
    end
  end
end


def build_routes(definitions = self.definitions)
        return RoutesProc.empty if definitions.empty?

        routes_proc = proc do
          definitions.each do |(name, args, kwargs, block)|
            if block
              public_send(name, *args, **kwargs, &block)
            else
              public_send(name, *args, **kwargs)
            end
          end
        end

        RoutesProc.new(routes_proc)
      end


# frozen_string_literal: true

require "dry/core/constants"
require "dry/configurable"
require_relative "errors"

module Hanami
  # Provides user-defined settings for an Hanami app or slice.
  #
  # Define your own settings by inheriting from this class in `config/settings.rb` within an app or
  # slice. Your settings will be loaded from matching ENV vars (with upper-cased names) and be
  # registered as a component as part of the Hanami app {Hanami::Slice::ClassMethods#prepare
  # prepare} step.
  #
  # The settings instance is registered in your app and slice containers as a `"settings"`
  # component. You can use the `Deps` mixin to inject this dependency and make settings available to
  # your other components as required.
  #
  # @example
  #   # config/settings.rb
  #   # frozen_string_literal: true
  #
  #   module MyApp
  #     class Settings < Hanami::Settings
  #       Secret = Types::String.constrained(min_size: 20)
  #
  #       setting :database_url, constructor: Types::String
  #       setting :session_secret, constructor: Secret
  #       setting :some_flag, default: false, constructor: Types::Params::Bool
  #     end
  #   end
  #
  # Settings are defined with [dry-configurable][dry-c]'s `setting` method. You may likely want to
  # provide `default:` and `constructor:` options for your settings.
  #
  # If you have [dry-types][dry-t] bundled, then a nested `Types` module will be available for type
  # checking your setting values. Pass type objects to the setting `constructor:` options to ensure
  # their values meet your type expectations. You can use dry-types' default type objects or define
  # your own.
  #
  # When the settings are initialized, all type errors will be collected and presented together for
  # correction. Settings are loaded early, as part of the Hanami app's
  # {Hanami::Slice::ClassMethods#prepare prepare} step, to ensure that the app boots only when valid
  # settings are present.
  #
  # Setting values are loaded from a configurable store, which defaults to
  # {Hanami::Settings::EnvStore}, which fetches the values from equivalent upper-cased keys in
  # `ENV`. You can configure an alternative store via {Hanami::Config#settings_store}. Setting stores
  # must implement a `#fetch` method with the same signature as `Hash#fetch`.
  #
  # [dry-c]: https://dry-rb.org/gems/dry-configurable/
  # [dry-t]: https://dry-rb.org/gems/dry-types/
  #
  # @see Hanami::Settings::DotenvStore
  #
  # @api public
  # @since 2.0.0
  class Settings
    # Error raised when setting values do not meet their type expectations.
    #
    # Its message collects all the individual errors that can be raised for each setting.
    #
    # @api public
    # @since 2.0.0
    class InvalidSettingsError < Hanami::Error
      # @api private
      def initialize(errors)
        super()
        @errors = errors
      end

      # Returns the exception's message.
      #
      # @return [String]
      #
      # @api public
      # @since 2.0.0
      def to_s
        <<~STR.strip
          Could not initialize settings. The following settings were invalid:

          #{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
        STR
      end
    end

    class << self
      # Defines a nested `Types` constant in `Settings` subclasses if dry-types is bundled.
      #
      # @see https://dry-rb.org/gems/dry-types
      #
      # @api private
      def inherited(subclass)
        super

        if Hanami.bundled?("dry-types")
          require "dry/types"
          subclass.const_set(:Types, Dry.Types())
        end
      end

      # Loads the settings for a slice.
      #
      # Returns nil if no settings class is defined.
      #
      # @return [Settings, nil]
      #
      # @api private
      def load_for_slice(slice)
        return unless settings_defined?(slice)

        require_slice_settings(slice) unless slice_settings_class?(slice)

        slice_settings_class(slice).new(slice.config.settings_store)
      end

      private

      # Returns true if settings are defined for the slice.
      #
      # Settings are considered defined if a `Settings` class is already defined in the slice
      # namespace, or a `config/settings.rb` exists under the slice root.
      def settings_defined?(slice)
        slice.namespace.const_defined?(SETTINGS_CLASS_NAME) ||
          slice.root.join("#{SETTINGS_PATH}#{RB_EXT}").file?
      end

      def slice_settings_class?(slice)
        slice.namespace.const_defined?(SETTINGS_CLASS_NAME)
      end

      def slice_settings_class(slice)
        slice.namespace.const_get(SETTINGS_CLASS_NAME)
      end

      def require_slice_settings(slice)
        require "hanami/settings"

        slice_settings_require_path = File.join(slice.root, SETTINGS_PATH)

        begin
          require slice_settings_require_path
        rescue LoadError => exception
          raise exception unless exception.path == slice_settings_require_path
        end
      end
    end

    # @api private
    Undefined = Dry::Core::Constants::Undefined

    # @api private
    EMPTY_STORE = Dry::Core::Constants::EMPTY_HASH

    include Dry::Configurable

    # @api private
    def initialize(store = EMPTY_STORE)
      errors = config._settings.map(&:name).each_with_object({}) do |name, errs|
        value = store.fetch(name, Undefined)

        if value.eql?(Undefined)
          # When a key is missing entirely from the store, _read_ its value from the config instead.
          # This ensures its setting constructor runs (with a `nil` argument given) and raises any
          # necessary errors.
          public_send(name)
        else
          public_send("#{name}=", value)
        end
      rescue => exception # rubocop:disable Style/RescueStandardError
        errs[name] = exception
      end

      raise InvalidSettingsError, errors if errors.any?

      config.finalize!
    end

    # Returns a string containing a human-readable representation of the settings.
    #
    # This includes setting names only, not any values, to ensure that sensitive values do not
    # inadvertently leak.
    #
    # Use {#inspect_values} to inspect settings with their values.
    #
    # @example
    #   settings.inspect
    #   # => #<MyApp::Settings [database_url, session_secret, some_flag]>
    #
    # @return [String]
    #
    # @see #inspect_values
    #
    # @api public
    # @since 2.0.0
    def inspect
      "#<#{self.class} [#{config._settings.map(&:name).join(", ")}]>"
    end

    # rubocop:disable Layout/LineLength

    # Returns a string containing a human-readable representation of the settings and their values.
    #
    # @example
    #   settings.inspect_values
    #   # => #<MyApp::Settings database_url="postgres://localhost/my_db", session_secret="xxx", some_flag=true]>
    #
    # @return [String]
    #
    # @see #inspect
    #
    # @api public
    # @since 2.0.0
    def inspect_values
      "#<#{self.class} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
    end

    # rubocop:enable Layout/LineLength

    private

    def method_missing(name, *args, &block)
      if config.respond_to?(name)
        config.send(name, *args, &block)
      else
        super
      end
    end

    def respond_to_missing?(name, _include_all = false)
      config.respond_to?(name) || super
    end
  end
end


# frozen_string_literal: true

require "uri"
require "pathname"
require "dry/configurable"
require "dry/inflector"

require_relative "constants"
require_relative "universal_logger"

module Hanami
  # Hanami app config
  #
  # @since 2.0.0
  class Config
    include Dry::Configurable

    # @!attribute [rw] root
    #   Sets the root for the app or slice.
    #
    #   For the app, this defaults to `Dir.pwd`. For slices detected in `slices/` `config/slices/`,
    #   this defaults to `slices/[slice_name]/`.
    #
    #   Accepts a string path and will return a `Pathname`.
    #
    #   @return [Pathname]
    #
    #   @api public
    #   @since 2.0.0
    setting :root, constructor: ->(path) { Pathname(path) if path }

    # @!attribute [rw] inflector
    #   Sets the app's inflector.
    #
    #   This expects a `Dry::Inflector` (or compatible) inflector instance.
    #
    #   To configure custom inflection rules without having to assign a whole inflector, see
    #   {#inflections}.
    #
    #   @return [Dry::Inflector]
    #
    #   @see #inflections
    #
    #   @api public
    #   @since 2.0.0
    setting :inflector, default: Dry::Inflector.new

    # @!attribute [rw] settings_store
    #   Sets the store used to retrieve {Hanami::Settings} values.
    #
    #   Defaults to an instance of {Hanami::Settings::EnvStore}.
    #
    #   @return [#fetch]
    #
    #   @see Hanami::Settings
    #   @see Hanami::Settings::EnvStore#fetch
    #
    #   @api public
    #   @since 2.0.0
    setting :settings_store, default: Hanami::Settings::EnvStore.new

    # @!attribute [rw] slices
    #   Sets the slices to load when the app is preared or booted.
    #
    #   Defaults to `nil`, which will load all slices. Set this to an array of slice names to load
    #   only those slices.
    #
    #   This attribute is also populated from the `HANAMI_SLICES` environment variable.
    #
    #   @example
    #     config.slices = ["admin", "search"]
    #
    #   @example
    #     ENV["HANAMI_SLICES"] # => "admin,search"
    #     config.slices # => ["admin", "search"]
    #
    #   @return [Array<String>, nil]
    #
    #   @api public
    #   @since 2.0.0
    setting :slices

    # @!attribute [rw] shared_app_component_keys
    #   Sets the keys for the components to be imported from the app into all other slices.
    #
    #   You should append items to this array, since the default shared components are essential for
    #   slices to operate within the app.
    #
    #   @example
    #     config.shared_app_component_keys += ["shared_component_a", "shared_component_b"]
    #
    #   @return [Array<String>]
    #
    #   @api public
    #   @since 2.0.0
    setting :shared_app_component_keys, default: %w[
      inflector
      logger
      notifications
      rack.monitor
      routes
      settings
    ]

    # @!attribute [rw] no_auto_register_paths
    #   Sets the paths to skip from container auto-registration.
    #
    #   Paths are relative to `/app` or a slice root.
    #
    #   Defaults to `["db", "entities", "relations", "structs"]`.
    #
    #   @return [Array<String>] array of relative paths
    #
    #   @api public
    #   @since 2.0.0
    setting :no_auto_register_paths, default: [
      "db",
      "entities",
      "relations",
      "structs"
    ]

    # @!attribute [rw] base_url
    #   Sets the base URL for app's web server.
    #
    #   This is passed to the {Slice::ClassMethods#router router} and used for generating links.
    #
    #   Defaults to `"http://0.0.0.0:2300"`. String values passed are turned into `URI` instances.
    #
    #   @return [URI]
    #
    #   @see Slice::ClassMethods#router
    #
    #   @api public
    #   @since 2.0.0
    setting :base_url, default: "http://0.0.0.0:2300", constructor: ->(url) { URI(url) }

    # @!attribute [rw] render_errors
    #   Sets whether to catch exceptions and render error pages.
    #
    #   For HTML responses, these error pages are in `public/{404,500}.html`.
    #
    #   Defaults to `true` in production mode, `false` in all others.
    #
    #   @return [Boolean]
    #
    #   @api public
    #   @since 2.1.0
    setting :render_errors, default: false

    # @!attribute [rw] render_detailed_errors
    #   Sets whether to catch exceptions and render detailed, interactive error pages.
    #
    #   Requires the hanami-webconsole gem to be available.
    #
    #   Defaults to `false` in production mode, `true` in all others.
    #
    #   @return [Boolean]
    #
    #   @api public
    #   @since 2.1.0
    setting :render_detailed_errors, default: false

    # @!attribute [rw] render_error_responses
    #   Sets a mapping of exception class names (as strings) to symbolic response status codes used
    #   for rendering error responses.
    #
    #   The response status codes will be passed to `Rack::Utils.status_code`.
    #
    #   In ordinary usage, you should not replace this hash. Instead, add keys and values for the
    #   errors you want handled.
    #
    #   @example
    #     config.render_error_responses
    #     # => {"Hanami::Router::NotFoundError" => :not_found}
    #
    #     config.render_error_responses["MyNotFoundError"] = :not_found
    #
    #   @return [Hash{String => Symbol}]
    #
    #   @see #render_errors
    #
    #   @api public
    #   @since 2.1.0
    setting :render_error_responses, default: Hash.new(:internal_server_error).merge!(
      "Hanami::Router::NotAllowedError" => :not_found,
      "Hanami::Router::NotFoundError" => :not_found
    )

    # @!attribute [rw] console
    #   Returns the app's console config
    #
    #   @example
    #     config.console.engine # => :irb
    #
    #   @return [Hanami::Config::Console]
    #
    #   @api public
    #   @since 2.3.0
    setting :console, default: Hanami::Config::Console.new

    # Returns the app or slice's {Hanami::SliceName slice_name}.
    #
    # This is useful for default config values that depend on this name.
    #
    # @return [Hanami::SliceName]
    #
    # @api private
    # @since 2.0.0
    attr_reader :app_name

    # Returns the app's environment.
    #
    # @example
    #   config.env # => :development
    #
    # @return [Symbol]
    #
    # @see #environment
    #
    # @api private
    # @since 2.0.0
    attr_reader :env

    # Returns the app's actions config, or a null config if hanami-action is not bundled.
    #
    # @example When hanami-action is bundled
    #   config.actions.default_request_format # => :html
    #
    # @example When hanami-action is not bundled
    #   config.actions.default_request_format # => NoMethodError
    #
    # @return [Hanami::Config::Actions, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.0.0
    attr_reader :actions

    # Returns the app's db config, or a null config if hanami-db is not bundled.
    #
    # @example When hanami-db is bundled
    #   config.db.import_from_parent # => false
    #
    # @example When hanami-db is not bundle
    #   config.db.import_from_parent # => NoMethodError
    #
    # @return [Hanami::Config::DB, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.2.0
    attr_reader :db

    # Returns the app's i18n config, or a null config if i18n is not bundled.
    #
    # @example When i18n is bundled
    #   config.i18n.default_locale # => :en
    #
    # @example When i18n is not bundled
    #   config.i18n.default_locale # => NoMethodError
    #
    # @return [Hanami::Config::I18n, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.2.0
    attr_reader :i18n

    # Returns the app's middleware stack, or nil if hanami-router is not bundled.
    #
    # Use this to configure middleware that should apply to all routes.
    #
    # @example
    #   config.middleware.use :body_parser, :json
    #   config.middleware.use MyCustomMiddleware
    #
    # @return [Hanami::Slice::Routing::Middleware::Stack, nil]
    #
    # @api public
    # @since 2.0.0
    attr_reader :middleware

    # @api private
    # @since 2.0.0
    alias_method :middleware_stack, :middleware

    # Returns the app's router config, or a null config if hanami-router is not bundled.
    #
    # @example When hanami-router is bundled
    #   config.router.resolver # => Hanami::Slice::Routing::Resolver
    #
    # @example When hanami-router is not bundled
    #   config.router.resolver # => NoMethodError
    #
    # @return [Hanami::Config::Router, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.0.0
    attr_reader :router

    # Returns the app's views config, or a null config if hanami-view is not bundled.
    #
    # @example When hanami-view is bundled
    #   config.views.paths # => [...]
    #
    # @example When hanami-view is not bundled
    #   config.views.paths # => NoMethodError
    #
    # @return [Hanami::Config::Views, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.1.0
    attr_reader :views

    # Returns the app's views config, or a null config if hanami-view is not bundled.
    #
    # @example When hanami-view is bundled
    #   config.views.paths # => [...]
    #
    # @example When hanami-view is not bundled
    #   config.views.paths # => NoMethodError
    #
    # @return [Hanami::Config::Assets, Hanami::Config::NullConfig]
    #
    # @api public
    # @since 2.1.0
    attr_reader :assets

    # @api private
    # rubocop:disable Metrics/AbcSize
    def initialize(app_name:, env:)
      @app_name = app_name
      @env = env

      # Apply default values that are only knowable at initialize-time (vs require-time)
      self.root = Dir.pwd
      self.render_errors = (env == :production)
      self.render_detailed_errors = (env == :development)
      load_from_env

      @actions = load_dependent_config("hanami-action") {
        require_relative "config/actions"
        Actions.new
      }

      @assets = load_dependent_config("hanami-assets") {
        require_relative "config/assets"
        Hanami::Config::Assets.new
      }

      @db = load_dependent_config("hanami-db") { DB.new }

      @i18n = load_dependent_config("i18n") {
        require_relative "config/i18n"
        I18n.new
      }

      @logger = Config::Logger.new(env: env, app_name: app_name)

      @middleware = load_dependent_config("hanami-router") {
        require_relative "slice/routing/middleware/stack"
        Slice::Routing::Middleware::Stack.new
      }

      @router = load_dependent_config("hanami-router") {
        require_relative "config/router"
        Router.new(self)
      }

      @views = load_dependent_config("hanami-view") {
        require_relative "config/views"
        Views.new
      }

      yield self if block_given?
    end
    # rubocop:enable Metrics/AbcSize

    # @api private
    def initialize_copy(source)
      super

      @app_name = app_name.dup

      @actions = source.actions.dup
      @assets = source.assets.dup
      @db = source.db.dup
      @i18n = source.i18n.dup
      @logger = source.logger.dup
      @middleware = source.middleware.dup
      @router = source.router.dup.tap do |router|
        router.instance_variable_set(:@base_config, self)
      end
      @views = source.views.dup
    end
    private :initialize_copy

    # Finalizes the config.
    #
    # This is called when the app or slice is prepared. After this, no further changes to config can
    # be made.
    #
    # @api private
    def finalize!
      # Finalize nested configs
      assets.finalize!
      actions.finalize!(self)
      views.finalize!
      logger.finalize!
      router.finalize!

      super
    end

    # Configures the app's custom inflections.
    #
    # You should call this one time only. Subsequent calls will override previously configured
    # inflections.
    #
    # @example
    #   config.inflections do |inflections|
    #     inflections.acronym "WNBA"
    #   end
    #
    # @see https://dry-rb.org/gems/dry-inflector
    #
    # @return [Dry::Inflector] the configured inflector
    #
    # @api public
    # @since 2.0.0
    def inflections(&block)
      self.inflector = Dry::Inflector.new(&block)
    end

    # Disabling this to permit distinct documentation for `#logger` vs `#logger=`
    #
    # rubocop:disable Style/TrivialAccessors

    # Returns the logger config.
    #
    # Use this to configure various options for the default `Dry::Logger::Dispatcher` logger instance.
    #
    # @example
    #   config.logger.level = :debug
    #
    # @return [Hanami::Config::Logger]
    #
    # @see Hanami::Config::Logger
    #
    # @api public
    # @since 2.0.0
    def logger
      @logger
    end

    # Sets the app's logger instance.
    #
    # This entirely replaces the default `Dry::Logger::Dispatcher` instance that would have been
    # configured via {#logger}.
    #
    # The provided logger will be wrapped with {Hanami::UniversalLogger} if it doesn't support both
    # keyword arguments and `#tagged` logging, ensuring compatibility with Hanami's logging
    # interface.
    #
    # @see #logger_instance
    # @see Hanami::UniversalLogger
    #
    # @api public
    # @since 2.0.0
    def logger=(logger_instance)
      @logger_instance = Hanami::UniversalLogger[logger_instance]
    end

    # rubocop:enable Style/TrivialAccessors

    # Returns the configured logger instance.
    #
    # Unless you've replaced the logger with {#logger=}, this returns a `Dry::Logger::Dispatcher` configured
    # with the options configured through {#logger}.
    #
    # This configured logger is registered in all app and slice containers as `"logger"`. For
    # typical usage, you should access the logger via this component, not directly from config.
    #
    # @example Accessing the logger component
    #   Hanami.app["logger"] # => #<Dry::Logger::Dispatcher>
    #
    # @example Injecting the logger as a dependency
    #   module MyApp
    #     class MyClass
    #       include Deps["logger"]
    #
    #       def my_method
    #         logger.info("hello")
    #       end
    #     end
    #   end
    #
    # @return [Dry::Logger::Dispatcher]
    #
    # @see #logger
    # @see Hanami::Config::Logger
    #
    # @api public
    # @since 2.0.0
    def logger_instance
      @logger_instance || logger.instance
    end

    private

    def load_from_env
      self.slices = ENV["HANAMI_SLICES"]&.split(",")&.map(&:strip)
    end

    def load_dependent_config(gem_name)
      if Hanami.bundled?(gem_name)
        yield
      else
        require_relative "config/null_config"
        NullConfig.new
      end
    end

    def method_missing(name, *args, &block)
      if config.respond_to?(name)
        config.public_send(name, *args, &block)
      else
        super
      end
    end

    def respond_to_missing?(name, _include_all = false)
      config.respond_to?(name) || super
    end
  end
end


def initialize_copy(source)
      super

      @app_name = app_name.dup

      @actions = source.actions.dup
      @assets = source.assets.dup
      @db = source.db.dup
      @i18n = source.i18n.dup
      @logger = source.logger.dup
      @middleware = source.middleware.dup
      @router = source.router.dup.tap do |router|
        router.instance_variable_set(:@base_config, self)
      end
      @views = source.views.dup
    end


# frozen_string_literal: true

require_relative "constants"
require_relative "env"

module Hanami
  # The Hanami app is a singular slice tasked with managing the core components of the app and
  # coordinating overall app boot.
  #
  # For smaller apps, the app may be the only slice present, whereas larger apps may consist of many
  # slices, with the app reserved for holding a small number of shared components only.
  #
  # @see Slice
  #
  # @api public
  # @since 2.0.0
  class App < Slice
    @_mutex = Mutex.new

    # @api private
    # @since 2.0.0
    def self.inherited(subclass)
      super

      Hanami.app = subclass

      subclass.extend(ClassMethods)

      @_mutex.synchronize do
        subclass.class_eval do
          @config = Hanami::Config.new(app_name: slice_name, env: Hanami.env)
          Hanami::Env.load
        end
      end
    end

    # App class interface
    module ClassMethods
      # Returns the app's config.
      #
      # @return [Hanami::Config]
      #
      # @api public
      # @since 2.0.0
      attr_reader :config

      # Returns the app's {SliceName}.
      #
      # @return [Hanami::SliceName]
      #
      # @see Slice::ClassMethods#slice_name
      #
      # @api public
      # @since 2.0.0
      def app_name
        slice_name
      end

      # Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/` directory
      # if it exists. If the lib directory is already added, this will do nothing.
      #
      # In ordinary circumstances, you should never have to call this method: this method is called
      # immediately upon subclassing {Hanami::App}, as a convenicence to put lib/ (under the default
      # root of `Dir.pwd`) on the load path automatically. This is helpful if you need to require
      # files inside the subclass body for performing certain app configuration steps.
      #
      # If you change your app's `config.root` and you need to require files from its `lib/`
      # directory within your {App} subclass body, you should call {.prepare_load_path} explicitly
      # after setting the new root.
      #
      # Otherwise, this method is called again as part of the app {.prepare} step, so if you've
      # changed your app's root and do _not_ need to require files within your {App} subclass body,
      # then you don't need to call this method.
      #
      # @example
      #   module MyApp
      #     class App < Hanami::App
      #       config.root = Pathname(__dir__).join("../src")
      #       prepare_load_path
      #
      #       # You can make requires for your files here
      #     end
      #   end
      #
      # @return [self]
      #
      # @api public
      # @since 2.0.0
      def prepare_load_path
        if (lib_path = root.join(LIB_DIR)).directory?
          path = lib_path.realpath.to_s
          $LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
        end

        self
      end

      private

      def prepare_all
        prepare_load_path

        # Make app-wide notifications available as early as possible
        container.use(:notifications)

        # Ensure all basic slice preparation is complete before we make adjustments below (which
        # rely on the basic prepare steps having already run)
        super

        # Run specific prepare steps for the app slice. Note also that some standard steps have been
        # skipped via the empty method overrides below.
        prepare_app_component_dirs
        prepare_app_providers
      end

      # Skip standard slice prepare steps that do not apply to the app
      def prepare_container_component_dirs; end
      def prepare_container_imports; end

      # rubocop:disable Metrics/AbcSize

      def prepare_app_component_dirs
        # Component files in both `app/` and `app/lib/` define classes in the
        # app's namespace

        if root.join(APP_DIR, LIB_DIR).directory?
          container.config.component_dirs.add(File.join(APP_DIR, LIB_DIR)) do |dir|
            dir.namespaces.add_root(key: nil, const: app_name.name)
          end
        end

        # When auto-registering components in `app/`, ignore files in `app/lib/` (these will be
        # auto-registered as above), as well as the configured no_auto_register_paths
        no_auto_register_paths = ([LIB_DIR] + config.no_auto_register_paths)
          .map { |path|
            path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
          }

        if root.join(APP_DIR).directory?
          container.config.component_dirs.add(APP_DIR) do |dir|
            dir.namespaces.add_root(key: nil, const: app_name.name)
            dir.auto_register = -> component {
              relative_path = component.file_path.relative_path_from(root.join(APP_DIR)).to_s
              !relative_path.start_with?(*no_auto_register_paths)
            }
          end
        end
      end

      def prepare_app_providers
        require_relative "providers/inflector"
        register_provider(:inflector, source: Hanami::Providers::Inflector)

        # Allow logger to be replaced by users with a manual provider, for advanced cases
        unless container.providers[:logger]
          require_relative "providers/logger"
          register_provider(:logger, source: Hanami::Providers::Logger)
        end

        if Hanami.bundled?("rack")
          require_relative "providers/rack"
          register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
        end

        if Hanami.bundled?("hanami-db")
          register_provider(:db_logging, source: Hanami::Providers::DBLogging)
        end
      end

      def prepare_autoloader
        autoloader.tag = "hanami.app.#{slice_name.name}"

        # Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
        # This method adds other dirs that are not otherwise configured as component dirs.

        # Autoload classes from `lib/[app_namespace]/`
        if root.join(LIB_DIR, app_name.name).directory?
          autoloader.push_dir(root.join(LIB_DIR, app_name.name), namespace: namespace)
        end

        autoloader.setup
      end

      # rubocop:enable Metrics/AbcSize
    end
  end
end


def prepare_app_component_dirs
        # Component files in both `app/` and `app/lib/` define classes in the
        # app's namespace

        if root.join(APP_DIR, LIB_DIR).directory?
          container.config.component_dirs.add(File.join(APP_DIR, LIB_DIR)) do |dir|
            dir.namespaces.add_root(key: nil, const: app_name.name)
          end
        end

        # When auto-registering components in `app/`, ignore files in `app/lib/` (these will be
        # auto-registered as above), as well as the configured no_auto_register_paths
        no_auto_register_paths = ([LIB_DIR] + config.no_auto_register_paths)
          .map { |path|
            path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
          }

        if root.join(APP_DIR).directory?
          container.config.component_dirs.add(APP_DIR) do |dir|
            dir.namespaces.add_root(key: nil, const: app_name.name)
            dir.auto_register = -> component {
              relative_path = component.file_path.relative_path_from(root.join(APP_DIR)).to_s
              !relative_path.start_with?(*no_auto_register_paths)
            }
          end
        end
      end


def prepare_app_providers
        require_relative "providers/inflector"
        register_provider(:inflector, source: Hanami::Providers::Inflector)

        # Allow logger to be replaced by users with a manual provider, for advanced cases
        unless container.providers[:logger]
          require_relative "providers/logger"
          register_provider(:logger, source: Hanami::Providers::Logger)
        end

        if Hanami.bundled?("rack")
          require_relative "providers/rack"
          register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
        end

        if Hanami.bundled?("hanami-db")
          register_provider(:db_logging, source: Hanami::Providers::DBLogging)
        end
      end


# frozen_string_literal: true

require_relative "constants"
require_relative "errors"

module Hanami
  # Calls `configure_for_slice(slice)` on the extended class whenever it is first
  # subclassed within a module namespace corresponding to a slice.
  #
  # @example
  #   class BaseClass
  #     extend Hanami::SliceConfigurable
  #   end
  #
  #   # slices/main/lib/my_class.rb
  #   module Main
  #     class MyClass < BaseClass
  #       # Will be called with `Main::Slice`
  #       def self.configure_for_slice(slice)
  #         # ...
  #       end
  #     end
  #   end
  #
  # @api private
  # @since 2.0.0
  module SliceConfigurable
    class << self
      def extended(klass)
        slice_for = method(:slice_for)

        inherited_mod = Module.new do
          define_method(:inherited) do |subclass|
            unless Hanami.app?
              raise ComponentLoadError, "Class #{klass} must be defined within an Hanami app"
            end

            super(subclass)

            subclass.instance_variable_set(:@configured_for_slices, configured_for_slices.dup)

            slice = slice_for.(subclass)
            return unless slice

            unless subclass.configured_for_slice?(slice)
              subclass.configure_for_slice(slice) if subclass.respond_to?(:configure_for_slice)
              subclass.configured_for_slices << slice
            end
          end
        end

        klass.singleton_class.prepend(inherited_mod)
      end

      private

      def slice_for(klass)
        return unless klass.name

        slices = Hanami.app.slices.with_nested + [Hanami.app]

        slices.detect { |slice| klass.name.start_with?("#{slice.namespace}#{MODULE_DELIMITER}") }
      end
    end

    def configured_for_slice?(slice)
      configured_for_slices.include?(slice)
    end

    def configured_for_slices
      @configured_for_slices ||= []
    end
  end
end


# frozen_string_literal: true

require "zeitwerk"
require "dry/system"

require_relative "constants"
require_relative "errors"

module Hanami
  # A slice represents any distinct area of concern within an Hanami app.
  #
  # For smaller apps, a slice may encompass the whole app itself (see
  # {Hanami::App}), whereas larger apps may consist of many slices.
  #
  # Each slice corresponds a single module namespace and a single root directory of source
  # files for loading as components into its container.
  #
  # Each slice has its own config, and may optionally have its own settings, routes, as well as
  # other nested slices.
  #
  # Slices expect an Hanami app to be defined (which itself is a slice). They will initialize their
  # config as a copy of the app's, and will also configure certain components
  #
  # Slices must be _prepared_ and optionally _booted_ before they can be used (see
  # {ClassMethods.prepare} and {ClassMethods.boot}). A prepared slice will lazily load its
  # components and nested slices (useful for minimising initial load time), whereas a
  # booted slice will eagerly load all its components and nested slices, then freeze its
  # container.
  #
  # @since 2.0.0
  class Slice
    @_mutex = Mutex.new

    # @api private
    def self.inherited(subclass)
      super

      subclass.extend(ClassMethods)

      @_mutex.synchronize do
        subclass.class_eval do
          @_mutex = Mutex.new
          @autoloader = Zeitwerk::Loader.new
          @container = Class.new(Dry::System::Container)
        end
      end
    end

    module ClassMethods
      # Returns the slice's parent.
      #
      # For top-level slices defined in `slices/` or `config/slices/`, this will be the Hanami app
      # itself (`Hanami.app`). For nested slices, this will be the slice in which they were
      # registered.
      #
      # @return [Hanami::Slice]
      #
      # @see #register_slice
      #
      # @api public
      # @since 2.0.0
      attr_reader :parent

      # Returns the slice's autoloader.
      #
      # Each slice has its own `Zeitwerk::Loader` autoloader instance, which is setup when the slice
      # is {#prepare prepared}.
      #
      # @return [Zeitwerk::Loader]
      #
      # @see https://github.com/fxn/zeitwerk
      #
      # @api public
      # @since 2.0.0
      attr_reader :autoloader

      # Returns the slice's container.
      #
      # This is a `Dry::System::Container` that is already configured for the slice.
      #
      # In ordinary usage, you shouldn't need direct access the container at all, since the slice
      # provides its own methods for interacting with the container (such as {#[]}, {#keys}, {#key?}
      # {#register}, {#register_provider}, {#prepare}, {#start}, {#stop}).
      #
      # If you need to configure the container directly, use {#prepare_container}.
      #
      # @see https://dry-rb.org/gems/dry-system
      #
      # @api public
      # @since 2.0.0
      attr_reader :container

      # Returns the Hanami app.
      #
      # @return [Hanami::App]
      #
      # @api public
      # @since 2.0.0
      def app
        Hanami.app
      end

      # Returns true if the slice is Hanami.app
      #
      # @return [Boolean]
      #
      # @api public
      # @since 2.2.0
      def app?
        eql?(app)
      end

      # Returns the slice's config.
      #
      # A slice's config is copied from the app config at time of first access.
      #
      # @return [Hanami::Config]
      #
      # @see App::ClassMethods.config
      #
      # @api public
      # @since 2.0.0
      def config
        @config ||= app.config.dup.tap do |slice_config|
          # Unset config from app that does not apply to ordinary slices
          slice_config.root = nil
        end
      end

      # Evaluates the block for a given app environment only.
      #
      # If the given `env_name` matches {Hanami.env}, then the block will be evaluated in the
      # context of `self` (the slice) via `instance_eval`. The slice is also passed as the block's
      # optional argument.
      #
      # If the env does not match, then the block is not evaluated at all.
      #
      # @example
      #   module MySlice
      #     class Slice < Hanami::Slice
      #       environment(:test) do
      #         config.logger.level = :info
      #       end
      #     end
      #   end
      #
      # @overload environment(env_name)
      #   @param env_name [Symbol] the environment name
      #
      # @overload environment(env_name)
      #   @param env_name [Symbol] the environment name
      #   @yieldparam slice [self] the slice
      #
      # @return [self]
      #
      # @see Hanami.env
      #
      # @api public
      # @since 2.0.0
      def environment(env_name, &block)
        instance_eval(&block) if env_name == config.env
        self
      end

      # Returns a {SliceName} for the slice, an object with methods returning the name of the slice
      # in various formats.
      #
      # @return [SliceName]
      #
      # @api public
      # @since 2.0.0
      def slice_name
        @slice_name ||= SliceName.new(self, inflector: method(:inflector))
      end

      # Returns the constant for the slice's module namespace.
      #
      # @example
      #   MySlice::Slice.namespace # => MySlice
      #
      # @return [Module] the namespace module constant
      #
      # @see SliceName#namespace
      #
      # @api public
      # @since 2.0.0
      def namespace
        slice_name.namespace
      end

      # Returns the slice's root, either the root as explicitly configured, or a default fallback of
      # the slice's name within the app's `slices/` dir.
      #
      # @return [Pathname]
      #
      # @see Config#root
      #
      # @api public
      # @since 2.0.0
      def root
        # Provides a best guess for a root when it is not yet configured.
        #
        # This is particularly useful for user-defined slice classes that access `settings` inside
        # the class body (since the root needed to find the settings file). In this case,
        # `configuration.root` may be nil when `settings` is called, since the root is configured by
        # `SliceRegistrar#configure_slice` _after_ the class is loaded.
        #
        # In common cases, this best guess will be correct since most Hanami slices will be expected
        # to live in the app SLICES_DIR. For advanced cases, the correct slice root should be
        # explicitly configured at the beginning of the slice class body, before any calls to
        # `settings`.
        config.root || app.root.join(SLICES_DIR, slice_name.to_s)
      end

      # Returns the slice's root component directory, accounting for App as a special case.
      #
      # @return [Pathname]
      #
      # @api public
      # @since 2.2.0
      def source_path
        app? ? root.join(APP_DIR) : root
      end

      # Returns the slice's root component directory, as a path relative to the app's root.
      #
      # @return [Pathname]
      #
      # @see #source_path
      #
      # @api public
      # @since 2.3.0
      def relative_source_path
        source_path.relative_path_from(app.root)
      end

      # Returns the slice's configured inflector.
      #
      # Unless explicitly re-configured for the slice, this will be the app's inflector.
      #
      # @return [Dry::Inflector]
      #
      # @see Config#inflector
      # @see Config#inflections
      #
      # @api public
      # @since 2.0.0
      def inflector
        config.inflector
      end

      # @overload prepare
      #   Prepares the slice.
      #
      #   This will define the slice's `Slice` and `Deps` constants, make all Ruby source files
      #   inside the slice's root dir autoloadable, as well as lazily loadable as container
      #   components.
      #
      #   Call `prepare` when you want to access particular components within the slice while still
      #   minimizing load time. Preparing slices is the approach taken when loading the Hanami
      #   console or when running tests.
      #
      #   @return [self]
      #
      #   @see #boot
      #
      #   @api public
      #   @since 2.0.0
      #
      # @overload prepare(provider_name)
      #   Prepares a provider.
      #
      #   This triggers the provider's `prepare` lifecycle step.
      #
      #   @param provider_name [Symbol] the name of the provider to start
      #
      #   @return [self]
      #
      #   @api public
      #   @since 2.0.0
      def prepare(provider_name = nil)
        if provider_name
          container.prepare(provider_name)
        else
          prepare_slice
        end

        self
      end

      # Captures the given block to be called with the slice's container during the slice's
      # `prepare` step, after the slice has already configured the container.
      #
      # This is intended for advanced usage only and should not be needed for ordinary slice
      # configuration and usage.
      #
      # @example
      #   module MySlice
      #     class Slice < Hanami::Slice
      #       prepare_container do |container|
      #         # ...
      #       end
      #     end
      #   end
      #
      # @yieldparam container [Dry::System::Container] the slice's container
      #
      # @return [self]
      #
      # @see #prepare
      #
      # @api public
      # @since 2.0.0
      def prepare_container(&block)
        @prepare_container_block = block
        self
      end

      # Boots the slice.
      #
      # This will prepare the slice (if not already prepared), start each of its providers, register
      # all the slice's components from its Ruby source files, and import components from any other
      # slices. It will also boot any of the slice's own registered nested slices. It will then
      # freeze its container so no further components can be registered.
      #
      # Call `boot` if you want to fully load a slice and incur all load time up front, such as when
      # preparing an app to serve web requests. Booting slices is the approach taken when running
      # Hanami's standard Puma setup (see `config.ru`).
      #
      # @return [self]
      #
      # @see #prepare
      #
      # @api public
      # @since 2.0.0
      def boot
        return self if booted?

        prepare

        container.finalize!
        slices.each(&:boot)

        @booted = true

        self
      end

      # Shuts down the slice's providers, as well as the providers in any nested slices.
      #
      # @return [self]
      #
      # @api public
      # @since 2.0.0
      def shutdown
        slices.each(&:shutdown)
        container.shutdown!
        self
      end

      # Returns true if the slice has been prepared.
      #
      # @return [Boolean]
      #
      # @see #prepare
      #
      # @api public
      # @since 2.0.0
      def prepared?
        !!@prepared
      end

      # Returns true if the slice has been booted.
      #
      # @return [Boolean]
      #
      # @see #boot
      #
      # @api public
      # @since 2.0.0
      def booted?
        !!@booted
      end

      # Returns the slice's collection of nested slices.
      #
      # @return [SliceRegistrar]
      #
      # @see #register_slice
      #
      # @api public
      # @since 2.0.0
      def slices
        @slices ||= SliceRegistrar.new(self)
      end

      # @overload register_slice(name, &block)
      #   Registers a nested slice with the given name.
      #
      #   This will define a new {Slice} subclass for the slice. If a block is given, it is passed
      #   the class object, and will be evaluated in the context of the class like `class_eval`.
      #
      #   @example
      #     MySlice::Slice.register_slice do
      #       # Configure the slice or do other class-level things here
      #     end
      #
      #   @param name [Symbol] the identifier for the slice to be registered
      #   @yieldparam slice [Hanami::Slice] the newly defined slice class
      #
      # @overload register_slice(name, slice_class)
      #   Registers a nested slice with the given name.
      #
      #   The given `slice_class` will be registered as the slice. It must be a subclass of {Slice}.
      #
      #   @param name [Symbol] the identifier for the slice to be registered
      #   @param slice_class [Hanami::Slice]
      #
      # @return [slices]
      #
      # @see SliceRegistrar#register
      #
      # @api public
      # @since 2.0.0
      def register_slice(...)
        slices.register(...)
      end

      # Registers a component in the slice's container.
      #
      # @overload register(key, object)
      #   Registers the given object as the component. This same object will be returned whenever
      #   the component is resolved.
      #
      #   @param key [String] the component's key
      #   @param object [Object] the object to register as the component
      #
      # @overload register(key, memoize: false, &block)
      #   Registers the given block as the component. When the component is resolved, the return
      #   value of the block will be returned.
      #
      #   Since the block is not called until resolution-time, this is a useful way to register
      #   components that have dependencies on other components in the container, which as yet may
      #   be unavailable at the time of registration.
      #
      #   All auto-registered components are registered in block form.
      #
      #   When `memoize` is true, the component will be memoized upon first resolution and the same
      #   object returned on all subsequent resolutions, meaning the block is only called once.
      #   Otherwise, the block will be called and a new object returned on every resolution.
      #
      #   @param key [String] the component's key
      #   @param memoize [Boolean]
      #   @yieldreturn [Object] the object to register as the component
      #
      # @overload register(key, call: true, &block)
      #   Registers the given block as the component. When `call: false` is given, then the block
      #   itself will become the component.
      #
      #   When such a component is resolved, the block will not be called, and instead the `Proc`
      #   object for that block will be returned.
      #
      #   @param key [String] the component's key
      #   @param call [Boolean]
      #
      # @return [container]
      #
      # @see #[]
      # @see #resolve
      #
      # @api public
      # @since 2.0.0
      def register(...)
        container.register(...)
      end

      # @overload register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block)
      #   Registers a provider and its lifecycle hooks.
      #
      #   In most cases, you should call this from a dedicated file for the provider in your app or
      #   slice's `config/providers/` dir. This allows the provider to be loaded when individual
      #   matching components are resolved (for prepared slices) or when slices are booted.
      #
      #   @example Simple provider
      #     # config/providers/db.rb
      #     Hanami.app.register_provider(:db) do
      #       start do
      #         require "db"
      #         register("db", DB.new)
      #       end
      #     end
      #
      #   @example Provider with lifecycle steps, also using dependencies from the target container
      #     # config/providers/db.rb
      #     Hanami.app.register_provider(:db) do
      #       prepare do
      #         require "db"
      #         db = DB.new(target_container["settings"].database_url)
      #         register("db", db)
      #       end
      #
      #       start do
      #         container["db"].establish_connection
      #       end
      #
      #       stop do
      #         container["db"].close_connection
      #       end
      #     end
      #
      #   @example Probvider registration under a namespace
      #     # config/providers/db.rb
      #     Hanami.app.register_provider(:persistence, namespace: true) do
      #       start do
      #         require "db"
      #
      #         # Namespace option above means this will be registered as "persistence.db"
      #         register("db", DB.new)
      #       end
      #     end
      #
      #   @param name [Symbol] the unique name for the provider
      #   @param namespace [Boolean, String, nil] register components from the provider with given
      #     namespace. May be an explicit string, or `true` for the namespace to be the provider's
      #     name
      #   @param from [Symbol, nil] the group for an external provider source to use, with the
      #     provider source name inferred from `name` or passed explicitly as `source:`
      #   @param source [Symbol, nil] the name of the external provider source to use, if different
      #     from the value provided as `name`
      #   @param if [Boolean] a boolean-returning expression to determine whether to register the
      #     provider
      #
      #   @return [container]
      #
      #   @api public
      #   @since 2.0.0
      def register_provider(...)
        container.register_provider(...)
      end

      # @api public
      # @since 2.1.0
      def configure_provider(*args, **kwargs, &block)
        container.register_provider(*args, **kwargs, from: :hanami, &block)
      end

      # @overload start(provider_name)
      #   Starts a provider.
      #
      #   This triggers the provider's `prepare` and `start` lifecycle steps.
      #
      #   @example
      #     MySlice::Slice.start(:persistence)
      #
      #   @param provider_name [Symbol] the name of the provider to start
      #
      #   @return [container]
      #
      #   @api public
      #   @since 2.0.0
      def start(...)
        container.start(...)
      end

      # @overload stop(provider_name)
      #   Stops a provider.
      #
      #   This triggers the provider's `stop` lifecycle hook.
      #
      #   @example
      #     MySlice::Slice.stop(:persistence)
      #
      #   @param provider_name [Symbol] the name of the provider to start
      #
      #   @return [container]
      #
      #   @api public
      #   @since 2.0.0
      def stop(...)
        container.stop(...)
      end

      # @overload key?(key)
      #   Returns true if the component with the given key is registered in the container.
      #
      #   For a prepared slice, calling `key?` will also try to load the component if not loaded
      #   already.
      #
      #   @param key [String, Symbol] the component key
      #
      #   @return [Boolean]
      #
      #   @api public
      #   @since 2.0.0
      def key?(...)
        container.key?(...)
      end

      # Required for the slice to act as a provider target
      # @api public
      # @since 2.2.0
      def registered?(...)
        container.registered?(...)
      end

      # Returns an array of keys for all currently registered components in the container.
      #
      # For a prepared slice, this will be the set of components that have been previously resolved.
      # For a booted slice, this will be all components available for the slice.
      #
      # @return [Array<String>]
      #
      # @api public
      # @since 2.0.0
      def keys
        container.keys
      end

      # @overload [](key)
      #   Resolves the component with the given key from the container.
      #
      #   For a prepared slice, this will attempt to load and register the matching component if it
      #   is not loaded already. For a booted slice, this will return from already registered
      #   components only.
      #
      #   @return [Object] the resolved component's object
      #
      #   @raise Dry::Container::KeyError if the component could not be found or loaded
      #
      #   @see #resolve
      #
      #   @api public
      #   @since 2.0.0
      def [](...)
        container.[](...)
      end

      # @see #[]
      #
      # @api public
      # @since 2.0.0
      def resolve(...)
        container.resolve(...)
      end

      # Specifies the components to export from the slice.
      #
      # Slices importing from this slice can import the specified components only.
      #
      # @example
      #   module MySlice
      #     class Slice < Hanami::Slice
      #       export ["search", "index_entity"]
      #     end
      #   end
      #
      # @param keys [Array<String>] the component keys to export
      #
      # @return [self]
      #
      # @api public
      # @since 2.0.0
      def export(keys)
        container.config.exports = keys
        self
      end

      # @overload import(from:, as: nil, keys: nil)
      #   Specifies components to import from another slice.
      #
      #   Booting a slice will register all imported components. For a prepared slice, these
      #   components will be be imported automatically when resolved.
      #
      #   @example
      #     module MySlice
      #       class Slice < Hanami:Slice
      #         # Component from Search::Slice will import as "search.index_entity"
      #         import keys: ["index_entity"], from: :search
      #       end
      #     end
      #
      #   @example Other import variations
      #     # Different key namespace: component will be "search_backend.index_entity"
      #     import keys: ["index_entity"], from: :search, as: "search_backend"
      #
      #     # Import to root key namespace: component will be "index_entity"
      #     import keys: ["index_entity"], from: :search, as: nil
      #
      #     # Import all components
      #     import from: :search
      #
      #   @param keys [Array<String>, nil] Array of component keys to import. To import all
      #     available components, omit this argument.
      #   @param from [Symbol] name of the slice to import from
      #   @param as [Symbol, String, nil]
      #
      #   @see #export
      #
      #   @api public
      #   @since 2.0.0
      def import(from:, **kwargs)
        slice = self

        container.after(:configure) do
          if from.is_a?(Symbol) || from.is_a?(String)
            slice_name = from
            from = slice.parent.slices[from.to_sym].container
          end

          as = kwargs[:as] || slice_name

          import(from: from, as: as, **kwargs)
        end
      end

      # Returns the slice's settings, or nil if no settings are defined.
      #
      # You can define your settings in `config/settings.rb`.
      #
      # @return [Hanami::Settings, nil]
      #
      # @see Hanami::Settings
      #
      # @api public
      # @since 2.0.0
      def settings
        return @settings if instance_variable_defined?(:@settings)

        @settings = Settings.load_for_slice(self)
      end

      # Returns the slice's routes, or nil if no routes are defined.
      #
      # You can define your routes in `config/routes.rb`.
      #
      # @return [Hanami::Routes, nil]
      #
      # @see Hanami::Routes
      #
      # @api public
      # @since 2.0.0
      def routes
        return @routes if instance_variable_defined?(:@routes)

        @routes = load_routes
      end

      # Returns the slice's router, if or nil if no routes are defined.
      #
      # An optional `inspector`, implementing the `Hanami::Router::Inspector` interface, may be
      # provided at first call (the router is then memoized for subsequent accesses). An inspector
      # is used by the `hanami routes` CLI comment to provide a list of available routes.
      #
      # The returned router is a {Slice::Router}, which provides all `Hanami::Router` functionality,
      # with the addition of support for slice mounting with the {Slice::Router#slice}.
      #
      # @param inspector [Hanami::Router::Inspector, nil] an optional routes inspector
      #
      # @return [Hanami::Slice::Router, nil]
      #
      # @api public
      # @since 2.0.0
      def router(inspector: nil)
        raise SliceLoadError, "#{self} must be prepared before loading the router" unless prepared?

        @_mutex.synchronize do
          @_router ||= load_router(inspector: inspector)
        end
      end

      # Returns a [Rack][rack] app for the slice, or nil if no routes are defined.
      #
      # The rack app will be memoized on first access.
      #
      # [rack]: https://github.com/rack/rack
      #
      # @return [#call, nil] the rack app, or nil if no routes are defined
      #
      # @see #routes
      # @see #router
      #
      # @api public
      # @since 2.0.0
      def rack_app
        return unless router

        @rack_app ||= router.to_rack_app
      end

      # @overload call(rack_env)
      #   Calls the slice's [Rack][rack] app and returns a Rack-compatible response object
      #
      #   [rack]: https://github.com/rack/rack
      #
      #   @param rack_env [Hash] the Rack environment for the request
      #
      #   @return [Array] the three-element Rack response array
      #
      #   @see #rack_app
      #
      #   @api public
      #   @since 2.0.0
      def call(...)
        rack_app.call(...)
      end

      private

      # rubocop:disable Metrics/AbcSize

      def prepare_slice
        return self if prepared?

        config.finalize!

        ensure_slice_name
        ensure_slice_consts
        ensure_root

        prepare_all

        instance_exec(container, &@prepare_container_block) if @prepare_container_block
        container.configured!

        prepare_autoloader

        # Load child slices last, ensuring their parent is fully prepared beforehand
        # (useful e.g. for slices that may wish to access constants defined in the
        # parent's autoloaded directories)
        prepare_slices

        @prepared = true

        self
      end

      def ensure_slice_name
        unless name
          raise SliceLoadError, "Slice must have a class name before it can be prepared"
        end
      end

      def ensure_slice_consts
        if namespace.const_defined?(:Container) || namespace.const_defined?(:Deps)
          raise(
            SliceLoadError,
            "#{namespace}::Container and #{namespace}::Deps constants must not already be defined"
          )
        end
      end

      def ensure_root
        unless config.root
          raise SliceLoadError, "Slice must have a `config.root` before it can be prepared"
        end
      end

      def prepare_all
        prepare_settings
        prepare_container_consts
        prepare_container_plugins
        prepare_container_base_config
        prepare_container_component_dirs
        prepare_container_imports
        prepare_container_providers
      end

      def prepare_settings
        container.register(:settings, settings) if settings
      end

      def prepare_container_consts
        namespace.const_set :Container, container
        namespace.const_set :Deps, container.injector
      end

      def prepare_container_plugins
        container.use(:env, inferrer: -> { Hanami.env })

        container.use(
          :zeitwerk,
          loader: autoloader,
          run_setup: false,
          eager_load: false
        )
      end

      def prepare_container_base_config
        container.config.name = slice_name.to_sym
        container.config.root = root
        container.config.provider_registrar = ProviderRegistrar.for_slice(self)
        container.config.provider_dirs = [File.join("config", "providers")]
        container.config.registrations_dir = File.join("config", "registrations")

        container.config.env = config.env
        container.config.inflector = config.inflector
      end

      def prepare_container_component_dirs
        return unless root.directory?

        # Component files in both the root and `lib/` define classes in the slice's
        # namespace

        if root.join(LIB_DIR)&.directory?
          container.config.component_dirs.add(LIB_DIR) do |dir|
            dir.namespaces.add_root(key: nil, const: slice_name.name)
          end
        end

        # When auto-registering components in the root, ignore files in `config/` (this is
        # for framework config only), `lib/` (these will be auto-registered as above), as
        # well as the configured no_auto_register_paths
        no_auto_register_paths = ([LIB_DIR, CONFIG_DIR] + config.no_auto_register_paths)
          .map { |path|
            path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
          }

        # TODO: Change `""` (signifying the root) once dry-rb/dry-system#238 is resolved
        container.config.component_dirs.add("") do |dir|
          dir.namespaces.add_root(key: nil, const: slice_name.name)
          dir.auto_register = -> component {
            relative_path = component.file_path.relative_path_from(root).to_s
            !relative_path.start_with?(*no_auto_register_paths)
          }
        end
      end

      def prepare_container_imports
        import(
          keys: config.shared_app_component_keys,
          from: app.container,
          as: nil
        )
      end

      # rubocop:disable Metrics/PerceivedComplexity
      def prepare_container_providers
        # Check here for the `routes` definition only, not `router` itself, because the
        # `router` requires the slice to be prepared before it can be loaded, and at this
        # point we're still in the process of preparing.
        if routes?
          require_relative "providers/routes"
          register_provider(:routes, source: Providers::Routes)
        end

        if assets_dir? && Hanami.bundled?("hanami-assets")
          require_relative "providers/assets"
          register_provider(:assets, source: Providers::Assets)
        end

        if Hanami.bundled?("hanami-db")
          # Explicit require here to ensure the provider source registers itself, to allow the user
          # to configure it within their own concrete provider file.
          require_relative "providers/db"

          if register_db_provider?
            # Only register providers if the user hasn't provided their own
            unless container.providers[:db]
              register_provider(:db, namespace: true, source: Providers::DB)
            end

            unless container.providers[:relations]
              register_provider(:relations, namespace: true, source: Providers::Relations)
            end
          end
        end

        if Hanami.bundled?("i18n")
          require_relative "providers/i18n"

          if i18n_config_dir? && !container.providers[:i18n]
            register_provider(:i18n, source: Providers::I18n)
          end
        end
      end
      # rubocop:enable Metrics/PerceivedComplexity

      def prepare_autoloader
        autoloader.tag = "hanami.slices.#{slice_name}"

        # Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
        # This method adds other dirs that are not otherwise configured as component dirs.

        # Everything in the slice root can be autoloaded except `config/` and `slices/`,
        # which are framework-managed directories

        if root.join(CONFIG_DIR)&.directory?
          autoloader.ignore(root.join(CONFIG_DIR))
        end

        if root.join(SLICES_DIR)&.directory?
          autoloader.ignore(root.join(SLICES_DIR))
        end

        autoloader.setup
      end

      def prepare_slices
        slices.load_slices.each(&:prepare)
        slices.freeze
      end

      def routes?
        return false unless Hanami.bundled?("hanami-router")

        return true if namespace.const_defined?(ROUTES_CLASS_NAME)

        root.join("#{ROUTES_PATH}#{RB_EXT}").file?
      end

      def load_routes
        return false unless Hanami.bundled?("hanami-router")

        if root.directory?
          routes_require_path = root.join(ROUTES_PATH).to_s

          begin
            require_relative "./routes"
            require routes_require_path
          rescue LoadError => exception
            raise exception unless exception.path == routes_require_path
          end
        end

        begin
          routes_class = namespace.const_get(ROUTES_CLASS_NAME)
          routes_class.routes
        rescue NameError => exception
          raise exception unless exception.name == ROUTES_CLASS_NAME.to_sym
        end
      end

      # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
      def load_router(inspector:)
        return unless routes

        require_relative "slice/router"

        slice = self
        config = self.config
        rack_monitor = self["rack.monitor"]

        show_welcome = Hanami.env?(:development) && routes.empty?
        render_errors = render_errors?
        render_detailed_errors = render_detailed_errors?

        error_handlers = {}.tap do |hsh|
          if render_errors || render_detailed_errors
            hsh[:not_allowed] = ROUTER_NOT_ALLOWED_HANDLER
            hsh[:not_found] = ROUTER_NOT_FOUND_HANDLER
          end
        end

        Slice::Router.new(
          inspector: inspector,
          inflector: inflector,
          routes: routes,
          resolver: config.router.resolver.new(slice: self),
          **error_handlers,
          **config.router.options
        ) do
          use(rack_monitor)

          use(Hanami::Web::Welcome) if show_welcome

          use(
            Hanami::Middleware::RenderErrors,
            config,
            Hanami::Middleware::PublicErrorsApp.new(slice.root.join("public"))
          )

          if render_detailed_errors
            require "hanami/webconsole"
            use(Hanami::Webconsole::Middleware, config)
          end

          if Hanami.bundled?("hanami-action")
            if config.actions.method_override
              require "rack/method_override"
              use(Rack::MethodOverride)
            end

            if config.actions.sessions.enabled?
              use(*config.actions.sessions.middleware)
            end

            if config.actions.content_security_policy && # rubocop:disable Style/SafeNavigation
               config.actions.content_security_policy.nonce?
              use(*config.actions.content_security_policy.middleware)
            end
          end

          if Hanami.bundled?("hanami-assets") && config.assets.serve
            use(Hanami::Middleware::Assets)
          end

          middleware_stack.update(config.middleware_stack)
        end
      end
      # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

      def render_errors?
        config.render_errors
      end

      def render_detailed_errors?
        config.render_detailed_errors && Hanami.bundled?("hanami-webconsole")
      end

      ROUTER_NOT_ALLOWED_HANDLER = -> env, allowed_http_methods {
        raise Hanami::Router::NotAllowedError.new(env, allowed_http_methods)
      }.freeze
      private_constant :ROUTER_NOT_ALLOWED_HANDLER

      ROUTER_NOT_FOUND_HANDLER = -> env {
        raise Hanami::Router::NotFoundError.new(env)
      }.freeze
      private_constant :ROUTER_NOT_FOUND_HANDLER

      def assets_dir?
        source_path.join("assets").directory?
      end

      def i18n_config_dir?
        root.join("config", "i18n").directory?
      end

      def register_db_provider?
        concrete_db_provider? ||
          db_config_dir? ||
          relations_dir? ||
          db_source_dir? ||
          import_db_from_parent?
      end

      def concrete_db_provider?
        root.join(CONFIG_DIR, "providers", "db.rb").exist?
      end

      def db_config_dir?
        root.join("config", "db").directory?
      end

      def relations_dir?
        source_path.join("relations").directory?
      end

      def db_source_dir?
        source_path.join("db").directory?
      end

      def import_db_from_parent?
        parent &&
          config.db.import_from_parent &&
          parent.container.providers[:db]
      end

      # rubocop:enable Metrics/AbcSize
    end
  end
end


def import(from:, **kwargs)
        slice = self

        container.after(:configure) do
          if from.is_a?(Symbol) || from.is_a?(String)
            slice_name = from
            from = slice.parent.slices[from.to_sym].container
          end

          as = kwargs[:as] || slice_name

          import(from: from, as: as, **kwargs)
        end
      end


def prepare_slice
        return self if prepared?

        config.finalize!

        ensure_slice_name
        ensure_slice_consts
        ensure_root

        prepare_all

        instance_exec(container, &@prepare_container_block) if @prepare_container_block
        container.configured!

        prepare_autoloader

        # Load child slices last, ensuring their parent is fully prepared beforehand
        # (useful e.g. for slices that may wish to access constants defined in the
        # parent's autoloaded directories)
        prepare_slices

        @prepared = true

        self
      end


def prepare_container_component_dirs
        return unless root.directory?

        # Component files in both the root and `lib/` define classes in the slice's
        # namespace

        if root.join(LIB_DIR)&.directory?
          container.config.component_dirs.add(LIB_DIR) do |dir|
            dir.namespaces.add_root(key: nil, const: slice_name.name)
          end
        end

        # When auto-registering components in the root, ignore files in `config/` (this is
        # for framework config only), `lib/` (these will be auto-registered as above), as
        # well as the configured no_auto_register_paths
        no_auto_register_paths = ([LIB_DIR, CONFIG_DIR] + config.no_auto_register_paths)
          .map { |path|
            path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
          }

        # TODO: Change `""` (signifying the root) once dry-rb/dry-system#238 is resolved
        container.config.component_dirs.add("") do |dir|
          dir.namespaces.add_root(key: nil, const: slice_name.name)
          dir.auto_register = -> component {
            relative_path = component.file_path.relative_path_from(root).to_s
            !relative_path.start_with?(*no_auto_register_paths)
          }
        end
      end


def prepare_container_providers
        # Check here for the `routes` definition only, not `router` itself, because the
        # `router` requires the slice to be prepared before it can be loaded, and at this
        # point we're still in the process of preparing.
        if routes?
          require_relative "providers/routes"
          register_provider(:routes, source: Providers::Routes)
        end

        if assets_dir? && Hanami.bundled?("hanami-assets")
          require_relative "providers/assets"
          register_provider(:assets, source: Providers::Assets)
        end

        if Hanami.bundled?("hanami-db")
          # Explicit require here to ensure the provider source registers itself, to allow the user
          # to configure it within their own concrete provider file.
          require_relative "providers/db"

          if register_db_provider?
            # Only register providers if the user hasn't provided their own
            unless container.providers[:db]
              register_provider(:db, namespace: true, source: Providers::DB)
            end

            unless container.providers[:relations]
              register_provider(:relations, namespace: true, source: Providers::Relations)
            end
          end
        end

        if Hanami.bundled?("i18n")
          require_relative "providers/i18n"

          if i18n_config_dir? && !container.providers[:i18n]
            register_provider(:i18n, source: Providers::I18n)
          end
        end
      end

      def register_provider(...)
        container.register_provider(...)
      end


def prepare_autoloader
        autoloader.tag = "hanami.slices.#{slice_name}"

        # Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
        # This method adds other dirs that are not otherwise configured as component dirs.

        # Everything in the slice root can be autoloaded except `config/` and `slices/`,
        # which are framework-managed directories

        if root.join(CONFIG_DIR)&.directory?
          autoloader.ignore(root.join(CONFIG_DIR))
        end

        if root.join(SLICES_DIR)&.directory?
          autoloader.ignore(root.join(SLICES_DIR))
        end

        autoloader.setup
      end


def load_routes
        return false unless Hanami.bundled?("hanami-router")

        if root.directory?
          routes_require_path = root.join(ROUTES_PATH).to_s

          begin
            require_relative "./routes"
            require routes_require_path
          rescue LoadError => exception
            raise exception unless exception.path == routes_require_path
          end
        end

        begin
          routes_class = namespace.const_get(ROUTES_CLASS_NAME)
          routes_class.routes
        rescue NameError => exception
          raise exception unless exception.name == ROUTES_CLASS_NAME.to_sym
        end
      end


def load_router(inspector:)
        return unless routes

        require_relative "slice/router"

        slice = self
        config = self.config
        rack_monitor = self["rack.monitor"]

        show_welcome = Hanami.env?(:development) && routes.empty?
        render_errors = render_errors?
        render_detailed_errors = render_detailed_errors?

        error_handlers = {}.tap do |hsh|
          if render_errors || render_detailed_errors
            hsh[:not_allowed] = ROUTER_NOT_ALLOWED_HANDLER
            hsh[:not_found] = ROUTER_NOT_FOUND_HANDLER
          end
        end

        Slice::Router.new(
          inspector: inspector,
          inflector: inflector,
          routes: routes,
          resolver: config.router.resolver.new(slice: self),
          **error_handlers,
          **config.router.options
        ) do
          use(rack_monitor)

          use(Hanami::Web::Welcome) if show_welcome

          use(
            Hanami::Middleware::RenderErrors,
            config,
            Hanami::Middleware::PublicErrorsApp.new(slice.root.join("public"))
          )

          if render_detailed_errors
            require "hanami/webconsole"
            use(Hanami::Webconsole::Middleware, config)
          end

          if Hanami.bundled?("hanami-action")
            if config.actions.method_override
              require "rack/method_override"
              use(Rack::MethodOverride)
            end

            if config.actions.sessions.enabled?
              use(*config.actions.sessions.middleware)
            end

            if config.actions.content_security_policy && # rubocop:disable Style/SafeNavigation
               config.actions.content_security_policy.nonce?
              use(*config.actions.content_security_policy.middleware)
            end
          end

          if Hanami.bundled?("hanami-assets") && config.assets.serve
            use(Hanami::Middleware::Assets)
          end

          middleware_stack.update(config.middleware_stack)
        end
      end

env?

  def self.env?(*names)
    names.map(&:to_sym).include?(env)
  end


# frozen_string_literal: true

module Hanami
  module Env
    # @since 2.0.1
    # @api private
    @_loaded = false

    # Uses [dotenv](https://github.com/bkeepers/dotenv) (if available) to populate `ENV` from
    # various `.env` files.
    #
    # For a given `HANAMI_ENV` environment, the `.env` files are looked up in the following order:
    #
    # - .env.{environment}.local
    # - .env.local (unless the environment is `test`)
    # - .env.{environment}
    # - .env
    #
    # If dotenv is unavailable, the method exits and does nothing.
    #
    # @since 2.0.1
    # @api private
    def self.load(env = Hanami.env)
      return unless Hanami.bundled?("dotenv")
      return if loaded?

      dotenv_files = [
        ".env.#{env}.local",
        (".env.local" unless env == :test),
        ".env.#{env}",
        ".env"
      ].compact

      require "dotenv"
      Dotenv.load(*dotenv_files)

      loaded!
    end

    # @since 2.0.1
    # @api private
    def self.loaded?
      @_loaded
    end

    # @since 2.0.1
    # @api private
    def self.loaded!
      @_loaded = true
    end
  end
end