module CLI
  module UI
    module Frame
      module FrameStack
        COLOR_ENVVAR = 'CLI_FRAME_STACK'
        STYLE_ENVVAR = 'CLI_STYLE_STACK'

        class StackItem
          attr_reader :color, :frame_style

          def initialize(color_name, style_name)
            @color = CLI::UI.resolve_color(color_name)
            @frame_style = CLI::UI.resolve_style(style_name)
          end
        end

        class << self
          # Fetch all items off the frame stack
          def items
            colors = ENV.fetch(COLOR_ENVVAR, '').split(':').map(&:to_sym)
            styles = ENV.fetch(STYLE_ENVVAR, '').split(':').map(&:to_sym)

            colors.length.times.map do |i|
              StackItem.new(colors[i], styles[i] || Frame.frame_style)
            end
          end

          # Push a new item onto the frame stack.
          #
          # Either an item or a :color/:style pair should be pushed onto the stack.
          #
          # ==== Attributes
          #
          # * +item+ a +StackItem+ to push onto the stack. Defaults to nil
          #
          # ==== Options
          #
          # * +:color+ the color of the new stack item. Defaults to nil
          # * +:style+ the style of the new stack item. Defaults to nil
          #
          # ==== Raises
          #
          # If both an item and a color/style pair are given, raises an +ArgumentError+
          # If the given item is not a +StackItem+, raises an +ArgumentError+
          #
          def push(item = nil, color: nil, style: nil)
            unless item.nil?
              unless item.is_a?(StackItem)
                raise ArgumentError, "item must be a StackItem"
              end

              unless color.nil? && style.nil?
                raise ArgumentError, "Must give one of item or color: and style:"
              end
            end

            item ||= StackItem.new(color, style)

            curr = items
            curr << item

            serialize(curr)
          end

          # Removes and returns the last stack item off the stack
          def pop
            curr = items
            ret = curr.pop

            serialize(curr)

            ret.nil? ? nil : ret
          end

          private

          # Serializes the item stack into two ENV variables.
          #
          # This is done to preserve backward compatibility with earlier versions of cli/ui.
          # This ensures that any code that relied upon previous stack behavior should continue
          # to work.
          def serialize(items)
            colors = []
            styles = []

            items.each do |item|
              colors << item.color.name
              styles << item.frame_style.name
            end

            ENV[COLOR_ENVVAR] = colors.join(':')
            ENV[STYLE_ENVVAR] = styles.join(':')
          end
        end
      end
    end
  end
end