# typed: false
# frozen_string_literal: true

require "json"

require "lazy_object"
require "locale"

require "extend/hash_validator"
using HashValidator

module Cask
  # Configuration for installing casks.
  #
  # @api private
  class Config
    DEFAULT_DIRS = {
      appdir:               "/Applications",
      colorpickerdir:       "~/Library/ColorPickers",
      prefpanedir:          "~/Library/PreferencePanes",
      qlplugindir:          "~/Library/QuickLook",
      mdimporterdir:        "~/Library/Spotlight",
      dictionarydir:        "~/Library/Dictionaries",
      fontdir:              "~/Library/Fonts",
      servicedir:           "~/Library/Services",
      input_methoddir:      "~/Library/Input Methods",
      internet_plugindir:   "~/Library/Internet Plug-Ins",
      audio_unit_plugindir: "~/Library/Audio/Plug-Ins/Components",
      vst_plugindir:        "~/Library/Audio/Plug-Ins/VST",
      vst3_plugindir:       "~/Library/Audio/Plug-Ins/VST3",
      screen_saverdir:      "~/Library/Screen Savers",
    }.freeze

    def self.defaults
      {
        languages: LazyObject.new { MacOS.languages },
      }.merge(DEFAULT_DIRS).freeze
    end

    def self.from_args(args)
      new(explicit: {
        appdir:               args.appdir,
        colorpickerdir:       args.colorpickerdir,
        prefpanedir:          args.prefpanedir,
        qlplugindir:          args.qlplugindir,
        mdimporterdir:        args.mdimporterdir,
        dictionarydir:        args.dictionarydir,
        fontdir:              args.fontdir,
        servicedir:           args.servicedir,
        input_methoddir:      args.input_methoddir,
        internet_plugindir:   args.internet_plugindir,
        audio_unit_plugindir: args.audio_unit_plugindir,
        vst_plugindir:        args.vst_plugindir,
        vst3_plugindir:       args.vst3_plugindir,
        screen_saverdir:      args.screen_saverdir,
        languages:            args.language,
      }.compact)
    end

    def self.from_json(json)
      config = begin
        JSON.parse(json)
      rescue JSON::ParserError => e
        raise e, "Cannot parse #{path}: #{e}", e.backtrace
      end

      new(
        default:  config.fetch("default",  {}),
        env:      config.fetch("env",      {}),
        explicit: config.fetch("explicit", {}),
      )
    end

    def self.canonicalize(config)
      config.map do |k, v|
        key = k.to_sym

        if DEFAULT_DIRS.key?(key)
          [key, Pathname(v).expand_path]
        else
          [key, v]
        end
      end.to_h
    end

    attr_accessor :explicit

    def initialize(default: nil, env: nil, explicit: {})
      @default = self.class.canonicalize(self.class.defaults.merge(default)) if default
      @env = self.class.canonicalize(env) if env
      @explicit = self.class.canonicalize(explicit)

      @env&.assert_valid_keys!(*self.class.defaults.keys)
      @explicit.assert_valid_keys!(*self.class.defaults.keys)
    end

    def default
      @default ||= self.class.canonicalize(self.class.defaults)
    end

    def env
      @env ||= self.class.canonicalize(
        Homebrew::EnvConfig.cask_opts
          .select { |arg| arg.include?("=") }
          .map { |arg| arg.split("=", 2) }
          .map do |(flag, value)|
            key = flag.sub(/^--/, "")

            if key == "language"
              key = "languages"
              value = value.split(",")
            end

            [key, value]
          end,
      )
    end

    def binarydir
      @binarydir ||= HOMEBREW_PREFIX/"bin"
    end

    def manpagedir
      @manpagedir ||= HOMEBREW_PREFIX/"share/man"
    end

    def languages
      [
        *explicit[:languages],
        *env[:languages],
        *default[:languages],
      ].uniq.select do |lang|
        # Ensure all languages are valid.
        Locale.parse(lang)
        true
      rescue Locale::ParserError
        false
      end
    end

    def languages=(languages)
      explicit[:languages] = languages
    end

    DEFAULT_DIRS.each_key do |dir|
      define_method(dir) do
        explicit.fetch(dir, env.fetch(dir, default.fetch(dir)))
      end

      define_method(:"#{dir}=") do |path|
        explicit[dir] = Pathname(path).expand_path
      end
    end

    def merge(other)
      self.class.new(explicit: other.explicit.merge(explicit))
    end

    def to_json(*args)
      {
        default:  default,
        env:      env,
        explicit: explicit,
      }.to_json(*args)
    end
  end
end