/**
 * 自然写互动课堂PC端应用软件 V1.0
 *
 * StrokeCanvas.vue - 笔迹画布组件
 *
 * 功能说明：
 * - Canvas 2D高性能笔迹渲染
 * - 压力感应笔锋效果
 * - 贝塞尔曲线平滑
 * - 多图层渲染（背景+已完成笔画+当前笔画）
 * - 笔迹回放动画
 * - 缩放与平移手势
 */

<template>
  <div class="stroke-canvas-container" ref="containerRef">
    <!-- 背景层：课件/试卷图片 -->
    <canvas ref="bgCanvas" class="canvas-layer canvas-bg"></canvas>
    <!-- 笔迹层：已完成的笔画 -->
    <canvas ref="strokeCanvas" class="canvas-layer canvas-stroke"></canvas>
    <!-- 活动层：当前正在绘制的笔画 -->
    <canvas ref="activeCanvas" class="canvas-layer canvas-active"></canvas>

    <!-- 工具栏 -->
    <div class="canvas-toolbar" v-if="showToolbar">
      <button @click="setPenColor('#000000')" :class="{ active: penColor === '#000000' }">黑</button>
      <button @click="setPenColor('#FF0000')" :class="{ active: penColor === '#FF0000' }">红</button>
      <button @click="setPenColor('#0000FF')" :class="{ active: penColor === '#0000FF' }">蓝</button>
      <button @click="toggleEraser" :class="{ active: eraserMode }">橡皮</button>
      <button @click="undo">撤销</button>
      <button @click="redo">重做</button>
      <button @click="clearAll">清空</button>
    </div>

    <!-- 缩放控件 -->
    <div class="zoom-controls">
      <span class="zoom-label">{{ Math.round(scale * 100) }}%</span>
      <button @click="zoomIn">+</button>
      <button @click="zoomOut">-</button>
      <button @click="resetZoom">适应</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';

/* ======================== Props与Emits ======================== */

interface Props {
    /** 画布宽度 */
    width?: number;
    /** 画布高度 */
    height?: number;
    /** 背景图片URL */
    backgroundUrl?: string;
    /** 是否显示工具栏 */
    showToolbar?: boolean;
    /** 是否只读模式（仅展示笔迹） */
    readonly?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
    width: 1920,
    height: 1080,
    showToolbar: true,
    readonly: false
});

const emit = defineEmits<{
    (e: 'stroke-complete', stroke: StrokeData): void;
    (e: 'stroke-point', point: PointData): void;
}>();

/* ======================== 类型定义 ======================== */

interface PointData {
    x: number;
    y: number;
    pressure: number;
    timestamp: number;
}

interface StrokeData {
    strokeId: string;
    color: string;
    width: number;
    points: PointData[];
}

/* ======================== 响应式数据 ======================== */

/** DOM引用 */
const containerRef = ref<HTMLDivElement>();
const bgCanvas = ref<HTMLCanvasElement>();
const strokeCanvas = ref<HTMLCanvasElement>();
const activeCanvas = ref<HTMLCanvasElement>();

/** 画布上下文 */
let bgCtx: CanvasRenderingContext2D | null = null;
let strokeCtx: CanvasRenderingContext2D | null = null;
let activeCtx: CanvasRenderingContext2D | null = null;

/** 画笔状态 */
const penColor = ref('#000000');
const penWidth = ref(3);
const eraserMode = ref(false);
const scale = ref(1.0);

/** 当前笔画 */
let currentStroke: StrokeData | null = null;
/** 已完成笔画列表 */
const completedStrokes: StrokeData[] = [];
/** 撤销栈 */
const undoStack: StrokeData[] = [];
/** 重做栈 */
const redoStack: StrokeData[] = [];
/** 是否正在绘制 */
let isDrawing = false;

/* ======================== 平滑算法常量 ======================== */

/** 贝塞尔曲线平滑最小距离 */
const SMOOTH_MIN_DIST = 2;
/** 笔锋最小宽度比 */
const PEN_MIN_WIDTH_RATIO = 0.25;
/** 笔锋最大宽度比 */
const PEN_MAX_WIDTH_RATIO = 1.6;

/* ======================== 生命周期 ======================== */

onMounted(() => {
    initCanvases();
    if (props.backgroundUrl) {
        loadBackground(props.backgroundUrl);
    }
    if (!props.readonly) {
        setupInputHandlers();
    }
});

onUnmounted(() => {
    removeInputHandlers();
});

/* ======================== 画布初始化 ======================== */

/**
 * 初始化三层画布
 */
