import { isNil } from 'lodash-es';
import { Animation, sleep, TimingFn } from 'mutate-animate';
import { Ref, ref } from 'vue';
import axios from 'axios';
import { decompressFromBase64 } from 'lz-string';
import { logger } from '@motajs/common';

type CanParseCss = keyof {
    [P in keyof CSSStyleDeclaration as CSSStyleDeclaration[P] extends string
        ? P extends string
            ? P
            : never
        : never]: CSSStyleDeclaration[P];
};

/**
 * 根据伤害大小获取颜色
 * @param damage 伤害大小
 */
export function getDamageColor(damage: number): string {
    if (typeof damage !== 'number') return '#f00';
    if (damage === 0) return '#2f2';
    if (damage < 0) return '#7f7';
    if (damage < core.status.hero.hp / 3) return '#fff';
    if (damage < (core.status.hero.hp * 2) / 3) return '#ff4';
    if (damage < core.status.hero.hp) return '#f93';
    return '#f22';
}

/**
 * 设置画布的长宽
 * @param canvas 画布
 * @param w 宽度
 * @param h 高度
 */
export function setCanvasSize(
    canvas: HTMLCanvasElement,
    w: number,
    h: number
): void {
    canvas.width = w;
    canvas.height = h;
    canvas.style.width = `${w}px`;
    canvas.style.height = `${h}px`;
}

/**
 * 解析css字符串为CSSStyleDeclaration对象
 * @param css 要解析的css字符串
 */
export function parseCss(css: string): Partial<Record<CanParseCss, string>> {
    if (css.length === 0) return {};

    let pointer = -1;
    let inProp = true;
    let prop = '';
    let value = '';
    let upper = false;
    const res: Partial<Record<CanParseCss, string>> = {};

    while (++pointer < css.length) {
        const char = css[pointer];

        if ((char === ' ' || char === '\n' || char === '\r') && inProp) {
            continue;
        }

        if (char === '-' && inProp) {
            if (prop.length !== 0) {
                upper = true;
            }
            continue;
        }

        if (char === ':') {
            if (!inProp) {
                logger.error(3, pointer.toString(), css);
                return res;
            }
            inProp = false;
            continue;
        }

        if (char === ';') {
            if (prop.length === 0) continue;
            if (inProp) {
                logger.error(4, pointer.toString(), css);
                return res;
            }
            res[prop as CanParseCss] = value.trim();
            inProp = true;
            prop = '';
            value = '';
            continue;
        }

        if (upper) {
            if (!inProp) {
                logger.error(5, pointer.toString(), css);
            }
            prop += char.toUpperCase();
            upper = false;
        } else {
            if (inProp) prop += char;
            else value += char;
        }
    }
    if (inProp && prop.length > 0) {
        logger.error(6, pointer.toString(), css);
        return res;
    }
    if (!inProp && value.trim().length === 0) {
        logger.error(7, pointer.toString(), css);
        return res;
    }
    if (prop.length > 0) res[prop as CanParseCss] = value.trim();

    return res;
}

export function stringifyCSS(css: Partial<Record<CanParseCss, string>>) {
    let str = '';

    for (const [key, value] of Object.entries(css)) {
        let pointer = -1;
        let prop = '';
        while (++pointer < key.length) {
            const char = key[pointer];
            if (char.toLowerCase() === char) {
                prop += char;
            } else {
                prop += `-${char.toLowerCase()}`;
            }
        }
        str += `${prop}:${value};`;
    }

    return str;
}

/**
 * 使用打字机效果显示一段文字
 * @param str 要打出的字符串
 * @param time 打出总用时，默认1秒，当第四个参数是true时，该项为每个字的平均时间
 * @param timing 打出时的速率曲线，默认为线性变化
 * @param avr 是否将第二个参数视为每个字的平均时间
 * @returns 文字的响应式变量
 */
export function type(
    str: string,
    time: number = 1000,
    timing: TimingFn = n => n,
    avr: boolean = false
): Ref<string> {
    const toShow = eval('`' + str + '`') as string;
    if (typeof toShow !== 'string') {
        throw new TypeError('Error str type in typing!');
    }
    if (toShow.startsWith('!!html')) return ref(toShow);
    if (avr) time *= toShow.length;
    const ani = new Animation();
    const content = ref('');
    const all = toShow.length;

    const fn = (time: number) => {
        if (isNil(time)) return;
        const now = ani.x;
        content.value = toShow.slice(0, Math.floor(now));
        if (Math.floor(now) === all) {
            ani.ticker.destroy();
            content.value = toShow;
        }
    };

    ani.ticker.add(fn);

    ani.mode(timing).time(time).move(all, 0);

    setTimeout(() => ani.ticker.destroy(), time + 100);

    return content;
}

/**
 * 设置文字分段换行等
 * @param str 文字
 */
