import { NotificationMessageType } from '@etsoo/notificationbase';
import { ApiDataError, createClient, IApi } from '@etsoo/restclient';
import { DataTypes, DomUtils, IActionResult, Utils } from '@etsoo/shared';
import { wxe } from '@etsoo/weixin';
import {
    INotifierContainer,
    NotifierContainer
} from '../notifier/NotifierContainer';
import { SendEmailRQ } from '../rq/site/SendEmailRQ';
import { SiteUtils } from './SiteUtils';

type ScrollActions = {
    backToTopSelector?: string;
    backToTopThreshold?: number;
    stickyTopSelector?: string;
    stickyTopThreshold?: number;
};

type SetupOptions = ScrollActions & {
    drawflowStyle?: string;
    drawVersion?: string;
};

/**
 * Client site
 */
export class ClientSite {
    /**
     * API
     * 接口调用对象
     */
    readonly api: IApi;

    /**
     * Notifier
     * 通知器
     */
    readonly notifier: INotifierContainer;

    private _isReady = false;
    /**
     * Is ready
     * 是否准备就绪
     */
    get isReady() {
        return this._isReady;
    }

    private backToTopDispose?: () => void;
    private windowScrollDispose?: () => void;
    private formSubmitDispose?: () => void;

    /**
     * Constructor
     * 构造函数
     * @param culture Culture, like en, zh-Hans
     * @param apiUrl Headless CMS API Url
     * @param errorHandler Custom error handler
     */
    constructor(
        public readonly culture: 'en' | 'zh-Hans' | 'zh-Hant' | string,
        apiUrl: string,
        errorHandler?: (e: ApiDataError) => void
    ) {
        // Notifier
        this.notifier = new NotifierContainer();

        // Default error hanlder
        errorHandler ??= (e) =>
            this.notifier.message(
                NotificationMessageType.Danger,
                e.message,
                'API error'
            );

        const api = createClient();
        api.baseUrl = apiUrl;
        api.onError = errorHandler;

        // Add content-language header
        api.setContentLanguage(culture);

        this.api = api;
    }

    /**
     * Dispose
     * 释放资源
     */
    dispose() {
        this.notifier.dispose();

        if (this.backToTopDispose) {
            this.backToTopDispose();
            this.backToTopDispose = undefined;
        }

        if (this.windowScrollDispose) {
            this.windowScrollDispose();
            this.windowScrollDispose = undefined;
        }

        if (this.formSubmitDispose) {
            this.formSubmitDispose();
            this.formSubmitDispose = undefined;
        }

        this._isReady = false;
    }

    /**
     * Get document meta content
     * 获取文档 Meta 内容
     * @param name Name
     * @returns content
     */
    getMeta(name: string) {
        return document.querySelector<HTMLMetaElement>(`meta[name="${name}"]`)
            ?.content;
    }

    /**
     * Get document Open Graph
     * https://ogp.me/
     * 获取文档 Open Graph 内容
     * @param name Name
     * @returns content
     */
    getOG(name: string) {
        return document.querySelector<HTMLMetaElement>(
            `meta[property="og:${name}"]`
        )?.content;
    }

    /**
     * Get Google reCaptcha token
     * @param action Action
     * @param callback Callback
     */
    grecaptcha(action: string, callback: (token: string) => void) {
        if (
            typeof grecaptcha == undefined ||
            typeof globalThis.googleGecaptchaSiteKey == undefined
        ) {
            callback('');
            return;
        }
        grecaptcha.ready(function () {
            grecaptcha
                .execute(globalThis.googleGecaptchaSiteKey, { action: action })
                .then(function (token) {
                    callback(token);
                });
        });
    }

    /**
     * Send email
     * 发送邮件
     * @param rq Request data
     * @param api Function API
     * @returns Result
     */
    async sendEmail(rq: SendEmailRQ, api = 'Public/SendEmail') {
        // Pass the JSON data
        if (typeof rq.data === 'object') rq.data = JSON.stringify(rq.data);

        // API call
        return await this.api.post<IActionResult<{ successMessage: string }>>(
            api,
            rq
        );
    }

    /**
     * Setup
     * @param resources Custom resources
     * @param options Setup options
     */
    setup(resources: DataTypes.StringRecord = {}, options?: SetupOptions) {
        // Spinner
        const spinner = document.getElementById('spinner');
        spinner?.classList.remove('show');

        // Check ready to avoid multiple setup
        if (this.isReady) return;

        // Destruct options
        const { drawflowStyle, drawVersion, ...actions } = options ?? {};

        // Setup utils
        SiteUtils.setup(this.culture, resources);

        // Setup scroll actions
        this.setupScrollActions(actions);

        // Setup contact form
        this.setupContactForm();

        // Setup drawflow viewer
        this.setupDrawflowViewer(drawflowStyle, drawVersion);

        // Ready
        this._isReady = true;
    }

