# frozen_string_literal: true

module LicenseFinder
  class Mix < PackageManager
    def initialize(options = {})
      super
      @command = options[:mix_command] || Mix.package_management_command
      @deps_path = Pathname(options[:mix_deps_dir] || 'deps')
    end

    def current_packages
      mix_output.map do |name, version|
        MixPackage.new(
          name,
          version,
          install_path: @deps_path.join(name),
          logger: logger,
          spec_licenses: licenses(name)
        )
      end
    end

    # Adapted from licenser: https://github.com/unnawut/licensir/blob/71f96f8734adc73c0651050bd9f0e20ff52c61a8/lib/licensir/scanner.ex#L61
    def licenses(name)
      config_path = @deps_path.join(name).join('hex_metadata.config')
      # rubocop:disable Metrics/LineLength
      args = "\\\"#{config_path}\\\" |> :file.consult() |> case do {:ok, metadata} -> metadata; {:error, _} -> [] end |> List.keyfind(\\\"licenses\\\", 0) |> case do {_, licenses} -> licenses; _ -> [] end |> Enum.join(\\\"\\t\\\") |> IO.puts()"
      # rubocop:enable Metrics/LineLength
      command = "#{@command} run --no-start --no-compile -e \"#{args}\""
      stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
      raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?

      stdout.strip.split("\t")
    end

    def self.package_management_command
      'mix'
    end

    def self.prepare_command
      'mix deps.get'
    end

    def possible_package_paths
      [project_path.join('mix.exs')]
    end

    private

    def end_of_package_lines?(line)
      line == 'ok'
    end

    def mix_output
      command = "#{@command} deps"
      stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) }
      raise "Command '#{command}' failed to execute: #{stderr}" unless status.success?

      packages_lines(stdout)
        .reject { |package_lines| package_lines.length == 1 } # in_umbrella: true dependencies
        .map { |package_lines| [package_lines[0].split(' ')[1], resolve_version(package_lines[1])] }
    end

    def packages_lines(stdout)
      packages_lines, last_package_lines =
        stdout
        .each_line
        .map(&:strip)
        .reject { |line| end_of_package_lines?(line) }
        .reduce([[], []]) do |(packages_lines, package_lines), line|
        if start_of_package_lines?(line)
          packages_lines.push(package_lines) unless package_lines.empty?

          [packages_lines, [line]]
        else
          package_lines.push(line)
          [packages_lines, package_lines]
        end
      end

      packages_lines.push(last_package_lines)
    end

    def resolve_version(line)
      line =~ /locked at ([^\s]+)/ ? Regexp.last_match(1) : line
    end

    def start_of_package_lines?(line)
      line.start_with?('* ')
    end
  end
end