#!/usr/bin/env ruby
# frozen_string_literal: true

$LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
require 'gli'
require 'doing/help_monkey_patch'
require 'doing/add_options'
require 'doing'
require 'tempfile'
require 'pp'

def class_exists?(class_name)
  klass = Module.const_get(class_name)
  klass.is_a?(Class)
rescue NameError
  false
end

if class_exists? 'Encoding'
  Encoding.default_external = Encoding::UTF_8 if Encoding.respond_to?('default_external')
  Encoding.default_internal = Encoding::UTF_8 if Encoding.respond_to?('default_internal')
end

include GLI::App
include Doing::Errors

version Doing::VERSION
hide_commands_without_desc true
autocomplete_commands false
wrap_help_text :one_line unless $stdout.isatty

include Doing::Types

@colors = Doing::Color
@wwid = Doing::WWID.new

Doing.logger.log_level = :info
env_log_level = nil

if ENV['DOING_LOG_LEVEL'] ||
   ENV['DOING_DEBUG'] || ENV['DOING_QUIET'] ||
   ENV['DOING_VERBOSE'] || ENV['DOING_PLUGIN_DEBUG']
  env_log_level = true
  # Quiet always wins
  if ENV['DOING_QUIET']&.truthy?
    Doing.logger.log_level = :error
  elsif ENV['DOING_PLUGIN_DEBUG']&.truthy?
    Doing.logger.log_level = :debug
  elsif ENV['DOING_DEBUG']&.truthy?
    Doing.logger.log_level = :debug
  elsif ENV['DOING_LOG_LEVEL']
    Doing.logger.log_level = ENV['DOING_LOG_LEVEL']
  end
end

Doing.logger.benchmark(:total, :start)

Doing.logger.benchmark(:configure, :start)
Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true }) if ENV['DOING_CONFIG']
Doing.logger.benchmark(:configure, :finish)

Doing.set('backup_dir', ENV['DOING_BACKUP_DIR']) if ENV['DOING_BACKUP_DIR']

# Set up class vars for backwards compatibility
@settings = Doing.settings

accept BooleanSymbol do |value|
  value.normalize_bool(:pattern)
end

accept CaseSymbol do |value|
  value.normalize_case(Doing.config.fetch('search', 'case', :smart))
end

accept AgeSymbol do |value|
  value.normalize_age(:newest)
end

accept OrderSymbol do |value|
  value.normalize_order(:asc)
end

accept MatchingSymbol do |value|
  value.normalize_matching(:pattern)
end

accept TagSortSymbol do |value|
  value.normalize_tag_sort(Doing.config.fetch('tag_sort', :name))
end

