/**
 * Interface defining the structure for the pattern matching handlers used by the `match` method.
 * @template T The type of the value.
 * @template U The return type of the Some handler.
 * @template V The return type of the None handler.
 */
interface OptionMatcher<T, U, V> {
  /**
   * Handler function for the Some case.
   * @param value The value of type T.
   * @returns A value of type U.
   */
  Some: (value: T) => U;
  /**
   * Handler function for the None case.
   * @returns A value of type V.
   */
  None: () => V;
}

/**
 * BaseOption interface defines the common methods for Some and None implementations.
 * It allows iteration over the contained value *only* if it's `Some` and the value itself is iterable.
 * @template T The type of the value.
 */
interface BaseOption<T> extends Iterable<T extends Iterable<infer U> ? U : never> {
  /**
   * Checks if the option is Some.
   * @returns True if the option is Some, false otherwise.
   * @example
   * ```typescript
   * Some(5).isSome(); // true
   * None().isSome(); // false
   * ```
   */
  isSome(): boolean;

  /**
   * Checks if the option is Some and the contained value satisfies a predicate.
   * @param fn The predicate function to apply to the Some value.
   * @returns True if the option is Some and the predicate returns true, false otherwise.
   * @example
   * ```typescript
   * Some(5).isSomeAnd(x => x > 3); // true
   * Some(5).isSomeAnd(x => x < 3); // false
   * None().isSomeAnd(x => x > 3); // false
   * ```
   */
  isSomeAnd(fn: (value: T) => boolean): boolean;

  /**
   * Checks if the option is None.
   * @returns True if the option is None, false otherwise.
   * @example
   * ```typescript
   * None().isNone(); // true
   * Some(5).isNone(); // false
   * ```
   */
  isNone(): boolean;

  /**
   * Returns the contained Some value.
   * Throws an error if the value is None, using the provided message.
   * @param message The message to use if the value is None.
   * @returns The Some value.
   * @throws {Error} Throws an error with `message` if the option is None.
   * @example
   * ```typescript
   * Some(5).expect("should be Some"); // 5
   * // None.expect("should be Some"); // Throws "should be Some"
   * ```
   */
  expect(message: string): T;

  /**
   * Returns the contained Some value.
   * Throws an error if the value is None.
   * @returns The Some value.
   * @throws {Error} Throws an error if the option is None.
   * @example
   * ```typescript
   * Some(5).unwrap(); // 5
   * // None.unwrap(); // Throws "Tried to unwrap a None value"
   * ```
   */
  unwrap(): T;

  /**
   * Returns the contained Some value or a provided default value.
   * @param defaultValue The default value to return if the option is None.
   * @returns The Some value or `defaultValue`.
   * @example
   * ```typescript
   * Some(5).unwrapOr(0); // 5
   * None().unwrapOr(0); // 0
   * ```
   */
  unwrapOr(defaultValue: T): T;

  /**
   * Returns the contained Some value or computes it from a closure.
   * @param fn The closure to compute the default value.
   * @returns The Some value or the value computed by `fn()`.
   * @example
   * ```typescript
   * Some(5).unwrapOrElse(() => 0); // 5
   * None().unwrapOrElse(() => 0); // 0
   * ```
   */
  unwrapOrElse(fn: () => T): T;

  /**
   * Maps an `Option<T>` to `Option<U>` by applying a function to a contained Some value,
   * leaving a None value untouched.
   * @template U The type of the mapped Some value.
   * @param fn The function to apply to the Some value.
   * @returns A new Option with the mapped Some value or None.
   * @example
   * ```typescript
   * Some(5).map(x => x.toString()).unwrap(); // "5"
   * None().map((x:any) => x.toString()).isNone(); // true
   * ```
   */
  map<U>(fn: (value: T) => U): Option<U>;