    /**
     * Setup scroll actions
     * 设置滚动动作
     * @param actions Scroll actions
     */
    setupScrollActions(actions?: ScrollActions) {
        const {
            backToTopSelector = '.back-to-top',
            backToTopThreshold = 300,
            stickyTopSelector = '.sticky-top',
            stickyTopThreshold = 100
        } = actions ?? {};

        const stickyNav =
            document.querySelector<HTMLElement>(stickyTopSelector);

        const gotoTop = document.querySelector<HTMLElement>(backToTopSelector);
        if (gotoTop) {
            const backToTopHandler = () => {
                window.scrollTo({ top: 0, behavior: 'smooth' });
            };
            gotoTop.addEventListener('click', backToTopHandler);
            this.backToTopDispose = () => {
                gotoTop.removeEventListener('click', backToTopHandler);
            };
        }

        if (stickyNav || gotoTop) {
            const windowScrollHandler = () => {
                if (stickyNav) {
                    if (window.scrollY > stickyTopThreshold) {
                        stickyNav.style.top = '0px';
                    } else {
                        stickyNav.style.top = `${-stickyTopThreshold}px`;
                    }
                }
                if (gotoTop) {
                    if (window.scrollY > backToTopThreshold) {
                        gotoTop.style.display = 'flex';
                    } else {
                        gotoTop.style.display = 'none';
                    }
                }
            };

            window.addEventListener('scroll', windowScrollHandler);
            this.windowScrollDispose = () => {
                window.removeEventListener('scroll', windowScrollHandler);
            };
        }
    }

    /**
     * Setup contact form
     * @param selectors Form selectors, default is "form[name='contact-form']"
     * @param recipientField Recipient field name, default is "email"
     * @param templateName Email template name
     */
    setupContactForm(
        selectors: string = "form[name='contact-form']",
        recipientField: string = 'email',
        templateName?: string
    ) {
        const form = document.querySelector<HTMLFormElement>(selectors);
        if (form) {
            // Labels with 'for' attribute
            const labels =
                form.querySelectorAll<HTMLLabelElement>('label[for]');

            labels.forEach((label) => {
                if (
                    label.control &&
                    'required' in label.control &&
                    label.control.required
                ) {
                    label.classList.add('form-label-required');
                }
            });

            if (typeof form.name !== 'string') {
                this.notifier.alert(
                    'The form name is required. And please do not name a form field with "name".'
                );
                return;
            }

            // Default values from query string
            const searchParams = new URLSearchParams(location.search);
            searchParams.forEach((value, key) => {
                const field = form.querySelector<HTMLInputElement>(
                    `[name="${key}"]`
                );
                if (field) field.value = value;
            });

            // Action (A-Za-z/_) from form name
            const action = form.name
                .replace('-', '_')
                .replace(/[^A-Za-z_]/g, '');

            // Default email template name
            const template =
                templateName ?? `${action.toUpperCase()}_EMAIL_TEMPLATE`;

            // Submit handler
            const submitHandler = (event: SubmitEvent) => {
                event.preventDefault();

                const recipentField = form.querySelector<HTMLInputElement>(
                    `[name="${recipientField}"]`
                );
                if (recipentField == null) {
                    this.notifier.alert('Recipient field not found');
                    return;
                }

                // Form data
                const data = Object.fromEntries(new FormData(form));

                // Remove empty values
                Utils.removeEmptyValues(data);

                // Check recipient
                const recipient = data[recipientField].toString();
                if (!recipient.isEmail()) {
                    recipentField.focus();
                    return;
                }

                const submitButton = form.querySelector<HTMLButtonElement>(
                    'button[type="submit"]'
                );
                if (submitButton) SiteUtils.toggleButtonSpinner(submitButton);

                this.grecaptcha(action, async (token) => {
                    const result = await this.sendEmail({
                        recipient,
                        template,
                        data,
                        token
                    });

                    if (result) {
                        if (result.ok) {
                            this.notifier.alert(
                                result.data?.successMessage ??
                                    'Your enquiry has been sent successfully',
                                () => form.reset(),
                                NotificationMessageType.Success
                            );
                        } else {
                            this.notifier.alert(result.title ?? 'Error');
                        }
                    }

                    if (submitButton)
                        SiteUtils.toggleButtonSpinner(submitButton);
                });
            };

            form.addEventListener('submit', submitHandler);

            this.formSubmitDispose = () => {
                form.removeEventListener('submit', submitHandler);
            };
        }
    }

