import { jsxs, jsx } from 'react/jsx-runtime';
import { useEffect, useState, useRef, useCallback } from 'react';

/* ── Brush Presets ── */
const DEFAULT_DYNAMICS = {
    source: 'none', min: 1, max: 1, curve: 'linear',
};
const PRESSURE_SIZE = {
    source: 'pressure', min: 0.1, max: 1.0, curve: 'linear',
};
const PRESSURE_OPACITY = {
    source: 'pressure', min: 0.2, max: 1.0, curve: 'ease-in',
};
const TILT_ROTATION = {
    source: 'tilt', min: 0, max: 1, curve: 'linear',
};
const BRUSH_PRESETS = {
    'hard-round': {
        name: 'Hard Round',
        size: 10, opacity: 1, flow: 1, hardness: 1, spacing: 0.1,
        tipShape: 'circle', tipRotation: 0, roundness: 1, scatter: 0,
        scatterBothAxes: false, blendMode: 'normal', smoothing: 0.5,
        sizeDynamics: PRESSURE_SIZE,
        opacityDynamics: DEFAULT_DYNAMICS,
        flowDynamics: DEFAULT_DYNAMICS,
        rotationDynamics: DEFAULT_DYNAMICS,
        scatterDynamics: DEFAULT_DYNAMICS,
        wetEdges: false,
        colorJitterHue: 0, colorJitterSat: 0, colorJitterBright: 0,
    },
    'soft-round': {
        name: 'Soft Round',
        size: 20, opacity: 0.8, flow: 0.5, hardness: 0, spacing: 0.08,
        tipShape: 'circle', tipRotation: 0, roundness: 1, scatter: 0,
        scatterBothAxes: false, blendMode: 'normal', smoothing: 0.6,
        sizeDynamics: PRESSURE_SIZE,
        opacityDynamics: PRESSURE_OPACITY,
        flowDynamics: DEFAULT_DYNAMICS,
        rotationDynamics: DEFAULT_DYNAMICS,
        scatterDynamics: DEFAULT_DYNAMICS,
        wetEdges: false,
        colorJitterHue: 0, colorJitterSat: 0, colorJitterBright: 0,
    },
    'calligraphy': {
        name: 'Calligraphy',
        size: 15, opacity: 1, flow: 1, hardness: 0.8, spacing: 0.05,
        tipShape: 'circle', tipRotation: Math.PI / 4, roundness: 0.2, scatter: 0,
        scatterBothAxes: false, blendMode: 'normal', smoothing: 0.7,
        sizeDynamics: PRESSURE_SIZE,
        opacityDynamics: DEFAULT_DYNAMICS,
        flowDynamics: DEFAULT_DYNAMICS,
        rotationDynamics: TILT_ROTATION,
        scatterDynamics: DEFAULT_DYNAMICS,
        wetEdges: false,
        colorJitterHue: 0, colorJitterSat: 0, colorJitterBright: 0,
    },
    'airbrush': {
        name: 'Airbrush',
        size: 40, opacity: 0.3, flow: 0.15, hardness: 0, spacing: 0.05,
        tipShape: 'circle', tipRotation: 0, roundness: 1, scatter: 0.3,
        scatterBothAxes: true, blendMode: 'normal', smoothing: 0.3,
        sizeDynamics: PRESSURE_SIZE,
        opacityDynamics: PRESSURE_OPACITY,
        flowDynamics: { source: 'pressure', min: 0.05, max: 0.4, curve: 'ease-in' },
        rotationDynamics: DEFAULT_DYNAMICS,
        scatterDynamics: { source: 'pressure', min: 0.5, max: 0.05, curve: 'linear' },
        wetEdges: false,
        colorJitterHue: 0, colorJitterSat: 0, colorJitterBright: 0,
    },
    'watercolor': {
        name: 'Watercolor',
        size: 30, opacity: 0.4, flow: 0.3, hardness: 0, spacing: 0.06,
        tipShape: 'circle', tipRotation: 0, roundness: 0.85, scatter: 0.15,
        scatterBothAxes: true, blendMode: 'multiply', smoothing: 0.8,
        sizeDynamics: PRESSURE_SIZE,
        opacityDynamics: PRESSURE_OPACITY,
        flowDynamics: DEFAULT_DYNAMICS,
        rotationDynamics: { source: 'random', min: 0, max: 1, curve: 'linear' },
        scatterDynamics: DEFAULT_DYNAMICS,
        wetEdges: true,
        colorJitterHue: 0.05, colorJitterSat: 0.1, colorJitterBright: 0.08,
    },
    'pencil': {
        name: 'Pencil',
        size: 3, opacity: 0.9, flow: 0.8, hardness: 0.7, spacing: 0.04,
        tipShape: 'circle', tipRotation: 0, roundness: 0.9, scatter: 0.02,
        scatterBothAxes: true, blendMode: 'multiply', smoothing: 0.4,
        sizeDynamics: { source: 'pressure', min: 0.5, max: 1.0, curve: 'ease-out' },
        opacityDynamics: { source: 'pressure', min: 0.3, max: 1.0, curve: 'ease-in' },
        flowDynamics: DEFAULT_DYNAMICS,
        rotationDynamics: DEFAULT_DYNAMICS,
        scatterDynamics: DEFAULT_DYNAMICS,
        wetEdges: false,
        colorJitterHue: 0, colorJitterSat: 0, colorJitterBright: 0.05,
    },
    'ink': {
        name: 'Ink',
        size: 5, opacity: 1, flow: 1, hardness: 1, spacing: 0.03,
        tipShape: 'circle', tipRotation: 0, roundness: 1, scatter: 0,
        scatterBothAxes: false, blendMode: 'normal', smoothing: 0.9,
        sizeDynamics: { source: 'speed', min: 0.3, max: 1.5, curve: 'ease-in-out' },
        opacityDynamics: DEFAULT_DYNAMICS,
        flowDynamics: DEFAULT_DYNAMICS,
        rotationDynamics: DEFAULT_DYNAMICS,
        scatterDynamics: DEFAULT_DYNAMICS,
        wetEdges: false,
        colorJitterHue: 0, colorJitterSat: 0, colorJitterBright: 0,
    },
    'scatter': {
        name: 'Scatter',
        size: 12, opacity: 0.7, flow: 1, hardness: 0.5, spacing: 0.4,
        tipShape: 'circle', tipRotation: 0, roundness: 1, scatter: 1,
        scatterBothAxes: true, blendMode: 'normal', smoothing: 0.2,
        sizeDynamics: { source: 'random', min: 0.3, max: 1.5, curve: 'linear' },
        opacityDynamics: { source: 'random', min: 0.4, max: 1.0, curve: 'linear' },
        flowDynamics: DEFAULT_DYNAMICS,
        rotationDynamics: { source: 'random', min: 0, max: 1, curve: 'linear' },
        scatterDynamics: DEFAULT_DYNAMICS,
        wetEdges: false,
        colorJitterHue: 0.1, colorJitterSat: 0.15, colorJitterBright: 0.1,
    },
};
/* ── Utility functions ── */
function applyCurve(value, curve) {
    const t = Math.max(0, Math.min(1, value));
    switch (curve) {
        case 'ease-in': return t * t;
        case 'ease-out': return 1 - (1 - t) * (1 - t);
        case 'ease-in-out': return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
        default: return t;
    }
}
function evaluateDynamic(dynamic, point, speed) {
    if (dynamic.source === 'none')
        return dynamic.max;
    let input;
    switch (dynamic.source) {
        case 'pressure':
            input = point.pressure;
            break;
        case 'tilt':
            input = Math.sqrt(point.tiltX * point.tiltX + point.tiltY * point.tiltY) / 90;
            break;
        case 'speed':
            input = Math.min(1, speed / 2000);
            break;
        case 'random':
            input = Math.random();
            break;
        default: input = 1;
    }
    const curved = applyCurve(input, dynamic.curve);
    return dynamic.min + (dynamic.max - dynamic.min) * curved;
}
function hexToRGBA(hex) {
    const h = hex.replace('#', '');
    return [
        parseInt(h.slice(0, 2), 16),
        parseInt(h.slice(2, 4), 16),
        parseInt(h.slice(4, 6), 16),
        255,
    ];
}
function hslToRgb(h, s, l) {
    h = ((h % 1) + 1) % 1;
    s = Math.max(0, Math.min(1, s));
    l = Math.max(0, Math.min(1, l));
    const a = s * Math.min(l, 1 - l);
    const f = (n) => {
        const k = (n + h * 12) % 12;
        return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
    };
    return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
}
function rgbToHsl(r, g, b) {
    r /= 255;
    g /= 255;
    b /= 255;
    const max = Math.max(r, g, b), min = Math.min(r, g, b);
    const l = (max + min) / 2;
    if (max === min)
        return [0, 0, l];
    const d = max - min;
    const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    let h = 0;
    if (max === r)
        h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
    else if (max === g)
        h = ((b - r) / d + 2) / 6;
    else
        h = ((r - g) / d + 4) / 6;
    return [h, s, l];
}
function smoothPoints(points, smoothing) {
    if (smoothing <= 0 || points.length < 3)
        return points;
    const windowSize = Math.max(2, Math.round(smoothing * 10));
    const result = [points[0]];
    for (let i = 1; i < points.length - 1; i++) {
        const start = Math.max(0, i - windowSize);
        const end = Math.min(points.length - 1, i + windowSize);
        let sumX = 0, sumY = 0, count = 0;
        for (let j = start; j <= end; j++) {
            const w = 1 - Math.abs(j - i) / (windowSize + 1);
            sumX += points[j].x * w;
            sumY += points[j].y * w;
            count += w;
        }
        result.push({
            ...points[i],
            x: sumX / count,
            y: sumY / count,
        });
    }
    result.push(points[points.length - 1]);
    return result;
}
/* ── Stamp rendering ── */
function renderStamp(ctx, x, y, size, settings, color, dynamicRotation, dynamicOpacity) {
    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(settings.tipRotation + dynamicRotation * Math.PI * 2);
    ctx.scale(1, settings.roundness);
    const half = size / 2;
    if (settings.wetEdges) {
        // Wet edges: stronger at periphery
        const gradient = ctx.createRadialGradient(0, 0, half * 0.6, 0, 0, half);
        const alpha = dynamicOpacity * settings.flow;
        gradient.addColorStop(0, `rgba(${color[0]},${color[1]},${color[2]},${alpha * 0.3})`);
        gradient.addColorStop(0.7, `rgba(${color[0]},${color[1]},${color[2]},${alpha * 0.6})`);
        gradient.addColorStop(1, `rgba(${color[0]},${color[1]},${color[2]},${alpha})`);
        ctx.fillStyle = gradient;
    }
    else if (settings.hardness < 1) {
        // Soft brush with radial gradient
        const gradient = ctx.createRadialGradient(0, 0, half * settings.hardness, 0, 0, half);
        const alpha = dynamicOpacity * settings.flow;
        gradient.addColorStop(0, `rgba(${color[0]},${color[1]},${color[2]},${alpha})`);
        gradient.addColorStop(1, `rgba(${color[0]},${color[1]},${color[2]},0)`);
        ctx.fillStyle = gradient;
    }
    else {
        ctx.fillStyle = `rgba(${color[0]},${color[1]},${color[2]},${dynamicOpacity * settings.flow})`;
    }
    ctx.globalCompositeOperation = settings.blendMode;
    switch (settings.tipShape) {
        case 'square':
            ctx.fillRect(-half, -half, size, size);
            break;
        case 'diamond':
            ctx.beginPath();
            ctx.moveTo(0, -half);
            ctx.lineTo(half, 0);
            ctx.lineTo(0, half);
            ctx.lineTo(-half, 0);
            ctx.closePath();
            ctx.fill();
            break;
        default: // circle
            ctx.beginPath();
            ctx.arc(0, 0, half, 0, Math.PI * 2);
            ctx.fill();
            break;
    }
    ctx.restore();
}
/* ── Hook ── */
function useBrushEngine(canvas, color = '#000000') {
    const [settings, setSettings] = useState({ ...BRUSH_PRESETS['hard-round'] });
    const [isDrawing, setIsDrawing] = useState(false);
    const strokeRef = useRef([]);
    const lastStampRef = useRef(null);
    const lastTimestampRef = useRef(0);
    const applyPreset = useCallback((presetName) => {
        const preset = BRUSH_PRESETS[presetName];
        if (preset)
            setSettings({ ...preset });
    }, []);
    const startStroke = useCallback((e) => {
        const point = {
            x: e.offsetX,
            y: e.offsetY,
            pressure: e.pressure || 0.5,
            tiltX: e.tiltX || 0,
            tiltY: e.tiltY || 0,
            twist: e.twist || 0,
            timestamp: performance.now(),
        };
        strokeRef.current = [point];
        lastStampRef.current = { x: point.x, y: point.y };
        lastTimestampRef.current = point.timestamp;
        setIsDrawing(true);
        // First stamp
        if (canvas) {
            const ctx = canvas.getContext('2d');
            if (ctx) {
                const rgba = hexToRGBA(color);
                const dynamicSize = settings.size * evaluateDynamic(settings.sizeDynamics, point, 0);
                const dynamicOpacity = settings.opacity * evaluateDynamic(settings.opacityDynamics, point, 0);
                const dynamicRotation = evaluateDynamic(settings.rotationDynamics, point, 0);
                renderStamp(ctx, point.x, point.y, dynamicSize, settings, rgba, dynamicRotation, dynamicOpacity);
            }
        }
    }, [canvas, color, settings]);
    const continueStroke = useCallback((e) => {
        if (!strokeRef.current.length || !canvas)
            return;
        const now = performance.now();
        const point = {
            x: e.offsetX,
            y: e.offsetY,
            pressure: e.pressure || 0.5,
            tiltX: e.tiltX || 0,
            tiltY: e.tiltY || 0,
            twist: e.twist || 0,
            timestamp: now,
        };
        strokeRef.current.push(point);
        const ctx = canvas.getContext('2d');
        if (!ctx || !lastStampRef.current)
            return;
        // Calculate speed
        const dt = now - lastTimestampRef.current;
        const dx = point.x - lastStampRef.current.x;
        const dy = point.y - lastStampRef.current.y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        const speed = dt > 0 ? dist / dt * 1000 : 0;
        // Stamp spacing
        const dynamicSize = settings.size * evaluateDynamic(settings.sizeDynamics, point, speed);
        const stampDist = Math.max(1, dynamicSize * settings.spacing);
        if (dist < stampDist)
            return;
        const steps = Math.floor(dist / stampDist);
        const baseRGBA = hexToRGBA(color);
        for (let i = 1; i <= steps; i++) {
            const t = i / steps;
            let sx = lastStampRef.current.x + dx * t;
            let sy = lastStampRef.current.y + dy * t;
            // Apply scatter
            const scatterAmount = settings.scatter * evaluateDynamic(settings.scatterDynamics, point, speed);
            if (scatterAmount > 0) {
                const scatterDist = scatterAmount * dynamicSize;
                sx += (Math.random() - 0.5) * 2 * scatterDist;
                if (settings.scatterBothAxes) {
                    sy += (Math.random() - 0.5) * 2 * scatterDist;
                }
            }
            // Color jitter
            let stampColor = baseRGBA;
            if (settings.colorJitterHue > 0 || settings.colorJitterSat > 0 || settings.colorJitterBright > 0) {
                const [h, s, l] = rgbToHsl(baseRGBA[0], baseRGBA[1], baseRGBA[2]);
                const jH = h + (Math.random() - 0.5) * settings.colorJitterHue;
                const jS = s + (Math.random() - 0.5) * settings.colorJitterSat;
                const jL = l + (Math.random() - 0.5) * settings.colorJitterBright;
                const [r, g, b] = hslToRgb(jH, jS, jL);
                stampColor = [r, g, b, 255];
            }
            const dynamicOpacity = settings.opacity * evaluateDynamic(settings.opacityDynamics, point, speed);
            const dynamicRotation = evaluateDynamic(settings.rotationDynamics, point, speed);
            renderStamp(ctx, sx, sy, dynamicSize, settings, stampColor, dynamicRotation, dynamicOpacity);
        }
        lastStampRef.current = { x: point.x, y: point.y };
        lastTimestampRef.current = now;
    }, [canvas, color, settings]);
    const endStroke = useCallback(() => {
        if (!strokeRef.current.length)
            return null;
        const smoothed = smoothPoints(strokeRef.current, settings.smoothing);
        const stroke = {
            id: `stroke-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
            points: smoothed,
            color,
            settings: { ...settings },
        };
        strokeRef.current = [];
        lastStampRef.current = null;
        setIsDrawing(false);
        return stroke;
    }, [color, settings]);
    // Auto-attach pointer events
    useEffect(() => {
        if (!canvas)
            return;
        const onDown = (e) => {
            canvas.setPointerCapture(e.pointerId);
            startStroke(e);
        };
        const onMove = (e) => {
            if (e.buttons > 0)
                continueStroke(e);
        };
        const onUp = () => { endStroke(); };
        canvas.addEventListener('pointerdown', onDown);
        canvas.addEventListener('pointermove', onMove);
        canvas.addEventListener('pointerup', onUp);
        canvas.addEventListener('pointercancel', onUp);
        // Enable pressure events
        canvas.style.touchAction = 'none';
        return () => {
            canvas.removeEventListener('pointerdown', onDown);
            canvas.removeEventListener('pointermove', onMove);
            canvas.removeEventListener('pointerup', onUp);
            canvas.removeEventListener('pointercancel', onUp);
        };
    }, [canvas, startStroke, continueStroke, endStroke]);
    return {
        settings, setSettings,
        applyPreset,
        isDrawing,
        startStroke, continueStroke, endStroke,
        presetNames: Object.keys(BRUSH_PRESETS),
    };
}
/* ── Component ── */
const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '', }) => {
    const engine = useBrushEngine(canvas, color);
    // Override endStroke to fire callback
    useEffect(() => {
        if (!canvas)
            return;
        const onUp = () => {
            const stroke = engine.endStroke();
            if (stroke && onStrokeEnd)
                onStrokeEnd(stroke);
        };
        canvas.addEventListener('pointerup', onUp);
        return () => canvas.removeEventListener('pointerup', onUp);
    }, [canvas, engine.endStroke, onStrokeEnd]);
    const s = engine.settings;
    return (jsxs("div", { className: `nice-brush-engine ${className}`, style: { display: 'grid', gap: 6, fontSize: 13 }, children: [jsx("strong", { children: "\uD83D\uDD8C\uFE0F Brush Engine v2" }), jsx("div", { style: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: engine.presetNames.map((name) => {
                    var _a, _b;
                    return (jsx("button", { type: "button", onClick: () => engine.applyPreset(name), style: {
                            padding: '2px 6px', fontSize: 10, cursor: 'pointer', borderRadius: 4,
                            border: '1px solid var(--border, #e2e8f0)',
                            background: s.name === ((_a = BRUSH_PRESETS[name]) === null || _a === void 0 ? void 0 : _a.name) ? 'var(--accent, #6366f1)' : 'transparent',
                            color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name) ? '#fff' : 'inherit',
                        }, children: BRUSH_PRESETS[name].name }, name));
                }) }), jsxs("label", { children: ["Size: ", s.size, "px", jsx("input", { type: "range", min: 1, max: 200, value: s.size, onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Opacity: ", Math.round(s.opacity * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.opacity, onChange: (e) => engine.setSettings({ ...s, opacity: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Flow: ", Math.round(s.flow * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.flow, onChange: (e) => engine.setSettings({ ...s, flow: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Hardness: ", Math.round(s.hardness * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.hardness, onChange: (e) => engine.setSettings({ ...s, hardness: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Smoothing: ", Math.round(s.smoothing * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.smoothing, onChange: (e) => engine.setSettings({ ...s, smoothing: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Tip:", jsxs("select", { value: s.tipShape, onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }), style: { width: '100%' }, children: [jsx("option", { value: "circle", children: "Circle" }), jsx("option", { value: "square", children: "Square" }), jsx("option", { value: "diamond", children: "Diamond" }), jsx("option", { value: "texture", children: "Texture" })] })] }), jsxs("label", { children: ["Roundness: ", Math.round(s.roundness * 100), "%", jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: s.roundness, onChange: (e) => engine.setSettings({ ...s, roundness: +e.target.value }), style: { width: '100%' } })] }), jsxs("details", { children: [jsx("summary", { style: { cursor: 'pointer', fontSize: 12 }, children: "\uD83D\uDCCA Size Dynamics" }), jsxs("label", { children: ["Source:", jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({ ...s, sizeDynamics: { ...s.sizeDynamics, source: e.target.value } }), style: { width: '100%' }, children: [jsx("option", { value: "none", children: "Off" }), jsx("option", { value: "pressure", children: "Pressure" }), jsx("option", { value: "tilt", children: "Tilt" }), jsx("option", { value: "speed", children: "Speed" }), jsx("option", { value: "random", children: "Random" })] })] })] }), jsxs("label", { children: ["Scatter: ", Math.round(s.scatter * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.scatter, onChange: (e) => engine.setSettings({ ...s, scatter: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Blend:", jsx("select", { value: s.blendMode, onChange: (e) => engine.setSettings({ ...s, blendMode: e.target.value }), style: { width: '100%' }, children: ['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten'].map((m) => (jsx("option", { value: m, children: m }, m))) })] }), jsxs("label", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsx("input", { type: "checkbox", checked: s.wetEdges, onChange: (e) => engine.setSettings({ ...s, wetEdges: e.target.checked }) }), "Wet Edges"] })] }));
};

export { BRUSH_PRESETS, NiceBrushEngine, useBrushEngine };
//# sourceMappingURL=NiceBrushEngine.js.map
