import React from 'react';
import Link  from 'app/utils/SlateEditor/Link';
import Image  from 'app/utils/SlateEditor/Image';
import Iframe  from 'app/utils/SlateEditor/Iframe';
import HRule  from 'app/utils/SlateEditor/HRule';
import Align  from 'app/utils/SlateEditor/Align';

const $ = require('cheerio');

/**
 * Slate editor/toolbar schema, defaults
 */
export const schema = {
    defaultNode: 'paragraph',
    toolbarMarks: [
        { type: 'bold', label: <strong>B</strong> },
        { type: 'italic', label: <i>i</i> },
        //{ type: 'underline', label: <u>U</u> },
        //{ type: 'strike',    label: <del>S</del> },
        { type: 'code', label: <code>{'{}'}</code> },
        {
            type: 'sup',
            label: (
                <span>
                    x<sup>2</sup>
                </span>
            ),
        },
        {
            type: 'sub',
            label: (
                <span>
                    x<sub>2</sub>
                </span>
            ),
        },
    ],

    // blockTypes: {...Blocks,},
    // toolbarTypes: [],
    // sidebarTypes: [],

    nodes: {
        paragraph: ({ children, attributes }) => <p {...attributes}>{children}</p>,
        'code-block': ({ children, attributes }) => (
            <pre {...attributes}>
                <code>{children}</code>
            </pre>
        ),
        'block-quote': ({ children, attributes }) => <blockquote {...attributes}>{children}</blockquote>,
        'bulleted-list': ({ children, attributes }) => <ul {...attributes}>{children}</ul>,
        'numbered-list': ({ children, attributes }) => <ol {...attributes}>{children}</ol>,
        'heading-one': ({ children, attributes }) => <h1 {...attributes}>{children}</h1>,
        'heading-two': ({ children, attributes }) => <h2 {...attributes}>{children}</h2>,
        'heading-three': ({ children, attributes }) => <h3 {...attributes}>{children}</h3>,
        'heading-four': ({ children, attributes }) => <h4 {...attributes}>{children}</h4>,
        'list-item': ({ children, attributes }) => <li {...attributes}>{children}</li>,
        table: ({ children, attributes }) => <table {...attributes}>{children}</table>,
        thead: ({ children, attributes }) => <thead {...attributes}>{children}</thead>,
        tbody: ({ children, attributes }) => <tbody {...attributes}>{children}</tbody>,
        tr: ({ children, attributes }) => <tr {...attributes}>{children}</tr>,
        td: ({ children, attributes }) => <td {...attributes}>{children}</td>,
        th: ({ children, attributes }) => <th {...attributes}>{children}</th>,
        hr: HRule,
        image: Image,
        link: Link,
        embed: Iframe,
        align: Align,
    },

    marks: {
        bold: (props) => <strong>{props.children}</strong>,
        code: (props) => <code>{props.children}</code>,
        italic: (props) => <em>{props.children}</em>,
        underline: (props) => <u>{props.children}</u>,
        strike: (props) => <del>{props.children}</del>,
        sub: (props) => <sub>{props.children}</sub>,
        sup: (props) => <sup>{props.children}</sup>,
    },
};

/**
 * Rules for de/serializing editor state to and from HTML
 */

// Map html --> block type
const BLOCK_TAGS = {
    p: 'paragraph',
    blockquote: 'block-quote',
    pre: 'code-block',
    h1: 'heading-one',
    h2: 'heading-two',
    h3: 'heading-three',
    h4: 'heading-four',
    ul: 'bulleted-list',
    ol: 'numbered-list',
    li: 'list-item',
    hr: 'hr',
    table: 'table',
    thead: 'thead',
    tbody: 'tbody',
    tr: 'tr',
    td: 'td',
    th: 'th',
};

// Map HTML --> mark type
const MARK_TAGS = {
    em: 'italic',
    i: 'italic',
    strong: 'bold',
    b: 'bold',
    u: 'underline',
    del: 'strike',
    strike: 'strike',
    sup: 'sup',
    sub: 'sub',
};

const validAligns = ['pull-right', 'pull-left', 'text-justify', 'text-rtl', 'text-center', 'text-right'];

/**
 * Rules for converting from and to HTML. The first rules are highest priority,
 * with unmatched cases (i.e. null return) falling through to those below.
 */
