"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasTagLink = hasTagLink;
exports.getDefaultTagsResourcePath = getDefaultTagsResourcePath;
exports.isIntraLink = isIntraLink;
exports.convertRelativeLinks = convertRelativeLinks;
exports.convertMdExtToHtmlExt = convertMdExtToHtmlExt;
exports.validateIntraLink = validateIntraLink;
exports.collectSource = collectSource;
const path_1 = __importDefault(require("path"));
const has_1 = __importDefault(require("lodash/has"));
const url_parse_1 = __importDefault(require("url-parse"));
const ignore_1 = __importDefault(require("ignore"));
const fsUtil = __importStar(require("../utils/fsUtil"));
const logger = __importStar(require("../utils/logger"));
const urlUtil = __importStar(require("../utils/urlUtil"));
const PluginManager_1 = require("../plugins/PluginManager");
const _ = { has: has_1.default };
const pluginTagConfig = PluginManager_1.PluginManager.tagConfig;
const defaultTagLinkMap = {
    img: 'src',
    pic: 'src',
    thumbnail: 'src',
    a: 'href',
    link: 'href',
    include: 'src',
    panel: 'src',
    popover: 'src',
    script: 'src',
};
function hasTagLink(node) {
    return node.name in defaultTagLinkMap || node.name in pluginTagConfig;
}
function getDefaultTagsResourcePath(node) {
    const linkAttribName = defaultTagLinkMap[node.name];
    const resourcePath = node.attribs[linkAttribName];
    return resourcePath;
}
function _getResourcePathFromRoot(rootPath, fullResourcePath) {
    return fsUtil.ensurePosix(path_1.default.relative(rootPath, fullResourcePath));
}
/**
 * @param {string} resourcePath parsed from the node's relevant attribute
 * @returns {boolean} whether the resourcePath is a valid intra-site link
 */
function isIntraLink(resourcePath) {
    const MAILTO_OR_TEL_REGEX = /^(?:mailto:|tel:)/i;
    return !!resourcePath
        && !urlUtil.isUrl(resourcePath)
        && !resourcePath.startsWith('#')
        && !MAILTO_OR_TEL_REGEX.test(resourcePath);
}
function _convertRelativeLink(node, cwf, rootPath, baseUrl, resourcePath, linkAttribName) {
    if (!resourcePath || !isIntraLink(resourcePath)) {
        return;
    }
    if (path_1.default.isAbsolute(resourcePath)) {
        // Do not rewrite.
        return;
    }
    const cwd = path_1.default.dirname(cwf);
    const fullResourcePath = path_1.default.join(cwd, resourcePath);
    const resourcePathFromRoot = _getResourcePathFromRoot(rootPath, fullResourcePath);
    node.attribs[linkAttribName] = path_1.default.posix.join(baseUrl || '/', resourcePathFromRoot);
}
/**
 * Converts relative links in elements to absolute ones, prepended by the {@param baseUrl}.
 * This is needed because a relative link may have been from an included file (through <include>, etc.),
 * hence we need to rewrite the link accordingly.
 *
 * TODO allow plugins to tap into this process / extend {@link defaultTagLinkMap}
 *
 * @param  node from the dom traversal
 * @param  cwf as flagged from {@link NodeProcessor}
 * @param  rootPath of the root site
 * @param  baseUrl
 */
