class Hiiro

def self.init(*oargs, plugins: [], logging: false, tasks: false, task_scope: nil, **values, &block)

def self.init(*oargs, plugins: [], logging: false, tasks: false, task_scope: nil, **values, &block)
  load_env
  if values[:args]
    args = values[:args]
  else
    args ||= oargs
    args = ARGV if args.empty?
  end
  bin_name = values[:bin_name] || $0
  new(bin_name, *args, logging: logging, tasks: tasks, task_scope: task_scope, **values).tap do |hiiro|
    hiiro.load_plugins(plugins)
    hiiro.add_subcommand(:pry) { |*args|
      binding.pry
    }
    hiiro.add_subcmd(:edit, **values) { |*args|
      hiiro.edit_files(hiiro.bin)
    }
    if hiiro.tasks_enabled?
      hiiro.add_subcmd(:task) do |*args|
        tm = TaskManager.new(hiiro, scope: :task)
        Tasks.build_hiiro(hiiro, tm).run
      end
      hiiro.add_subcmd(:subtask) do |*args|
        tm = TaskManager.new(hiiro, scope: :subtask)
        Tasks.build_hiiro(hiiro, tm).run
      end
    end
    if block
      if block.arity == 1
        block.call(hiiro)
      else
        hiiro.instance_eval(&block)
      end
    end
  end
end

def self.load_env

def self.load_env
  Config.plugin_files.each do |plugin_file|
    require plugin_file
  end
  self
end

def self.options(&block)

def self.options(&block)
  Options.setup(&block)
end

def self.run(*args, plugins: [], logging: false, tasks: false, task_scope: nil, **values, &block)

def self.run(*args, plugins: [], logging: false, tasks: false, task_scope: nil, **values, &block)
  hiiro = init(*args, plugins:, logging:, tasks:, task_scope:, **values, &block)
  hiiro.run
end

def add_default(**values, &handler)

def add_default(**values, &handler)
  runners.add_default(handler, **global_values, **values)
end

def add_subcommand(*names, opts: nil, **values, &handler)

def add_subcommand(*names, opts: nil, **values, &handler)
  names.each do |name|
    runners.add_subcommand(name, handler, opts: opts, **global_values, **values)
  end
end

def attach_method(name, &block)

def attach_method(name, &block)
  define_singleton_method(name.to_sym, &block)
end

def auto_var_name(path, existing_vars)

def auto_var_name(path, existing_vars)
  parts = path.delete_prefix('/').split('/')
  (1..parts.length).each do |n|
    candidate = parts.last(n).join('_').upcase.gsub(/[^A-Z0-9]/, '_').squeeze('_').delete_prefix('_').delete_suffix('_')
    return candidate unless existing_vars.key?(candidate)
  end
  "DIR_#{existing_vars.length + 1}"
end

def build_location_vars(list)

def build_location_vars(list)
  paths = list.map { |r| r.location }.compact.map { |loc| loc.sub(/:\d+$/, '') }
  dirs  = paths.map { |p| File.dirname(p) }.uniq
  ancestors = consolidate_dirs(dirs, min_depth: 4)
  vars = {}
  ancestors.sort.each do |ancestor|
    name = auto_var_name(ancestor, vars)
    vars[name] = ancestor
  end
  vars
end

def consolidate_dirs(dirs, min_depth:)

ancestor is deep enough to be a useful alias (>= min_depth components).
Recursively group directories under their common ancestor when that
def consolidate_dirs(dirs, min_depth:)
  return dirs if dirs.length <= 1
  lca       = dirs.reduce { |a, b| path_lca(a, b) }
  lca_depth = lca.delete_prefix('/').split('/').reject(&:empty?).length
  if lca_depth >= min_depth
    [lca]
  else
    lca_parts = lca.split('/')
    subgroups = dirs.group_by { |d| d.split('/').first(lca_parts.length + 1).join('/') }
    subgroups.flat_map { |_, group| consolidate_dirs(group, min_depth: min_depth) }.uniq
  end
end

def default_subcommand

def default_subcommand
  Runners::Subcommand.new(
    bin_name,
    :DEFAULT,
    lambda { |*args| help; false },
  )
end

def edit_files(*files, max_splits: 3)

def edit_files(*files, max_splits: 3)
  if editor.match?(/vim/i)
    if files.count > max_splits.to_i
      system(editor, '-O' + max_splits.to_i.to_s, *files)
    else
      system(editor, '-O', *files)
    end
  else
    system(editor, *files)
  end
end

def editor

def editor
  editors = Bins.glob(%w[nvim vim vi]).find(&File.method(:executable?))
  ENV['EDITOR'] || editors.first
end

def environment

def environment
  @environment ||= Environment.current
end

def full_name

def full_name
  runner&.full_name || [bin_name, subcmd].join(?-)
end

def fuzzyfind(lines)

def fuzzyfind(lines)
  Fuzzyfind.select(lines)
end

def fuzzyfind_from_map(mapping)

def fuzzyfind_from_map(mapping)
  Fuzzyfind.map_select(mapping)
end

def get_value(name)

def get_value(name)
  runner&.values&.[](name)
end

def git

def git
  @git ||= Git.new(self, Dir.pwd)
end

def handle_result(result)

def handle_result(result)
  exit 0 if result.nil? || result
  exit 1