  /**
   * Returns the provided default value (if None), or applies a function to the contained value (if Some).
   * @template U The type returned by the function `fn` and the type of `defaultValue`.
   * @param defaultValue The default value to return if the option is None.
   * @param fn The function to apply to the Some value.
   * @returns The result of `fn(Some_value)` or `defaultValue`.
   * @example
   * ```typescript
   * Some("foo").mapOr("bar", v => v.length); // 3
   * None().mapOr("bar", (v:any) => v.length); // "bar"
   * ```
   */
  mapOr<U>(defaultValue: U, fn: (value: T) => U): U;

  /**
   * Computes a default value (if None), or applies a function to the contained value (if Some).
   * @template U The type returned by both functions.
   * @param defaultFn The function to compute the default value if None.
   * @param fn The function to apply to the Some value.
   * @returns The result of `fn(Some_value)` or `defaultFn()`.
   * @example
   * ```typescript
   * Some(5).mapOrElse(() => "None!", x => `Some: ${x}`); // "Some: 5"
   * None().mapOrElse(() => "None!", (x:any) => `Some: ${x}`); // "None!"
   * ```
   */
  mapOrElse<U>(defaultFn: () => U, fn: (value: T) => U): U;
  
  /**
   * Calls the provided function with the contained value (if Some). Returns the original option.
   * Useful for debugging or side-effects.
   * @param fn The function to call with the Some value.
   * @returns The original `Option<T>`.
   * @example
   * ```typescript
   * Some(5).inspect(x => console.log(x)); // Logs 5, returns Some(5)
   * None().inspect(x => console.log(x)); // Returns None
   * ```
   */
  inspect(fn: (value: T) => void): Option<T>;

  /**
   * Returns `res` if the option is Some, otherwise returns None.
   * @template U The type of the Some value of the `res` Option.
   * @param res The other Option to return if self is Some.
   * @returns `res` if self is Some, otherwise None.
   * @example
   * ```typescript
   * Some(2).and(Some("late success")).unwrap(); // "late success"
   * None().and(Some("late success")).isNone(); // true
   * Some(2).and(None).isNone(); // true
   * ```
   */
  and<U>(res: Option<U>): Option<U>;

  /**
   * Calls `fn` if the option is Some, otherwise returns None.
   * This is often used for chaining operations that might return an Option.
   * (Also known as `flatMap`)
   * @template U The type of the Some value of the Option returned by `fn`.
   * @param fn The function to call with the Some value, which returns a new Option.
   * @returns The Option returned by `fn`, or None if self is None.
   * @example
   * ```typescript
   * Some(5).andThen(x => Some(x > 0)).unwrap(); // true
   * Some(-1).andThen(x => x > 0 ? Some(x) : None).isNone(); // true
   * None().andThen((x:any) => Some(x > 0)).isNone(); // true
   * ```
   */
  andThen<U>(fn: (value: T) => Option<U>): Option<U>; // flatMap

  /**
   * Returns the option if it contains a value, otherwise returns `res`.
   * @param res The other Option to return if self is None.
   * @returns The option if it is Some, otherwise `res`.
   * @example
   * ```typescript
   * Some(5).or(Some(10)).unwrap(); // 5
   * None().or(Some(10)).unwrap(); // 10
   * None().or(None()).isNone(); // true
   * ```
   */
  or(res: Option<T>): Option<T>;

  /**
   * Returns the option if it contains a value, otherwise calls `fn` and returns its result.
   * @param fn The function to call to produce an alternative Option if self is None.
   * @returns The option if it is Some, otherwise the result of `fn()`.
   * @example
   * ```typescript
   * Some(5).orElse(() => Some(0)).unwrap(); // 5
   * None().orElse(() => Some(0)).unwrap(); // 0
   * None().orElse(() => None()).isNone(); // true
   * ```
   */
  orElse(fn: () => Option<T>): Option<T>;

