<script lang="ts" setup>
/**
 * 环形进度条组件
 * @description 一个可自定义的环形进度条组件，支持渐变色、动画效果和文本显示
 * 
 * @example
 * ```vue
 * <template>
 *   <ProgressBar 
 *     :options="{
 *       data: 75,                    // 当前值
 *       maxValue: 200,               // 最大值
 *       lineWidth: 10,               // 圆环宽度
 *       radius: 50,                  // 圆环半径
 *       color: ['#66DD00', '#0066FF'], // 渐变色
 *       text: {
 *         showPercentage: false      // 显示原始值
 *       }
 *     }" 
 *   />
 * </template>
 * ```
 * 
 * @props
 * options: {
 *   data: number;                  // 当前值
 *   maxValue?: number;             // 最大值，默认 100
 *   lineWidth?: number;            // 圆环宽度，默认 40
 *   radius?: number;               // 圆环半径，默认 100
 *   color?: string | string[];     // 圆环颜色，支持单色或渐变色数组
 *   width?: number;                // 画布宽度，默认自适应
 *   height?: number;               // 画布高度，默认自适应
 *   background?: string;           // 画布背景色，默认 #fff
 *   lineCap?: CanvasLineCap;       // 线条末端样式：'butt' | 'round' | 'square'，默认 'round'
 *   fillStyle?: string;           // 圆环中心填充样式，默认 'transparent'
 *   text?: {
 *     size?: number | null;        // 字体大小，null 时自动计算
 *     color?: string;              // 字体颜色，默认 #333
 *     font?: string;               // 字体样式，默认 Arial
 *     content?: string | number;   // 自定义显示文本
 *     animation?: boolean;         // 是否启用数字动画，默认 true
 *     showPercentage?: boolean;    // true显示百分比，false显示原始值，默认 true
 *     show?: boolean;             // 是否显示文本，默认 true
 *   }
 * }
 * 
 * @features
 * - 自适应画布大小：根据圆环大小自动计算画布尺寸
 * - 渐变色支持：可使用数组配置多个颜色实现渐变
 * - 文本显示：支持自定义文本、百分比/原始值切换
 * - 动画效果：数值变化时平滑过渡
 * - 自适应字体：根据圆环大小自动计算合适的字体大小
 * - 圆角样式：圆环端点为圆角
 * - 透明中心：圆环中心区域透明
 */

import { ref, watch, computed, onMounted } from 'vue';

interface ChartOptions {
    data: number;
    // 最大值，默认 100
    maxValue?: number;
    // 圆环宽度，默认 40
    lineWidth?: number;
    // 圆环半径，默认 100
    radius?: number;
    // 圆环颜色，支持单色或渐变色数组
    color?: string | string[];
    // 画布宽度，默认 500
    width?: number;
    // 画布高度，默认 500
    height?: number;
    // 画布背景色，默认 #fff
    background?: string;
    // 线条末端样式：'butt' | 'round' | 'square'，默认 'round'
    lineCap?: CanvasLineCap;
    // 圆环中心填充样式，默认 'transparent'
    fillStyle?: string;
    // 文字相关配置
    text?: {
        // 字体大小，默认自适应（为 null 时自动计算）
        size?: number | null;
        // 字体颜色，默认 #333
        color?: string;
        // 字体样式，默认 Arial
        font?: string;
        // 添加自定义文本配置
        content?: string | number;
        // 是否启用数字动画，默认true
        animation?: boolean;
        // 是否显示百分比（包括值和符号），默认 true
        showPercentage?: boolean;
        // 是否显示文本，默认 true
        show?: boolean;
    };
}

const props = withDefaults(defineProps<{
    options: ChartOptions
}>(), {
    options: () => ({
        data: 0,
        maxValue: 100,
        lineWidth: 40,
        radius: 100,
        color: 'red',
        width: 500,
        height: 500,
        background: '#fff',
        lineCap: 'round',
        fillStyle: 'transparent',
        text: {
            size: null,
            color: '#333',
            font: 'Arial',
            showPercentage: true,
            show: true
        }
    })
})

const myCanvas = ref<HTMLCanvasElement | null>(null);
const chartOptions = ref<ChartOptions | null>(null);

