class Hiiro::TaskManager

def app_path(app_name = nil)

def app_path(app_name = nil)
  task = current_task
  tree_root = if task
    tree = environment.find_tree(task.tree_name)
    tree&.path || File.join(Hiiro::WORK_DIR, task.tree_name)
  else
    Hiiro::Git.new(nil, Dir.pwd).root
  end
  if app_name.nil?
    print tree_root
    return
  end
  result = environment.app_matcher.find_all(app_name)
  case result.count
  when 0
    puts "ERROR: No matches found"
    puts
    puts "Possible Apps:"
    environment.all_apps.each { |a| puts format("  %-20s => %s", a.name, a.relative_path) }
  when 1
    print result.first.item.resolve(tree_root)
  else
    puts "Multiple matches found:"
    result.matches.each { |m| puts format("  %-20s => %s", m.item.name, m.item.relative_path) }
  end
end

def apply_sparse_checkout(path, group_names)

def apply_sparse_checkout(path, group_names)
  dirs = SparseGroups.dirs_for_groups(group_names)
  if dirs.empty?
    puts "WARNING: No directories found for sparse groups: #{group_names.join(', ')}"
    return
  end
  puts "Applying sparse checkout (groups: #{group_names.join(', ')})..."
  Hiiro::Git.new(nil, path).sparse_checkout(path, dirs)
end

def branch(task_name = nil)

def branch(task_name = nil)
  if task_name.nil?
    branch = select_branch_interactive
    return unless branch
    print branch
    return
  end
  task = task_by_name(task_name)
  unless task
    puts "Task not found: #{task_name}"
    return
  end
  if task.branch
    print task.branch
  elsif task.tree&.detached?
    puts "(detached HEAD)"
  else
    puts "(no branch)"
  end
end

def capture_tmux_windows(session)

def capture_tmux_windows(session)
  output = `tmux list-windows -t #{session} -F '\#{window_index}:\#{window_name}:\#{pane_current_path}' 2>/dev/null`
  output.lines.map(&:strip).map { |line|
    idx, name, path = line.split(':')
    { 'index' => idx, 'name' => name, 'path' => path }
  }
end

def cd_to_app(app_name = nil)

def cd_to_app(app_name = nil)
  task = current_task
  unless task
    puts "ERROR: Not currently in a task session"
    return
  end
  if app_name.nil? || app_name.empty?
    tree = environment.find_tree(task.tree_name)
    send_cd(tree&.path || File.join(Hiiro::WORK_DIR, task.tree_name))
    return
  end
  result = resolve_app(app_name, task)
  return unless result
  _resolved_name, app_path = result
  send_cd(app_path)
end

def cd_to_task(task)

def cd_to_task(task)
  unless task
    puts "Task not found"
    return
  end
  tree = environment.find_tree(task.tree_name)
  path = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
  send_cd(path)
end

def config

def config
  environment.config
end

def current_parent_task

def current_parent_task
  task = current_task
  return nil unless task
  if task.subtask?
    environment.find_task(task.parent_name)
  else
    task
  end
end

def current_session

def current_session
  environment.session
end

def current_task

def current_task
  environment.task
end

def current_tree

def current_tree
  environment.tree
end

def find_available_tree

def find_available_tree
  assigned_tree_names = environment.all_tasks.map(&:tree_name)
  environment.all_trees.find { |tree| !assigned_tree_names.include?(tree.name) }
end

def initialize(hiiro, scope: :task, environment: nil)

def initialize(hiiro, scope: :task, environment: nil)
  @hiiro = hiiro
  @scope = scope
  @environment = environment || Environment.current
end

def list

