import { Matcher } from "../Matcher.js";
import { AllOf } from "./AllOf.js";
import { AnyOf } from "./AnyOf.js";
import { DescribeAs } from "./DescribeAs.js";
import { IsAnything } from "./IsAnything.js";
import { IsEqual } from "./IsEqual.js";
import { IsFinite } from "./IsFinite.js";
import { IsIn } from "./IsIn.js";
import { IsInteger } from "./IsInteger.js";
import { IsIterableContaining } from "./IsIterableContaining.js";
import { IsNot } from "./IsNot.js";
import { IsNull } from "./IsNull.js";
import { IsUndefined } from "./IsUndefined.js";

/**
 * @template T
 * @param {Matcher<T>} matchers
 * @return {Matcher<T>}
 */
export function anyOf(...matchers) {
    return new AnyOf(matchers);
}

/**
 * @template T
 * @param {Matcher<T>} matchers
 * @return {Matcher<T>}
 */
export function allOf(...matchers) {
    return new AllOf(matchers);
}

/**
 * @template T
 * @param {Matcher<T>} matcher
 * @return {Matcher<T>}
 */
export function not(matcher) {
    return new IsNot(matcher);
}

/**
 * @template T
 * @param {T} thing
 * @return {Matcher<T>}
 */
export function equalTo(thing) {
    return new IsEqual(thing);
}

/**
 * @template T
 * @param {T} thing
 * @return {Matcher<T>}
 */
export function notEqualTo(thing) {
    return not(equalTo(thing));
}

/**
 * @template T
 * @return {Matcher<T>}
 */
export function isNull() {
    return new IsNull();
}

/**
 * @template T
 * @return {Matcher<T>}
 */
export function isNotNull() {
    return not(isNull());
}

/**
 * @template T
 * @return {Matcher<T>}
 */
export function isUndefined() {
    return new IsUndefined();
}

/**
 * @template T
 * @return {Matcher<T>}
 */
export function isDefined() {
    return not(isUndefined());
}

/**
 * @template T
 * @return {Matcher<T>}
 */
export const isNotUndefined = isDefined;

export function isFinite(){
    return new IsFinite();
}

export function isInfinite(){
    return not(isFinite());
}

export function isInteger(){
    return new IsInteger();
}

/**
 * @template T
 * @param {Matcher|T} value
 * @return {Matcher<T>}
 */
export function hasItem(value) {
    if (value instanceof Matcher) {
        return new IsIterableContaining(value);
    } else {
        return new IsIterableContaining(equalTo(value));
    }
}

/**
 * @template T
 * @param {T[]} array
 * @returns {Matcher<T>}
 */
export function isIn(array) {
    return new IsIn(array);
}


/**
 * @template T
 * @param {T} elements
 * @return {Matcher<T>}
 */
export function isOneOf(...elements) {
    return isIn(elements);
}


/**
 * @template T
 * @param {string} template
 * @param {Matcher<T>} matcher
 * @param {T[]} values
 * @returns {Matcher<T>}
 */
function describeAs(template, matcher, values) {
    return new DescribeAs(template, matcher, values);
}

/**
 *
 * @template T
 * @param {string} [message]
 * @return {Matcher<T>}
 */
export function anything(message) {
    return new IsAnything(message);
}

/**
 * @template T
 * @param {string} [message]
 * @return {Matcher<T>}
 */
export function nothing(message) {
    const matcher = not(anything());

    if (message !== undefined) {
        return describeAs(message, matcher, []);
    } else {
        return matcher;
    }
}

