import { ConfigStore } from 'lib2/stores';

/**
 * преобразует словесный идентификатор метки в формат RUM по BlockStat-словарю
 * @see https://stat.yandex-team.ru/DictionaryEditor/BlockStat
 * @example ('ru.connect.portal') > '28.2732.2971'
 * @param {String|Array} id
 * @returns {String}
 */
function toBlockStatCode(id) {
    const { rum } = window.ya.connect;

    return (Array.isArray(id) ? id : id.split('.'))
        .map(component => rum.dictionary[component])
        .filter(Boolean)
        .join('.');
}

function log(...args) {
    let debugMode = ConfigStore.get('app.env') === 'development' ||
        new URL(window.location.href).searchParams.get('debug');

    if (debugMode) {
        // eslint-disable-next-line no-console
        console.log.apply(null, ['[rum]'].concat(args));
    }
}

/**
 * возвращает идентификатор замера для форм
 * @param {DOMNode} formElement
 * @returns {String}
 */
function getFormId(formElement) {
    if (!formElement || !formElement.className) {
        return;
    }

    // выделяем класс, заканчивающийся на '-form';
    // все интересующие нас формы содержат такой класс, идентифицирующий форму
    let timingId = formElement.className
        .split(/\s+/)
        .filter(item => /-form$/.test(item))[0];

    if (!timingId) {
        return;
    }

    timingId = timingId
        .replace(/-form$/, '')
        .replace(/[-_\.]+/g, '.');

    return `form.${timingId}`;
}

/**
 * возвращает идентификатор текущего роута
 * @returns {String}
 */
function getRouteId() {
    const { rum } = window.ya.connect;

    if (!rum) {
        return;
    }

    const appRoot = ConfigStore.get('app.root');
    let path = window.location.pathname;

    // убираем путь до корня приложения из всех путей
    // как неинформативную часть
    if (appRoot && path.indexOf(appRoot) === 0) {
        path = path.substring(appRoot.length);
    }

    path = path
        .split('/')
        .filter(item => {
            // выкидываем id: числа
            if (/^\d+$/.test(item)) {
                return false;
            }

            // выкидываем домены: с точкой
            if (item.indexOf('.') !== -1) {
                return false;
            }

            return true;
        })
        .join('.');

    return `route.${path}`;
}

export class Timing {
    /**
     * создаёт объект для замера времени
     * @param {String?} subpageId
     * @see start()
     */
    constructor(subpageId) {
        if (subpageId) {
            this.start(subpageId);
        }
    }

    /**
     * отмечает начало замера
     * @param {String} subpageId - идентификатор подстраницы замера; например, 'page.welcome'
     */
    start(subpageId) {
        if (!window.Ya || !Ya.Rum) {
            return;
        }

        this._subpageId = subpageId;
        this.subpageId = toBlockStatCode(subpageId);

        this.subpage = Ya.Rum.makeSubPage(this.subpageId);
        this.startTime = Ya.Rum.getTime();

        log('timing started', this);
    }

    /**
     * производит замер времени относительно времени вызова start();
     * замер времени производится один раз независимо от количества вызовов end()
     *
     * @param {String} deltaId - идентификатор замера; например, 'page.loading'
     * @returns {Boolean} - сделан ли замер
     */
    end(deltaId) {
        if (!deltaId) {
            throw new Error('Can\'t end timing with empty delta id');
        }

        if (!window.Ya || !Ya.Rum || this._locked || this._ended) {
            return false;
        }

        this._ended = true;

        this._deltaId = deltaId;
        this.deltaId = toBlockStatCode(deltaId);

        this.endTime = Ya.Rum.getTime();
        this.delta = this.endTime - this.startTime;

        log('timing ended', this);

        Ya.Rum.sendDelta(this.deltaId, this.delta, this.subpage);

        return true;
    }

    /**
     * ставит лок на замер времени;
     * пока замер залочен, вызовы end() не регистрируют окончание замера времени
     */
    lock() {
        if (this._locked || this._ended) {
            return;
        }

        this._locked = true;

        log('timing locked', this);
    }

    /**
     * снимает лок с замера времени
     * @see lock()
     */
    unlock() {
        if (!this._locked || this._ended) {
            return;
        }

        this._locked = false;

        log('timing unlocked', this);
    }
}

/**
 * класс для замеров в формах
 * @see Timing
 */
export class FormTiming extends Timing {
    constructor(formElement) {
        super(getFormId(formElement));
    }
}

/**
 * синглтон для замера загрузки роута;
 * роуты замеряются одним общим для всех компонентов инстансом
 * @see Timing
 *
 * end() может вызываться из componentDidMount и, опционально, из componentDidUpdate;
 * замер времени производится один раз независимо от количества вызовов end()
 *
 * lock() полезен для компонентов, не ремаунтящихся при переходе между роутами
 * и вызывающих end() из componentDidUpdate;
 * чтобы замер произошёл после всех промежуточных апдейтов, роут можно залочить
 * до финального экшена (например, снимающего спиннер)
 */
export const RouteTiming = {
    start() {
        this._timing = new Timing(getRouteId());
    },
    end() {
        this._timing && this._timing.end('route.loading') && (this._timing = null);
    },
    lock() {
        this._timing && this._timing.lock();
    },
    unlock() {
        this._timing && this._timing.unlock();
    },
};

/**
 * отправляет метку времени с указанным id
 * @param {String} id
 */
function mark(id) {
    if (!window.Ya || !Ya.Rum) {
        return;
    }

    Ya.Rum.sendTimeMark(toBlockStatCode(id));
}

export default {
    mark,
};
