# typed: strict
# frozen_string_literal: true

require "utils/user"
require "open3"
require "utils/output"

module Cask
  # Helper functions for various cask operations.
  module Utils
    extend ::Utils::Output::Mixin

    BUG_REPORTS_URL = "https://github.com/Homebrew/homebrew-cask#reporting-bugs"
    FULL_DISK_ACCESS_TCC_PATH = "~/Library/Application Support/com.apple.TCC"

    sig { params(access: String).returns(String) }
    def self.privacy_security_preference_pane(access)
      navigation_path = if MacOS.version >= :ventura
        "System Settings → Privacy & Security"
      else
        "System Preferences → Security & Privacy → Privacy"
      end

      "#{navigation_path} → #{access}"
    end

    sig { returns(T::Boolean) }
    def self.full_disk_access_enabled?
      File.readable?(File.expand_path(FULL_DISK_ACCESS_TCC_PATH))
    end

    sig { params(path: Pathname, command: T.class_of(SystemCommand)).void }
    def self.gain_permissions_mkpath(path, command: SystemCommand)
      dir = path.ascend.find(&:directory?)
      return if path == dir

      if dir&.writable?
        path.mkpath
      else
        command.run!("mkdir", args: ["-p", "--", path], sudo: true, print_stderr: false)
      end
    end

    sig { params(path: Pathname, command: T.class_of(SystemCommand)).void }
    def self.gain_permissions_rmdir(path, command: SystemCommand)
      gain_permissions(path, [], command) do |p|
        if p.parent.writable?
          FileUtils.rmdir p
        else
          command.run!("rmdir", args: ["--", p], sudo: true, print_stderr: false)
        end
      end
    end

    sig { params(path: Pathname, command: T.class_of(SystemCommand)).void }
    def self.gain_permissions_remove(path, command: SystemCommand)
      directory = false
      permission_flags = if path.symlink?
        ["-h"]
      elsif path.directory?
        directory = true
        ["-R"]
      elsif path.exist?
        []
      else
        # Nothing to remove.
        return
      end

      gain_permissions(path, permission_flags, command) do |p|
        if p.parent.writable?
          if directory
            FileUtils.rm_r p
          else
            FileUtils.rm_f p
          end
        else
          recursive_flag = directory ? ["-R"] : []
          command.run!("/bin/rm", args: recursive_flag + ["-f", "--", p], sudo: true, print_stderr: false)
        end
      end
    end

    sig {
      params(
        path:         Pathname,
        command_args: T::Array[String],
        command:      T.class_of(SystemCommand),
        _block:       T.proc.params(path: Pathname).void,
      ).void
    }
    def self.gain_permissions(path, command_args, command, &_block)
      tried_permissions = false
      tried_ownership = false
      begin
        yield path
      rescue
        # in case of permissions problems
        unless tried_permissions
          print_stderr = Context.current.debug? || Context.current.verbose?
          # TODO: Better handling for the case where path is a symlink.
          #       The `-h` and `-R` flags cannot be combined and behavior is
          #       dependent on whether the file argument has a trailing
          #       slash. This should do the right thing, but is fragile.
          command.run("/usr/bin/chflags",
                      print_stderr:,
                      args:         command_args + ["--", "000", path])
          command.run("chmod",
                      print_stderr:,
                      args:         command_args + ["--", "u+rwx", path])
          command.run("chmod",
                      print_stderr:,
                      args:         command_args + ["-N", path])
          tried_permissions = true
          retry # rmtree
        end

        unless tried_ownership
          # in case of ownership problems
          # TODO: Further examine files to see if ownership is the problem
          #       before using `sudo` and `chown`.
          ohai "Using sudo to gain ownership of path '#{path}'"
          command.run("chown",
                      args: command_args + ["--", User.current.to_s, path],
                      sudo: true)
          tried_ownership = true
          # retry chflags/chmod after chown
          tried_permissions = false
          retry # rmtree
        end

        raise
      end
    end

    sig { params(path: Pathname).returns(T::Boolean) }
    def self.path_occupied?(path)
      path.exist? || path.symlink?
    end

    sig { params(name: String).returns(String) }
    def self.token_from(name)
      name.downcase
          .gsub("+", "-plus-")
          .gsub(/[ _·•]/, "-")
          .gsub(/[^\w@-]/, "")
          .gsub(/--+/, "-")
          .delete_prefix("-")
          .delete_suffix("-")
    end
  end
end