  /**
   * Returns Some if exactly one of self, optb is Some, otherwise returns None.
   * If self is Some and optb is None, returns Some(self).
   * If self is None and optb is Some, returns Some(optb).
   * Otherwise (both are Some or both are None), returns None.
   * @param optb The other Option.
   * @returns An Option containing the value of the single Some, or None.
   * @example
   * ```typescript
   * Some(1).xor(None()).unwrap(); // 1
   * None().xor(Some(2)).unwrap(); // 2
   * Some(1).xor(Some(2)).isNone(); // true
   * None().xor(None()).isNone(); // true
   * ```
   */
  xor(optb: Option<T>): Option<T>;
  
  /**
   * Returns a new Option containing a clone of the contained Some value.
   * Uses `structuredClone` for objects. Primitive values are copied directly.
   * If the value is None, returns None.
   * @returns A new Option with a clone of the Some value, or None.
   * @note Uses `structuredClone`.
   * @example
   * ```typescript
   * Some({ a: 1 }).cloned().unwrap(); // { a: 1 } (new object)
   * None().cloned().isNone(); // true
   * ```
   */
  cloned(): Option<T>;

  /**
   * Zips self with another Option.
   * If self is `Some(s)` and other is `Some(o)`, returns `Some([s, o])`.
   * Otherwise, returns `None`.
   * @template U The type of the value in the other Option.
   * @param other The other Option.
   * @returns An Option containing a tuple of the two Some values, or None.
   * @example
   * ```typescript
   * Some(1).zip(Some("a")).unwrap(); // [1, "a"]
   * Some(1).zip(None).isNone(); // true
   * None().zip(Some("a")).isNone(); // true
   * ```
   */
  zip<U>(other: Option<U>): Option<[T, U]>;

  /**
   * Zips self with another Option using a custom function.
   * If self is `Some(s)` and other is `Some(o)`, returns `Some(fn(s, o))`.
   * Otherwise, returns `None`.
   * @template U The type of the value in the other Option.
   * @template R The type of the value returned by the zipping function.
   * @param other The other Option.
   * @param fn The function to combine the two Some values.
   * @returns An Option containing the result of `fn`, or None.
   * @example
   * ```typescript
   * Some(1).zipWith(Some("a"), (n, s) => `${n}${s}`).unwrap(); // "1a"
   * Some(1).zipWith(None, (n, s) => `${n}${s}`).isNone(); // true
   * ```
   */
  zipWith<U, R>(other: Option<U>, fn: (s: T, o: U) => R): Option<R>;

  /**
   * Executes one of two provided functions based on whether the Option is Some or None.
   * This allows for pattern matching on the Option type.
   *
   * @template U The return type of the `Some` handler.
   * @template V The return type of the `None` handler.
   * @param matcher An object containing `Some` and `None` functions.
   * @returns The value returned by the executed handler (`U` or `V`).
   * @example
   * ```typescript
   * const option: Option<number> = Some(10);
   * const message = option.match({
   *   Some: (value) => `Success: ${value}`,
   *   None: () => `It was None`
   * });
   * // message is "Success: 10"
   *
   * const option2: Option<number> = None;
   * const message2 = option2.match({
   *   Some: (value) => `Success: ${value}`,
   *   None: () => `It was None`
   * });
   * // message2 is "It was None"
   * ```
   */
  match<U, V>(matcher: OptionMatcher<T, U, V>): U | V;

  /**
   * Flattens an `Option<Option<T>>` to `Option<T>`.
   * @returns Some(value) if the option contains Some(Some(value)), None otherwise.
   * @example
   * ```typescript
   * Some(Some(5)).flatten().unwrap(); // 5
   * Some(None).flatten().isNone(); // true
   * None().flatten().isNone(); // true
   * ```
   */
  flatten<U>(this: Option<Option<U>>): Option<U>;

  /**
   * Returns None if the option is None, otherwise calls predicate with the wrapped value and returns:
   * - Some(value) if predicate returns true
   * - None if predicate returns false
   * @param predicate The predicate function to test the Some value.
   * @returns Some(value) if predicate is true, None otherwise.
   * @example
   * ```typescript
   * Some(5).filter(x => x > 3).unwrap(); // 5
   * Some(5).filter(x => x > 10).isNone(); // true
   * None().filter(x => x > 3).isNone(); // true
   * ```
   */
  filter(predicate: (value: T) => boolean): Option<T>;

