"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RichTextConverter = exports.FontStyles = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const color_1 = require("@cntrl-site/color");
const sdk_1 = require("@cntrl-site/sdk");
const LinkWrapper_1 = require("../../components/LinkWrapper");
const getFontFamilyValue_1 = require("../getFontFamilyValue");
exports.FontStyles = {
    'normal': {},
    'bold': { 'font-weight': 'bold' },
    'italic': { 'font-style': 'italic' }
};
class RichTextConverter {
    toHtml(richText, layouts) {
        var _a, _b, _c;
        const { text, blocks = [] } = richText.commonParams;
        const root = [];
        const styleRules = layouts.reduce((rec, layout) => {
            rec[layout.id] = [];
            return rec;
        }, {});
        let currentLineHeight = layouts.reduce((rec, layout) => {
            var _a;
            const styles = richText.layoutParams[layout.id].rangeStyles;
            rec[layout.id] = (_a = styles === null || styles === void 0 ? void 0 : styles.find(s => s.style === 'LINEHEIGHT')) === null || _a === void 0 ? void 0 : _a.value;
            return rec;
        }, {});
        for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
            const block = blocks[blockIndex];
            const content = text.slice(block.start, block.end);
            const entities = (_a = block.entities.sort((a, b) => a.start - b.start)) !== null && _a !== void 0 ? _a : [];
            if (content.length === 0) {
                const id = `rt_${richText.id}_br_${blockIndex}`;
                root.push((0, jsx_runtime_1.jsx)("div", { className: id, children: (0, jsx_runtime_1.jsx)("br", {}) }, id));
                layouts.forEach(l => {
                    const lhForLayout = currentLineHeight[l.id];
                    if (lhForLayout === undefined)
                        return;
                    const lh = RichTextConverter.fromRangeStylesToInline({
                        name: 'LINEHEIGHT',
                        value: lhForLayout
                    }, l.exemplary);
                    styleRules[l.id].push(`.rt_${richText.id}_br_${blockIndex} {${lh}}`);
                });
                continue;
            }
            const newStylesGroup = layouts.map(({ id: layoutId }) => {
                const params = richText.layoutParams[layoutId];
                const styles = params.rangeStyles
                    .filter(s => s.start >= block.start && s.end <= block.end)
                    .map(s => (Object.assign(Object.assign({}, s), { start: s.start - block.start, end: s.end - block.start })));
                return ({
                    layout: layoutId,
                    styles: this.normalizeStyles(styles, entities)
                });
            });
            const sameLayouts = groupBy(newStylesGroup, (item) => { var _a; return this.serializeRanges((_a = item.styles) !== null && _a !== void 0 ? _a : []); });
            for (const group of Object.values(sameLayouts)) {
                const blockClass = `rt_${richText.id}-b${blockIndex}_${layouts.map(l => group.some(g => g.layout === l.id) ? '1' : '0').join('')}`;
                const kids = [];
                layouts.forEach(l => {
                    const ta = richText.layoutParams[l.id].textAlign;
                    styleRules[l.id].push(`
            .${blockClass} {
              display: ${group.some(g => g.layout === l.id) ? 'block' : 'none'};
              text-align: ${ta};
              white-space: pre-wrap;
              overflow-wrap: break-word;
            }
          `);
                });
                const item = group[0];
                const entitiesGroups = (_b = this.groupEntities(entities, item.styles)) !== null && _b !== void 0 ? _b : [];
                let offset = 0;
                for (const entity of entitiesGroups) {
                    const entityKids = [];
                    if (offset < entity.start) {
                        kids.push(sliceSymbols(content, offset, entity.start));
                        offset = entity.start;
                    }
                    for (const style of entity.stylesGroup) {
                        if (offset < style.start) {
                            entityKids.push(sliceSymbols(content, offset, style.start));
                        }
                        entityKids.push((0, jsx_runtime_1.jsx)("span", { className: `s-${style.start}-${style.end}`, children: sliceSymbols(content, style.start, style.end) }, style.start));
                        offset = style.end;
                    }
                    if (offset < entity.end) {
                        entityKids.push(sliceSymbols(content, offset, entity.end));
                        offset = entity.end;
                    }
                    if (entity.link) {
                        kids.push((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, { url: entity.link, target: entity.target, children: entityKids }, entity.start));
                        continue;
                    }
                    kids.push(...entityKids);
                }
                if (offset < getSymbolsCount(content)) {
                    kids.push(sliceSymbols(content, offset));
                }
                for (const item of group) {
                    const { exemplary } = layouts.find(l => l.id === item.layout);
                    const entitiesGroups = (_c = this.groupEntities(entities, item.styles)) !== null && _c !== void 0 ? _c : [];
                    for (const entitiesGroup of entitiesGroups) {
                        if (!entitiesGroup.stylesGroup)
                            continue;
                        for (const styleGroup of entitiesGroup.stylesGroup) {
                            const lineHeight = styleGroup.styles.find(s => s.name === 'LINEHEIGHT');
                            const color = styleGroup.styles.find(s => s.name === 'COLOR');
                            if (lineHeight === null || lineHeight === void 0 ? void 0 : lineHeight.value) {
                                currentLineHeight[item.layout] = lineHeight.value;
                            }
                            styleRules[item.layout].push(`
                .${blockClass} .s-${styleGroup.start}-${styleGroup.end} {
                  ${styleGroup.styles.map(s => RichTextConverter.fromRangeStylesToInline(s, exemplary)).join('\n')}
                }
              `);
                            if (color) {
                                styleRules[item.layout].push(`
                @supports not (color: oklch(42% 0.3 90 / 1)) {
                  .${blockClass} .s-${styleGroup.start}-${styleGroup.end} {
                    color: ${color_1.CntrlColor.parse(getResolvedValue(color.value, 'COLOR')).fmt('rgba')};
                  }
                }
              `);
                            }
                        }
                    }
                }
                root.push((0, jsx_runtime_1.jsx)("div", { className: blockClass, children: kids }, blockClass));
            }
        }
        const styles = layouts.map(l => `
      ${(0, sdk_1.getLayoutMediaQuery)(l.id, layouts)} {
        ${styleRules[l.id].join('\n')}
      }
    `).join('\n');
        return [
            root,
            styles
        ];
    }
    serializeRanges(ranges) {
        return ranges.map(r => `${r.start},${r.end}`).join(' ');
    }
    normalizeStyles(styles, entities) {
        const styleGroups = [];
        const dividers = [...styles, ...entities].reduce((ds, s) => {
            ds.add(s.start);
            ds.add(s.end);
            return ds;
        }, new Set());
        if (dividers.size === 0)
            return;
        const edges = Array.from(dividers).sort((a, b) => a - b);
        for (let i = 0; i < edges.length - 1; i += 1) {
            const start = edges[i];
            const end = edges[i + 1];
            const applied = styles.filter(s => Math.max(s.start, start) < Math.min(s.end, end));
            if (applied.length === 0)
                continue;
            styleGroups.push({
                start,
                end,
                styles: applied.map(s => ({ name: s.style, value: s.value }))
            });
        }
        return styleGroups;
    }
    groupEntities(entities, styleGroups) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        const entitiesGroups = [];
        if (!entities.length && !styleGroups)
            return;
        if (!styleGroups || styleGroups.length === 0) {
            const dividersSet = entities.reduce((ds, e) => {
                // some entities may have no data, need to filter them out
                // (case with deleting a section/page that was linked to)
                if (!e.hasOwnProperty('data'))
                    return ds;
                ds.add(e.start);
                ds.add(e.end);
                return ds;
            }, new Set([entities[0].start, entities[entities.length - 1].end]));
            const dividers = Array.from(dividersSet).sort((a, b) => a - b);
            for (let i = 0; i < dividers.length - 1; i += 1) {
                const start = dividers[i];
                const end = dividers[i + 1];
                const entity = entities.find(e => e.start === start);
                entitiesGroups.push(Object.assign({ stylesGroup: [], start,
                    end }, (entity && { link: (_b = (_a = entity.data) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '', target: (_d = (_c = entity.data) === null || _c === void 0 ? void 0 : _c.target) !== null && _d !== void 0 ? _d : '_self' })));
            }
            return entitiesGroups;
        }
        if (entities.length === 0) {
            entitiesGroups.push({
                stylesGroup: styleGroups,
                start: styleGroups[0].start,
                end: styleGroups[styleGroups.length - 1].end
            });
            return entitiesGroups;
        }
        const start = entities[0].start < styleGroups[0].start ? entities[0].start : styleGroups[0].start;
        const end = entities[entities.length - 1].end > styleGroups[styleGroups.length - 1].end ? entities[entities.length - 1].end : styleGroups[styleGroups.length - 1].end;
        const entitiesDividers = entities.reduce((ds, e) => {
            if (!e.hasOwnProperty('data'))
                return ds;
            ds.add(e.start);
            ds.add(e.end);
            return ds;
        }, new Set([start, end]));
        const entityDividers = Array.from(entitiesDividers).sort((a, b) => a - b);
        for (let i = 0; i < entityDividers.length - 1; i += 1) {
            const start = entityDividers[i];
            const end = entityDividers[i + 1];
            const entity = entities.find(e => e.start === start);
            entitiesGroups.push(Object.assign({ stylesGroup: styleGroups.filter(s => s.start >= start && s.end <= end), start,
                end }, (entity && { link: (_f = (_e = entity.data) === null || _e === void 0 ? void 0 : _e.url) !== null && _f !== void 0 ? _f : '', target: (_h = (_g = entity.data) === null || _g === void 0 ? void 0 : _g.target) !== null && _h !== void 0 ? _h : '_self' })));
        }
        return entitiesGroups;
    }
    static fromRangeStylesToInline(draftStyle, exemplary) {
        const { value, name } = draftStyle;
        const map = {
            'COLOR': { 'color': getResolvedValue(value, name) },
            'TYPEFACE': { 'font-family': `${(0, getFontFamilyValue_1.getFontFamilyValue)(value)}` },
            'FONTSTYLE': value ? Object.assign({}, exports.FontStyles[value]) : {},
            'FONTWEIGHT': { 'font-weight': value },
            'FONTSIZE': { 'font-size': `${parseFloat(value) * exemplary}px` },
            'LINEHEIGHT': { 'line-height': `${parseFloat(value) * exemplary}px` },
            'LETTERSPACING': { 'letter-spacing': `${parseFloat(value) * exemplary}px` },
            'WORDSPACING': { 'word-spacing': `${parseFloat(value) * exemplary}px` },
            'TEXTTRANSFORM': value ? { 'text-transform': value } : { 'text-transform': sdk_1.TextTransform.None },
            'VERTICALALIGN': value ? { 'vertical-align': value } : { 'vertical-align': sdk_1.VerticalAlign.Unset },
            'TEXTDECORATION': { 'text-decoration': value },
            'FONTVARIANT': { 'font-variant': value }
        };
        const css = map[name];
        if (!css) {
            return '';
        }
        return Object.entries(css).filter(([, value]) => !!value).map(([prop, value]) => `${prop}: ${value};`).join('\n');
    }
}
exports.RichTextConverter = RichTextConverter;
function groupBy(items, getKey) {
    const groups = {};
    for (const item of items) {
        const key = getKey(item);
        if (!groups[key]) {
            groups[key] = [];
        }
        groups[key].push(item);
    }
    return groups;
}
function getResolvedValue(value, name) {
    if (name !== 'COLOR')
        return value;
    return value ? color_1.CntrlColor.parse(value).toCss() : value;
}
function sliceSymbols(text, start, end = NaN) {
    let startOffset = NaN;
    let endOffset = 0;
    let count = -1;
    for (const ch of text) {
        count += 1;
        if (count === start) {
            startOffset = endOffset;
        }
        if (count === end)
            break;
        endOffset += ch.length;
    }
    if (isNaN(startOffset))
        return '';
    return text.slice(startOffset, endOffset);
}
function getSymbolsCount(input) {
    let count = 0;
    let ch;
    for (ch of input) {
        count += 1;
    }
    return count;
}