// 添加动画执行标志
const isAnimating = ref(false);

// 添加当前显示的数值状态
const currentDisplayNumber = ref(0);

// 计算圆环所需的最小画布尺寸
const minCanvasSize = computed(() => {
    const radius = props.options.radius || 100;
    const lineWidth = props.options.lineWidth || 40;
    // 圆环直径 + 线宽 + 边距
    return (radius * 2) + lineWidth + 40;
});

// 修改画布尺寸的计算属性
const canvasWidth = computed(() => {
    return props.options.width ?? minCanvasSize.value;
});

const canvasHeight = computed(() => {
    return props.options.height ?? minCanvasSize.value;
});

// 修改 canvasContainerStyle 计算属性
const canvasContainerStyle = computed(() => ({
    width: `${canvasWidth.value}px`,
    height: `${canvasHeight.value}px`
}));

const canvasStyle = computed(() => ({
    backgroundColor: props.options.background
}));

// 修改最大半径的计算
const maxRadius = computed(() => {
    const minSize = Math.min(canvasWidth.value, canvasHeight.value);
    // 预留边距和线宽，取最小边长的40%作为半径
    return Math.floor(minSize * 0.4);
});

// 计算实际使用的字体大小：区分自定义文本和默认百分比显示
const fontSize = computed(() => {
    const lineWidth = props.options.lineWidth || 10;
    const radius = Math.min(props.options.radius || 100, maxRadius.value);
    const innerRadius = radius - lineWidth;
    
    // 获取实际显示的文本内容
    let displayText: string;
    let isCustomText = false;
    if (props.options.text?.content !== undefined) {
        displayText = String(props.options.text.content);
        isCustomText = true;
    } else {
        const percentage = Math.round((props.options.data / 100) * 100);
        displayText = `${percentage}%`;
    }

    // 文字长度系数：自定义文本时才考虑长度影响
    const textLengthFactor = isCustomText 
        ? Math.max(0.2, 1 - (displayText.length - 3) * 0.2)
        : 1;
    
    if (props.options.text?.size) {
        // 如果配置了字体大小，根据是否自定义文本决定缩放
        return Math.min(
            props.options.text.size,
            isCustomText 
                ? innerRadius * 0.5 * textLengthFactor
                : innerRadius * 0.8
        );
    }
    
    // 自动计算字体大小：区分自定义文本和默认百分比显示
    return Math.floor(
        isCustomText 
            ? innerRadius * 0.25 * textLengthFactor
            : innerRadius * 0.45
    );
});

// 监听 props.options 的变化，更新 chartOptions
watch(() => props.options, (newVal) => {
    // 只在没有通过 init 设置配置时更新
    if (!chartOptions.value || Object.keys(chartOptions.value).length === 0) {
        chartOptions.value = { ...newVal };
    }
}, { deep: true });

// 监听 data 的变化
watch(() => props.options.data, (_, oldVal) => {
    update(oldVal);
});

function startDraw(end: number, start?: number, dontClear?: boolean) {
    const ctx = myCanvas.value?.getContext("2d");
    if (!ctx) return;
    if (!dontClear) {
        ctx.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
    }

    const centerX = canvasWidth.value / 2;
    const centerY = canvasHeight.value / 2;
    const radius = Math.min(props.options.radius || 100, maxRadius.value);
    const lineWidth = props.options.lineWidth;
    
    let strokeStyle: string | CanvasGradient = 'red';
    if (props.options.color) {
        if (Array.isArray(props.options.color)) {
            if (props.options.color.length) {
                const gradient = ctx.createLinearGradient(
                    centerX - radius,
                    centerY - radius,
                    centerX + radius,
                    centerY + radius
                );
                const colors = props.options.color;
                if (colors?.length === 1) {
                    strokeStyle = colors[0];
                } else {
                    colors?.forEach((color, index) => {
                        gradient.addColorStop(index / (colors.length - 1), color);
                    });
                    strokeStyle = gradient;
                }
            }
        } else {
            strokeStyle = props.options.color;
        }
    }

    // 绘制外圆
    ctx.beginPath();
    ctx.lineWidth = lineWidth || 10;
    ctx.lineCap = props.options.lineCap || 'round';
    ctx.strokeStyle = strokeStyle;
    ctx.arc(centerX, centerY, radius, start || Math.PI * 1.5, end || Math.PI * 2);
    ctx.stroke();
    ctx.closePath();

    // 绘制内圆
    ctx.beginPath();
    ctx.arc(centerX, centerY, (radius) - (lineWidth || 10), 0, Math.PI * 2);
    ctx.fillStyle = props.options.fillStyle || 'transparent';
    ctx.fill();
    ctx.closePath();

    // 只在 show 为 true 时绘制文本
    if (props.options.text?.show !== false) {
        ctx.beginPath();
        ctx.font = `${fontSize.value}px ${props.options.text?.font || 'Arial'}`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillStyle = props.options.text?.color || '#333';

        let displayText: string;
        if (props.options.text?.content !== undefined) {
            displayText = String(props.options.text.content);
        } else {
            const maxValue = props.options.maxValue || 100;
            const currentValue = currentDisplayNumber.value;
            
            if (props.options.text?.showPercentage !== false) {
                const percentage = Math.round((currentValue / maxValue) * 100);
                displayText = `${percentage}%`;
            } else {
                displayText = String(Math.round(currentValue));
            }
        }
        
        ctx.fillText(displayText, centerX, centerY);
        ctx.closePath();
    }
}