accept ExportTemplate do |value|
  if value !~ Doing::Plugins.plugin_regex(type: :export)
    raise Doing::Errors::InvalidPlugin.new('output', value)

  end

  tpl = nil

  Doing::Plugins.plugins[:export].each do |k, options|
    next unless value =~ /^(#{options[:trigger].normalize_trigger})$/i

    tpl = k
    break
  end

  tpl.nil? ? 'template' : value
end

accept TemplateName do |value|
  res = Doing.setting('templates').keys.select { |k| k =~ value.to_rx(distance: 2) }
  raise Doing::Errors::InvalidArgument, "Unknown template: #{value}" unless res.good?

  res.group_by(&:length).min.last[0]
end

accept DateBeginString do |value|
  Doing.original_options[:date_begin] = value
  res = if value =~ REGEX_TIME
          value
        else
          value.chronify(guess: :begin, future: false)
        end
  raise InvalidTimeExpression, 'Invalid start date' unless res

  res
end

accept DateEndString do |value|
  Doing.original_options[:date_end] = value
  res = if value =~ REGEX_TIME
          value
        else
          value.chronify(guess: :end, future: false)
        end
  raise InvalidTimeExpression, 'Invalid end date' unless res

  res
end

accept DateRangeString do |value|
  Doing.original_options[:date_range] = value
  start, finish = value.split_date_range
  raise InvalidTimeExpression, 'Invalid range' unless start

  finish ||= Time.now
  [start, finish]
end

accept DateRangeOptionalString do |value|
  Doing.original_options[:date_range] = value
  start, finish = value.split_date_range
  raise InvalidTimeExpression, 'Invalid range' unless start

  [start, finish]
end

accept DateIntervalString do |value|
  Doing.original_options[:date_interval] = value
  res = value.chronify_qty
  raise InvalidTimeExpression, 'Invalid time quantity' unless res

  res
end

accept TagArray do |value|
  value.gsub(/[, ]+/, ' ').split(' ').map { |tag| tag.sub(/^@/, '') }.map(&:strip)
end

program_desc 'A CLI for a What Was I Doing system'
program_long_desc %(Doing uses a TaskPaper-like formatting to keep a plain text
record of what you've been doing, complete with tag-based time tracking. The
command line tool allows you to add entries, annotate with tags and notes, and
view your entries with myriad options, with a focus on a "natural" language syntax.)

default_command :recent
# sort_help :manually

## Global options

desc 'Output notes if included in the template'
switch [:notes], default_value: true, negatable: true

desc 'Send results report to STDOUT instead of STDERR'
switch [:stdout], default_value: false, negatable: false

desc 'Use a pager when output is longer than screen'
switch %i[p pager], default_value: Doing.setting('paginate')

desc 'Answer yes/no menus with default option'
switch [:default], default_value: false, negatable: false

desc 'Answer all yes/no menus with yes'
switch [:yes], negatable: false

desc 'Answer all yes/no menus with no'
switch [:no], negatable: false

desc 'Exclude auto tags and default tags'
switch %i[x noauto], default_value: false, negatable: false

desc 'Colored output'
switch %i[color], default_value: true

desc 'Silence info messages'
switch %i[q quiet], default_value: false, negatable: false

desc 'Verbose output'
switch %i[debug], default_value: false, negatable: false

desc 'Use a specific configuration file. Deprecated, set $DOING_CONFIG instead'
flag [:config_file], default_value: Doing.config.config_file

desc 'Specify a different doing_file'
flag %i[f doing_file]

def add_commands(commands)
  commands = [commands] unless commands.is_a?(Array)
  hidden = Doing.setting('disabled_commands')
  hidden = hidden.set_type('array') if hidden.good? && !hidden.is_a?(Array)
  commands.delete_if { |c| hidden.include?(c) }
  commands.each { |cmd| require_relative "commands/#{cmd}" }
end

## Add/modify commands
add_commands(%w[now done finish note select tag])
## View commands
add_commands(%w[grep last recent show on view])
## Utility commands
add_commands(%w[config open commands])
## File handling/batch modification commands
add_commands(%w[archive import rotate])
## History commands
add_commands(%w[undo redo])
## Hidden commands
add_commands(%w[commands_accepting install_fzf update])
## Optional commands
add_commands(%w[again cancel flag meanwhile reset tags today yesterday since])
add_commands(%w[tag_dir colors completion plugins sections template views changes])

pre do |global, _command, _options, _args|
  # global[:pager] ||= Doing.setting('paginate')
  Doing::Pager.paginate = global[:pager]

  $stdout.puts "doing v#{Doing::VERSION}" if global[:version]
  Doing::Color.coloring = if $stdout.isatty
                            global[:color]
                          else
                            global[:pager] ? global[:color] : false
                          end

  # Return true to proceed; false to abort and not call the
  # chosen command
  # Use skips_pre before a command to skip this block
  # on that command only
  true
end

on_error do |exception|
  case exception
  when GLI::UnknownCommand
    if ARGV.count > 1
      exit run(['view'].concat(ARGV.unshift(@command))) if @wwid.get_view(@command, fallback: false)
      exit run(['now'].concat(ARGV.unshift(@command)))
    else
      exit run(['view'].concat(ARGV.unshift(@command))) if @wwid.get_view(@command, fallback: false)

      Doing::Color.coloring = $stdout.isatty
      Doing.logger.error('Unknown Command:', @command)
      Doing.logger.output_results
      false
    end
  when SystemExit
    false
  else
    # Doing.logger.error('Fatal:', exception)
    Doing.logger.output_results
    true
  end
end

post do |_global, _command, _options, _args|
  # Use skips_post before a command to skip this
  # block on that command only
  Doing.logger.output_results
  Doing.logger.benchmark(:total, :finish)
  Doing.logger.log_benchmarks
end

around do |global, command, _options, _arguments, code|
  Doing.logger.benchmark("command_#{command.name}".to_sym, :start)
  # pager_msg = "Global: #{global[:pager]}, Config: #{Doing.setting('paginate')}, Pager: #{Doing::Pager.paginate}"
  # Doing.logger.debug('Pager:', pager_msg)
  Doing.logger.adjust_verbosity(global) if env_log_level.nil?

  global[:stdin] = $stdin.read.strip if $stdin.stat.size.positive? || $stdin.fcntl(Fcntl::F_GETFL, 0).zero?
  global[:stdin] = nil unless global[:stdin].good?

  Doing.logger.logdev = $stdout if global[:stdout]

  if global[:yes]
    Doing::Prompt.force_answer = :yes
    Doing.config.force_answer = true
  elsif global[:no]
    Doing::Prompt.force_answer = :no
    Doing.config.force_answer = false
  else
    Doing::Prompt.default_answer = if $stdout.isatty
                                     global[:default]
                                   else
                                     true
                                   end

    Doing.config.force_answer = global[:default] ? true : false
  end

  if global[:config_file] && global[:config_file] != Doing.config.config_file
    msg = Doing::Color.template(['{Rwb}WARNING:{x} {br}The use of --config_file is deprecated,',
                                 'please set the environment variable DOING_CONFIG instead.{x}'])
    Doing.logger.warn(msg)
    msg = Doing::Color.template(['{r}To set it just for the current command, use:',
                                 '{bw}DOING_CONFIG=/path/to/doingrc doing [command]{x}'])
    Doing.logger.warn(msg)

    cf = File.expand_path(global[:config_file])
    raise MissingConfigFile, "Config file not found (#{global[:config_file]})" unless File.exist?(cf)

    Doing.config.config_file = cf
    Doing.config_with(cf, { ignore_local: true })
  end
  Doing.logger.benchmark(:init, :start)
  if global[:doing_file]
    @wwid.init_doing_file(global[:doing_file])
  else
    @wwid.init_doing_file
  end
  Doing.logger.benchmark(:init, :finish)
  Doing.auto_tag = !global[:noauto]

  Doing.set('include_notes', false) unless global[:notes]

  global[:wwid] = @wwid

  code.call

  Doing.logger.benchmark("command_#{command.name}".to_sym, :finish)
end

commands_from File.expand_path(Doing.setting('plugins.command_path')) if Doing.setting('plugins.command_path')

@command = ARGV[0]

exit run(ARGV)