import { escape } from "html-escaper";
import { pickBy, negate } from "lodash-es";
export class ColumnDefinitionFactory {
    constructor(pool) {
        this.pool = pool;
        this.create = this.create.bind(this);
    }
    /**
     * Create Tabulator column definitions from a limel-table column configuration
     *
     * @param column - config describing the column
     * @returns Tabulator column
     */
    create(column) {
        var _a;
        const definition = {
            title: column.title,
            field: column.field,
            hozAlign: column.horizontalAlign,
            headerSort: column.headerSort,
            headerSortStartingDir: 'desc',
        };
        if (column.headerComponent) {
            definition.titleFormatter = formatHeader(column);
        }
        if (((_a = column.component) === null || _a === void 0 ? void 0 : _a.name) || column.formatter) {
            definition.formatter = createFormatter(column, this.pool);
            definition.formatterParams = column;
        }
        if (column.aggregator) {
            definition.bottomCalc = getColumnAggregator(column);
        }
        return definition;
    }
}
/**
 * Formats the header of the column
 *
 * @param column - the configuration for the column
 * @returns custom component that renders a column header
 */
export const formatHeader = (column) => () => {
    const headerElement = document.createElement('div');
    headerElement.setAttribute('class', 'lime-col-title__custom-component');
    const titleElement = document.createElement('span');
    titleElement.setAttribute('class', 'title-component-text');
    titleElement.textContent = column.title;
    const customElement = document.createElement(column.headerComponent.name);
    customElement.setAttribute('class', 'title-component-slot');
    let props = column.headerComponent.props || {};
    if (column.headerComponent.propsFactory) {
        props = Object.assign(Object.assign({}, props), column.headerComponent.propsFactory(null));
    }
    setElementProperties(customElement, props);
    headerElement.append(titleElement);
    headerElement.append(customElement);
    return headerElement;
};
/**
 * Create a formatter to be used to format values in a column
 *
 * @param column - config describing the column
 * @param pool - pool to get custom components from
 * @returns Tabulator formatter
 */
export function createFormatter(column, pool) {
    var _a;
    if (!((_a = column.component) === null || _a === void 0 ? void 0 : _a.name)) {
        return formatCell;
    }
    if (!columnElementExists(column)) {
        console.warn(`Failed to render custom component for column "${column.field.toString()}". Custom element <${column.component.name}/> does not exist. Using the default formatter.`);
        return formatCell;
    }
    return (cell) => {
        const value = formatCell(cell, column);
        return createCustomComponent(cell, column, value, pool);
    };
}
function columnElementExists(column) {
    const name = column.component.name;
    if (typeof name === 'string') {
        const isNativeElement = !name.includes('-');
        const customElementExists = customElements.get(column.component.name);
        return isNativeElement || customElementExists;
    }
    else {
        return false;
    }
}
/**
 * Format the value of a cell in the table
 *
 * @param cell - the cell being rendered in the table
 * @param column - configuration for the current column
 * @returns the formatted value
 */
export function formatCell(cell, column) {
    const data = cell.getData();
    let value = cell.getValue();
    if (column.formatter) {
        value = column.formatter(value, data);
    }
    if (typeof value === 'string' && !column.component) {
        value = escape(value);
    }
    return value;
}
/**
 * Create a custom component that renders a cell value
 *
 * @param cell - Tabulator cell
 * @param column - lime-elements column configuration
 * @param value - the value of the cell being rendered
 * @param pool - pool to get custom components from
 * @returns custom component that renders a value in the table
 */
export function createCustomComponent(cell, column, value, pool) {
    const field = cell.getField();
    const data = cell.getData();
    const element = pool.get(column.component.name);
    let props = column.component.props || {};
    if (column.component.propsFactory) {
        props = Object.assign(Object.assign({}, props), column.component.propsFactory(data));
    }
    props = Object.assign(Object.assign({}, props), { field: field, value: value, data: data });
    element.style.display = 'inline-block';
    setElementProperties(element, props);
    createResizeObserver(element, cell.getColumn());
    return element;
}
/**
 * Set all properties for a custom element, including event listeners
 *
 * @param element - the custom element
 * @param props - object of properties and event listeners
 */
export function setElementProperties(element, props) {
    const properties = pickBy(props, negate(isEventListener));
    Object.assign(element, properties);
    const listeners = pickBy(props, isEventListener);
    for (const [key, value] of Object.entries(listeners)) {
        const event = getEventName(key);
        element.addEventListener(event, value);
    }
}
/**
 * Check if a property is an event listener.
 *
 * An event listener has to be a function and its property name have to start
 * with "on" followed by the name of the event in camel case, e.g. "onEventName"
 *
 * @param value - the value to check
 * @param key - name of the property
 * @returns true if the property of the object is an event listener
 */
function isEventListener(value, key) {
    if (typeof value !== 'function') {
        return false;
    }
    return /^on[A-Z]/.test(key);
}
/**
 * Get the name of an event from the name of an event listener
 *
 * E.g. "onMyEvent" will return "myEvent"
 *
 * @param eventListener - name of the event listener
 * @returns the name of the event
 */
function getEventName(eventListener) {
    return eventListener.charAt(2).toLowerCase() + eventListener.slice(3);
}
function createResizeObserver(element, column) {
    if (!('ResizeObserver' in window)) {
        return;
    }
    const RESIZE_TIMEOUT = 1000;
    const COLUMN_PADDING = 16;
    const observer = new ResizeObserver(() => {
        const width = element.getBoundingClientRect().width;
        const newWidth = width + COLUMN_PADDING;
        if (newWidth < column.getWidth()) {
            return;
        }
        column.setWidth(newWidth);
    });
    observer.observe(element);
    // We give the component some time to resize itself before we
    // stop listening for resize events
    setTimeout(() => {
        observer.unobserve(element);
    }, RESIZE_TIMEOUT);
}
/**
 * Create a column sorter from a tabulator sorter
 *
 * @param columns - all available columns in the table
 * @returns function that creates a sorter from a tabulator sorter
 */
export const createColumnSorter = (columns) => (sorter) => {
    const column = columns.find((col) => col.field === sorter.field);
    const direction = sorter.dir.toUpperCase();
    return {
        column: column,
        direction: direction,
    };
};
/**
 *
 * @param column
 */
export function getColumnAggregator(column) {
    const aggregator = column.aggregator;
    if (isAggregatorFunction(aggregator)) {
        return (values, data) => {
            return aggregator(column, values, data);
        };
    }
    return aggregator;
}
function isAggregatorFunction(value) {
    return typeof value === 'function';
}