    /**
     * Setup drawflow viewer
     * 设置 Drawflow 视图
     * @param drawflowStyle Drawflow style
     * @param drawVersion Drawflow version
     */
    setupDrawflowViewer(drawflowStyle?: string, drawVersion?: string) {
        // Check the export data container
        const dataContainer = document.querySelector<HTMLElement>(
            "pre[name='drawflow-data']"
        );
        if (dataContainer == null) return;

        // Copy styles
        const iframe = document.createElement('iframe');
        iframe.style.width = dataContainer.style.width;
        iframe.style.height = dataContainer.style.height;

        // Copy classes
        dataContainer.classList.forEach((c) => {
            if (c === 'd-none') return;
            iframe.classList.add(c);
        });

        // Parent font-awesome icons, keep the same version and avoid duplicate loading
        const fontAwesome = document.querySelector<HTMLLinkElement>(
            'link[rel="stylesheet"][href*="/font-awesome"]'
        );

        // Drawflow iframe
        drawflowStyle ??= '/drawflow.css';
        const version = drawVersion ? `@${drawVersion}` : '';

        const html = `<!DOCTYPE html>
        <html>
          <head>
            <link href="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow${version}/dist/drawflow.min.css" rel="stylesheet" />
            <link href="${drawflowStyle}" rel="stylesheet" />
            ${
                fontAwesome
                    ? `<link href="${fontAwesome.href}" rel="stylesheet" />`
                    : ''
            }
            <script src="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow${version}/dist/drawflow.min.js"></script>
          </head>
          <body>
          </body>
          <script>
          (function() {
            const jsonData = ${dataContainer.innerHTML};
            const drawflow = new Drawflow(document.body);
            drawflow.editor_mode = "view";
            drawflow.start();
            drawflow.import(jsonData);
          })();
          </script>
        </html>
        `;

        if ('srcdoc' in iframe && !DomUtils.isWechatClient()) {
            iframe.srcdoc = html;
            dataContainer.after(iframe);
        } else {
            // contentWindow is null when the iframe is not in the DOM
            dataContainer.after(iframe);
            const doc = iframe.contentWindow?.document;
            if (doc) {
                doc.open();
                doc.write(html);
                doc.close();
            }
        }

        dataContainer.remove();
    }

    /**
     * Setup wechat share
     * 设置微信分享
     * @param share Shared data
     * @param api Wechat configuration API
     * @returns Result
     */
    async setupWechat(
        share?: wx.UpdateAppMessageShareDataParams,
        api = 'Public/CreateJsApiSignature'
    ) {
        try {
            // Load config
            const data = await this.api.put<wx.ConfigBase>(
                api,
                {
                    url: location.href
                },
                { showLoading: false }
            );

            if (data == null) return;

            // Check exists
            if (typeof wx === undefined) return;

            // Apis
            const apis: wx.ApiName[] = [
                'updateAppMessageShareData',
                'onMenuShareAppMessage',
                'updateTimelineShareData',
                'onMenuShareTimeline'
            ];

            // Config
            const result = await wxe.configAsync({
                ...data,
                jsApiList: apis
            });

            if (result != null) {
                console.log('wxe.configAsync', result);
                return;
            }

            // Check
            const ckResult = await wxe.checkJsApiAsync({ jsApiList: apis });

            if (!ckResult.errMsg.endsWith('ok')) {
                console.log('checkJsApiAsync', ckResult.errMsg);
            }

            // Share data
            if (share == null) {
                const title = this.getOG('title') ?? document.title;
                const link = this.getOG('url') ?? location.href;
                const desc =
                    this.getOG('description') ??
                    this.getMeta('description') ??
                    '';

                let imgUrl =
                    this.getOG('image') ??
                    this.getMeta('image_src') ??
                    '/og.jpg';

                if (!imgUrl.includes('://'))
                    imgUrl = location.protocol + '//' + location.host + imgUrl;

                share = {
                    title,
                    link,
                    imgUrl,
                    desc
                };
            }

            // Setup share
            wxe.setupShare(share, ckResult.checkResult);
        } catch (e) {
            console.log('WX setup failed with an error', e);
        }
    }
}