function convertRelativeLinks(node, cwf, rootPath, baseUrl) {
    if (node.name in defaultTagLinkMap) {
        const resourcePath = getDefaultTagsResourcePath(node);
        const linkAttribName = defaultTagLinkMap[node.name];
        _convertRelativeLink(node, cwf, rootPath, baseUrl, resourcePath, linkAttribName);
    }
    if (node.name in pluginTagConfig) {
        const tagConfig = pluginTagConfig[node.name];
        if (tagConfig.attributes) {
            tagConfig.attributes.forEach((attrConfig) => {
                if (attrConfig.isRelative && node.attribs) {
                    const resourcePath = node.attribs[attrConfig.name];
                    _convertRelativeLink(node, cwf, rootPath, baseUrl, resourcePath, attrConfig.name);
                }
            });
        }
    }
}
function convertMdExtToHtmlExt(node) {
    if (node.name === 'a' && node.attribs.href) {
        const hasNoConvert = _.has(node.attribs, 'no-convert');
        if (hasNoConvert) {
            return;
        }
        const { href } = node.attribs;
        if (urlUtil.isUrl(href)) {
            // Not intralink
            return;
        }
        const hrefUrl = (0, url_parse_1.default)(href);
        // get the first instance of URL fragment (first encounter of hash)
        const fragment = hrefUrl.hash === null ? '' : hrefUrl.hash;
        const pathName = hrefUrl.pathname === null ? '' : hrefUrl.pathname;
        const ext = path_1.default.posix.extname(pathName);
        const isExtMd = ext === '.md';
        if (!isExtMd) {
            // extension is not .md, we do not need to process the link
            return;
        }
        const pathNameWithoutExt = pathName.substring(0, pathName.length - ext.length);
        const newHref = `${pathNameWithoutExt}.html${fragment}`;
        node.attribs.href = newHref;
    }
}
function isValidPageSource(resourcePath, config) {
    const relativeResourcePath = resourcePath.startsWith('/')
        ? resourcePath.substring(1)
        : resourcePath;
    const relativeResourcePathWithNoExt = fsUtil.removeExtensionPosix(relativeResourcePath);
    const isPageSrc = config.addressablePagesSource.includes(relativeResourcePathWithNoExt);
    return isPageSrc;
}
function isValidFileAsset(resourcePath, config) {
    const relativeResourcePath = resourcePath.startsWith('/')
        ? resourcePath.substring(1)
        : resourcePath;
    const fileIgnore = (0, ignore_1.default)().add(config.ignore);
    if (relativeResourcePath && fileIgnore.ignores(relativeResourcePath)) {
        return true;
    }
    const fullResourcePath = path_1.default.join(config.rootPath, relativeResourcePath);
    return fsUtil.fileExists(fullResourcePath);
}
/**
 * Validates paths ending with '/' by checking if they represent valid page sources or file assets
 * with implicit index.html
 */
function validatePathEndingWithSlash(pathname, config, err) {
    // append index.html to e.g. /userGuide/
    const implicitResourcePath = `${pathname}index.html`;
    if (!isValidPageSource(implicitResourcePath, config) && !isValidFileAsset(implicitResourcePath, config)) {
        logger.warn(err);
        return 'Intralink ending with "/" is neither a Page Source nor File Asset';
    }
    return 'Intralink ending with "/" is a valid Page Source or File Asset';
}
/**
 * Validates paths without file extensions by checking various possible interpretations
 */
function validatePathWithNoExtension(pathname, config, err, hashErr, hash, filePathToHashesMap) {
    // does not end with '/' and no file ext (e.g. /userGuide)
    const implicitResourcePath = `${pathname}/index.html`;
    const asFileAsset = pathname;
    if (!isValidPageSource(implicitResourcePath, config) && !isValidFileAsset(implicitResourcePath, config)
        && !isValidFileAsset(asFileAsset, config)) {
        logger.warn(err);
        return 'Intralink with no extension is neither a Page Source nor File Asset';
    }
    if (hash !== undefined
        && (!filePathToHashesMap.get(asFileAsset) || !filePathToHashesMap.get(asFileAsset).has(hash))) {
        logger.warn(hashErr);
        return 'Intralink with no extension is a valid Page Source or File Asset but hash is not found';
    }
    return 'Intralink with no extension is a valid Page Source or File Asset';
}
/**
 * Validates paths with .html extensions by checking page sources and file assets
 */
