import type { Color } from '@/types/layout';
import { createPatternCanvas } from './patterns';

export type KonvaFillProps = {
    fill?: string;
    fillLinearGradientStartPoint?: { x: number; y: number };
    fillLinearGradientEndPoint?: { x: number; y: number };
    fillLinearGradientColorStops?: (number | string)[];
    fillRadialGradientStartPoint?: { x: number; y: number };
    fillRadialGradientStartRadius?: number;
    fillRadialGradientEndPoint?: { x: number; y: number };
    fillRadialGradientEndRadius?: number;
    fillRadialGradientColorStops?: (number | string)[];
    fillPatternImage?: HTMLCanvasElement;
    fillPatternRepeat?: string;
};

export function colorToKonvaFill(
    color: Color | undefined,
    width: number,
    height: number,
): KonvaFillProps {
    if (!color) return {};
    if (color.type === 'solid') return { fill: color.value };
    if (color.type === 'pattern') {
        try {
            const tile = createPatternCanvas(color.patternId, color.color, color.scale ?? 3);
            return { fillPatternImage: tile, fillPatternRepeat: 'repeat' };
        } catch {
            return { fill: color.color };
        }
    }
    // Defensive: a partial deepMerge of a Color override (e.g. type swapped from
    // solid → linear without stops) can land here with a non-iterable stops.
    // Treat as the first stop's color or transparent rather than throwing.
    const rawStops = Array.isArray(color.stops) ? color.stops : [];
    if (rawStops.length === 0) {
        return { fill: 'transparent' };
    }
    if (color.type === 'linear') {
        const rad = (color.angle * Math.PI) / 180;
        const cx = width / 2;
        const cy = height / 2;
        const half = Math.max(width, height) / 2;
        const dx = Math.cos(rad) * half;
        const dy = Math.sin(rad) * half;
        const stops: (number | string)[] = [];
        for (const s of rawStops) {
            stops.push(s.offset, s.color);
        }
        return {
            fillLinearGradientStartPoint: { x: cx - dx, y: cy - dy },
            fillLinearGradientEndPoint: { x: cx + dx, y: cy + dy },
            fillLinearGradientColorStops: stops,
        };
    }
    const stops: (number | string)[] = [];
    for (const s of rawStops) {
        stops.push(s.offset, s.color);
    }
    // cx/cy/radius are stored as 0-1 normalized values so the gradient survives
    // device resizes; Konva expects pixel coordinates relative to the shape.
    const cxPx = color.cx * width;
    const cyPx = color.cy * height;
    const rPx = color.radius * Math.max(width, height);
    return {
        fillRadialGradientStartPoint: { x: cxPx, y: cyPx },
        fillRadialGradientStartRadius: 0,
        fillRadialGradientEndPoint: { x: cxPx, y: cyPx },
        fillRadialGradientEndRadius: rPx,
        fillRadialGradientColorStops: stops,
    };
}

/**
 * Build a canvas fill (solid / linear / radial / pattern) for a sceneFunc-drawn
 * shape — the raw-`ctx` twin of {@link colorToKonvaFill}, used by vector
 * graphics (laurel badges) that paint via `ctx.fill()` instead of Konva props.
 * Coordinates are in the shape's `width × height` space (apply before any local
 * scale transform so the gradient spans the graphic).
 */
export function colorToCanvasFill(
    ctx: CanvasRenderingContext2D,
    color: Color | undefined,
    width: number,
    height: number,
): string | CanvasGradient | CanvasPattern {
    if (!color) return '#111111';
    if (color.type === 'solid') return color.value;
    if (color.type === 'pattern') {
        try {
            const tile = createPatternCanvas(color.patternId, color.color, color.scale ?? 3);
            return ctx.createPattern(tile, 'repeat') ?? color.color;
        } catch {
            return color.color;
        }
    }
    const rawStops = Array.isArray(color.stops) ? color.stops : [];
    if (rawStops.length === 0) return 'transparent';
    const clamp01 = (n: number) => Math.max(0, Math.min(1, n));
    const addStops = (g: CanvasGradient) => {
        for (const s of rawStops) {
            try {
                g.addColorStop(clamp01(s.offset), s.color);
            } catch {
                /* invalid stop — skip rather than throw mid-render */
            }
        }
        return g;
    };
    if (color.type === 'linear') {
        const rad = (color.angle * Math.PI) / 180;
        const cx = width / 2;
        const cy = height / 2;
        const half = Math.max(width, height) / 2;
        const dx = Math.cos(rad) * half;
        const dy = Math.sin(rad) * half;
        return addStops(ctx.createLinearGradient(cx - dx, cy - dy, cx + dx, cy + dy));
    }
    const cxPx = color.cx * width;
    const cyPx = color.cy * height;
    const rPx = Math.max(0.0001, color.radius * Math.max(width, height));
    return addStops(ctx.createRadialGradient(cxPx, cyPx, 0, cxPx, cyPx, rPx));
}

export type KonvaStrokeProps = {
    stroke?: string;
    strokeLinearGradientStartPoint?: { x: number; y: number };
    strokeLinearGradientEndPoint?: { x: number; y: number };
    strokeLinearGradientColorStops?: (number | string)[];
    strokeRadialGradientStartPoint?: { x: number; y: number };
    strokeRadialGradientStartRadius?: number;
    strokeRadialGradientEndPoint?: { x: number; y: number };
    strokeRadialGradientEndRadius?: number;
    strokeRadialGradientColorStops?: (number | string)[];
};

/**
 * Normalize legacy `stroke.color: string` into a `Color` union. Strings are
 * treated as `{ type: 'solid', value: string }`.
 */
export function strokeColorAsColor(color: string | Color | undefined): Color | undefined {
    if (color == null) return undefined;
    if (typeof color === 'string') return { type: 'solid', value: color };
    return color;
}

export function colorToKonvaStroke(
    color: Color | undefined,
    width: number,
    height: number,
): KonvaStrokeProps {
    if (!color) return {};
    if (color.type === 'solid') return { stroke: color.value };
    if (color.type === 'pattern') return {}; // pattern fills don't apply to strokes
    const rawStops = Array.isArray(color.stops) ? color.stops : [];
    if (rawStops.length === 0) {
        return {};
    }
    if (color.type === 'linear') {
        const rad = (color.angle * Math.PI) / 180;
        const cx = width / 2;
        const cy = height / 2;
        const half = Math.max(width, height) / 2;
        const dx = Math.cos(rad) * half;
        const dy = Math.sin(rad) * half;
        const stops: (number | string)[] = [];
        for (const s of rawStops) {
            stops.push(s.offset, s.color);
        }
        return {
            strokeLinearGradientStartPoint: { x: cx - dx, y: cy - dy },
            strokeLinearGradientEndPoint: { x: cx + dx, y: cy + dy },
            strokeLinearGradientColorStops: stops,
        };
    }
    const stops: (number | string)[] = [];
    for (const s of rawStops) {
        stops.push(s.offset, s.color);
    }
    const cxPx = color.cx * width;
    const cyPx = color.cy * height;
    const rPx = color.radius * Math.max(width, height);
    return {
        strokeRadialGradientStartPoint: { x: cxPx, y: cyPx },
        strokeRadialGradientStartRadius: 0,
        strokeRadialGradientEndPoint: { x: cxPx, y: cyPx },
        strokeRadialGradientEndRadius: rPx,
        strokeRadialGradientColorStops: stops,
    };
}