export const HtmlRules = [
    // Catch-all debug wrapper
    {
        //deserialize: (el, next) => console.log("** deserialize: ", $.html(el).replace(/\n/g, "\\n")),
        //serialize: (object, children) => console.log("** serialize:", object.type, object.kind, 'data:', JSON.stringify(object.data))
    },

    // Alignment wrapper
    {
        deserialize: (el, next) => {
            if (el.tagName == 'center') {
                return {
                    kind: 'block',
                    type: 'align',
                    data: { align: 'text-center' },
                    nodes: next(el.children),
                };
            }
            if (el.tagName == 'div') {
                const align = el.attribs.class;
                if (!validAligns.includes(align)) return;
                return {
                    kind: 'block',
                    type: 'align',
                    data: { align },
                    nodes: next(el.children),
                };
            }
        },
        serialize: (object, children) => {
            if (object.kind == 'block' && object.type == 'align') {
                const align = object.data.get('align');
                return <div className={align}>{children}</div>;
            }
        },
    },

    // Block rules
    {
        deserialize: (el, next) => {
            const type = BLOCK_TAGS[el.tagName];
            if (!type) return;

            // Special case for <pre>: ignore its inner <code> element.
            const code = el.tagName == 'pre' ? el.children[0] : null;
            let children = code && code.tagName == 'code' ? code.children : el.children;

            // due to disabled/broken whitespace normalization in cheerio/htmlparser2, perform basic cleaning...
            //   i.e. removal of text nodes where they are invalid -- otherwise they may convert to <br />s in bad places
            const noTextChildren = 'ol,ul,table,thead,tbody,tr'.split(',');
            if (noTextChildren.includes(el.tagName)) {
                children = children.filter((el) => el.type !== 'text');
            }

            // If this block-level node contains *any* <center> tags, strip them out and wrap-align node
            let center = false;
            children = children.reduce((out, child) => {
                if (child.tagName == 'center') {
                    center = true;
                    //child.children.map(c => out.push(c))
                    out.push(...child.children);
                } else {
                    out.push(child);
                }
                return out;
            }, []);

            // Generate output block with clean children
            const block = {
                kind: 'block',
                type: type,
                isVoid: type == 'hr',
                nodes: next(children),
            };

            // Wrap output block with align node if needed
            if (center) {
                console.log('** force-centering node');
                return {
                    kind: 'block',
                    type: 'align',
                    data: { align: 'text-center' },
                    nodes: [block],
                };
            }

            // Otherwise return plain block
            return block;
        },

        serialize: (object, children) => {
            if (object.kind !== 'block') return;
            switch (object.type) {
                case 'paragraph':
                    return <p>{children}</p>;
                case 'block-quote':
                    return <blockquote>{children}</blockquote>;
                case 'code-block':
                    return (
                        <pre>
                            <code>{children}</code>
                        </pre>
                    );
                case 'heading-one':
                    return <h1>{children}</h1>;
                case 'heading-two':
                    return <h2>{children}</h2>;
                case 'heading-three':
                    return <h3>{children}</h3>;
                case 'heading-four':
                    return <h4>{children}</h4>;
                case 'bulleted-list':
                    return <ul>{children}</ul>;
                case 'numbered-list':
                    return <ol>{children}</ol>;
                case 'list-item':
                    return <li>{children}</li>;
                case 'hr':
                    return <hr />;
                case 'table':
                    return <table>{children}</table>;
                case 'thead':
                    return <thead>{children}</thead>;
                case 'tbody':
                    return <tbody>{children}</tbody>;
                case 'tr':
                    return <tr>{children}</tr>;
                case 'td':
                    return <td>{children}</td>;
                case 'th':
                    return <th>{children}</th>;
            }
        },
    },

    // Mark rules
    {
        deserialize: (el, next) => {
            const type = MARK_TAGS[el.tagName];
            if (!type) return;
            return {
                kind: 'mark',
                type: type,
                nodes: next(el.children),
            };
        },
        serialize: (object, children) => {
            if (object.kind !== 'mark') return;
            switch (object.type) {
                case 'bold':
                    return <strong>{children}</strong>;
                case 'italic':
                    return <em>{children}</em>;
                case 'underline':
                    return <u>{children}</u>;
                case 'strike':
                    return <del>{children}</del>;
                case 'code':
                    return <code>{children}</code>;
                case 'sup':
                    return <sup>{children}</sup>;
                case 'sub':
                    return <sub>{children}</sub>;
            }
        },
    },

    // Custom
    {
        deserialize: (el, next) => {
            switch (el.tagName) {
                case 'iframe':
                    return {
                        kind: 'block',
                        type: 'embed',
                        isVoid: true,
                        data: { src: el.attribs.src },
                        nodes: next(el.children),
                    };
                case 'img':
                    return {
                        kind: 'inline',
                        type: 'image',
                        isVoid: true,
                        data: {
                            src: el.attribs.src,
                            alt: el.attribs.alt,
                        },
                        nodes: next(el.children),
                    };
                case 'a':
                    return {
                        kind: 'inline',
                        type: 'link',
                        data: { href: el.attribs.href },
                        nodes: next(el.children),
                    };
                case 'br':
                    return {
                        kind: 'text',
                        ranges: [{ text: '\n' }],
                    };
                case 'code':
                    // may not be necessary after pr #406
                    if ($(el).closest('pre').length == 0) {
                        return {
                            kind: 'mark',
                            type: 'code',
                            nodes: next(el.children),
                        };
                    } else {
                        console.log('** skipping <code> within a <pre>');
                    }
            }
        },

        serialize: (object, children) => {
            if (object.kind == 'string') return;
            if (object.kind == 'inline' && object.type == 'link') {
                const href = object.data.get('href');
                return <a href={href}>{children}</a>;
            }
            if (object.kind == 'block' && object.type == 'embed') {
                const src = object.data.get('src');
                return <iframe src={src} />;
            }
            if (object.kind == 'inline' && object.type == 'image') {
                const src = object.data.get('src');
                const alt = object.data.get('alt');
                if (!src) console.log('** ERR: serializing image with no src...', JSON.stringify(object));
                return <img src={src} alt={alt} />;
            }
        },
    },

    // debug uncaught nodes/elements
    {
        deserialize: (el, next) => {
            if (el.type !== 'text') console.log('** no deserializer for: ', $.html(el).replace(/\n/g, '\\n'));
        },
        serialize: (object, children) => {
            if (object.kind != 'string')
                console.log('** no serializer for:', object.type, object.kind, 'data:', JSON.stringify(object));
        },
    },
];

export const getMarkdownType = (chars) => {
    switch (chars) {
        case '1.':
        case '*':
        case '-':
            return 'list-item';
        case '>':
            return 'block-quote';
        case '#':
            return 'heading-one';
        case '##':
            return 'heading-two';
        case '###':
            return 'heading-three';
        case '####':
            return 'heading-four';
        case '   ':
            return 'code-block';
        case '---':
            return 'hr';
        default:
            return null;
    }
};
