# typed: strict
# frozen_string_literal: true

require "system_command"

module UnpackStrategy
  # Strategy for unpacking tar archives.
  class Tar
    include UnpackStrategy
    extend SystemCommand::Mixin

    sig { override.returns(T::Array[String]) }
    def self.extensions
      [
        ".tar",
        ".tbz", ".tbz2", ".tar.bz2",
        ".tgz", ".tar.gz",
        ".tlzma", ".tar.lzma",
        ".txz", ".tar.xz",
        ".tar.zst",
        ".crate"
      ]
    end

    sig { override.params(path: Pathname).returns(T::Boolean) }
    def self.can_extract?(path)
      return true if path.magic_number.match?(/\A.{257}ustar/n)

      return false unless [Bzip2, Gzip, Lzip, Xz, Zstd].any? { |s| s.can_extract?(path) }

      # Check if `tar` can list the contents, then it can also extract it.
      stdout, _, status = system_command("tar", args: ["--list", "--file", path], print_stderr: false).to_a
      (status.success? && !stdout.empty?) || false
    end

    private

    sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void }
    def extract_to_dir(unpack_dir, basename:, verbose:)
      Dir.mktmpdir("homebrew-tar", HOMEBREW_TEMP) do |tmpdir|
        tar_path = if DependencyCollector.tar_needs_xz_dependency? && Xz.can_extract?(path)
          subextract(Xz, Pathname(tmpdir), verbose)
        elsif DependencyCollector.tar_needs_bzip2_dependency? && Bzip2.can_extract?(path)
          subextract(Bzip2, Pathname(tmpdir), verbose)
        elsif Zstd.can_extract?(path)
          subextract(Zstd, Pathname(tmpdir), verbose)
        else
          path
        end

        system_command! "tar",
                        args:    ["--extract", "--no-same-owner",
                                  "--file", tar_path,
                                  "--directory", unpack_dir],
                        verbose:
      end
    end

    sig {
      params(extractor: T.any(T.class_of(Bzip2), T.class_of(Xz), T.class_of(Zstd)), dir: Pathname,
             verbose: T::Boolean).returns(Pathname)
    }
    def subextract(extractor, dir, verbose)
      extractor.new(path).extract(to: dir, verbose:)
      dir.children.fetch(0)
    end
  end
end