def list
  items = tasks
  if items.empty?
    puts scope == :subtask ? "No subtasks found" : "No tasks found"
    puts "Use 'h #{scope} start NAME' to create one."
    return
  end
  current = current_task
  label = scope == :subtask ? "Subtasks" : "Tasks"
  if scope == :subtask && current
    parent = current_parent_task
    label = "Subtasks of '#{parent&.name}'" if parent
  end
  puts "#{label}:"
  puts
  client_map = Hiiro::Tmux::Session.client_map
  # Collect rows as {prefix, name, tree, branch, session} so we can
  # compute max column widths before rendering.
  rows = []
  items.each do |task|
    marker = (current && current.name == task.name) ? "*" : " "
    attach = client_map.key?(task.session_name) ? "@" : " "
    rows << { prefix: "#{marker}#{attach} ", **task.display_data(scope: scope, environment: environment) }
    if scope == :task
      subtasks(task).each do |st|
        sub_marker = (current && current.name == st.name) ? "*" : " "
        sub_attach = client_map.key?(st.session_name) ? "@" : " "
        rows << { prefix: "#{sub_marker}#{sub_attach} - ", **st.display_data(scope: :subtask, environment: environment) }
      end
    end
  end
  # Column widths: the name column absorbs the variable-length prefix so
  # that tree/branch/session always start at the same position.
  name_col   = rows.map { |r| r[:prefix].length + r[:name].length }.max
  tree_col   = rows.map { |r| r[:tree].length }.max
  branch_col = rows.map { |r| r[:branch].length }.max
  rows.each do |r|
    name_pad = name_col - r[:prefix].length
    print r[:prefix]
    puts format("%-#{name_pad}s  %-#{tree_col}s  %-#{branch_col}s  %s",
                r[:name], r[:tree], r[:branch], r[:session])
  end
  available = environment.all_trees.reject { |t|
    environment.all_tasks.any? { |task| task.tree_name == t.name }
  }
  if available.any?
    puts
    avail_name_col = [available.map { |t| t.name.length }.max, name_col].max
    available.each do |tree|
      branch_str = tree.branch ? "[#{tree.branch}]" : tree.detached? ? "[(detached)]" : ""
      puts format("  %-#{avail_name_col}s  (available)  %s", tree.name, branch_str).rstrip
    end
  end
  if scope == :task
    task_session_names = environment.all_tasks.map(&:session_name)
    extra_sessions = environment.all_sessions.reject { |s| task_session_names.include?(s.name) }
    if extra_sessions.any?
      puts
      extra_name_col = [extra_sessions.map { |s| s.name.length }.max, name_col].max
      extra_sessions.sort_by(&:name).each do |session|
        attach = client_map.key?(session.name) ? "@" : " "
        puts format(" %s %-#{extra_name_col}s  (tmux session)", attach, session.name)
      end
    end
  end
end

def list_apps

def list_apps
  apps = environment.all_apps
  if apps.any?
    puts "Configured apps:"
    puts
    apps.each do |app|
      puts format("  %-20s => %s", app.name, app.relative_path)
    end
  else
    puts "No apps configured."
    puts "Create #{APPS_FILE} with format:"
    puts "  app_name: relative/path/from/repo"
  end
end

def open_app(app_name)

def open_app(app_name)
  task = current_task
  unless task
    puts "ERROR: Not currently in a task session"
    return
  end
  result = resolve_app(app_name, task)
  return unless result
  resolved_name, app_path = result
  system('tmux', 'new-window', '-n', resolved_name, '-c', app_path)
  puts "Opened '#{resolved_name}' in new window (#{app_path})"
end

def resolve_app(app_name, task)

def resolve_app(app_name, task)
  tree = environment.find_tree(task.tree_name)
  tree_root = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
  result = environment.app_matcher.find_all(app_name)
  case result.count
  when 0
    exact = File.join(tree_root, app_name)
    return [app_name, exact] if Dir.exist?(exact)
    nested = File.join(tree_root, app_name, app_name)
    return [app_name, nested] if Dir.exist?(nested)
    puts "ERROR: App '#{app_name}' not found"
    list_apps
    nil
  when 1
    app = result.first.item
    [app.name, app.resolve(tree_root)]
  else
    exact = result.matches.find { |m| m.item.name == app_name }
    if exact
      [exact.item.name, exact.item.resolve(tree_root)]
    else
      puts "ERROR: '#{app_name}' matches multiple apps:"
      result.matches.each { |m| puts "  #{m.item.name}" }
      nil
    end
  end