  /**
   * Transforms the Option<T> into a Result<T, E>, mapping Some(v) to Ok(v) and None to Err(err).
   * @template E The error type.
   * @param err The error value to use if the option is None.
   * @returns Ok(value) if Some, Err(err) if None.
   * @example
   * ```typescript
   * Some(5).okOr("error").unwrap(); // 5
   * None().okOr("error").unwrapErr(); // "error"
   * ```
   */
  okOr<E>(err: E): import("./result.ts").Result<T, E>;

  /**
   * Transforms the Option<T> into a Result<T, E>, mapping Some(v) to Ok(v) and None to Err(fn()).
   * @template E The error type.
   * @param fn The function to compute the error if the option is None.
   * @returns Ok(value) if Some, Err(fn()) if None.
   * @example
   * ```typescript
   * Some(5).okOrElse(() => "error").unwrap(); // 5
   * None().okOrElse(() => "error").unwrapErr(); // "error"
   * ```
   */
  okOrElse<E>(fn: () => E): import("./result.ts").Result<T, E>;




  /**
   * Transposes an Option of a Result into a Result of an Option.
   * @returns Result<Option<T>, E> based on the inner Result.
   * @example
   * ```typescript
   * Some(Ok(5)).transpose().unwrap().unwrap(); // 5
   * Some(Err("error")).transpose().unwrapErr(); // "error"
   * None().transpose().unwrap().isNone(); // true
   * ```
   */
  transpose<U, E>(this: Option<import("./result.ts").Result<U, E>>): import("./result.ts").Result<Option<U>, E>;

  /**
   * Returns the contained Some value or a default value for the type.
   * Note: Unlike Rust, TypeScript doesn't have a Default trait, so this method
   * will throw an error. Use unwrapOr(defaultValue) instead.
   * @returns The Some value or throws an error.
   * @throws {Error} Always throws for None since TypeScript has no Default trait.
   * @example
   * ```typescript
   * Some(5).unwrapOrDefault(); // 5
   * None().unwrapOrDefault(); // throws Error
   * ```
   */
  unwrapOrDefault(): T;
}

/**
 * @internal Implementation of the Some case for Option. Users should use the `Some` factory function.
 */
class SomeImpl<T> implements BaseOption<T> {
  #value!: T;

  constructor(value: T) {
    if (!(this instanceof SomeImpl)) {
      return new SomeImpl(value);
    }
    this.#value = value;
  }

