# lib/aia/cli.rb

HOME    = Pathname.new(ENV['HOME'])
MY_NAME = 'aia'


require 'hashie'
require 'pathname'
require 'yaml'
require 'toml-rb'


class AIA::Cli
  CF_FORMATS    = %w[yml yaml toml]
  ENV_PREFIX    = self.name.split('::').first.upcase + "_"
  MAN_PAGE_PATH = Pathname.new(__dir__) + '../../man/aia.1'
  

  def initialize(args)
    args = args.split(' ') if args.is_a? String

    setup_options_with_defaults(args) # 1. defaults
    load_env_options                  # 2. over-ride with envars
    process_command_line_arguments    # 3. over-ride with command line options

    # 4. over-ride everything with config file
    load_config_file unless AIA.config.config_file.nil?

    convert_to_pathname_objects

    setup_prompt_manager

    execute_immediate_commands
  end


  def convert_pathname_objects!(converting_to_pathname: true)
    path_keys = AIA.config.keys.grep(/_(dir|file)\z/)
    path_keys.each do |key|
      case AIA.config[key]
      when String
        AIA.config[key] = string_to_pathname(AIA.config[key])
      when Pathname
        AIA.config[key] = pathname_to_string(AIA.config[key]) unless converting_to_pathname
      end
    end
  end


  def string_to_pathname(string)
    ['~/', '$HOME/'].each do |prefix|
      if string.start_with? prefix
        string = string.gsub(prefix, HOME.to_s+'/')
        break
      end
    end

    pathname = Pathname.new(string)
    pathname.relative? ? Pathname.pwd + pathname : pathname
  end


  def pathname_to_string(pathname)
    pathname.to_s
  end


  def convert_to_pathname_objects
    convert_pathname_objects!(converting_to_pathname: true)
  end


  def convert_from_pathname_objects
    convert_pathname_objects!(converting_to_pathname: false)
  end


  def load_env_options
    known_keys = @options.keys

    keys  = ENV.keys
              .select{|k| k.start_with?(ENV_PREFIX)}
              .map{|k| k.gsub(ENV_PREFIX,'').downcase.to_sym}

    keys.each do |key|
      envar_key       = ENV_PREFIX + key.to_s.upcase
      if known_keys.include?(key)
        AIA.config[key] = ENV[envar_key]
      elsif known_keys.include?("#{key}?".to_sym)
        key = "#{key}?".to_sym
        AIA.config[key] = %w[true t yes yea y 1].include?(ENV[envar_key].strip.downcase) ? true : false
      else
        # This is a new config key
        AIA.config[key] = ENV[envar_key]
      end
    end
  end


  def load_config_file
    AIA.config.config_file = Pathname.new(AIA.config.config_file)
    if AIA.config.config_file.exist?
      AIA.config.merge! parse_config_file
    else
      abort "Config file does not exist: #{AIA.config.config_file}"
    end
  end


  def setup_options_with_defaults(args)
    # TODO: This structure if flat; consider making it
    #       at least two levels totake advantage of
    #       YAML and TOML capabilities to isolate
    #       common options within a section.
    #
    @options    = {
      #           Default
      # Key       Value,      switches
      arguments:  [args], # NOTE: after process, prompt_id and context_files will be left
      directives: [[]],   # an empty Array as the default value
      extra:      [''],   # 
      #
      model:      ["gpt-4-1106-preview",  "--llm --model"],
      #
      dump:       [nil,       "--dump"],
      completion: [nil,       "--completion"],
      #
      edit?:      [false,     "-e --edit"],
      debug?:     [false,     "-d --debug"],
      verbose?:   [false,     "-v --verbose"],
      version?:   [false,     "--version"],
      help?:      [false,     "-h --help"],
      fuzzy?:     [false,     "-f --fuzzy"],
      markdown?:  [true,      "-m --markdown --no-markdown --md --no-md"],
      chat?:      [false,     "--chat"],
      terse?:     [false,     "--terse"],
      speak?:     [false,     "--speak"],
      #
      # TODO: May have to process the
      #       "~" character and replace it with HOME
      #
      # TODO: Consider using standard suffix of _dif and _file
      #       to signal Pathname objects fo validation
      #
      config_file:[nil,                       "-c --config"],
      prompts_dir:["~/.prompts",              "-p --prompts"],
      output_file:["temp.md",                 "-o --output --no-output"],
      log_file:   ["~/.prompts/_prompts.log", "-l --log --no-log"],
      #
      backend:    ['mods',    "-b --be --backend --no-backend"],
    }
    
    AIA.config = AIA::Config.new(@options.transform_values { |values| values.first })
  end


  def arguments
    AIA.config.arguments
  end


  def execute_immediate_commands
    show_usage        if AIA.config.help?
    show_version      if AIA.config.version?
    dump_config_file  if AIA.config.dump
    show_completion   if AIA.config.completion
  end


  def dump_config_file
    a_hash = prepare_config_as_hash

    case AIA.config.dump.downcase
    when 'yml', 'yaml'
      puts YAML.dump(a_hash)
    when 'toml'
      puts TomlRB.dump(a_hash)
    else
      abort "Invalid config file format request.  Only #{CF_FORMATS.join(', ')} are supported."
    end

    exit
  end


  def prepare_config_as_hash
    convert_from_pathname_objects
    
    a_hash          = AIA.config.to_h
    a_hash['dump']  = nil

    a_hash.delete('arguments')
    a_hash.delete('config_file')

    a_hash
  end


  def process_command_line_arguments
    # get the options meant for the backend AI command
    # doing this first in case there are any options that conflict
    # between frontend and backend.
    extract_extra_options

    @options.keys.each do |option|
      check_for option
    end

    bad_options = arguments.select{|a| a.start_with?('-')}

    unless bad_options.empty?
      puts <<~EOS

        ERROR: Unknown options: #{bad_options.join(' ')}

      EOS
      
      show_usage

      exit
    end
  end


  def check_for(option_sym)
    # sometimes @options has stuff that is not a command line option
    return if @options[option_sym].nil? || @options[option_sym].size <= 1

    boolean   = option_sym.to_s.end_with?('?')
    switches  = @options[option_sym][1].split

    switches.each do |switch|
      if arguments.include?(switch)
        index = arguments.index(switch)

        if boolean
          AIA.config[option_sym] = switch.include?('-no-') ? false : true
          arguments.slice!(index,1)
        else
          if switch.include?('-no-')
            AIA.config[option_sym] = switch.include?('output') ? STDOUT : nil
            arguments.slice!(index,1)
          else
            AIA.config[option_sym] = arguments[index + 1]
            arguments.slice!(index,2)
          end
        end
        
        break
      end
    end
  end

  # aia usage is maintained in a man page
  def show_usage
    @options[:help?][0] = false 
    puts `man #{MAN_PAGE_PATH}`
    show_verbose_usage if AIA.config.verbose?
    exit
  end
  alias_method :show_help, :show_usage


  def show_verbose_usage
    puts <<~EOS

      ======================================
      == Currently selected Backend: #{AIA.config.backend} ==
      ======================================

    EOS
    puts `mods --help` if "mods" == AIA.config.backend
    puts `sgpt --help` if "sgpt" == AIA.config.backend
    puts
  end
  # alias_method :show_verbose_help, :show_verbose_usage


  def show_completion
    shell   = AIA.config.completion
    script  = Pathname.new(__dir__) + "aia_completion.#{shell}"

    if script.exist?
      puts
      puts script.read
      puts
    else
      STDERR.puts <<~EOS

        ERROR: The shell '#{shell}' is not supported.

      EOS
    end

    exit    
  end


  def show_version
    puts AIA::VERSION
    exit
  end


  def setup_prompt_manager
    @prompt     = nil

    PromptManager::Prompt.storage_adapter = 
      PromptManager::Storage::FileSystemAdapter.config do |config|
        config.prompts_dir        = AIA.config.prompts_dir
        config.prompt_extension   = '.txt'
        config.params_extension   = '.json'
        config.search_proc        = nil
        # TODO: add the rgfzf script for search_proc
      end.new
  end


  # Get the additional CLI arguments intended for the
  # backend gen-AI processor.
  def extract_extra_options
    extra_index = arguments.index('--')

    if extra_index
      AIA.config.extra = arguments.slice!(extra_index..-1)[1..].join(' ')
    end
  end


  def parse_config_file
    case AIA.config.config_file.extname.downcase
    when '.yaml', '.yml'
      YAML.safe_load(AIA.config.config_file.read)
    when '.toml'
      TomlRB.parse(AIA.config.config_file.read)
    else
      abort "Unsupported config file type: #{AIA.config.config_file.extname}"
    end
  end
end