type StorageRecord = Record<string, unknown>;

function isStorageRecord(value: unknown): value is StorageRecord {
    return Object.prototype.toString.call(value) === '[object Object]';
}

export function $save<T>(key: string, value: T): void;
export function $save(key: StorageRecord): void;
export function $save<T>(key: string | StorageRecord, value?: T): void {
    if (isStorageRecord(key) && value === undefined) {
        for (const k in key) {
            if (Object.prototype.hasOwnProperty.call(key, k)) {
                localStorage.setItem(k, JSON.stringify(key[k]));
            }
        }
        return;
    }

    if (typeof key !== 'string') {
        return;
    }

    localStorage.setItem(key, JSON.stringify(value));
}

export function $read<T>(key: string, defaultValue: T): T;
export function $read<T>(key: string): T | null;
export function $read<T extends StorageRecord>(key: T): T;
export function $read<T>(key: string | StorageRecord, defaultValue?: T): T | StorageRecord | null {
    if (isStorageRecord(key) && defaultValue === undefined) {
        const result: StorageRecord = {};
        for (const k in key) {
            if (Object.prototype.hasOwnProperty.call(key, k)) {
                result[k] = $read(k, key[k]);
            }
        }
        return result;
    }

    if (typeof key !== 'string') {
        return defaultValue ?? null;
    }

    const value = localStorage.getItem(key);

    if (value === null) {
        return defaultValue ?? null;
    }

    try {
        return JSON.parse(value) as T;
    } catch {
        return defaultValue ?? null;
    }
}

export function $remove_socket_storage(): void {
    localStorage.removeItem('passphrase');
    localStorage.removeItem('token');
    localStorage.removeItem('socket_path');
    localStorage.removeItem('client_ns');
}

export function $read_socket_storge(): any {
    let passphrase = $read('passphrase', '');
    let token = $read('token', '');
    const socket_path = $read('socket_path', '');
    const client_ns = $read('client_ns', '')
    const domain = $read('domain', '');
    return {
        passphrase, token, socket_path, client_ns, domain
    }
}

export function $format_str(str: string, num: number) {
    if (str.length <= num) {
        return str;
    }
    return str.substring(0, num) + '...';
}

export function $is_run_local(): boolean {
    if (/https?:\/\/localhost|192.168/gim.test(location.href)) {
        return true;
    }
    return false;
}