  isSome(): true { return true; }
  isSomeAnd(fn: (value: T) => boolean): boolean { return fn(this.#value); }
  isNone(): false { return false; }

  expect(_message: string): T {
    return this.#value;
  }

  unwrap(): T {
    return this.#value;
  }

  unwrapOr(_defaultValue: T): T {
    return this.#value;
  }

  unwrapOrElse(_fn: () => T): T {
    return this.#value;
  }

  map<U>(fn: (value: T) => U): Option<U> {
    return Some(fn(this.#value));
  }

  mapOr<U>(_defaultValue: U, fn: (value: T) => U): U {
    return fn(this.#value);
  }

  mapOrElse<U>(_defaultFn: () => U, fn: (value: T) => U): U {
    return fn(this.#value);
  }
  
  inspect(fn: (value: T) => void): Option<T> {
    fn(this.#value);
    return this;
  }

  and<U>(res: Option<U>): Option<U> {
    return res;
  }

  andThen<U>(fn: (value: T) => Option<U>): Option<U> {
    return fn(this.#value);
  }

  or(_res: Option<T>): Option<T> {
    return this;
  }

  orElse(_fn: () => Option<T>): Option<T> {
    return this;
  }

  xor(optb: Option<T>): Option<T> {
    if (optb.isSome()) {
      return None();
    }
    return this;
  }

  cloned(): Option<T> {
    try {
      const clonedValue = structuredClone(this.#value);
      return Some(clonedValue);
    } catch (e) {
      console.warn("Failed to structuredClone Some value:", this.#value, e);
      return this; 
    }
  }

  zip<U>(other: Option<U>): Option<[T, U]> {
    if (other.isSome()) {
      return Some([this.#value, other.unwrap()] as [T, U]);
    }
    return None();
  }

  zipWith<U, R>(other: Option<U>, fn: (s: T, o: U) => R): Option<R> {
    if (other.isSome()) {
      return Some(fn(this.#value, other.unwrap()));
    }
    return None();
  }
  
  match<U, V>(matcher: OptionMatcher<T, U, V>): U | V {
    return matcher.Some(this.#value);
  }

  flatten<U>(this: SomeImpl<Option<U>>): Option<U> {
    return this.#value;
  }

  filter(predicate: (value: T) => boolean): Option<T> {
    if (predicate(this.#value)) {
      return this;
    }
    return None();
  }

  okOr<E>(_err: E): import("./result.ts").Result<T, E> {
    const { Ok } = require("./result.ts");
    return Ok(this.#value);
  }

  okOrElse<E>(_fn: () => E): import("./result.ts").Result<T, E> {
    const { Ok } = require("./result.ts");
    return Ok(this.#value);
  }

  transpose<U, E>(this: SomeImpl<import("./result.ts").Result<U, E>>): import("./result.ts").Result<Option<U>, E> {
    const result = this.#value as any;
    if (result.isOk()) {
      const { Ok } = require("./result.ts");
      return Ok(Some(result.unwrap()));
    } else {
      const { Err } = require("./result.ts");
      return Err(result.unwrapErr());
    }
  }

  unwrapOrDefault(): T {
    return this.#value;
  }
  
  [Symbol.iterator](): Iterator<T extends Iterable<infer U> ? U : never> {
    const value = this.#value as T;
    if (value !== null && value !== undefined && typeof (value as any)[Symbol.iterator] === "function") {
      return (value as unknown as Iterable<T extends Iterable<infer U> ? U : never>)[Symbol.iterator]();
    } else {
      return {
        next(): IteratorResult<T extends Iterable<infer U> ? U : never> {
          return { done: true, value: undefined! };
        }
      } as Iterator<T extends Iterable<infer U> ? U : never>;
    }
  }
}

/**
 * @internal Implementation of the None case for Option. Users should use the `None` singleton.
 */
class NoneImpl<T = never> implements BaseOption<T> {
  #value?: T;
  
  constructor() {
  }

  isSome(): false { return false; }
  isSomeAnd(_fn: (value: T) => boolean): false { return false; }
  isNone(): true { return true; }

  expect(message: string): T {
    throw new Error(message);
  }

  unwrap(): T {
    throw new Error('Tried to unwrap a None value');
  }

  unwrapOr(defaultValue: T): T {
    return defaultValue;
  }

  unwrapOrElse(fn: () => T): T {
    return fn();
  }

  map<U>(_fn: (value: T) => U): Option<U> {
    return None();
  }

  mapOr<U>(defaultValue: U, _fn: (value: T) => U): U {
    return defaultValue;
  }

  mapOrElse<U>(defaultFn: () => U, _fn: (value: T) => U): U {
    return defaultFn();
  }
  
  inspect(_fn: (value: T) => void): Option<T> {
    return None();
  }

  and<U>(_res: Option<U>): Option<U> {
    return None();
  }

  andThen<U>(_fn: (value: T) => Option<U>): Option<U> {
    return None();
  }

  or<U>(res: Option<U>): Option<U> {
    return res;
  }

  orElse<U>(fn: () => Option<U>): Option<U> {
    return fn();
  }
  
  xor<U>(optb: Option<U>): Option<U> {
    return optb;
  }

  cloned(): Option<T> {
    return None();
  }

  zip<U>(_other: Option<U>): Option<[T, U]> {
    return None()
  }

  zipWith<U, R>(_other: Option<U>, _fn: (s: T, o: U) => R): Option<R> {
    return None()
  }
  
  match<U, V>(matcher: OptionMatcher<T, U, V>): U | V {
    return matcher.None();
  }

  flatten<U>(): Option<U> {
    return None();
  }

  filter(_predicate: (value: T) => boolean): Option<T> {
    return None();
  }

  okOr<E>(err: E): import("./result.ts").Result<T, E> {
    const { Err } = require("./result.ts");
    return Err(err);
  }

  okOrElse<E>(fn: () => E): import("./result.ts").Result<T, E> {
    const { Err } = require("./result.ts");
    return Err(fn());
  }

  transpose<U, E>(): import("./result.ts").Result<Option<U>, E> {
    const { Ok } = require("./result.ts");
    return Ok(None());
  }

  unwrapOrDefault(): T {
    throw new Error("Cannot unwrap None to default value. TypeScript doesn't have a Default trait. Use unwrapOr(defaultValue) instead.");
  }
  
  [Symbol.iterator](): Iterator<never> {
    return {
      next(): IteratorResult<never> {
        return { done: true, value: undefined! };
      }
    };
  }
}

/**
 * Represents the presence of a value (`Some`) in an {@link Option}.
 * Contains the value of type `T`.
 * @template T The type of the value.
 * @example
 * ```typescript
 * Some(10).unwrap(); // 10
 * ```
 */
export type Some<T> = SomeImpl<T>;
export const Some = <T>(value: T): Some<T> => new SomeImpl(value);


/**
 * Represents the absence of a value (`None`) in an {@link Option}.
 * @template T The type parameter for the Option
 * @example
 * ```typescript
 * None().isNone(); // true
 * const noValue: Option<number> = None<number>();
 * // Or with type inference:
 * const noValue: Option<number> = None();
 * ```
 */
export type None<T = never> = NoneImpl<T>;
export const None = <T = never>(): None<T> => new NoneImpl<T>();


/**
 * `Option<T>` is a type that represents an optional value: 
 * every `Option` is either `Some` and contains a value, or `None` and does not.
 * @template T The type of the value that might be contained.
 */
export type Option<T> = Some<T> | None<T>;

interface OptionTypeStatics {
  /**
   * Wraps a function that might return null or undefined, converting its result to an `Option<T>`.
   * - If `fn()` returns a non-null/non-undefined value `v`, it's wrapped in `Some(v)`.
   * - If `fn()` returns `null` or `undefined`, it's converted to `None`.
   *
   * @template T The type of the successful result of `fn`.
   * @param fn The function to wrap and execute. It should return `T | null | undefined`.
   * @returns An `Option<T>` representing the outcome.
   * @example
   * ```typescript
   * Option.fromNullable((): number | null => null).isNone(); // true
   * Option.fromNullable((): number | null => 5).unwrap(); // 5
   * Option.fromNullable(() => null) === None; // true
   * ```
   */
  fromNullable<T>(fn: () => T | null | undefined): Option<NonNullable<T>>;
  
  /**
   * Checks if a value is an Option (either Some or None) created by this library.
   * Useful for type guards or conditional logic.
   * @param value The value to check.
   * @returns True if the value is a Some or None instance, false otherwise.
   * @example
   * ```typescript
   * Option.isOption(Some(1)); // true
   * Option.isOption(None); // true
   * Option.isOption(null); // false
   * ```
   */
  isOption<T>(value: unknown): value is Option<T>;
}


/**
 * Provides static methods for creating and handling Option instances.
 */
export const Option: OptionTypeStatics = {
  fromNullable<T>(fn: () => T | null | undefined): Option<NonNullable<T>> {
    const value = fn();
    if (value === null || value === undefined) {
      return None();
    }
    return Some(value as NonNullable<T>);
  },
  isOption<T>(value: unknown): value is Option<T> {
    return value instanceof SomeImpl || value instanceof NoneImpl;
  }
};