export function splitText(str: string[]) {
    return str
        .map((v, i, a) => {
            if (/^\d+\./.test(v)) return `${'&nbsp;'.repeat(12)}${v}`;
            else if (
                (!isNil(a[i - 1]) && v !== '<br>' && a[i - 1] === '<br>') ||
                i === 0
            ) {
                return `${'&nbsp;'.repeat(8)}${v}`;
            } else return v;
        })
        .join('');
}

/**
 * 在下一帧执行某个函数
 * @param cb 执行的函数
 */
export function nextFrame(cb: (time: number) => void) {
    requestAnimationFrame(() => {
        requestAnimationFrame(cb);
    });
}

/**
 * 下载一个画布对应的图片
 * @param canvas 画布
 * @param name 图片名称
 */
export function downloadCanvasImage(
    canvas: HTMLCanvasElement,
    name: string
): void {
    const data = canvas.toDataURL('image/png');
    download(data, name);
}

/**
 * 下载一个文件
 * @param content 下载的内容
 * @param name 文件名称
 */
export function download(content: string, name: string) {
    const a = document.createElement('a');
    a.download = `${name}.png`;
    a.href = content;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
}

/**
 * 间隔一段时间调用一个函数
 * @param funcs 函数列表
 * @param interval 调用间隔
 */
export async function doByInterval(
    funcs: (() => void)[],
    interval: number,
    awaitFirst: boolean = false
) {
    if (awaitFirst) {
        await sleep(interval);
    }
    for await (const fn of funcs) {
        fn();
        await sleep(interval);
    }
}

/**
 * 更改一个本地存储
 * @deprecated
 * @param name 要更改的信息
 * @param fn 更改时执行的函数
 * @param defaultValue 如果不存在时获取的默认值
 */
export function changeLocalStorage<T>(
    name: string,
    fn: (data: T) => T,
    defaultValue?: T
) {
    const now = core.getLocalStorage(name, defaultValue);
    const to = fn(now);
    core.setLocalStorage(name, to);
}

export async function swapChapter(chapter: number, hard: number) {
    const h = hard === 2 ? 'hard' : 'easy';
    const save = await axios.get(
        `${import.meta.env.BASE_URL}swap/${chapter}.${h}.h5save`,
        {
            responseType: 'text',
            responseEncoding: 'utf-8'
        }
    );
    const data = JSON.parse(decompressFromBase64(save.data));

    core.loadData(data.data, () => {
        core.removeFlag('__fromLoad__');
        core.drawTip('读档成功');
    });
}

export function ensureArray<T>(arr: T): T extends any[] ? T : T[] {
    // @ts-expect-error 暂时无法推导
    return arr instanceof Array ? arr : [arr];
}

export async function triggerFullscreen(full: boolean) {
    if (!!document.fullscreenElement && !full) {
        if (window.jsinterface) {
            window.jsinterface.requestPortrait();
            return;
        }
        await document.exitFullscreen();
    }
    if (full && !document.fullscreenElement) {
        if (window.jsinterface) {
            window.jsinterface.requestLandscape();
            return;
        }
        await document.body.requestFullscreen();
    }
}

/**
 * 获得某个状态的中文名
 * @param name 要获取的属性名
 */
export function getStatusLabel(name: string) {
    return (
        {
            name: '名称',
            lv: '等级',
            hpmax: '生命回复',
            hp: '生命',
            manamax: '魔力上限',
            mana: '额外攻击',
            atk: '攻击',
            def: '防御',
            mdef: '智慧',
            money: '金币',
            exp: '经验',
            point: '加点',
            steps: '步数',
            up: '升级',
            magicDef: '魔法防御',
            none: '无'
        }[name] || name
    );
}

export function formatSize(size: number) {
    return size < 1 << 10
        ? `${size.toFixed(2)}B`
        : size < 1 << 20
          ? `${(size / (1 << 10)).toFixed(2)}KB`
          : size < 1 << 30
            ? `${(size / (1 << 20)).toFixed(2)}MB`
            : `${(size / (1 << 30)).toFixed(2)}GB`;
}

export function getIconHeight(icon: AllIds | 'hero') {
    if (icon === 'hero') {
        if (core.isPlaying()) {
            return (
                core.material.images.images[core.status.hero.image].height / 4
            );
        } else {
            return 48;
        }
    }
    return core.getBlockInfo(icon)?.height ?? 32;
}

const ascii = 16 ** 2;
const other = 16 ** 4;
const emoji = 16 ** 5;
export function calStringSize(str: string) {
    let size = 0;

    for (const char of str) {
        const num = char.charCodeAt(0);
        if (num < ascii) size += 1;
        else if (num < other) size += 2;
        else if (num < emoji) size += 2.5;
    }

    return size;
}

export function clamp(num: number, start: number, end: number) {
    const s = Math.min(start, end);
    const e = Math.max(start, end);
    if (num < s) return s;
    else if (num > e) return e;
    return num;
}