export function $query(q: string | string[]): string | string[] {
    const query = location.href.match(/\?([^#]*)/)?.[1]?.replace(/\/$/, '');
    const params = new URLSearchParams(query);
    if (Array.isArray(q)) {
        return q.map((key) => params.get(key)).filter((v): v is string => !!v);
    }
    return params.get(q) || '';
}


let toastElement: HTMLDivElement | undefined;
let toastTimer: ReturnType<typeof window.setTimeout> | undefined;

export function $toast(message?: string, duration = 2000) {
    if (!message) {
        return;
    }

    if (!toastElement) {
        toastElement = document.createElement('div');
        toastElement.style.cssText = [
            'position: fixed',
            'left: 50%',
            'top: 45%',
            'z-index: 99999',
            'max-width: 800px',
            'padding: 18px 28px',
            'border-radius: 18px',
            'background: rgba(0, 0, 0, 0.72)',
            'color: #fff',
            'font-size: 22px',
            'line-height: 1.4',
            'text-align: center',
            'transform: translate(-50%, -40%)',
            'opacity: 0',
            'pointer-events: none',
            'transition: opacity 0.2s ease, transform 0.2s ease',
        ].join(';');
        document.body.appendChild(toastElement);
    }

    if (toastTimer) {
        window.clearTimeout(toastTimer);
        toastTimer = undefined;
    }

    toastElement.textContent = message;
    toastElement.style.opacity = '1';
    toastElement.style.transform = 'translate(-50%, -50%)';

    toastTimer = window.setTimeout(() => {
        if (!toastElement) {
            return;
        }

        toastElement.style.opacity = '0';
        toastElement.style.transform = 'translate(-50%, -40%)';
        toastTimer = undefined;
    }, duration);
}

export type NotifyType = 'info' | 'success' | 'warning' | 'error';

export type NotifyOptions = {
    title?: string;
    message?: string;
    type?: NotifyType;
    duration?: number;
}

let notifyElement: HTMLDivElement | undefined;
let notifyTitleElement: HTMLDivElement | undefined;
let notifyMessageElement: HTMLDivElement | undefined;
let notifyTimer: ReturnType<typeof window.setTimeout> | undefined;
let notifyHideTimer: ReturnType<typeof window.setTimeout> | undefined;

function closeNotify() {
    if (!notifyElement) {
        return;
    }

    if (notifyTimer) {
        window.clearTimeout(notifyTimer);
        notifyTimer = undefined;
    }
    if (notifyHideTimer) {
        window.clearTimeout(notifyHideTimer);
    }

    notifyElement.style.opacity = '0';
    notifyElement.style.transform = 'translate(-50%, -130%)';
    notifyHideTimer = window.setTimeout(() => {
        if (notifyElement) {
            notifyElement.style.display = 'none';
        }
        notifyHideTimer = undefined;
    }, 260);
}

function ensureNotifyElement() {
    if (notifyElement) {
        return;
    }

    notifyElement = document.createElement('div');
    notifyElement.style.cssText = [
        'position: fixed',
        'left: 50%',
        'top: 24px',
        'z-index: 100000',
        'display: none',
        'width: min(560px, calc(100vw - 40px))',
        'box-sizing: border-box',
        'padding: 18px 54px 18px 24px',
        'border-radius: 16px',
        'background: rgba(55, 55, 55, 0.88)',
        'color: #fff',
        'box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22)',
        'opacity: 0',
        'transform: translate(-50%, -130%)',
        'transition: opacity 0.26s ease, transform 0.26s ease',
    ].join(';');

    notifyTitleElement = document.createElement('div');
    notifyTitleElement.style.cssText = [
        'font-size: 22px',
        'font-weight: 700',
        'line-height: 1.35',
    ].join(';');

    notifyMessageElement = document.createElement('div');
    notifyMessageElement.style.cssText = [
        'margin-top: 5px',
        'color: rgba(255, 255, 255, 0.88)',
        'font-size: 18px',
        'line-height: 1.45',
        'word-break: break-word',
    ].join(';');

    const closeElement = document.createElement('button');
    closeElement.type = 'button';
    closeElement.textContent = '×';
    closeElement.setAttribute('aria-label', 'Close notification');
    closeElement.style.cssText = [
        'position: absolute',
        'top: 12px',
        'right: 14px',
        'width: 34px',
        'height: 34px',
        'padding: 0',
        'border: 0',
        'background: transparent',
        'color: rgba(255, 255, 255, 0.8)',
        'font-size: 28px',
        'line-height: 32px',
        'cursor: pointer',
    ].join(';');
    closeElement.onclick = closeNotify;

    notifyElement.append(
        notifyTitleElement,
        notifyMessageElement,
        closeElement,
    );
    document.body.appendChild(notifyElement);
}

export function $notify(message?: string, title?: string): () => void;
export function $notify(options?: NotifyOptions): () => void;
export function $notify(messageOrOptions?: string | NotifyOptions, title?: string): () => void {
    const options: NotifyOptions = typeof messageOrOptions === 'object'
        ? messageOrOptions
        : { message: messageOrOptions, title };

    if (!options.message) {
        return closeNotify;
    }

    ensureNotifyElement();

    if (notifyTimer) {
        window.clearTimeout(notifyTimer);
    }
    if (notifyHideTimer) {
        window.clearTimeout(notifyHideTimer);
        notifyHideTimer = undefined;
    }

    notifyTitleElement!.textContent = options.title ?? '通知';
    notifyMessageElement!.textContent = options.message;
    notifyElement!.style.display = 'block';

    window.requestAnimationFrame(() => {
        if (!notifyElement) {
            return;
        }
        notifyElement.style.opacity = '1';
        notifyElement.style.transform = 'translate(-50%, 0)';
    });

    if ((options.duration ?? 4000) > 0) {
        notifyTimer = window.setTimeout(closeNotify, options.duration ?? 4000);
    }

    return closeNotify;
}


type ConfirmOptions = {
    title?: string;
    message?: string;
    confirmText?: string;
    cancelText?: string;
}

let confirmMaskElement: HTMLDivElement | undefined;
let confirmBoxElement: HTMLDivElement | undefined;
let confirmTitleElement: HTMLDivElement | undefined;
let confirmMessageElement: HTMLDivElement | undefined;
let confirmCancelElement: HTMLButtonElement | undefined;
let confirmOkElement: HTMLButtonElement | undefined;
let confirmResolve: ((value: boolean) => void) | undefined;
let confirmHideTimer: ReturnType<typeof window.setTimeout> | undefined;

function closeConfirm(result: boolean) {
    if (!confirmMaskElement || !confirmBoxElement) {
        confirmResolve?.(result);
        confirmResolve = undefined;
        return;
    }

    if (confirmHideTimer) {
        window.clearTimeout(confirmHideTimer);
        confirmHideTimer = undefined;
    }

    confirmMaskElement.style.opacity = '0';
    confirmBoxElement.style.transform = 'scale(0.96)';
    confirmHideTimer = window.setTimeout(() => {
        if (confirmMaskElement) {
            confirmMaskElement.style.display = 'none';
        }
        confirmHideTimer = undefined;
    }, 200);

    confirmResolve?.(result);
    confirmResolve = undefined;
}

function ensureConfirmElement() {
    if (confirmMaskElement) {
        return;
    }

    confirmMaskElement = document.createElement('div');
    confirmMaskElement.style.cssText = [
        'position: fixed',
        'inset: 0',
        'z-index: 99998',
        'display: none',
        'align-items: center',
        'justify-content: center',
        'background: rgba(0, 0, 0, 0.45)',
        'opacity: 0',
        'transition: opacity 0.2s ease',
    ].join(';');

    confirmBoxElement = document.createElement('div');
    confirmBoxElement.style.cssText = [
        'width: min(560px, calc(100vw - 80px))',
        'box-sizing: border-box',
        'padding: 34px 32px 28px',
        'border-radius: 22px',
        'background: #fff',
        'color: #333',
        'font-size: 24px',
        'line-height: 1.45',
        'text-align: center',
        'box-shadow: 0 18px 48px rgba(0, 0, 0, 0.22)',
        'transform: scale(0.96)',
        'transition: transform 0.2s ease',
    ].join(';');

    confirmTitleElement = document.createElement('div');
    confirmTitleElement.style.cssText = [
        'margin-bottom: 16px',
        'font-size: 30px',
        'font-weight: 700',
        'color: #222',
    ].join(';');

    confirmMessageElement = document.createElement('div');
    confirmMessageElement.style.cssText = [
        'min-height: 34px',
        'word-break: break-word',
    ].join(';');

    const buttonWrap = document.createElement('div');
    buttonWrap.style.cssText = [
        'display: flex',
        'gap: 18px',
        'margin-top: 30px',
    ].join(';');

    confirmCancelElement = document.createElement('button');
    confirmCancelElement.type = 'button';
    confirmCancelElement.style.cssText = [
        'flex: 1',
        'height: 58px',
        'border: 1px solid #ddd',
        'border-radius: 29px',
        'background: #fff',
        'color: #666',
        'font-size: 24px',
        'cursor: pointer',
    ].join(';');
    confirmCancelElement.onclick = () => closeConfirm(false);

    confirmOkElement = document.createElement('button');
    confirmOkElement.type = 'button';
    confirmOkElement.style.cssText = [
        'flex: 1',
        'height: 58px',
        'border: 0',
        'border-radius: 29px',
        'background: #AA0000',
        'color: #fff',
        'font-size: 24px',
        'cursor: pointer',
    ].join(';');
    confirmOkElement.onclick = () => closeConfirm(true);

    confirmMaskElement.onclick = (event) => {
        if (event.target === confirmMaskElement) {
            closeConfirm(false);
        }
    };

    buttonWrap.append(confirmCancelElement, confirmOkElement);
    confirmBoxElement.append(confirmTitleElement, confirmMessageElement, buttonWrap);
    confirmMaskElement.appendChild(confirmBoxElement);
    document.body.appendChild(confirmMaskElement);
}

export function $confirm(message?: string, title?: string): Promise<boolean>;
export function $confirm(options?: ConfirmOptions): Promise<boolean>;
export function $confirm(messageOrOptions?: string | ConfirmOptions, title?: string): Promise<boolean> {
    const options: ConfirmOptions = typeof messageOrOptions === 'object'
        ? messageOrOptions
        : { message: messageOrOptions, title };

    if (!options.message) {
        return Promise.resolve(false);
    }

    ensureConfirmElement();

    if (confirmResolve) {
        confirmResolve(false);
        confirmResolve = undefined;
    }

    if (confirmHideTimer) {
        window.clearTimeout(confirmHideTimer);
        confirmHideTimer = undefined;
    }

    confirmTitleElement!.textContent = options.title ?? '提示';
    confirmMessageElement!.textContent = options.message;
    confirmCancelElement!.textContent = options.cancelText ?? '取消';
    confirmOkElement!.textContent = options.confirmText ?? '确定';
    confirmMaskElement!.style.display = 'flex';
    window.requestAnimationFrame(() => {
        if (!confirmMaskElement || !confirmBoxElement) {
            return;
        }

        confirmMaskElement.style.opacity = '1';
        confirmBoxElement.style.transform = 'scale(1)';
    });

    return new Promise<boolean>((resolve) => {
        confirmResolve = resolve;
    });
}