/**
 * @description: 自定义动画函数
 * @param {Object} options 动画配置项
 * @param {number} options.duration 动画持续时间(ms)
 * @param {Function} options.draw 绘制函数,接收动画进度(0-1)
 * @param {Function} options.timing 时序函数,用于控制动画速度曲线
 * @param {Function} [options.callback] 动画完成回调函数
 */
function customAnimate(options: {
    duration: number;
    draw: (progress: number) => void;
    timing: (timeFraction: number) => number;
    callback?: () => void;
}) {
    const { duration, draw, timing, callback } = options;
    const startTime = performance.now();

    function animate(currentTime: number) {
        // 计算动画进度(0-1)
        let progress = Math.min((currentTime - startTime) / duration, 1);

        // 应用时序函数
        const easedProgress = timing(progress);

        // 执行绘制
        draw(easedProgress);

        if (progress < 1) {
            requestAnimationFrame(animate);
        } else if (callback) {
            callback();
        }
    }

    requestAnimationFrame(animate);
}

function update(oldData: number) {
    if (isAnimating.value) return;
    isAnimating.value = true;

    currentDisplayNumber.value = oldData;

    customAnimate({
        duration: 500,
        timing: (timeFraction) => timeFraction,
        draw: (progress) => {
            progress = Math.max(0, progress);
            const maxValue = props.options.maxValue || 100;
            const maxData = Math.min(props.options.data, maxValue);
            const currentData = oldData + (maxData - oldData) * progress;
            const end = Math.PI * (1.5 + (currentData / maxValue) * 2);

            if (props.options.text?.animation !== false && 
                props.options.text?.content === undefined) {
                currentDisplayNumber.value = currentData;
            }

            startDraw(end);
        },
        callback: () => {
            isAnimating.value = false;
        }
    });
}

function init() {
    if (isAnimating.value) return;
    isAnimating.value = true;

    currentDisplayNumber.value = 0;

    customAnimate({
        duration: 500,
        timing: (timeFraction) => timeFraction,
        draw: (progress) => {
            progress = Math.max(0, progress);
            const maxValue = props.options.maxValue || 100;
            const maxData = Math.min(props.options.data, maxValue);
            const currentData = maxData * progress;
            const end = Math.PI * (1.5 + (currentData / maxValue) * 2);

            if (props.options.text?.animation !== false && props.options.text?.content === undefined) {
                currentDisplayNumber.value = currentData;
            }

            startDraw(end);
        },
        callback: () => {
            isAnimating.value = false;
        }
    });
}

onMounted(() => {
    init();
});


</script>

<template>
    <div class="my-canvas" :style="canvasContainerStyle">
        <canvas 
            ref="myCanvas" 
            id="myCanvas" 
            class="canvas" 
            :width="canvasWidth" 
            :height="canvasHeight"
            :style="canvasStyle"
        >你的浏览器不支持canvas</canvas>
    </div>
</template>

<style scoped></style>