export type AbsoluteUrl = string;
export type Base64 = string;
export type Base64Url = string;
export type Code = string;
export type EmailAddress = string;
export type Guid = string;
export type LowerCase = string;
export type Json = Code;
export type NumberLike = string;
export type PhoneNumber = string;
export type UpperCase = string;

export const DATE_STR = "date";
export const EMAIL = "email";
export const GUID = "guid";
export const NAME = "name";
export const PASSWORD = "password";
export const REGEX = "regex";
export const STRING = "string";
export const URL = "url";
export const USERNAME = "username";

export const STRING_TYPES = [
  DATE_STR,
  EMAIL,
  GUID,
  NAME,
  PASSWORD,
  REGEX,
  STRING,
  URL,
  USERNAME,
] as const;

export type StringType = typeof STRING_TYPES[number];

const EMAIL_EX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const GUID_EX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/;
const PHONE_EX = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im;
const URL_EX = /^[a-z][a-z0-9+.-]*:/;

export const isAbsoluteUrl = (val: unknown): val is AbsoluteUrl =>
  isString(val) && URL_EX.test(val);

export const isEmailAddress = (val: unknown): val is EmailAddress =>
  isString(val) && EMAIL_EX.test(val);

export const isGuid = (val: unknown): val is Guid =>
  isString(val) && GUID_EX.test(val);

export const isIntegerLike = (val: unknown): val is number =>
  isString(val) && val == String(parseInt(val));

export const isLowerCase = (val: unknown): val is LowerCase =>
  isString(val) && val === val.toLowerCase();

export const isNumberLike = (val: unknown): val is NumberLike =>
  isString(val) && val == String(parseFloat(val));

/*
(123) 456-7890
(123)456-7890
123-456-7890
123.456.7890
1234567890
+31636363634
075-63546725
*/

export const isAlphaNumeric = (val: unknown): val is String => {
  const regExp = /^[A-Za-z0-9]+$/;
  return isString(val) && !!val.match(regExp);
};
export const isPhoneNumber = (val: unknown): val is PhoneNumber =>
  isString(val) && PHONE_EX.test(val);

export const isString = (val: unknown): val is string => typeof val === STRING;

export const isUpperCase = (val: unknown): val is UpperCase =>
  isString(val) && val === val.toUpperCase();

export const isValidJSON = (val: unknown): val is Json => {
  if (!isString(val)) {
    return false;
  }
  try {
    JSON.parse(val);
    return true;
  } catch (e) {
    return false;
  }
};

/**
 * http://crockford.com/javascript/
 * alert(supplant("I'm {age} years old!", { age: 29 }));
 * alert(supplant("The {a} says {n}, {n}, {n}!", { a: 'cow', n: 'moo' }));
 * @param template 
 * @param obj 
 * @returns 
 */
export const supplant = (template: string, obj: Record<string, any>) => {
  return template.replace(/{([^{}]*)}/g, // (substring: string, ...args: any[])
    (substring: string, key: any): string => {
      const val = obj[key];
      switch (typeof val) {
        case 'string':
          return val;
        case 'number':
          return String(val);
        case 'bigint':
          return String(val);
        case 'boolean':
          return String(val);
        case 'function':
          return String(val.bind(obj)());
        case 'object':
          return val === null ? '' : JSON.stringify(val);
        default:
          return substring;
      }
    }
  );
};