end

def save

def save
  task = current_task
  unless task
    puts "ERROR: Not currently in a task session"
    return
  end
  windows = capture_tmux_windows(task.session_name)
  puts "Saved task '#{task.name}' state (#{windows.count} windows)"
end

def select_branch_interactive(prompt = nil)

def select_branch_interactive(prompt = nil)
  name_map = if scope == :subtask
    tasks.sort_by(&:short_name).each_with_object({}) { |t, h| h[format('%-25s  | %s', t.short_name, t.branch)] = t.branch }
  else
    environment.all_tasks.sort_by(&:name).each_with_object({}) { |t, h| h[format('%-25s  | %s', t.name, t.branch)] = t.branch }
  end
  return nil if name_map.empty?
  hiiro.fuzzyfind_from_map(name_map)
end

def select_task_interactive(prompt = nil)

def select_task_interactive(prompt = nil)
  task_list = if scope == :subtask
    tasks.sort_by(&:short_name)
  else
    environment.all_tasks.sort_by(&:name)
  end
  mapping = {}
  all_data = task_list.map { |t| [t, t.display_data(scope: scope, environment: environment)] }
  name_col   = all_data.map { |_, d| d[:name].length }.max || 0
  tree_col   = all_data.map { |_, d| d[:tree].length }.max || 0
  branch_col = all_data.map { |_, d| d[:branch].length }.max || 0
  all_data.each do |task, d|
    line = format("%-#{name_col}s  %-#{tree_col}s  %-#{branch_col}s  %s",
                  d[:name], d[:tree], d[:branch], d[:session])
    mapping[line] = task
  end
  # Add non-task tmux sessions (exclude sessions that belong to tasks)
  if scope == :task
    task_session_names = environment.all_tasks.map(&:session_name)
    extra_sessions = environment.all_sessions.reject { |s| task_session_names.include?(s.name) }
    extra_sessions.sort_by(&:name).each do |session|
      line = format("%-25s  (tmux session)", session.name)
      mapping[line] = session
    end
  end
  return nil if mapping.empty?
  selected = hiiro.fuzzyfind_from_map(mapping)
  selected
end

def send_cd(path)

def send_cd(path)
  pane = ENV['TMUX_PANE']
  if pane
    system('tmux', 'send-keys', '-t', pane, "cd #{path}\n")
  else
    system('tmux', 'send-keys', "cd #{path}\n")
  end
end

def slash_lookup(input)

def slash_lookup(input)
  environment.find_task(input)
end

def start_task(name, app_name: nil, sparse_groups: [])

def start_task(name, app_name: nil, sparse_groups: [])
  existing = task_by_name(name)
  if existing
    puts "Task '#{existing.name}' already exists. Switching..."
    tree_path = existing.tree&.path
    if tree_path
      if sparse_groups.any?
        apply_sparse_checkout(tree_path, sparse_groups)
      else
        Hiiro::Git.new(nil, tree_path).disable_sparse_checkout(tree_path)
      end
    end
    switch_to_task(existing, app_name: app_name)
    return
  end
  task_name = scope == :subtask ? "#{current_parent_task.name}/#{name}" : name
  subtree_name = scope == :subtask ? "#{current_parent_task.name}/#{name}" : "#{name}/main"
  target_path = File.join(Hiiro::WORK_DIR, subtree_name)
  git = Hiiro::Git.new(nil, Hiiro::REPO_PATH)
  available = find_available_tree
  if available
    puts "Renaming worktree '#{available.name}' to '#{subtree_name}'..."
    FileUtils.mkdir_p(File.dirname(target_path))
    unless git.move_worktree(available.path, target_path, repo_path: Hiiro::REPO_PATH)
      puts "ERROR: Failed to rename worktree"
      return
    end
  else
    puts "Creating new worktree '#{subtree_name}'..."
    FileUtils.mkdir_p(File.dirname(target_path))
    unless git.add_worktree_detached(target_path, repo_path: Hiiro::REPO_PATH)
      puts "ERROR: Failed to create worktree"
      return
    end
  end
  apply_sparse_checkout(target_path, sparse_groups) if sparse_groups.any?
  session_name = task_name
  task = Task.new(name: task_name, tree: subtree_name, session: session_name)
  config.save_task(task)
  base_dir = target_path
  if app_name
    app = environment.find_app(app_name)
    base_dir = app.resolve(target_path) if app
  end
  Dir.chdir(base_dir)
  hiiro.start_tmux_session(session_name)
  puts "Started task '#{task_name}' in worktree '#{subtree_name}'"
