class_name Option extends RefCounted

static var Unit = Option.some(null)

static func some(value: Variant) -> Option:
    return Some.new(value)

static var none: Option.None = None.new()

static func from(value: Variant) -> Option:
    if typeof(value) == TYPE_NIL:
        return Option.none
    else: return Option.some(value)

static func collect_some(options: Array[Option], ignore_none: bool = false) -> Option:
    var result = []
    for option in options:
        if option.is_some():
            result.push_back(option.unwrap())
        elif not ignore_none:
            return Option.none
    return Option.some(result)

class Some extends Option:
    var value: Variant

    func _init(val: Variant) -> void:
        value = val

    func chain(fn: Callable) -> Option:
        return fn.call(value)

    func filter(fn: Callable) -> Option:
        if fn.call(value): return self
        else: return Option.none

    func flatten() -> Option:
        if value is Option:
            return value.flatten()
        else: return Option.some(value)

    func map(fn: Callable) -> Option:
        return Option.some(fn.call(value))

    func is_some() -> bool:
        return true

    func is_none() -> bool:
        return false

    func or_default(_default_value: Variant) -> Variant:
        return value

    func or_else(_fn: Callable) -> Variant:
        return value

    func unwrap() -> Variant:
        return value

class None extends Option:
    func chain(_fn: Callable) -> Option:
        return self

    func filter(_f: Callable) -> Option:
        return self

    func flatten() -> Option:
        return self

    func map(_fn: Callable) -> Option:
        return self

    func is_some() -> bool:
        return false

    func is_none() -> bool:
        return true

    func or_default(default_value: Variant) -> Variant:
        return default_value

    func or_else(fn: Callable) -> Variant:
        return fn.call()

    func unwrap() -> Variant:
        assert(false, "Called unwrap() on a None value")
        return null
