import { Decimal } from "decimal.js";
import { ZERO } from "./constants.js";
import { invariant } from "./utils.js";
/**
* https://tc39.es/ecma262/#sec-tostring
*/
export function ToString(o) {
	// Only symbol is irregular...
	if (typeof o === "symbol") {
		throw TypeError("Cannot convert a Symbol value to a string");
	}
	return String(o);
}
/**
* https://tc39.es/ecma262/#sec-tonumber
* @param val
*/
export function ToNumber(arg) {
	if (typeof arg === "number") {
		return new Decimal(arg);
	}
	// Support bigint values by converting to string first
	// https://tc39.es/ecma402/#sec-intl.pluralrules.prototype.select
	if (typeof arg === "bigint") {
		return new Decimal(arg.toString());
	}
	invariant(typeof arg !== "symbol", "Symbol is not supported", TypeError);
	if (arg === undefined) {
		return new Decimal(NaN);
	}
	if (arg === null || arg === 0) {
		return ZERO;
	}
	if (arg === true) {
		return new Decimal(1);
	}
	if (typeof arg === "string") {
		try {
			return new Decimal(arg);
		} catch {
			return new Decimal(NaN);
		}
	}
	invariant(typeof arg === "object", "object expected", TypeError);
	let primValue = ToPrimitive(arg, "number");
	invariant(typeof primValue !== "object", "object expected", TypeError);
	return ToNumber(primValue);
}
/**
* https://tc39.es/ecma262/#sec-tointeger
* @param n
*/
function ToInteger(n) {
	const number = ToNumber(n);
	if (number.isNaN() || number.isZero()) {
		return ZERO;
	}
	if (number.isFinite()) {
		return number;
	}
	let integer = number.abs().floor();
	if (number.isNegative()) {
		integer = integer.negated();
	}
	return integer;
}
/**
* https://tc39.es/ecma262/#sec-timeclip
* @param time
*/
export function TimeClip(time) {
	if (!time.isFinite()) {
		return new Decimal(NaN);
	}
	if (time.abs().greaterThan(8.64 * 0x38d7ea4c68000)) {
		return new Decimal(NaN);
	}
	return ToInteger(time);
}
/**
* https://tc39.es/ecma262/#sec-toobject
* @param arg
*/
export function ToObject(arg) {
	if (arg == null) {
		throw new TypeError("undefined/null cannot be converted to object");
	}
	return Object(arg);
}
/**
* https://www.ecma-international.org/ecma-262/11.0/index.html#sec-samevalue
* @param x
* @param y
*/
export function SameValue(x, y) {
	if (Object.is) {
		return Object.is(x, y);
	}
	// SameValue algorithm
	if (x === y) {
		// Steps 1-5, 7-10
		// Steps 6.b-6.e: +0 != -0
		return x !== 0 || 1 / x === 1 / y;
	}
	// Step 6.a: NaN == NaN
	return x !== x && y !== y;
}
/**
* https://www.ecma-international.org/ecma-262/11.0/index.html#sec-arraycreate
* @param len
*/
export function ArrayCreate(len) {
	return Array.from({ length: len });
}
/**
* https://www.ecma-international.org/ecma-262/11.0/index.html#sec-hasownproperty
* @param o
* @param prop
*/
export function HasOwnProperty(o, prop) {
	return Object.prototype.hasOwnProperty.call(o, prop);
}
/**
* https://www.ecma-international.org/ecma-262/11.0/index.html#sec-type
* @param x
*/
export function Type(x) {
	if (x === null) {
		return "Null";
	}
	if (typeof x === "undefined") {
		return "Undefined";
	}
	if (typeof x === "function" || typeof x === "object") {
		return "Object";
	}
	if (typeof x === "number") {
		return "Number";
	}
	if (typeof x === "boolean") {
		return "Boolean";
	}
	if (typeof x === "string") {
		return "String";
	}
	if (typeof x === "symbol") {
		return "Symbol";
	}
	if (typeof x === "bigint") {
		return "BigInt";
	}
}
const MS_PER_DAY = 864e5;
/**
* https://www.ecma-international.org/ecma-262/11.0/index.html#eqn-modulo
* @param x
* @param y
* @return k of the same sign as y
*/
function mod(x, y) {
	return x - Math.floor(x / y) * y;
}
/**
* https://tc39.es/ecma262/#eqn-Day
* @param t
*/
export function Day(t) {
	return Math.floor(t / MS_PER_DAY);
}
/**
* https://tc39.es/ecma262/#sec-week-day
* @param t
*/
export function WeekDay(t) {
	return mod(Day(t) + 4, 7);
}
/**
* https://tc39.es/ecma262/#sec-year-number
* @param y
*/
export function DayFromYear(y) {
	if (y < 100) {
		// Date.UTC parses 0 - 99 as 1900 - 1999
		const date = new Date(0);
		date.setUTCFullYear(y, 0, 1);
		date.setUTCHours(0, 0, 0, 0);
		return date.getTime() / MS_PER_DAY;
	}
	return Date.UTC(y, 0) / MS_PER_DAY;
}
/**
* https://tc39.es/ecma262/#sec-year-number
* @param y
*/
export function TimeFromYear(y) {
	return Date.UTC(y, 0);
}
/**
* https://tc39.es/ecma262/#sec-year-number
* @param t
*/
export function YearFromTime(t) {
	return new Date(t).getUTCFullYear();
}
export function DaysInYear(y) {
	if (y % 4 !== 0) {
		return 365;
	}
	if (y % 100 !== 0) {
		return 366;
	}
	if (y % 400 !== 0) {
		return 365;
	}
	return 366;
}
export function DayWithinYear(t) {
	return Day(t) - DayFromYear(YearFromTime(t));
}
export function InLeapYear(t) {
	return DaysInYear(YearFromTime(t)) === 365 ? 0 : 1;
}
/**
* https://tc39.es/ecma262/#sec-month-number
* @param t
*/
export function MonthFromTime(t) {
	const dwy = DayWithinYear(t);
	const leap = InLeapYear(t);
	if (dwy >= 0 && dwy < 31) {
		return 0;
	}
	if (dwy < 59 + leap) {
		return 1;
	}
	if (dwy < 90 + leap) {
		return 2;
	}
	if (dwy < 120 + leap) {
		return 3;
	}
	if (dwy < 151 + leap) {
		return 4;
	}
	if (dwy < 181 + leap) {
		return 5;
	}
	if (dwy < 212 + leap) {
		return 6;
	}
	if (dwy < 243 + leap) {
		return 7;
	}
	if (dwy < 273 + leap) {
		return 8;
	}
	if (dwy < 304 + leap) {
		return 9;
	}
	if (dwy < 334 + leap) {
		return 10;
	}
	if (dwy < 365 + leap) {
		return 11;
	}
	throw new Error("Invalid time");
}
export function DateFromTime(t) {
	const dwy = DayWithinYear(t);
	const mft = MonthFromTime(t);
	const leap = InLeapYear(t);
	if (mft === 0) {
		return dwy + 1;
	}
	if (mft === 1) {
		return dwy - 30;
	}
	if (mft === 2) {
		return dwy - 58 - leap;
	}
	if (mft === 3) {
		return dwy - 89 - leap;
	}
	if (mft === 4) {
		return dwy - 119 - leap;
	}
	if (mft === 5) {
		return dwy - 150 - leap;
	}
	if (mft === 6) {
		return dwy - 180 - leap;
	}
	if (mft === 7) {
		return dwy - 211 - leap;
	}
	if (mft === 8) {
		return dwy - 242 - leap;
	}
	if (mft === 9) {
		return dwy - 272 - leap;
	}
	if (mft === 10) {
		return dwy - 303 - leap;
	}
	if (mft === 11) {
		return dwy - 333 - leap;
	}
	throw new Error("Invalid time");
}
const HOURS_PER_DAY = 24;
const MINUTES_PER_HOUR = 60;
const SECONDS_PER_MINUTE = 60;
const MS_PER_SECOND = 1e3;
const MS_PER_MINUTE = MS_PER_SECOND * SECONDS_PER_MINUTE;
const MS_PER_HOUR = MS_PER_MINUTE * MINUTES_PER_HOUR;
export function HourFromTime(t) {
	return mod(Math.floor(t / MS_PER_HOUR), HOURS_PER_DAY);
}
export function MinFromTime(t) {
	return mod(Math.floor(t / MS_PER_MINUTE), MINUTES_PER_HOUR);
}
export function SecFromTime(t) {
	return mod(Math.floor(t / MS_PER_SECOND), SECONDS_PER_MINUTE);
}
function IsCallable(fn) {
	return typeof fn === "function";
}
/**
* The abstract operation OrdinaryHasInstance implements
* the default algorithm for determining if an object O
* inherits from the instance object inheritance path
* provided by constructor C.
* @param C class
* @param O object
* @param internalSlots internalSlots
*/
export function OrdinaryHasInstance(C, O, internalSlots) {
	if (!IsCallable(C)) {
		return false;
	}
	if (internalSlots?.boundTargetFunction) {
		let BC = internalSlots?.boundTargetFunction;
		return O instanceof BC;
	}
	if (typeof O !== "object") {
		return false;
	}
	let P = C.prototype;
	if (typeof P !== "object") {
		throw new TypeError("OrdinaryHasInstance called on an object with an invalid prototype property.");
	}
	return Object.prototype.isPrototypeOf.call(P, O);
}
export function msFromTime(t) {
	return mod(t, MS_PER_SECOND);
}
function OrdinaryToPrimitive(O, hint) {
	let methodNames;
	if (hint === "string") {
		methodNames = ["toString", "valueOf"];
	} else {
		methodNames = ["valueOf", "toString"];
	}
	for (const name of methodNames) {
		const method = O[name];
		if (IsCallable(method)) {
			let result = method.call(O);
			if (typeof result !== "object") {
				return result;
			}
		}
	}
	throw new TypeError("Cannot convert object to primitive value");
}
export function ToPrimitive(input, preferredType) {
	if (typeof input === "object" && input != null) {
		const exoticToPrim = Symbol.toPrimitive in input ? input[Symbol.toPrimitive] : undefined;
		let hint;
		if (exoticToPrim !== undefined) {
			if (preferredType === undefined) {
				hint = "default";
			} else if (preferredType === "string") {
				hint = "string";
			} else {
				invariant(preferredType === "number", "preferredType must be \"string\" or \"number\"");
				hint = "number";
			}
			let result = exoticToPrim.call(input, hint);
			if (typeof result !== "object") {
				return result;
			}
			throw new TypeError("Cannot convert exotic object to primitive.");
		}
		if (preferredType === undefined) {
			preferredType = "number";
		}
		return OrdinaryToPrimitive(input, preferredType);
	}
	return input;
}
