import { Command } from '@ckeditor/ckeditor5-core';
import { rangeToText, getAttributeValueOn, setAttributeOn, TOC_ELEMENTS, TOC_ATTRIBUTES} from './utils';
import { findOptimalInsertionPosition } from '@ckeditor/ckeditor5-widget/src/utils';
import { trimStart } from '../common/qmsCKEditorUtil';

export default class TableOfContentsCommand extends Command {

    execute( options = {} ) {
        const editor = this.editor;
        const model = editor.model;
        model.change(writer => {
            const headingObject = this._findHeadingElements(options.headings);
            if(Object.keys(headingObject).length > 0){
                const tocObject = this._buildTOCElements(headingObject);
                this._setAnchorIdAttribute(headingObject, tocObject, writer);
                this._insertTOCElements(tocObject, writer);
            }
        });
    }
    
    _insertTOCElements(tocObject, writer){
        let position;
        Object.keys(tocObject).forEach((key) => {
            const section = tocObject[key];
            const paragraphElement = writer.createElement(TOC_ELEMENTS.PARAGRAPH, { ...section.attributes });
            const textElement = writer.createText(section.text, { [TOC_ATTRIBUTES.LINK_HREF]: '#' + section.id });
            writer.insert(textElement, writer.createPositionAt(paragraphElement, 0));
            if(!position){
                position = findOptimalInsertionPosition(this.editor.model.document.selection, this.editor.model);
            }
            const { end: positionAfter } = this.editor.model.insertContent(paragraphElement, position);
            position = positionAfter;
        });
    }
 
    _setAnchorIdAttribute(headingObject, tocObject, writer){
        Object.keys(tocObject).forEach((key) =>{
            const node = headingObject[key];
            const sectionOject = tocObject[key];
            if(!sectionOject.hasAnchorId){
                const range = this.editor.model.createRangeIn(node);
                const attribute = { key: TOC_ATTRIBUTES.ANCHOR_ID, value: sectionOject.id }
                const hasAnchorId = setAttributeOn(range, this.editor.model.schema, writer, attribute);
                if(!hasAnchorId){
                    let childNode = writer.createText('\u00A0', { [TOC_ATTRIBUTES.ANCHOR_ID]: sectionOject.id });//insert non-breaking space
                    writer.insert(childNode, writer.createPositionAt(node, 0));
                }
            }
        });
    }

    _buildTOCElements(headingObject){
        const tocObject = {};
        let index = 1;
        Object.keys(headingObject).forEach(key =>{
            const node = headingObject[key];
            const range = this.editor.model.createRangeIn(node);
            const attributes = Object.fromEntries(node.getAttributes());
            const name = node.name.toLowerCase();
            attributes[TOC_ATTRIBUTES.HEADING_CLASS] = name;  
            const text = rangeToText(range) || '';
            let id = trimStart(text, '#').replace(/[^a-z0-9\+]/gi, '').toLowerCase() + index;
            let hasAnchorId = false;
            const anchorId = getAttributeValueOn(range, TOC_ATTRIBUTES.ANCHOR_ID);
            if(!!anchorId){
                id = trimStart(anchorId, '#');
                hasAnchorId = true;
            }
            tocObject[key] = { id, name, text, hasAnchorId, attributes };
            index++;
        });
        return tocObject;
    }

    _findHeadingElements(supportedHeadings){
        const headingObject = {};
        const range = this.editor.model.createRangeIn(this.editor.model.document.getRoot());
        for (const value of range.getWalker()) {
            const node = value.item;
            if (node.is('element') && supportedHeadings.indexOf(node.name) > -1) {
                let path = node.getPath();
                headingObject[path] = node;
            }
        }
        return headingObject;
    }
}
