import { isObject, isString, IS_BROWSER } from '@redocly/theme/core/openapi';
import { isNamedDefinition } from './openapi.js';
import { JsonPointer } from './JsonPointer.js';
import { urlParse } from './url.js';
/**
 * Maps over array passing `isLast` bool to iterator as the second argument
 */
export function mapWithLast(array, iteratee) {
    const res = [];
    for (let i = 0; i < array.length - 1; i++) {
        res.push(iteratee(array[i], false));
    }
    if (array.length !== 0) {
        res.push(iteratee(array[array.length - 1], true));
    }
    return res;
}
/**
 * Creates an object with the same keys as object and values generated by running each
 * own enumerable string keyed property of object thru iteratee.
 * The iteratee is invoked with three arguments: (value, key, object).
 *
 * @param object the object to iterate over
 * @param iteratee the function invoked per iteration.
 */
export function mapValues(object, iteratee) {
    const res = {};
    for (const key in object) {
        if (Object.prototype.hasOwnProperty.call(object, key)) {
            res[key] = iteratee(object[key], key, object);
        }
    }
    return res;
}
/**
 * flattens collection using `prop` field as a children
 * @param collectionItems collection items
 * @param prop item property with child elements
 */
export function flattenByProp(collectionItems, prop) {
    const res = [];
    const iterate = (items) => {
        for (const item of items) {
            res.push(item);
            if (item[prop]) {
                iterate(item[prop]);
            }
        }
    };
    iterate(collectionItems);
    return res;
}
export function stripTrailingSlash(path) {
    if (path.endsWith('/')) {
        return path.substring(0, path.length - 1);
    }
    return path;
}
export function isNumeric(value) {
    return !isNaN(parseFloat(value)) && isFinite(value);
}
export function appendToMdHeading(md, heading, content) {
    // if  heading is already in md and append to the end of it
    const testRegex = new RegExp(`(^|\\n)#\\s?${heading}\\s*\\n`, 'i');
    const replaceRegex = new RegExp(`((\\n|^)#\\s*${heading}\\s*(\\n|$)(?:.|\\n)*?)(\\n#|$)`, 'i');
    if (testRegex.test(md)) {
        return md.replace(replaceRegex, `$1\n\n${content}\n$4`);
    }
    else {
        // else append heading itself
        const br = md === '' || md.endsWith('\n\n') ? '' : md.endsWith('\n') ? '\n' : '\n\n';
        return `${md}${br}# ${heading}\n\n${content}`;
    }
}
export const isArrayOfObjects = (items) => {
    return Array.isArray(items) && items.some((item) => isObject(item));
};
export const deleteEmptyArrayItem = (items) => {
    return items.filter((item) => !!item);
};
export const isMergeableObject = (item) => {
    return isObject(item) && !Array.isArray(item);
};
export function isAbsoluteUrl(url) {
    return /(?:^[a-z][a-z0-9+.-]*:|\/\/)/i.test(url);
}
/**
 * simple resolve URL which doesn't break on strings with url fragments
 * e.g. resolveUrl('http://test.com:{port}', 'path') results in http://test.com:{port}/path
 */
export function resolveUrl(url, to) {
    let res;
    if (to.startsWith('//')) {
        res = `${urlParse(url, true)?.protocol || 'https:'}${to}`;
    }
    else if (isAbsoluteUrl(to)) {
        res = to;
    }
    else if (!to.startsWith('/')) {
        res = stripTrailingSlash(url) + '/' + to;
    }
    else {
        const parsedUrl = urlParse(url);
        if (parsedUrl) {
            parsedUrl.pathname = to;
            res = parsedUrl.toString();
        }
        else {
            res = to;
        }
    }
    return stripTrailingSlash(res);
}
export function titleize(text) {
    return text.charAt(0).toUpperCase() + text.slice(1);
}
export function removeQueryStringAndHash(serverUrl) {
    try {
        if (!serverUrl) {
            return '';
        }
        const url = new URL(serverUrl);
        url.search = '';
        url.hash = '';
        return url.toString();
    }
    catch {
        // when using with redoc-cli serverUrl can be empty resulting in crash
        return serverUrl;
    }
}
export function escapeHTMLAttrChars(str) {
    return str.replace(/["\\]/g, '\\$&');
}
export function unescapeHTMLChars(str) {
    return str
        .replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(parseInt(code, 10)))
        .replace(/&amp;/g, '&')
        .replace(/&quot;/g, '"');
}
export function sanitizeItemId(id) {
    // we probably need to replace other symbols too
    return id && id.replace(/#/g, '_').toLowerCase();
}
export function useSchemaTitle(pointer, title) {
    return title || (isNamedDefinition(pointer) && JsonPointer.baseName(pointer)) || '';
}
export function get(object = {}, path, defval) {
    if (typeof path === 'string')
        path = path.split('.');
    return path.reduce((xs, x) => (xs && xs[x] ? xs[x] : defval), object);
}
export function joinStringFactory(separator) {
    return (...args) => args.filter(Boolean).join(separator);
}
export const dottedString = joinStringFactory('.');
export function getWindowReferenceOptions(option) {
    return IS_BROWSER ? window?.REFERENCE_DOCS_OPTIONS?.[option] : null;
}
export function getJsUrl() {
    if (!IS_BROWSER) {
        return '';
    }
    const id = +new Date() + Math.random();
    try {
        if (!document.currentScript || // IE doesn't have currentScript
            !(document.currentScript.async ||
                document.currentScript.defer)) {
            // write empty script to the document. It will get placed directly after the current script
            document.write(`<script id="dummy${id}"></script>`);
        }
        // find appended script and return src of the previous script which is the current script
        return (document.getElementById('dummy' + id)?.previousSibling).src;
    }
    catch {
        return '';
    }
}
export const normalizeText = (text) => isString(text) ? text : text?.raw;
export const getValueFromMdParsedExtension = (item, key) => {
    const value = item?.[`x-parsed-md-${key}`] || item?.[key];
    if (!value) {
        return;
    }
    if (!item?.[`x-parsed-md-${key}`] && key !== 'x-enumDescriptions') {
        return item?.[key];
    }
    if (key !== 'x-enumDescriptions') {
        return normalizeMarkdownValue(item, key);
    }
    let result = {};
    for (const property in value) {
        if (property.startsWith('x-parsed-md')) {
            continue;
        }
        result[property] = value?.[`x-parsed-md-${property}`] || value?.[property];
    }
    if (Object.keys(result).length) {
        return result;
    }
    return value;
};
// We need to normalize the value when running inside Realm which parses md to markdoc AST.
// The value can be a string or an object with result property (which contains AST)
function normalizeMarkdownValue(item, key) {
    const value = item?.[`x-parsed-md-${key}`] || item?.[key];
    if (value?.raw) {
        return value;
    }
    return {
        result: value?.result || item?.[key]?.result || item?.[key],
        raw: item?.[key],
    };
}
export function isLastInArray(arr, index) {
    return index === arr.length - 1;
}
export function isLastProperty(obj, key) {
    const objectKeys = Object.keys(obj);
    return objectKeys.indexOf(key) === objectKeys.length - 1;
}
//# sourceMappingURL=helpers.js.map