function initCanvases(): void {
    const canvases = [bgCanvas.value, strokeCanvas.value, activeCanvas.value];
    canvases.forEach(canvas => {
        if (canvas) {
            canvas.width = props.width;
            canvas.height = props.height;
        }
    });

    bgCtx = bgCanvas.value?.getContext('2d') ?? null;
    strokeCtx = strokeCanvas.value?.getContext('2d') ?? null;
    activeCtx = activeCanvas.value?.getContext('2d') ?? null;

    /* 笔迹层抗锯齿 */
    if (strokeCtx) {
        strokeCtx.lineCap = 'round';
        strokeCtx.lineJoin = 'round';
    }
    if (activeCtx) {
        activeCtx.lineCap = 'round';
        activeCtx.lineJoin = 'round';
    }

    console.log(`[画布] 初始化: ${props.width}x${props.height}`);
}

/**
 * 加载背景图片
 */
function loadBackground(url: string): void {
    const img = new Image();
    img.onload = () => {
        bgCtx?.drawImage(img, 0, 0, props.width, props.height);
        console.log(`[画布] 背景加载完成: ${url}`);
    };
    img.onerror = () => {
        console.error(`[画布] 背景加载失败: ${url}`);
    };
    img.src = url;
}

/* ======================== 输入事件处理 ======================== */

function setupInputHandlers(): void {
    const canvas = activeCanvas.value;
    if (!canvas) return;

    canvas.addEventListener('pointerdown', onPointerDown);
    canvas.addEventListener('pointermove', onPointerMove);
    canvas.addEventListener('pointerup', onPointerUp);
    canvas.addEventListener('pointercancel', onPointerUp);

    /* 禁止默认触摸行为（防止页面滚动） */
    canvas.style.touchAction = 'none';
}

function removeInputHandlers(): void {
    const canvas = activeCanvas.value;
    if (!canvas) return;

    canvas.removeEventListener('pointerdown', onPointerDown);
    canvas.removeEventListener('pointermove', onPointerMove);
    canvas.removeEventListener('pointerup', onPointerUp);
    canvas.removeEventListener('pointercancel', onPointerUp);
}

/**
 * 指针按下 - 开始新笔画
 */
function onPointerDown(e: PointerEvent): void {
    if (props.readonly) return;

    isDrawing = true;
    const { canvasX, canvasY } = screenToCanvas(e.offsetX, e.offsetY);
    const pressure = e.pressure || 0.5;

    currentStroke = {
        strokeId: `stroke_${Date.now()}`,
        color: eraserMode.value ? '#FFFFFF' : penColor.value,
        width: penWidth.value,
        points: [{ x: canvasX, y: canvasY, pressure, timestamp: Date.now() }]
    };
}

/**
 * 指针移动 - 添加采样点并实时绘制
 */
function onPointerMove(e: PointerEvent): void {
    if (!isDrawing || !currentStroke) return;

    const { canvasX, canvasY } = screenToCanvas(e.offsetX, e.offsetY);
    const pressure = e.pressure || 0.5;

    const lastPt = currentStroke.points[currentStroke.points.length - 1];
    const dx = canvasX - lastPt.x;
    const dy = canvasY - lastPt.y;
    const dist = Math.sqrt(dx * dx + dy * dy);

    /* 距离过近跳过 */
    if (dist < SMOOTH_MIN_DIST) return;

    const point: PointData = { x: canvasX, y: canvasY, pressure, timestamp: Date.now() };
    currentStroke.points.push(point);
    emit('stroke-point', point);

    /* 增量渲染最新线段 */
    drawSegment(activeCtx!, lastPt, point, currentStroke.color, currentStroke.width);
}

/**
 * 指针抬起 - 完成笔画
 */
function onPointerUp(e: PointerEvent): void {
    if (!isDrawing || !currentStroke) return;

    isDrawing = false;

    if (currentStroke.points.length >= 2) {
        completedStrokes.push(currentStroke);
        undoStack.push(currentStroke);
        redoStack.length = 0;

        /* 将笔画绘制到笔迹层 */
        drawFullStroke(strokeCtx!, currentStroke);
        emit('stroke-complete', currentStroke);
    }

    /* 清空活动层 */
    activeCtx?.clearRect(0, 0, props.width, props.height);
    currentStroke = null;
}

/* ======================== 绘制函数 ======================== */

/**
 * 绘制单个线段（带压力笔锋）
 */
function drawSegment(ctx: CanvasRenderingContext2D, from: PointData,
                     to: PointData, color: string, baseWidth: number): void {
    /* 压力感应笔锋：宽度随压力变化 */
    const widthRatio = PEN_MIN_WIDTH_RATIO +
        (PEN_MAX_WIDTH_RATIO - PEN_MIN_WIDTH_RATIO) * to.pressure;
    const lineWidth = baseWidth * widthRatio;

    ctx.strokeStyle = color;
    ctx.lineWidth = lineWidth;
    ctx.beginPath();
    ctx.moveTo(from.x, from.y);
    ctx.lineTo(to.x, to.y);
    ctx.stroke();
}

/**
 * 绘制完整笔画（贝塞尔曲线平滑）
 */
