'use strict';

const matchOptions = require('./match-options');
const paths = require('./paths');

const getPathDeclFile = paths.getPathDeclFile;
const getDirDeclFile = paths.getDirDeclFile;
const prepareAsset = paths.prepareAsset;

/**
 * @typedef UrlRegExp
 * @name UrlRegExp
 * @desc A regex for match url with parentheses:
 *   (before url)(the url)(after url).
 *    (the url) will be replace with new url, and before and after will remain
 * @type RegExp
 */
/**
 * @type {UrlRegExp[]}
 */
const URL_PATTERNS = [
    /(url\(\s*['"]?)([^"')]+)(["']?\s*\))/g,
    /(AlphaImageLoader\(\s*src=['"]?)([^"')]+)(["'])/g
];

const WITH_QUOTES = /^['"]/;

/**
 * Restricted modes
 *
 * @type {String[]}
 */
const PROCESS_TYPES = ['rebase', 'inline', 'copy', 'custom'];

const getUrlProcessorType = (optionUrl) =>
    typeof optionUrl === 'function' ? 'custom' : (optionUrl || 'rebase');

/**
 * @param {String} optionUrl
 * @returns {PostcssUrl~UrlProcessor}
 */
function getUrlProcessor(optionUrl) {
    const mode = getUrlProcessorType(optionUrl);

    if (PROCESS_TYPES.indexOf(mode) === -1) {
        throw new Error(`Unknown mode for postcss-url: ${mode}`);
    }

    return require(`../type/${mode}`);
}

/**
 * @param {PostcssUrl~UrlProcessor} urlProcessor
 * @param {Result} result
 * @param {Decl} decl
 * @returns {Function}
 */
const wrapUrlProcessor = (urlProcessor, result, decl) => {
    const warn = (message) => decl.warn(result, message);
    const addDependency = (file) => result.messages.push({
        type: 'dependency',
        file,
        parent: getPathDeclFile(decl)
    });

    return (asset, dir, option) =>
        urlProcessor(asset, dir, option, decl, warn, result, addDependency);
};

/**
 * @param {Decl} decl
 * @returns {RegExp}
 */
const getPattern = (decl) =>
    URL_PATTERNS.find((pattern) => pattern.test(decl.value));

/**
 * @param {String} url
 * @param {Dir} dir
 * @param {Options} options
 * @param {Result} result
 * @param {Decl} decl
 * @returns {String|undefined}
 */
const replaceUrl = (url, dir, options, result, decl) => {
    const asset = prepareAsset(url, dir, decl);

    const matchedOptions = matchOptions(asset, options);

    if (!matchedOptions) return;

    const process = (option) => {
        const wrappedUrlProcessor = wrapUrlProcessor(getUrlProcessor(option.url), result, decl);

        return wrappedUrlProcessor(asset, dir, option);
    };

    if (Array.isArray(matchedOptions)) {
        matchedOptions.forEach((option) => asset.url = process(option));
    } else {
        asset.url = process(matchedOptions);
    }

    return asset.url;
};

/**
 * @param {String} from
 * @param {String} to
 * @param {PostcssUrl~Options} options
 * @param {Result} result
 * @param {Decl} decl
 * @returns {PostcssUrl~DeclProcessor}
 */
const declProcessor = (from, to, options, result, decl) => {
    const dir = { from, to, file: getDirDeclFile(decl) };
    const pattern = getPattern(decl);

    if (!pattern) return;

    decl.value = decl.value
        .replace(pattern, (matched, before, url, after) => {
            const newUrl = replaceUrl(url, dir, options, result, decl);

            if (!newUrl) return matched;

            if (WITH_QUOTES.test(newUrl) && WITH_QUOTES.test(after)) {
                before = before.slice(0, -1);
                after = after.slice(1);
            }

            return `${before}${newUrl}${after}`;
        });
};

module.exports = {
    replaceUrl,
    declProcessor
};

/**
 * @typedef {Object} PostcssUrl~Options - postcss-url Options
 * @property {String} [url=^rebase|inline|copy|custom] - processing mode
 * @property {Minimatch|RegExp|Function} [filter] - filter assets by relative pathname
 * @property {String} [assetsPath] - absolute or relative path to copy assets
 * @property {String|String[]} [basePath] - absolute or relative paths to search, when copy or inline
 * @property {Number} [maxSize] - max file size in kbytes for inline mode
 * @property {String} [fallback] - fallback mode if file exceeds maxSize
 * @property {Boolean} [useHash] - use file hash instead filename
 * @property {HashOptions} [hashOptions] - params for generating hash name
 */