require_relative "board_spec"
require_relative "config"

module Backlog
  # Pure repo -> board routing decision. No filesystem, no network: every
  # input (repo path, config contents, board inventory) is passed in.
  #
  # Decision, exactly one of:
  #   "use <board>"                 - an active board to work against
  #   "activate <archived board>"   - an archived board matches; rename it back
  #   "stop-and-discuss"            - no confident match; ask the human
  class Resolver
    Decision = Struct.new(:action, :board) do
      def to_s
        action == :use || action == :activate ? "#{action} #{board}" : "stop-and-discuss"
      end
    end

    DEV_ROOT = File.expand_path("~/dev")
    CLIENTS_ROOT = File.expand_path("~/clients")
    SERVERS_ROOT = File.expand_path("~/servers")

    # Path-based Planka project heuristic, shared by the resolver's own
    # ambiguity gate and the `board-ensure` CLI's --project default: "Dev"
    # under ~/dev/*, "Clients" under ~/clients/*, "Servers" under
    # ~/servers/* (the servers umbrella repo — ADR: infrastructure is a
    # distinct domain from Dev), nil (ambiguous) otherwise. Board-ensure
    # always accepts an arbitrary --project name too — this heuristic only
    # supplies the default when none is given.
    #
    # @param repo_path [String]
    # @return [String, nil]
    def self.project_for(repo_path)
      path = File.expand_path(repo_path)
      return "Dev" if under_root?(path, DEV_ROOT)
      return "Clients" if under_root?(path, CLIENTS_ROOT)
      return "Servers" if under_root?(path, SERVERS_ROOT)

      nil
    end

    def self.under_root?(path, root)
      path == root || path.start_with?("#{root}/")
    end
    private_class_method :under_root?

    # @param repo_path [String] absolute path to the repo
    # @param config_contents [String, nil] raw contents of .cc-os/config, or nil/absent
    # @param boards [Array<String>] board names known to exist on the Planka instance
    #   (both active and archived--prefixed names)
    def initialize(repo_path:, config_contents:, boards:)
      @repo_path = File.expand_path(repo_path)
      @config = Config.new(config_contents)
      @boards = boards
    end

    # @return [Decision]
    def resolve
      configured_board = @config.planka_board
      return decision_for_name(configured_board) || stop_and_discuss if configured_board

      derived_decision || stop_and_discuss
    end

    # @return [String] one of "use <board>" / "activate <board>" / "stop-and-discuss"
    def resolve_string = resolve.to_s

    private

    def derived_decision
      return nil if project.nil?

      decision_for_name(board_name)
    end

    def decision_for_name(name)
      return use(name) if @boards.include?(name)
      return activate(name) if @boards.include?(BoardSpec.archived_name(name))

      nil
    end

    def stop_and_discuss = Decision.new(:stop_and_discuss, nil)
    def use(name) = Decision.new(:use, name)
    def activate(name) = Decision.new(:activate, BoardSpec.archived_name(name))

    def board_name = File.basename(@repo_path)

    def project = self.class.project_for(@repo_path)
  end
end