function drawFullStroke(ctx: CanvasRenderingContext2D, stroke: StrokeData): void {
    const points = stroke.points;
    if (points.length < 2) return;

    ctx.strokeStyle = stroke.color;

    for (let i = 1; i < points.length; i++) {
        const prev = points[i - 1];
        const curr = points[i];

        const widthRatio = PEN_MIN_WIDTH_RATIO +
            (PEN_MAX_WIDTH_RATIO - PEN_MIN_WIDTH_RATIO) * curr.pressure;
        ctx.lineWidth = stroke.width * widthRatio;

        if (i >= 2) {
            /* 二次贝塞尔曲线平滑 */
            const prevPrev = points[i - 2];
            const midX1 = (prevPrev.x + prev.x) / 2;
            const midY1 = (prevPrev.y + prev.y) / 2;
            const midX2 = (prev.x + curr.x) / 2;
            const midY2 = (prev.y + curr.y) / 2;

            ctx.beginPath();
            ctx.moveTo(midX1, midY1);
            ctx.quadraticCurveTo(prev.x, prev.y, midX2, midY2);
            ctx.stroke();
        } else {
            ctx.beginPath();
            ctx.moveTo(prev.x, prev.y);
            ctx.lineTo(curr.x, curr.y);
            ctx.stroke();
        }
    }
}

/* ======================== 坐标转换 ======================== */

function screenToCanvas(sx: number, sy: number): { canvasX: number; canvasY: number } {
    return {
        canvasX: sx / scale.value,
        canvasY: sy / scale.value
    };
}

/* ======================== 工具栏操作 ======================== */

function setPenColor(color: string): void {
    penColor.value = color;
    eraserMode.value = false;
}

function toggleEraser(): void {
    eraserMode.value = !eraserMode.value;
}

function undo(): void {
    const stroke = undoStack.pop();
    if (!stroke) return;

    redoStack.push(stroke);
    completedStrokes.splice(completedStrokes.indexOf(stroke), 1);
    redrawAllStrokes();
}

function redo(): void {
    const stroke = redoStack.pop();
    if (!stroke) return;

    undoStack.push(stroke);
    completedStrokes.push(stroke);
    redrawAllStrokes();
}

function clearAll(): void {
    completedStrokes.length = 0;
    undoStack.length = 0;
    redoStack.length = 0;
    strokeCtx?.clearRect(0, 0, props.width, props.height);
    activeCtx?.clearRect(0, 0, props.width, props.height);
}

function redrawAllStrokes(): void {
    strokeCtx?.clearRect(0, 0, props.width, props.height);
    completedStrokes.forEach(stroke => {
        drawFullStroke(strokeCtx!, stroke);
    });
}

/* ======================== 缩放控制 ======================== */

function zoomIn(): void {
    scale.value = Math.min(scale.value * 1.25, 3.0);
}

function zoomOut(): void {
    scale.value = Math.max(scale.value / 1.25, 0.25);
}

function resetZoom(): void {
    scale.value = 1.0;
}

/* ======================== 外部笔迹接收 ======================== */

/**
 * 接收外部笔迹数据（学生端通过WebSocket推送）
 */
function addExternalStroke(stroke: StrokeData): void {
    completedStrokes.push(stroke);
    drawFullStroke(strokeCtx!, stroke);
}

/**
 * 笔迹回放动画
 */
async function replayStrokes(strokes: StrokeData[], speedMultiplier: number = 1): Promise<void> {
    for (const stroke of strokes) {
        for (let i = 1; i < stroke.points.length; i++) {
            const prev = stroke.points[i - 1];
            const curr = stroke.points[i];

            drawSegment(strokeCtx!, prev, curr, stroke.color, stroke.width);

            const delay = (curr.timestamp - prev.timestamp) / speedMultiplier;
            await new Promise(resolve => setTimeout(resolve, Math.max(delay, 5)));
        }
    }
}

/* 导出方法供父组件调用 */
defineExpose({ addExternalStroke, replayStrokes, clearAll, loadBackground });
</script>

<style scoped>
.stroke-canvas-container {
    position: relative;
    overflow: hidden;
    background: #f5f5f5;
}
.canvas-layer {
    position: absolute;
    top: 0;
    left: 0;
}
.canvas-bg { z-index: 1; }
.canvas-stroke { z-index: 2; }
.canvas-active { z-index: 3; cursor: crosshair; }
.canvas-toolbar {
    position: absolute;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    display: flex;
    gap: 8px;
    padding: 8px 16px;
    background: rgba(255,255,255,0.95);
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.canvas-toolbar button {
    padding: 6px 14px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background: #fff;
    cursor: pointer;
    font-size: 13px;
}
.canvas-toolbar button.active {
    background: #1976d2;
    color: #fff;
    border-color: #1976d2;
}
.zoom-controls {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 10;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    background: rgba(255,255,255,0.9);
    border-radius: 6px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
.zoom-label { font-size: 12px; color: #666; min-width: 36px; text-align: center; }
.zoom-controls button {
    width: 28px;
    height: 28px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background: #fff;
    cursor: pointer;
    font-size: 14px;
}
</style>