function validatePathWithHtmlExtension(pathname, config, err, hashErr, hash, filePathToHashesMap) {
    if (!isValidPageSource(pathname, config) && !isValidFileAsset(pathname, config)) {
        logger.warn(err);
        return 'Intralink with ".html" extension is neither a Page Source nor File Asset';
    }
    if (hash !== undefined) {
        const filePath = `${pathname.slice(0, -5)}.md`;
        if (!filePathToHashesMap.get(filePath) || !filePathToHashesMap.get(filePath).has(hash)) {
            logger.warn(hashErr);
            return 'Intralink with ".html" extension is a valid Page Source or File Asset but hash is not found';
        }
    }
    return 'Intralink with ".html" extension is a valid Page Source or File Asset';
}
/**
 * Serves as an internal intra-link validator. Checks if the intra-links are valid.
 * If the intra-links are suspected to be invalid, a warning message will be logged.
 *
 * @param  resourcePath parsed from the node's relevant attribute
 * @param  cwf as flagged from {@link NodePreprocessor}
 * @param  config passed for page metadata access
 * @returns  these string return values are for unit testing purposes only
 */
function validateIntraLink(resourcePath, cwf, config, filePathToHashesMap = new Map()) {
    var _a;
    if (!isIntraLink(resourcePath)) {
        return 'Not Intralink';
    }
    const err = `You might have an invalid intra-link! Ignore this warning if it was intended.
'${resourcePath}' found in file '${cwf}'`;
    const hashErr = `You might have an invalid hash for intra-link! Ignore this warning if it was intended.'
  ${resourcePath}' found in file '${cwf}'`;
    const strippedResourcePath = urlUtil.stripBaseUrl(resourcePath, config.baseUrl);
    const resourcePathUrl = (0, url_parse_1.default)(strippedResourcePath);
    const hash = resourcePathUrl.hash ? resourcePathUrl.hash.substring(1) : undefined;
    const pathname = (_a = resourcePathUrl.pathname) !== null && _a !== void 0 ? _a : '';
    // Route to appropriate validator based on path characteristics
    if (pathname.endsWith('/')) {
        return validatePathEndingWithSlash(pathname, config, err);
    }
    const hasNoFileExtension = path_1.default.posix.extname(pathname) === '';
    if (hasNoFileExtension) {
        return validatePathWithNoExtension(pathname, config, err, hashErr, hash, filePathToHashesMap);
    }
    const hasHtmlExt = pathname.slice(-5) === '.html';
    if (hasHtmlExt) {
        return validatePathWithHtmlExtension(pathname, config, err, hashErr, hash, filePathToHashesMap);
    }
    // basic asset check
    if (!isValidFileAsset(pathname, config)) {
        logger.warn(err);
        return 'Intralink is not a File Asset';
    }
    return 'Intralink is a valid File Asset';
}
/**
 * Resolves and collects source file paths pointed to by attributes in nodes for live reload.
 * Only necessary for plugins for now.
 *
 * @param  node from the dom traversal
 * @param  rootPath site root path to resolve the link from
 * @param  baseUrl base url to strip off the link (if any)
 * @param  pageSources {@link PageSources} object to add the resolved file path to
 * @returns  these string return values are for unit testing purposes only
 */
function collectSource(node, rootPath, baseUrl, pageSources) {
    const tagConfig = pluginTagConfig[node.name];
    if (!tagConfig || !tagConfig.attributes) {
        return;
    }
    tagConfig.attributes.forEach((attrConfig) => {
        if (!attrConfig.isSourceFile) {
            return;
        }
        const sourceFileLink = node.attribs[attrConfig.name];
        if (!sourceFileLink || urlUtil.isUrl(sourceFileLink)) {
            return;
        }
        const linkWithoutBaseUrl = urlUtil.stripBaseUrl(sourceFileLink, baseUrl);
        const linkWithoutLeadingSlash = linkWithoutBaseUrl.startsWith('/')
            ? linkWithoutBaseUrl.substring(1)
            : linkWithoutBaseUrl;
        const fullResourcePath = path_1.default.join(rootPath, linkWithoutLeadingSlash);
        pageSources.staticIncludeSrc.push({ to: fullResourcePath });
    });
}
//# sourceMappingURL=linkProcessor.js.map