'use strict';
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Str = void 0;
const crypto_1 = __importDefault(require("crypto"));
const uuid_1 = require("uuid");
class Str {
    /**
     * Create a new String instance providing chainable string operations.
     * This instance clones the original string and works with the clone.
     * It won’t modify the original string.
     *
     * @param {String} value
     *
     * @returns {Str}
     */
    constructor(value) {
        this.value = String(value || '').slice(0);
    }
    /**
     * Returns a URL-friendly alphabet containing the symbols `a-z A-Z 0-9 _-`.
     * The symbols order was changed for better gzip compression.
     *
     * The alphabet comes from the https://github.com/ai/nanoid package.
     *
     * @returns {String}
     */
    static get alphabet() {
        return 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
    }
    /**
     * Returns the string value.
     *
     * @returns {String}
     */
    get() {
        return this.toString();
    }
    /**
     * Returns the string value.
     *
     * @returns {String}
     */
    toString() {
        return this.value;
    }
    /**
     * Returns the portion of the string after the first occurrence of the given `delimiter`.
     *
     * @param {String} delimiter
     *
     * @returns {Str}
     */
    after(delimiter) {
        if (delimiter === '') {
            return this;
        }
        const substrings = this.split(delimiter);
        return substrings.length === 1
            ? this // delimiter is not part of the string
            : new Str(substrings.slice(1).join(delimiter));
    }
    /**
     * Returns the portion of the string after the last occurrence of the given `delimiter`.
     *
     * @param {String} delimiter
     *
     * @return {Str}
     */
    afterLast(delimiter) {
        return delimiter === ''
            ? this
            : new Str(this.split(delimiter).pop());
    }
    /**
     * Append the given values to the string.
     *
     * @param {String|String[]} values
     *
     * @return {Str}
     */
    append(...values) {
        return new Str(this.value + [].concat(...values).join(''));
    }
    /**
     * Returns the portion of the string before the first occurrence of the given `delimiter`.
     *
     * @param {String} delimiter
     *
     * @returns {Str}
     */
    before(delimiter) {
        return delimiter === ''
            ? this
            : new Str(this.split(delimiter).shift());
    }
    /**
     * Returns the portion of the string before the last occurrence of the given `delimiter`.
     *
     * @param {String} delimiter
     *
     * @return {Str}
     */
    beforeLast(delimiter) {
        if (delimiter === '') {
            return this;
        }
        const substrings = this.split(delimiter);
        return substrings.length === 1
            ? this // delimiter is not part of the string
            : new Str(substrings.slice(0, -1).join(delimiter));
    }
    /**
     * Convert the string to camelCase.
     *
     * @returns {Str}
     */
    camel() {
        return this.studly().lcFirst();
    }
    /**
     * Returns a string that contains the concatenation of two or more strings.
     *
     * @param {String|Array} strings
     *
     * @returns {Str}
     */
    concat(...strings) {
        strings = [].concat(...strings);
        return new Str(this.value.concat(...strings));
    }
    /**
     * Determine whether the haystack contains any of the given `needles`.
     *
     * @param {String} needle
     *
     * @returns {Boolean}
     */
    contains(...needles) {
        return this.includes(...needles);
    }
    /**
     * Determine whether the haystack contains all items if the `needles` array.
     *
     * @param {String} needle
     *
     * @returns {Boolean}
     */
    containsAll(...needles) {
        return this.includesAll(...needles);
    }
    /**
     * Determine whether the haystack does not contain the given `needle`.
     *
     * @param {String} needle
     *
     * @returns {Boolean}
     */
    notContains(needle) {
        return !this.contains(needle);
    }
    /**
     * Determine whether the string ends with the given `needle`.
     * Optionally, accepts a `length` used as the string length.
     *
     * @param {String} needle
     * @param {Number} length
     *
     * @returns {Boolean}
     */
    endsWith(needle, length) {
        return this.value.endsWith(needle, length);
    }
    /**
     * Determine whether the string equals the given `value`.
     *
     * @param {String} value
     *
     * @returns {Boolean}
     */
    equals(value) {
        return this.value === value;
    }
    /**
     * Determine whether the string does not equal the given `value`.
     *
     * @param {String} value
     *
     * @returns {Boolean}
     */
    notEquals(value) {
        return !this.equals(value);
    }
    /**
     * Append a single instance of the given `suffix` to the end of
     * the string if it doesn’t already ends with the given suffix.
     *
     * @param {String} suffix
     *
     * @returns {Str}
     */
    finish(suffix) {
        return this.endsWith(suffix)
            ? this
            : this.append(suffix);
    }
    /**
     * Determine whether the given string contains the `needle`.
     *
     * @param {*} needle
     *
     * @returns {Boolean}
     */
    includes(...needles) {
        const includes = []
            .concat(...needles)
            .filter(needle => needle !== '')
            .find(needle => this.value.includes(needle));
        return !!includes;
    }
    /**
     * Determine whether the haystack contains all items if the `needles` array.
     *
     * @param {String} needle
     *
     * @returns {Boolean}
     */
    includesAll(...needles) {
        return []
            .concat(...needles)
            .every(needle => {
            return this.includes(needle);
        });
    }
    /**
     * Determine whether the string does not contain the given `needle`.
     *
     * @param {String} needle
     *
     * @returns {Boolean}
     */
    notIncludes(needle) {
        return !this.includes(needle);
    }
    /**
     * Determine whether the given string is empty.
     *
     * @returns {Boolean}
     */
    isEmpty() {
        return this.length() === 0;
    }
    /**
     * Determine whether the string is lowercase.
     *
     * @returns {Boolean}
     */
    isLower() {
        return this.isLowercase();
    }
    /**
     * Determine whether the string is lowercase.
     *
     * @returns {Boolean}
     */
    isLowercase() {
        return this.value === this.lower().get();
    }
    /**
     * Determine whether the given string is not empty.
     *
     * @returns {Boolean}
     */
    isNotEmpty() {
        return !this.isEmpty();
    }
    /**
     * Determine whether the given `input` is a string.
     *
     * @param {*} input
     *
     * @returns {Boolean}
     */
    isString(input) {
        return typeof input === 'string' && Object.prototype.toString.call(input) === '[object String]';
    }
    /**
     * Determine whether the given string is uppercase.
     *
     * @returns {Boolean}
     */
    isUpper() {
        return this.isUppercase();
    }
    /**
     * Determine whether the given string is uppercase.
     *
     * @returns {Boolean}
     */
    isUppercase() {
        return this.value === this.upper().get();
    }
    /**
     * Convert the string to kebab-case.
     *
     * @returns {Str}
     */
    kebab(separator = '-') {
        return new Str(this.value.replace(/[_-\s]+/g, separator))
            .strip()
            .toLowerCase();
    }
    /**
     * Lowercases the first character in the string.
     *
     * @returns {Str}
     */
    lcFirst() {
        return new Str(this.value[0].toLowerCase() + this.value.slice(1));
    }
    /**
     * Returns the string’s length.
     *
     * @returns {Number}
     */
    length() {
        return this.value.length;
    }
    /**
     * Returns the first `limit` characters and ends the limited string with `end`.
     *
     * @param {Number} limit
     * @param {String} end
     *
     * @returns {Str}
     */
    limit(limit = 0, end = '') {
        return this.length() <= limit
            ? this
            : new Str(this.value.substring(0, limit).concat(end));
    }
    /**
     * Lowercases the string.
     *
     * @returns {Str}
     */
    lower() {
        return this.toLowerCase();
    }
    /**
     * Lowercases the string. Alias for `.lower()`.
     *
     * @returns {Str}
     */
    lowercase() {
        return this.toLowerCase();
    }
    /**
     * Removes the whitespace from the front of the string when
     * no argument is present. It trims the given `characters`
     * from the left of the string if you pass along a value.
     *
     * @param {String} characters
     *
     * @returns {Str}
     */
    ltrim(characters = '') {
        if (!characters) {
            return new Str(this.value.trimLeft());
        }
        while (this.startsWith(characters)) {
            this.value = this.replace(characters, '').get();
        }
        return new Str(this.value);
    }
    /**
     * Pad the left side of the string with the given `pad` string (repeatedly
     * if needed) so that the resulting string reaches a given `length`.
     *
     * @param {Number} length
     * @param {String} pad
     *
     * @returns {Str}
     */
    padLeft(length, pad = ' ') {
        return new Str(this.value.padStart(length, pad));
    }
    /**
     * Pad the right side of the string with the given `pad` string (repeatedly
     * if needed) so that the resulting string reaches a given `length`.
     *
     * @param {Number} length
     * @param {String} pad
     *
     * @returns {Str}
     */
    padRight(length, pad = ' ') {
        return new Str(this.value.padEnd(length, pad));
    }
    /**
     * Parse a Class[@]method style string into the Class and method names.
     *
     * @returns {String[]}
     */
    parseCallback(separator = '@', defaultValue) {
        if (this.notContains(separator)) {
            return [this.value, defaultValue];
        }
        const [Class, method] = this.split(separator, 2);
        return [Class, method];
    }
    /**
     * Convert the string to PascalCase (same as StudlyCase). Alias for `.studly()`.
     *
     * @returns {Str}
     */
    pascal() {
        return this.studly();
    }
    /**
     * Prepend the given values to the string.
     *
     * @param {String|String[]} values
     *
     * @return {Str}
     */
    prepend(...values) {
        return new Str([].concat(...values).join('') + this.value);
    }
    /**
     * Create a random, URL-friendly string. The default length will have 21 symbols.
     *
     * @param {Number} [size=21] number of symbols in string
     *
     * @returns {String}
     */
    random(size = 21) {
        const bytes = crypto_1.default.randomBytes(size);
        const alphabetLength = Str.alphabet.length - 1;
        let random = '';
        while (size--) {
            random += Str.alphabet[bytes[size] & alphabetLength];
        }
        return random;
    }
    /**
     * Replace the first occurence of the string.
     *
     * @param {String} search
     * @param {String} replace
     *
     * @returns {Str}
     */
    replace(search, replace) {
        return new Str(this.value.replace(search, replace));
    }
    /**
     * Replace all occurrences of `search` with `replace` in the string.
     *
     * @param {*} search
     * @param {*} replace
     *
     * @returns {Str}
     */
    replaceAll(search, replace) {
        const replacer = new RegExp(search, 'g');
        return new Str(this.value.replace(replacer, replace));
    }
    /**
     * Replace the last occurence of the string.
     *
     * @param {String} search
     * @param {String} replace
     *
     * @returns {Str}
     */
    replaceLast(search, replace) {
        return this.notContains(search)
            ? this
            : new Str(this.beforeLast(search).get() + replace + this.afterLast(search).get());
    }
    /**
     * Returns the reversed string.
     *
     * @returns {Str}
     */
    reverse() {
        return new Str(this.value.split('').reverse().join(''));
    }
    /**
     * Removes the whitespace from the end of the string when
     * no argument is present. It trims the given `characters`
     * from the left of the string if you pass along a value.
     *
     * @param {String} characters
     *
     * @returns {Str}
     */
    rtrim(characters = '') {
        if (!characters) {
            return new Str(this.value.trimRight());
        }
        while (this.endsWith(characters)) {
            this.value = this.replaceLast(characters, '').get();
        }
        return new Str(this.value);
    }
    /**
     * Convert the string to a URL-friendly “slug” in kebab-case.
     *
     * @param {String} separator
     *
     * @returns {Str}
     */
    slug(separator = '-') {
        return this.kebab(separator);
    }
    /**
     * Convert the string to snake_case.
     *
     * @returns {Str}
     */
    snake() {
        return new Str(this.value.replace(/[_-\s]+/g, '_'))
            .strip()
            .toLowerCase();
    }
    /**
     * Splits up the string into an array of strings by separating
     * the string at each of the specified `separator` string.
     *
     * @param {String} separator
     * @param {Number} limit
     *
     * @returns {Array}
     */
    split(separator, limit) {
        return this.value.split(separator, limit);
    }
    /**
     * Prepends a single instance of the given `prefix` to the start of
     * the string if it doesn’t already start with the given prefix.
     *
     * @param {String} prefix
     *
     * @returns {Str}
     */
    start(prefix) {
        return this.startsWith(prefix)
            ? this
            : this.prepend(prefix);
    }
    /**
     * Determine whether the string starts with the given `needle`.
     * Optionally, accepts the position in the string at which
     * to begin searching for the `needle` (defaults to `0`).
     *
     * @param {*} needle
     * @param {Number} position
     *
     * @returns {Boolean}
     */
    startsWith(needle, position) {
        return this.value.startsWith(needle, position);
    }
    /**
     * Removes all whitespace from the string, everywhere.
     *
     * @returns {Str}
     */
    strip() {
        return new Str(this.value.replace(/\s+/g, ''));
    }
    /**
     * Removes all numbers from the string.
     *
     * @returns {Str}
     */
    stripNums() {
        return this.replaceAll(/\d+/, '');
    }
    /**
     * Convert the string to StudlyCase. StudlyCase is similar to camelCase
     * except that the first character is uppercase instead of lowercase.
     *
     * @returns {Str}
     */
    studly() {
        return new Str(this.value.replace(/[_-]+/g, ' '))
            .title()
            .strip();
    }
    /**
     * Returns the substring.
     *
     * @param {Number} start
     * @param {Number} length
     *
     * @returns {Str}
     */
    substr(start, length) {
        return new Str(this.value.substring(start, length));
    }
    /**
     * Convert the string to title case.
     *
     * @returns {Str}
     */
    title() {
        return new Str(this
            .lowercase()
            .split(' ')
            .filter(s => s)
            .map(s => s[0].toUpperCase() + s.slice(1))
            .join(' '));
    }
    /**
     * Lowercases the string. Alias for `.lower()` and convenience
     * method to comply with the global String’s `.toLowerCase()`.
     *
     * @returns {Str}
     */
    toLowerCase() {
        return new Str(this.value.toLowerCase());
    }
    /**
     * Uppercases the string. Alias for `.upper()` and convenience
     * method to comply with the global String’s `.toUpperCase()`.
     *
     * @returns {Str}
     */
    toUpperCase() {
        return new Str(this.value.toUpperCase());
    }
    /**
     * Removes whitespace around the string, front and back when no argument
     * is present. It trims the given `characters` from the left and right
     * of the string if you pass along a value.
     *
     * @param {String} characters
     *
     * @returns {Str}
     */
    trim(characters = '') {
        return this
            .ltrim(characters)
            .rtrim(characters);
    }
    /**
     * Uppercases the first character in the string.
     *
     * @returns {Str}
     */
    ucFirst() {
        return new Str(this.value[0].toUpperCase() + this.value.slice(1));
    }
    /**
     * Uppercases the string.
     *
     * @returns {Str}
     */
    upper() {
        return this.toUpperCase();
    }
    /**
     * Uppercases the string. Alias for `.upper()`.
     *
     * @returns {Str}
     */
    uppercase() {
        return this.toUpperCase();
    }
    /**
     * Create a UUID (version 4).
     *
     * @returns {String}
     */
    uuid() {
        return uuid_1.v4();
    }
}
exports.Str = Str;
