// @ts-ignore
import { App, inject, watchEffect, getCurrentInstance, ComponentOptions } from 'vue'
import { HeadsOptions, MetasOptions, HeadAttrs, isClient } from "../types/index";

function htmlspecialchars(str: string){
    str = str.replace(/&/g, '&amp;');
    str = str.replace(/</g, '&lt;');
    str = str.replace(/>/g, '&gt;');
    str = str.replace(/"/g, '&quot;');
    str = str.replace(/'/g, '&#039;');
    return str;
}

export class CpMetas {
    readonly #SSR!: boolean
    readonly #isPrivateApp: boolean = false
    private head!: HeadsOptions
    private readonly defaultTitle: string | undefined

    constructor(heads: HeadsOptions, isSSR: boolean, isPrivateApp: boolean) {
        this.head = heads
        this.#SSR = isSSR
        this.defaultTitle = heads.title
        this.#isPrivateApp = isPrivateApp
    }

    setHead(heads: HeadsOptions = {}) {
        !!heads.title && (this.head.title = heads.title || this.defaultTitle)
        const metas: MetasOptions[] = []
        for (const i in heads.meta) {
            if (heads.meta.hasOwnProperty(i)) {
                const item: MetasOptions = heads.meta[i]
                const type = item.name || item.property
                switch (type) {
                    case 'description':
                        this.head.description = item.content
                        break;

                    case 'keywords':
                        this.head.keywords = item.content
                        break;

                    case 'og:image':
                        this.head.image = item.content
                        break;

                    default:
                        metas.push(item);
                        break;
                }
            }
        }
        this.head.metas = metas
        if (!this.#SSR) {
            const title: HeadAttrs<HTMLElement> = document.getElementsByTagName('title')
            if (title && title.length) {
                title[0].innerText = heads.title || this.defaultTitle
            } else {
                const title = document.createElement('title');
                (title.innerText as any) = heads.title || this.defaultTitle;
                document.getElementsByTagName('head')[0].appendChild(title)
            }

            if (isClient && this.#isPrivateApp) {
                const oldMetas = document.querySelectorAll('meta[data-mark="cp"]');
                for (let i in oldMetas) {
                    if (oldMetas.hasOwnProperty(i)) {
                        const _item = oldMetas[i];
                        _item.remove();
                    }

                }
                for (let i in this.head.metas) {
                    const item = this.head.metas[i];
                    let key = '';
                    let val = '';
                    let content = '';
                    if (item.name) {
                        key = 'name';
                        val = item.name;
                    } else if (item.property) {
                        key = 'property';
                        val = item.property;
                    }
                    if (!key) {
                        return;
                    }
                    if (item.content) {
                        content = item.content;
                    }
                    const meta = document.createElement('meta');
                    meta.setAttribute(key, val);
                    meta.setAttribute('content', content);
                    meta.setAttribute('data-mark', 'cp');
                    document.getElementsByTagName('head')[0].appendChild(meta)
                }
            }
        }
    }

    getHead() {
        let description = this.head.description;
        let keywords = this.head.keywords;
        let ogDescription = this.head.description;
        let ogImage = this.head.image;
        const metas = [];
        for (const i in this.head.metas) {
            if (this.head.metas.hasOwnProperty(i)) {
                const item: HeadAttrs<keyof MetasOptions> = this.head.metas[parseInt(i)];
                const type = item.name || item.property;
                switch (type) {
                    case 'description':
                        description = htmlspecialchars(item.content);
                        break;

                    case 'keywords':
                        if (typeof item.content === "string" ) {
                            keywords = htmlspecialchars(item.content);
                        } else if (item.content instanceof Array) {
                            let k = [];
                            for (const i in item.content) {
                                k.push(htmlspecialchars(item.content[i]))
                            }
                            keywords = k.join("、");
                        }
                        break;

                    case 'og:description':
                        ogDescription = htmlspecialchars(item.content);
                        break;

                    case 'og:image':
                        ogImage = item.content;
                        break;

                    default:
                        item.content = htmlspecialchars(item.content);
                        metas.push(item);
                        break;
                }
            }
        }

        return {
            title: this.head.title,
            meta: [
                {
                    name: 'description',
                    content: description
                },
                {
                    name: 'keywords',
                    content: keywords
                },
                {
                    property: 'og:title',
                    content: this.head.title
                },
                {
                    property: 'og:description',
                    content: ogDescription
                },
                {
                    property: 'og:image',
                    content: ogImage
                },
                ...metas
            ],
        };
    }

    renderToString() {
        const heads = this.getHead();

        let string = `<title>${heads.title}</title>`;
        for(const i in heads.meta) {
            const meta = heads.meta[i];
            let key = '';
            let val = '';
            if (meta.name) {
                key = 'name';
                val = meta.name;
            } else if (meta.property) {
                key = 'property';
                val = meta.property;
            }
            if (!key) {
                return;
            }

            string += `<meta ${key}="${val}" content="${meta.content}">`;
        }

        const date = new Date();
        const time = date.getFullYear().toString()
            + '-' + (date.getMonth() + 1).toString()
            + '-' + date.getDate().toString()
            + ' ' + date.getHours().toString()
            + ':' + date.getMinutes().toString()
            + ':' + date.getSeconds().toString();
        string += `<meta name="updatetime" content="${time}">`;
        return string;
    }
}

export function usMeta(obj: HeadsOptions | undefined): void {
    const head: CpMetas | undefined = inject('cpMeta')
    !!head && head.setHead(obj)
}

export const cpMeta = {
    data() {
        const instance = getCurrentInstance();
        return {
            // @ts-ignore
            _cpMetaHeadFn: instance?.type.head
        }
    },
    created() {
        // @ts-ignore
        if (this._cpMetaHeadFn) {
            // @ts-ignore
            this.$cpMeta.setHead(this._cpMetaHeadFn());
        }
    },
    activated() {
        // @ts-ignore
        if (this._cpMetaHeadFn) {
            // @ts-ignore
            this.$cpMeta.setHead(this._cpMetaHeadFn());
        }
    },
    mounted() {
        // @ts-ignore
        if (this._cpMetaHeadFn) {
            watchEffect(() => {
                // @ts-ignore
                this.$cpMeta.setHead(this._cpMetaHeadFn());
            })
        }
    }
}
export default function createMeta(heads: HeadsOptions, isSSR: boolean = false, isPrivateApp: boolean = false) {
    return {
        install(app: App, option: ComponentOptions) {
            const $cpMeta = new CpMetas(heads, isSSR, isPrivateApp);
            if (option.mixin) {
                app.mixin(cpMeta)
            }
            app.provide('cpMeta', $cpMeta);
            app.config.globalProperties.$cpMeta = $cpMeta;
        }
    }
}