end

def help(options=nil)

def help(options=nil)
  ambiguous = runners.ambiguous_matches
  puts "Current command: #{bin_name}!"
  if ambiguous.any?
    puts "Ambiguous subcommand #{subcmd.inspect}!"
    puts
    puts "Did you mean one of these?"
    list_runners(ambiguous)
    puts
  else
    puts "Subcommand required for #{bin_name}"
    puts
    puts "Possible subcommands:"
    list_runners(runners.all_runners)
    puts
  end
  if options
    puts "Options:"
    puts options.help_text
    puts
  end
  exit 1
end

def initialize(bin, *all_args, logging: false, tasks: false, task_scope: nil, **values)

def initialize(bin, *all_args, logging: false, tasks: false, task_scope: nil, **values)
  @bin = bin
  @bin_name = File.basename(bin)
  @all_args = all_args
  @subcmd, *@args = all_args # normally i would never do this
  @loaded_plugins = []
  @logging = logging
  @tasks_enabled = tasks
  @task_scope = task_scope
  @global_values = values
  @full_command = [
    bin_name,
    *all_args,
  ].map(&:shellescape).join(' ')
end

def list_runners(list)

def list_runners(list)
  sorted = list.sort_by(&:subcommand_name)
  vars = build_location_vars(sorted)
  vars.each { |var_name, path| puts "export #{var_name}=\"#{path}\"" }
  puts if vars.any?
  max_name   = sorted.map { |r| r.subcommand_name.length }.max || 0
  max_type   = sorted.map { |r| r.type.to_s.length }.max || 0
  max_params = sorted.map { |r| r.params_string.to_s.length }.max || 0
  sorted.each do |r|
    name       = r.subcommand_name.ljust(max_name)
    type       = "(#{r.type})".ljust(max_type + 2)
    params     = r.params_string
    params_col = params ? params.ljust(max_params) : ''.ljust(max_params)
    location   = shorten_location(r.location, vars)
    puts "  #{name}  #{params_col}  #{type}  #{location}"
  end
end

def load_plugin(plugin_const)

def load_plugin(plugin_const)
  if plugin_const.is_a?(String) || plugin_const.is_a?(Symbol)
    begin
      plugin_const = Kernel.const_get(plugin_const.to_sym)
    rescue => e
      puts "unable to load plugin: #{plugin_const}"
      puts "Error message: #{e.message}"
      return
    end
  end
  return if @loaded_plugins.include?(plugin_const)
  plugin_const.load(self)
  @loaded_plugins.push(plugin_const)
end

def load_plugins(*plugins)

def load_plugins(*plugins)
  plugins.flatten.each { |plugin| load_plugin(plugin) }
end

def log(message)

def log(message)
  return unless logging
  puts "[Hiiro: #{bin_name} #{(runner&.subcommand_name || subcmd).inspect}]: #{message}"
end

def make_child(custom_subcmd=nil, custom_args=nil, **kwargs, &block)

def make_child(custom_subcmd=nil, custom_args=nil, **kwargs, &block)
  child_subcmd = custom_subcmd || subcmd
  child_args = custom_args || args
  child_bin_name = [bin, child_subcmd.to_s].join(?-)
  Hiiro.init(bin_name: child_bin_name, args: child_args, **kwargs, &block)
end

def parsed_args

def parsed_args
  i = Args.new(*args)
end

def path_lca(a, b)

def path_lca(a, b)
  a_parts = a.split('/')
  b_parts = b.split('/')
  a_parts.zip(b_parts).take_while { |x, y| x == y }.map(&:first).join('/')
end

def pins = @pins ||= Pin.new(self)

def pins = @pins ||= Pin.new(self)

def queue

def queue
  @queue ||= Queue.current
end

def run

def run
  result = runner.run(*args)
  handle_result(result)
  exit 1
rescue => e
  puts "ERROR: #{e.message}"
  puts e.backtrace
  exit 1
end

def run_subcommand(name, *args)

def run_subcommand(name, *args)
  runners.run_subcommand(name, *args)
end

def runnable?

def runnable?
  runner
end

def runner

def runner
  runners.runner || runners.default_subcommand
end

def runners

def runners
  @runners ||= Runners.new(self)
end

def shorten_location(loc, vars)

def shorten_location(loc, vars)
  return loc if loc.nil?
  vars.sort_by { |_, path| -path.length }.each do |var_name, path|
    return loc.sub(path, "$#{var_name}") if loc.start_with?(path + '/') || loc == path
  end
  loc
end

def start_tmux_session(name, **opts)

def start_tmux_session(name, **opts)
  tmux_client.open_session(name, **opts)
end

def subcommand_names

def subcommand_names
  runners.subcommand_names
end

def task_manager

def task_manager
  return nil unless tasks_enabled?
  @task_manager ||= TaskManager.new(self, scope: task_scope || :task)
end

def tasks

def tasks
  task_manager&.tasks
end

def tasks_enabled?

def tasks_enabled?
  @tasks_enabled
end

def this

def this
  self
end

def tmux_client

def tmux_client
  @tmux_client ||= Tmux.client!(self)
end

def todo_manager

def todo_manager
  @todo_manager ||= TodoManager.new
end

def vim?

def vim?
  editor.to_s.match?(/vim/i)
end