# typed: strict
# frozen_string_literal: true

require_relative "result/control_signal"

module Mangrove
  # Result is a type that represents either success (`Ok`) or failure (`Err`).
  module Result
    extend T::Sig
    extend T::Generic
    extend T::Helpers

    include Kernel

    sealed!
    interface!

    OkType = type_member
    ErrType = type_member

    sig { abstract.params(other: BasicObject).returns(T::Boolean) }
    def ==(other); end

    sig { abstract.returns(T::Boolean) }
    def ok?; end

    sig { abstract.returns(T::Boolean) }
    def err?; end

    sig { abstract.returns(OkType) }
    def unwrap!; end

    sig { abstract.params(block: T.proc.params(err: ErrType).returns(Exception)).returns(OkType) }
    def unwrap_or_raise_with!(&block); end

    sig { abstract.params(exception: Exception).returns(OkType) }
    def unwrap_or_raise!(exception); end

    sig { abstract.returns(OkType) }
    def unwrap_or_raise_inner!; end

    sig { abstract.params(message: String).returns(OkType) }
    def expect!(message); end

    sig { abstract.type_parameters(:E).params(block: T.proc.params(err: ErrType).returns(T.type_parameter(:E))).returns(OkType) }
    def expect_with!(&block); end

    sig { abstract.type_parameters(:NewOkType, :NewErrType).params(block: T.proc.params(this: Result[OkType, ErrType]).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)])).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]) }
    def map(&block); end

    sig { abstract.type_parameters(:NewOkType, :NewErrType).params(_t_new_ok: T.type_parameter(:NewOkType), _t_new_err: T.type_parameter(:NewErrType), block: T.proc.params(this: Result[OkType, ErrType]).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)])).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]) }
    def map_wt(_t_new_ok, _t_new_err, &block); end

    sig { abstract.type_parameters(:NewOkType).params(block: T.proc.params(this: OkType).returns(T.type_parameter(:NewOkType))).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
    def map_ok(&block); end

    sig { abstract.type_parameters(:NewOkType).params(_t_new_ok: T::Class[T.type_parameter(:NewOkType)], block: T.proc.params(this: OkType).returns(T.type_parameter(:NewOkType))).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
    def map_ok_wt(_t_new_ok, &block); end

    sig { abstract.type_parameters(:NewErrType).params(block: T.proc.params(this: ErrType).returns(T.type_parameter(:NewErrType))).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
    def map_err(&block); end

    sig { abstract.type_parameters(:NewErrType).params(_t_new_err: T::Class[T.type_parameter(:NewErrType)], block: T.proc.params(this: ErrType).returns(T.type_parameter(:NewErrType))).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
    def map_err_wt(_t_new_err, &block); end

    sig { abstract.type_parameters(:NewOkType).params(other: Result[T.type_parameter(:NewOkType), ErrType]).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
    def and(other); end

    sig { abstract.type_parameters(:NewOkType).params(block: T.proc.params(this: OkType).returns(Result[T.type_parameter(:NewOkType), ErrType])).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
    def and_then(&block); end

    sig { abstract.type_parameters(:NewOkType).params(_t_new_ok: T::Class[T.type_parameter(:NewOkType)], block: T.proc.params(this: OkType).returns(Result[T.type_parameter(:NewOkType), ErrType])).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
    def and_then_wt(_t_new_ok, &block); end

    sig {
      abstract
        .params(
          new_err_inner: ErrType,
          condition: T.proc.params(inner: OkType).returns(T::Boolean)
        )
        .returns(
          Result[OkType, ErrType]
        )
    }
    def and_err_if(new_err_inner, &condition); end

    sig { abstract.params(other: Result[OkType, ErrType]).returns(Result[OkType, ErrType]) }
    def or(other); end

    sig { abstract.type_parameters(:NewErrType).params(block: T.proc.params(this: ErrType).returns(Result[OkType, T.type_parameter(:NewErrType)])).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
    def or_else(&block); end

    sig { abstract.type_parameters(:NewErrType).params(_t_new_err: T::Class[T.type_parameter(:NewErrType)], block: T.proc.params(this: ErrType).returns(Result[OkType, T.type_parameter(:NewErrType)])).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
    def or_else_wt(_t_new_err, &block); end

    sig {
      abstract
        .params(
          new_ok_inner: OkType,
          condition: T.proc.params(inner: ErrType).returns(T::Boolean)
        )
        .returns(
          Result[OkType, ErrType]
        )
    }
    def or_ok_if(new_ok_inner, &condition); end

    class << self
      extend T::Sig
      extend T::Generic

      OkType = type_member
      ErrType = type_member

      sig { type_parameters(:T, :E).params(results: T::Enumerable[Result[T.type_parameter(:T), T.type_parameter(:E)]]).returns(Result[T::Enumerable[T.type_parameter(:T)], T::Enumerable[T.type_parameter(:E)]]) }
      def from_results(results)
        errs = results.filter(&:err?)

        if errs.empty?
          # This is safe as errs is empty.
          Result::Ok[T::Enumerable[T.type_parameter(:T)], T::Enumerable[T.type_parameter(:E)]].new(results.map { |r| T.cast(r, Result::Ok[T.type_parameter(:T), T.type_parameter(:E)]).ok_inner })
        else
          # This is safe as errs is results where err? is true
          Result::Err[T::Enumerable[T.type_parameter(:T)], T::Enumerable[T.type_parameter(:E)]].new(errs.map { |r| T.cast(r, Result::Err[T.type_parameter(:T), T.type_parameter(:E)]).err_inner })
        end
      end

      sig { type_parameters(:OkType, :ErrType).params(inner: T.type_parameter(:OkType)).returns(Result::Ok[T.type_parameter(:OkType), T.type_parameter(:ErrType)]) }
      def ok(inner)
        Result::Ok[T.type_parameter(:OkType), T.type_parameter(:ErrType)].new(inner)
      end

      sig { type_parameters(:OkType, :ErrType).params(inner: T.type_parameter(:OkType), _t_err: T::Class[T.type_parameter(:ErrType)]).returns(Result::Ok[T.type_parameter(:OkType), T.type_parameter(:ErrType)]) }
      def ok_wt(inner, _t_err)
        Result::Ok[T.type_parameter(:OkType), T.type_parameter(:ErrType)].new(inner)
      end

      sig { type_parameters(:OkType, :ErrType).params(inner: T.type_parameter(:ErrType)).returns(Result::Err[T.type_parameter(:OkType), T.type_parameter(:ErrType)]) }
      def err(inner)
        Result::Err[T.type_parameter(:OkType), T.type_parameter(:ErrType)].new(inner)
      end

      sig { type_parameters(:OkType, :ErrType).params(_t_ok: T::Class[T.type_parameter(:OkType)], inner: T.type_parameter(:ErrType)).returns(Result::Err[T.type_parameter(:OkType), T.type_parameter(:ErrType)]) }
      def err_wt(_t_ok, inner)
        Result::Err[T.type_parameter(:OkType), T.type_parameter(:ErrType)].new(inner)
      end
    end

    class Ok
      extend T::Sig
      extend T::Generic
      extend T::Helpers

      include Result

      OkType = type_member
      ErrType = type_member

      sig { params(inner: OkType).void }
      def initialize(inner)
        @inner = inner
      end

      sig { override.params(other: BasicObject).returns(T::Boolean) }
      def ==(other)
        case other
        when Result::Ok
          other.instance_variable_get(:@inner) == @inner
        when Result::Err
          false
        else # rubocop:disable Lint/DuplicateBranch
          # Because == is defined on BasicObject, we can't be sure that `other` is an Option
          false
        end
      end

      sig { returns(OkType) }
      def ok_inner
        @inner
      end

      sig { override.returns(OkType) }
      def unwrap!
        @inner
      end

      sig { override.params(_exception: Exception).returns(OkType) }
      def unwrap_or_raise!(_exception)
        @inner
      end

      sig { override.params(_block: T.proc.params(err: ErrType).returns(Exception)).returns(OkType) }
      def unwrap_or_raise_with!(&_block)
        @inner
      end

      sig { override.returns(OkType) }
      def unwrap_or_raise_inner!
        @inner
      end

      sig { override.params(_message: String).returns(OkType) }
      def expect!(_message)
        @inner
      end

      sig { override.type_parameters(:E).params(_block: T.proc.params(err: ErrType).returns(T.type_parameter(:E))).returns(OkType) }
      def expect_with!(&_block)
        @inner
      end

      sig { override.returns(T::Boolean) }
      def ok? = true

      sig { override.returns(T::Boolean) }
      def err? = false

      sig { override.type_parameters(:NewOkType, :NewErrType).params(block: T.proc.params(this: Result[OkType, ErrType]).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)])).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]) }
      def map(&block)
        block.call(self)
      end

      sig { override.type_parameters(:NewOkType, :NewErrType).params(_t_new_ok: T.type_parameter(:NewOkType), _t_new_err: T.type_parameter(:NewErrType), block: T.proc.params(this: Result[OkType, ErrType]).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)])).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]) }
      def map_wt(_t_new_ok, _t_new_err, &block)
        block.call(self)
      end

      sig { override.type_parameters(:NewOkType).params(block: T.proc.params(this: OkType).returns(T.type_parameter(:NewOkType))).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def map_ok(&block)
        Result::Ok[T.type_parameter(:NewOkType), ErrType].new(block.call(@inner))
      end

      # Because sorbet does not deduct types from return values well. This method takes a type of new inner values.
      sig { override.type_parameters(:NewOkType).params(_t_new_ok: T::Class[T.type_parameter(:NewOkType)], block: T.proc.params(this: OkType).returns(T.type_parameter(:NewOkType))).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def map_ok_wt(_t_new_ok, &block)
        Result::Ok[T.type_parameter(:NewOkType), ErrType].new(block.call(@inner))
      end

      sig { override.type_parameters(:NewErrType).params(_block: T.proc.params(this: ErrType).returns(T.type_parameter(:NewErrType))).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
      def map_err(&_block)
        T.cast(self, Result::Ok[OkType, T.type_parameter(:NewErrType)])
      end

      # Because sorbet does not deduct types from return values well. This method takes a type of new inner values.
      sig { override.type_parameters(:NewErrType).params(_t_new_err: T::Class[T.type_parameter(:NewErrType)], _block: T.proc.params(this: ErrType).returns(T.type_parameter(:NewErrType))).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
      def map_err_wt(_t_new_err, &_block)
        T.cast(self, Result::Ok[OkType, T.type_parameter(:NewErrType)])
      end

      sig { override.type_parameters(:NewOkType).params(other: Result[T.type_parameter(:NewOkType), ErrType]).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def and(other)
        other
      end

      sig { override.type_parameters(:NewOkType).params(block: T.proc.params(this: OkType).returns(Result[T.type_parameter(:NewOkType), ErrType])).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def and_then(&block)
        block.call(@inner)
      end

      sig { override.type_parameters(:NewOkType).params(_t_new_ok: T::Class[T.type_parameter(:NewOkType)], block: T.proc.params(this: OkType).returns(Result[T.type_parameter(:NewOkType), ErrType])).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def and_then_wt(_t_new_ok, &block)
        block.call(@inner)
      end

      sig {
        override
          .params(
            new_err_inner: ErrType,
            condition: T.proc.params(inner: OkType).returns(T::Boolean)
          )
          .returns(
            Result[OkType, ErrType]
          )
      }
      def and_err_if(new_err_inner, &condition)
        if condition.call(@inner)
          Result::Err[OkType, ErrType].new(new_err_inner)
        else
          self
        end
      end

      sig { override.params(_other: Result[OkType, ErrType]).returns(Result[OkType, ErrType]) }
      def or(_other)
        self
      end

      sig { override.type_parameters(:NewErrType).params(_block: T.proc.params(this: ErrType).returns(Result[OkType, T.type_parameter(:NewErrType)])).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
      def or_else(&_block)
        T.cast(self, Result::Ok[OkType, T.type_parameter(:NewErrType)])
      end

      sig { override.type_parameters(:NewErrType).params(_t_new_err: T::Class[T.type_parameter(:NewErrType)], _block: T.proc.params(this: ErrType).returns(Result[OkType, T.type_parameter(:NewErrType)])).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
      def or_else_wt(_t_new_err, &_block)
        T.cast(self, Result::Ok[OkType, T.type_parameter(:NewErrType)])
      end

      sig {
        override
          .params(
            _new_ok_inner: OkType,
            _condition: T.proc.params(inner: ErrType).returns(T::Boolean)
          )
          .returns(
            Result::Ok[OkType, ErrType]
          )
      }
      def or_ok_if(_new_ok_inner, &_condition)
        self
      end

      sig { returns(String) }
      def to_s
        "#{super}: inner=`#{@inner}`"
      end
    end

    class Err
      extend T::Sig
      extend T::Generic
      extend T::Helpers

      include Result

      OkType = type_member
      ErrType = type_member

      sig { params(inner: ErrType).void }
      def initialize(inner)
        @inner = inner
      end

      sig { override.params(other: BasicObject).returns(T::Boolean) }
      def ==(other)
        case other
        when Result::Ok
          false
        when Result::Err
          other.instance_variable_get(:@inner) == @inner
        else # rubocop:disable Lint/DuplicateBranch
          # Because == is defined on BasicObject, we can't be sure that `other` is an Option
          false
        end
      end

      sig { returns(ErrType) }
      def err_inner
        @inner
      end

      sig { override.returns(OkType) }
      def unwrap!
        raise Result::ControlSignal, @inner
      end

      sig { override.params(exception: Exception).returns(OkType) }
      def unwrap_or_raise!(exception)
        raise exception
      end

      sig { override.params(block: T.proc.params(err: ErrType).returns(Exception)).returns(OkType) }
      def unwrap_or_raise_with!(&block)
        raise block.call(@inner)
      end

      sig { override.returns(OkType) }
      def unwrap_or_raise_inner!
        raise T.unsafe(@inner)
      end

      sig { override.params(message: String).returns(OkType) }
      def expect!(message)
        raise Result::ControlSignal, message
      end

      sig { override.type_parameters(:E).params(block: T.proc.params(err: ErrType).returns(T.type_parameter(:E))).returns(OkType) }
      def expect_with!(&block)
        raise Result::ControlSignal, block.call(@inner)
      end

      sig { override.returns(T::Boolean) }
      def ok? = false

      sig { override.returns(T::Boolean) }
      def err? = true

      sig { override.type_parameters(:NewOkType, :NewErrType).params(block: T.proc.params(this: Result[OkType, ErrType]).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)])).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]) }
      def map(&block)
        block.call(self)
      end

      sig { override.type_parameters(:NewOkType, :NewErrType).params(_t_new_ok: T.type_parameter(:NewOkType), _t_new_err: T.type_parameter(:NewErrType), block: T.proc.params(this: Result[OkType, ErrType]).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)])).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]) }
      def map_wt(_t_new_ok, _t_new_err, &block)
        block.call(self)
      end

      sig { override.type_parameters(:NewOkType).params(_block: T.proc.params(this: OkType).returns(T.type_parameter(:NewOkType))).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def map_ok(&_block)
        T.cast(self, Result::Err[T.type_parameter(:NewOkType), ErrType])
      end

      # Because sorbet does not deduct types from return values well. This method takes a type of new inner values.
      sig { override.type_parameters(:NewOkType).params(_t_new_ok: T::Class[T.type_parameter(:NewOkType)], _block: T.proc.params(this: OkType).returns(T.type_parameter(:NewOkType))).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def map_ok_wt(_t_new_ok, &_block)
        T.cast(self, Result::Err[T.type_parameter(:NewOkType), ErrType])
      end

      sig { override.type_parameters(:NewErrType).params(block: T.proc.params(this: ErrType).returns(T.type_parameter(:NewErrType))).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
      def map_err(&block)
        Result::Err[OkType, T.type_parameter(:NewErrType)].new(block.call(@inner))
      end

      # Because sorbet does not deduct types from return values well. This method takes a type of new inner values.
      sig { override.type_parameters(:NewErrType).params(_t_new_err: T::Class[T.type_parameter(:NewErrType)], block: T.proc.params(this: ErrType).returns(T.type_parameter(:NewErrType))).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
      def map_err_wt(_t_new_err, &block)
        Result::Err[OkType, T.type_parameter(:NewErrType)].new(block.call(@inner))
      end

      sig { override.type_parameters(:NewOkType).params(_other: Result[T.type_parameter(:NewOkType), ErrType]).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def and(_other)
        T.cast(self, Result::Err[T.type_parameter(:NewOkType), ErrType])
      end

      sig { override.type_parameters(:NewOkType).params(_block: T.proc.params(this: OkType).returns(Result[T.type_parameter(:NewOkType), ErrType])).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def and_then(&_block)
        T.cast(self, Result::Err[T.type_parameter(:NewOkType), ErrType])
      end

      sig { override.type_parameters(:NewOkType).params(_t_new_ok: T::Class[T.type_parameter(:NewOkType)], _block: T.proc.params(this: OkType).returns(Result[T.type_parameter(:NewOkType), ErrType])).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
      def and_then_wt(_t_new_ok, &_block)
        T.cast(self, Result::Err[T.type_parameter(:NewOkType), ErrType])
      end

      sig {
        override
          .params(
            _new_err_inner: ErrType,
            _condition: T.proc.params(inner: OkType).returns(T::Boolean)
          )
          .returns(
            Result::Err[OkType, ErrType]
          )
      }
      def and_err_if(_new_err_inner, &_condition)
        self
      end

      sig { override.params(other: Result[OkType, ErrType]).returns(Result[OkType, ErrType]) }
      def or(other)
        other
      end

      sig { override.type_parameters(:NewErrType).params(block: T.proc.params(this: ErrType).returns(Result[OkType, T.type_parameter(:NewErrType)])).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
      def or_else(&block)
        block.call(@inner)
      end

      sig { override.type_parameters(:NewErrType).params(_t_new_err: T::Class[T.type_parameter(:NewErrType)], block: T.proc.params(this: ErrType).returns(Result[OkType, T.type_parameter(:NewErrType)])).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
      def or_else_wt(_t_new_err, &block)
        block.call(@inner)
      end

      sig {
        override
          .params(
            new_ok_inner: OkType,
            condition: T.proc.params(inner: ErrType).returns(T::Boolean)
          )
          .returns(
            Result[OkType, ErrType]
          )
      }
      def or_ok_if(new_ok_inner, &condition)
        if condition.call(@inner)
          Result::Ok[OkType, ErrType].new(new_ok_inner)
        else
          self
        end
      end

      sig { returns(String) }
      def to_s
        "#{super}: inner=`#{@inner}`"
      end
    end
  end
end