# frozen_string_literal: true

module Doing
  class WWID
    ##
    ## Create a process for an editor and wait for the file handle to return
    ##
    ## @param      input  [String] Text input for editor
    ##
    def fork_editor(input = '', message: :default)
      # raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']

      raise MissingEditor, 'No EDITOR variable defined in environment' if Util.default_editor.nil?

      tmpfile = Tempfile.new(['doing_temp', '.doing'])

      File.open(tmpfile.path, 'w+') do |f|
        f.puts input
        unless message.nil?
          f.puts message == :default ? '# First line is the entry title, lines after are added as a note' : message
        end
      end

      pid = Process.fork { system("#{Util.editor_with_args} #{tmpfile.path}") }

      trap('INT') do
        begin
          Process.kill(9, pid)
        rescue StandardError
          Errno::ESRCH
        end
        tmpfile.unlink
        tmpfile.close!
        exit 0
      end

      Process.wait(pid)

      begin
        if $?.exitstatus == 0
          input = IO.read(tmpfile.path)
        else
          exit_now! 'Cancelled'
        end
      ensure
        tmpfile.close
        tmpfile.unlink
      end

      input.split(/\n/).delete_if(&:ignore?).join("\n")
    end

    ##
    ## Takes a multi-line string and formats it as an entry
    ##
    ## @param      input  [String] The string to parse
    ##
    ## @return     [Array] [[String]title, [Note]note]
    ##
    def format_input(input)
      raise EmptyInput, 'No content in entry' if input.nil? || input.strip.empty?

      input_lines = input.split(/[\n\r]+/).delete_if(&:ignore?)
      title = input_lines[0]&.strip
      raise EmptyInput, 'No content in first line' if title.nil? || title.strip.empty?

      date = nil
      iso_rx = /\d{4}-\d\d-\d\d \d\d:\d\d/
      date_rx = /^(?:\s*- )?(?<date>.*?) \| (?=\S)/

      raise EmptyInput, 'No content' if title.sub(/^.*?\| */, '').strip.empty?

      title.expand_date_tags(Doing.setting('date_tags'))

      if title =~ date_rx
        m = title.match(date_rx)
        d = m['date']
        date = if d =~ iso_rx
                 Time.parse(d)
               else
                 d.chronify(guess: :begin)
               end
        title.sub!(date_rx, '').strip!
      end

      note = Note.new
      note.add(input_lines[1..-1]) if input_lines.length > 1
      # If title line ends in a parenthetical, use that as the note
      if note.empty? && title =~ /\s+\(.*?\)$/
        title.sub!(/\s+\((?<note>.*?)\)$/) do
          m = Regexp.last_match
          note.add(m['note'])
          ''
        end
      end

      note.strip_lines!
      note.compress

      [date, title, note]
    end

    def add_with_editor(**options)
      raise MissingEditor, 'No EDITOR variable defined in environment' if Util.default_editor.nil?

      input = options[:date].strftime('%F %R | ')
      input += options[:title]
      input += "\n#{options[:note]}" if options[:note]
      input = fork_editor(input).strip

      d, title, note = format_input(input)
      raise EmptyInput, 'No content' if title.empty?

      if options[:ask]
        ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
        note.add(ask_note) unless ask_note.empty?
      end

      date = d.nil? ? options[:date] : d
      finish = options[:finish_last] || false
      add_item(title.cap_first, options[:section], { note: note, back: date, timed: finish })
      write(@doing_file)
    end

    def edit_items(items)
      items.sort_by! { |i| i.date }
      editable_items = []

      items.each do |i|
        editable = "#{i.date.strftime('%F %R')} | #{i.title}"
        old_note = i.note ? i.note.strip_lines.join("\n") : nil
        editable += "\n#{old_note}" unless old_note.nil?
        editable_items << editable
      end
      divider = "-----------"
      notice =<<~EONOTICE

      # - You may delete entries, but leave all divider lines (---) in place.
      # - Start and @done dates replaced with a time string (yesterday 3pm) will
      #   be parsed automatically. Do not delete the pipe (|) between start date
      #   and entry title.
      EONOTICE
      input =  "#{editable_items.map(&:strip).join("\n#{divider}\n")}\n"

      new_items = fork_editor(input, message: notice).split(/^#{divider}/).map(&:strip)

      new_items.each_with_index do |new_item, i|
        input_lines = new_item.split(/[\n\r]+/).delete_if(&:ignore?)
        first_line = input_lines[0]&.strip

        if first_line.nil? || first_line =~ /^#{divider.strip}$/ || first_line.strip.empty?
          deleted = @content.delete_item(items[i], single: new_items.count == 1)
          Hooks.trigger :post_entry_removed, self, deleted
          Doing.logger.info('Deleted:', deleted.title)
        else
          date, title, note = format_input(new_item)

          note.map!(&:strip)
          note.delete_if(&:ignore?)
          item = items[i]
          old_item = item.clone
          item.date = date || items[i].date
          item.title = title
          item.note = note
          if (item.equal?(old_item))
            Doing.logger.count(:skipped, level: :debug)
          else
            Doing.logger.count(:updated)
            Hooks.trigger :post_entry_updated, self, item, old_item
          end
        end
      end
    end

    ##
    ## Edit the last entry
    ##
    ## @param      section  [String] The section, default "All"
    ##
    def edit_last(section: 'All', options: {})
      options[:section] = guess_section(section)

      item = last_entry(options)

      if item.nil?
        logger.debug('Skipped:', 'No entries found')
        return
      end

      old_item = item.clone
      content = ["#{item.date.strftime('%F %R')} | #{item.title.dup}"]
      content << item.note.strip_lines.join("\n") unless item.note.empty?
      new_item = fork_editor(content.join("\n"))
      raise UserCancelled, 'No change' if new_item.strip == content.join("\n").strip

      date, title, note = format_input(new_item)
      date ||= item.date

      if title.nil? || title.empty?
        logger.debug('Skipped:', 'No content provided')
      elsif title == item.title && note.equal?(item.note) && date.equal?(item.date)
        logger.debug('Skipped:', 'No change in content')
      else
        item.date = date unless date.nil?
        item.title = title
        item.note.add(note, replace: true)
        logger.info('Edited:', item.title)
        Hooks.trigger :post_entry_updated, self, item, old_item

        write(@doing_file)
      end
    end
  end
end