end

def status

def status
  task = current_task
  unless task
    puts "Not currently in a task session"
    return
  end
  puts "Task: #{task.name}"
  puts "Worktree: #{task.tree_name}"
  tree = environment.find_tree(task.tree_name)
  puts "Path: #{tree&.path || '(unknown)'}"
  puts "Session: #{task.session_name}"
  puts "Parent: #{task.parent_name}" if task.subtask?
end

def stop_task(task)

def stop_task(task)
  unless task
    puts "Task not found"
    return
  end
  config.remove_task(task.name)
  subtasks(task).each { |st| config.remove_task(st.name) }
  puts "Stopped task '#{task.name}' (worktree available for reuse)"
end

def subtasks(task)

def subtasks(task)
  environment.all_tasks.select { |t| t.parent_name == task.name }
end

def switch_to_task(task, app_name: nil)

def switch_to_task(task, app_name: nil)
  unless task
    puts "Task not found"
    return
  end
  tree = environment.find_tree(task.tree_name)
  tree_path = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
  session_name = task.session_name
  session_exists = system('tmux', 'has-session', '-t', session_name, err: File::NULL)
  if session_exists
    hiiro.start_tmux_session(session_name)
  else
    base_dir = tree_path
    if app_name
      app = environment.find_app(app_name)
      base_dir = app.resolve(tree_path) if app
    end
    if Dir.exist?(base_dir)
      Dir.chdir(base_dir)
      hiiro.start_tmux_session(session_name)
    else
      puts "ERROR: Path '#{base_dir}' does not exist"
      return
    end
  end
  puts "Switched to '#{task.name}'"
end

def task_by_name(name)

def task_by_name(name)
  return slash_lookup(name) if name.include?('/')
  key = (scope == :subtask) ? :short_name : :name
  Hiiro::Matcher.new(tasks, key).by_prefix(name).first&.item
end

def task_by_service_info(info)

def task_by_service_info(info)
  if name = info['task']
    task_by_name(name)
  elsif session = info['tmux_session']
    task_by_session(session)
  elsif tree = info['tree']
    task_by_tree(tree)
  end
end

def task_by_session(session_name)

def task_by_session(session_name)
  environment.task_matcher.resolve(session_name, :session_name).resolved&.item
end

def task_by_tree(tree_name)

def task_by_tree(tree_name)
  environment.task_matcher.resolve(tree_name, :tree_name).resolved&.item
end

def tasks

def tasks
  if scope == :subtask
    parent = current_parent_task
    return [] unless parent
    main_task = Task.new(name: "#{parent.name}/main", tree: parent.tree_name, session: parent.session_name)
    subtask_list = environment.all_tasks.select { |t| t.parent_name == parent.name }
    [main_task, *subtask_list]
  else
    environment.all_tasks.select(&:top_level?)
  end
end

def value_for_task(task_name = nil, &block)

def value_for_task(task_name = nil, &block)
  if task_name
    task = task_by_name(task_name)
    return block.call(task) if task
  end
  task_list = scope == :subtask ? tasks.sort_by(&:short_name) : environment.all_tasks.sort_by(&:name)
  mapping = task_list.each_with_object({}) do |task, h|
    name = scope == :subtask ? task.short_name : task.name
    val = block.call(task)&.to_s
    line = format("%-25s  | %s", name, val)
    h[line] = val
  end
  hiiro.fuzzyfind_from_map(mapping)
end