module ElmInstall
  # Identifies dependencies
  class Identifier < Base
    # @return [Array<Dependency>] The initial dependencies
    attr_reader :initial_dependencies

    # @return [Hash] The options
    attr_reader :options

    # @return [String] The initial Elm version
    attr_reader :initial_elm_version

    Contract Dir, Hash => Identifier
    # Initialize a new identifier.
    #
    # @param directory [Dir] The initial directory
    # @param options [Hash] The options
    #
    # @return [Indentifier] The identifier instance
    def initialize(directory, options = {})
      @options = options
      @dependency_sources = dependency_sources directory
      @initial_elm_version = elm_version directory
      @initial_dependencies = identify directory
      self
    end

    Contract Dir => HashOf[String => Any]
    # Returns the dependency sources for the given directory.
    #
    # @param directory [Dir] The directory
    #
    # @return [Hash] The directory sources
    def dependency_sources(directory)
      json(directory)['dependency-sources'].to_h
    end

    Contract Dir => Semverse::Version
    # Returns the version of a package in the given directory.
    #
    # @param directory [Dir] The directory
    #
    # @return [Semverse::Version] The version
    def version(directory)
      Semverse::Version.new(json(directory)['version'])
    end

    Contract Dir => String
    # Returns the Elm version for a package
    #
    # @param directory [Dir] The directory
    #
    # @return [String] The version
    def elm_version(directory)
      if json(directory)['elm-version'] =~ /<\s*0\.19/
        '0.18'
      elsif json(directory)['elm-version'] =~ /<\s*0\.18/
        '0.17'
      elsif json(directory)['elm-version'] =~ /<\s*0\.17/
        '0.16'
      else
        ''
      end
    end

    Contract Dir => ArrayOf[Dependency]
    # Identifies dependencies from a directory
    #
    # @param directory [Dir] The directory
    #
    # @return [Array] The dependencies
    def identify(directory)
      raw = json(directory)

      dependencies = raw['dependencies'].to_h

      dependency_sources =
        raw['dependency-sources']
        .to_h
        .merge(@dependency_sources)

      dependencies.map do |package, constraint|
        constraints = Utils.transform_constraint constraint

        type =
          if dependency_sources.key?(package)
            source = dependency_sources[package]
            case source
            when Hash
              uri_type source['url'], Branch::Just(source['ref'])
            when String
              if File.exist?(source)
                Type::Directory(Pathname.new(source))
              else
                uri_type source, Branch::Just('master')
              end
            end
          else
            Type::Git(Uri::Github(package), Branch::Nothing())
          end

        type.source.identifier = self
        type.source.options = @options

        Dependency.new(package, type.source, constraints)
      end
    end

    Contract String, Branch => Type
    # Returns the type from the given arguments.
    #
    # @param url [String] The base url
    # @param branch [Branch] The branch
    #
    # @return [Type] The type
    def uri_type(url, branch)
      uri = GitCloneUrl.parse(url)
      case uri
      when URI::SshGit::Generic
        Type::Git(Uri::Ssh(uri), branch)
      when URI::HTTP
        Type::Git(Uri::Http(uri), branch)
      end
    end

    Contract Dir => HashOf[String => Any]
    # Returns the contents of the 'elm-package.json' for the given directory.
    #
    # @param directory [Dir] The directory
    #
    # @return [Hash] The contents
    def json(directory)
      path = File.join(directory, 'elm-package.json')
      JSON.parse(File.read(path))
    rescue JSON::ParserError
      warn "Invalid JSON in file: #{path.bold}"
    rescue Errno::ENOENT
      warn "Could not find file: #{path.bold}"
    end

    Contract String => Hash
    # Exits the current process and logs a given message.
    #
    # @param message [String] The message
    #
    # @return [Hash] An empty hash
    def warn(message)
      Logger.arrow message if @options[:vebose]
      {}
    end
  end
end
