import type {
    BackgroundLayer,
    CalloutLayer,
    FrameLayer,
    FrameScreen,
    ImageLayer,
    Layer,
    ShapeKind,
    ShapeLayer,
    TextLayer,
} from '@/types/layout';

const newId = () =>
    typeof crypto !== 'undefined' && 'randomUUID' in crypto
        ? crypto.randomUUID()
        : `l_${Math.random().toString(36).slice(2, 10)}`;

export function createTextLayer(overrides: Partial<TextLayer> = {}): TextLayer {
    return {
        id: newId(),
        type: 'text',
        anchors: {
            left: { type: 'percent', value: 50, align: 'center' },
            top: { type: 'percent', value: 10 },
            width: { type: 'percent', value: 80 },
            height: { type: 'percent', value: 12 },
        },
        content: { default: 'Your headline' },
        fontFamily: 'Inter',
        fontWeight: 700,
        fontSize: 96,
        minFontSize: 32,
        maxFontSize: 160,
        lineHeight: 1.1,
        align: 'center',
        color: '#0F172A',
        fitMode: 'auto',
        ...overrides,
    };
}

export function createShapeLayer(
    shape: ShapeKind = 'rect',
    overrides: Partial<ShapeLayer> = {},
): ShapeLayer {
    const base = {
        id: newId(),
        type: 'shape' as const,
        shape,
    };

    if (shape === 'line' || shape === 'arrow') {
        // Line/arrow have no fill area — stroke is the visible body. Centered,
        // 800×48 px so the hit target stays reachable around a thin stroke.
        return {
            ...base,
            anchors: {
                left: { type: 'percent', value: 50, align: 'center' },
                top: { type: 'percent', value: 50, align: 'center' },
                width: { type: 'px', value: 800 },
                height: { type: 'px', value: 48 },
            },
            style: {
                fill: { type: 'solid', value: '#0F172A' },
                stroke: { color: '#0F172A', width: 6 },
            },
            ...(shape === 'arrow' ? { arrowHead: 1 } : {}),
            ...overrides,
        };
    }

    if (shape === 'polygon') {
        return {
            ...base,
            anchors: {
                left: { type: 'percent', value: 50, align: 'center' },
                top: { type: 'percent', value: 50, align: 'center' },
                width: { type: 'px', value: 400 },
                height: { type: 'px', value: 400 },
            },
            style: { fill: { type: 'solid', value: '#F0E8DE' } },
            sides: 6,
            ...overrides,
        };
    }

    if (shape === 'star') {
        return {
            ...base,
            anchors: {
                left: { type: 'percent', value: 50, align: 'center' },
                top: { type: 'percent', value: 50, align: 'center' },
                width: { type: 'px', value: 400 },
                height: { type: 'px', value: 400 },
            },
            style: { fill: { type: 'solid', value: '#F0E8DE' } },
            starPoints: 5,
            innerRatio: 0.4,
            ...overrides,
        };
    }

    if (shape === 'path') {
        // Pen-tool output. Size is set at commit time from the node extents;
        // this is the fallback when a path is created without nodes.
        return {
            ...base,
            anchors: {
                left: { type: 'percent', value: 50, align: 'center' },
                top: { type: 'percent', value: 50, align: 'center' },
                width: { type: 'px', value: 400 },
                height: { type: 'px', value: 400 },
            },
            style: {
                stroke: { color: '#0F172A', width: 6 },
            },
            nodes: [],
            closed: false,
            ...overrides,
        };
    }

    return {
        ...base,
        anchors: {
            left: { type: 'percent', value: 50, align: 'center' },
            top: { type: 'percent', value: 40 },
            width: { type: 'percent', value: 40 },
            height: { type: 'percent', value: 10 },
        },
        style: {
            fill: { type: 'solid', value: '#F0E8DE' },
            radius: shape === 'rect' ? 24 : 0,
        },
        ...overrides,
    };
}

type FramePresetSeed = {
    id: number;
    export_w: number;
    export_h: number;
};

export function createFrameLayer(
    preset: FramePresetSeed,
    canvas: { width: number; height: number },
    overrides: Partial<FrameLayer> = {},
): FrameLayer {
    // Size the frame to ~80% of canvas height, preserving preset aspect ratio.
    const aspect = preset.export_w / preset.export_h;
    const heightPct = 80;
    const canvasAspect = canvas.width / canvas.height;
    const widthPct = (heightPct * aspect) / canvasAspect;

    return {
        id: newId(),
        type: 'frame',
        devicePresetId: preset.id,
        anchors: {
            left: { type: 'percent', value: 50, align: 'center' },
            top: { type: 'percent', value: 50, align: 'center' },
            width: { type: 'percent', value: +widthPct.toFixed(3) },
            height: { type: 'percent', value: heightPct },
        },
        ...overrides,
    };
}

export function createBackgroundLayer(
    overrides: Partial<BackgroundLayer> = {},
): BackgroundLayer {
    return {
        id: newId(),
        type: 'background',
        anchors: {
            left: { type: 'percent', value: 0 },
            top: { type: 'percent', value: 0 },
            width: { type: 'percent', value: 100 },
            height: { type: 'percent', value: 100 },
        },
        // Warm cream solid — a neutral starting point that reads well with
        // both light and dark device frames + doesn't compete with whatever
        // content the user drops on top. Easy to swap to a gradient via
        // the Fill panel; harder to recover from a bold default like the
        // earlier blue gradient when the author wanted neutral.
        fill: { type: 'solid', value: '#F0E8DE' },
        ...overrides,
    };
}

type ImageSeed = {
    storagePath: string;
    width?: number | null;
    height?: number | null;
    fileName?: string;
};

export function createImageLayer(
    seed: ImageSeed,
    canvas: { width: number; height: number },
    overrides: Partial<ImageLayer> = {},
): ImageLayer {
    const rect = initialRectForImage(seed, canvas);
    return {
        id: newId(),
        type: 'image',
        name: seed.fileName ?? 'Image',
        anchors: {
            left: { type: 'percent', value: rect.leftPct },
            top: { type: 'percent', value: rect.topPct },
            width: { type: 'percent', value: rect.widthPct },
            height: { type: 'percent', value: rect.heightPct },
        },
        storagePath: seed.storagePath,
        fit: 'cover',
        ...overrides,
    };
}

type CalloutSeed = {
    sourceLayerId: string;
    /** Percent of source's resolved rect (0-100). */
    sourceRect: { x: number; y: number; width: number; height: number };
    /** Display rect in canvas pixels — the callout's own bounding box. */
    displayRect: { x: number; y: number; width: number; height: number };
};

/**
 * Build a CalloutLayer from the two-phase authoring gesture. `displayRect`
 * is converted into percent anchors (cross-factor derivation safe).
 * `sourceRect` is already in percent and stored verbatim. Shape defaults
 * to `ellipse` — the most common app store callout style.
 */
export function createCalloutLayer(
    seed: CalloutSeed,
    canvas: { width: number; height: number },
    overrides: Partial<CalloutLayer> = {},
): CalloutLayer {
    const leftPct = (seed.displayRect.x / canvas.width) * 100;
    const topPct = (seed.displayRect.y / canvas.height) * 100;
    const widthPct = (seed.displayRect.width / canvas.width) * 100;
    const heightPct = (seed.displayRect.height / canvas.height) * 100;
    return {
        id: newId(),
        type: 'callout',
        anchors: {
            left: { type: 'percent', value: +leftPct.toFixed(3) },
            top: { type: 'percent', value: +topPct.toFixed(3) },
            width: { type: 'percent', value: +widthPct.toFixed(3) },
            height: { type: 'percent', value: +heightPct.toFixed(3) },
        },
        sourceLayerId: seed.sourceLayerId,
        sourceRect: {
            x: +seed.sourceRect.x.toFixed(3),
            y: +seed.sourceRect.y.toFixed(3),
            width: +seed.sourceRect.width.toFixed(3),
            height: +seed.sourceRect.height.toFixed(3),
        },
        shape: 'ellipse',
        ...overrides,
    };
}

/**
 * Build a `FrameScreen` from an uploaded raster. Used when the user adds a
 * screenshot to a frame via the properties panel — the frame absorbs the
 * source path + fit so the device mockup stays one layer.
 */
export function createFrameScreen(
    seed: ImageSeed,
    overrides: Partial<FrameScreen> = {},
): FrameScreen {
    return {
        storagePath: seed.storagePath,
        // `fill` matches how App Store / Play Store screenshots are captured:
        // the file is already at the device's native screen aspect, so it
        // should stretch to fill the bezel cutout edge-to-edge with no crop
        // or letterbox. `cover` (the previous default) would crop the edges
        // when source dims drift slightly from the bezel cutout; `contain`
        // letterboxes. Caller can still override.
        fit: 'fill',
        ...overrides,
    };
}

function initialRectForImage(
    seed: ImageSeed,
    canvas: { width: number; height: number },
): { leftPct: number; topPct: number; widthPct: number; heightPct: number } {
    const srcAspect =
        seed.width && seed.height && seed.width > 0 && seed.height > 0
            ? seed.width / seed.height
            : canvas.width / canvas.height;

    const maxWidthPct = 70;
    const maxHeightPct = 70;
    const canvasAspect = canvas.width / canvas.height;

    let widthPct: number;
    let heightPct: number;
    if (srcAspect > canvasAspect) {
        widthPct = maxWidthPct;
        heightPct = (widthPct * canvasAspect) / srcAspect;
        if (heightPct > maxHeightPct) {
            heightPct = maxHeightPct;
            widthPct = (heightPct * srcAspect) / canvasAspect;
        }
    } else {
        heightPct = maxHeightPct;
        widthPct = (heightPct * srcAspect) / canvasAspect;
        if (widthPct > maxWidthPct) {
            widthPct = maxWidthPct;
            heightPct = (widthPct * canvasAspect) / srcAspect;
        }
    }
    return {
        leftPct: +((100 - widthPct) / 2).toFixed(3),
        topPct: +((100 - heightPct) / 2).toFixed(3),
        widthPct: +widthPct.toFixed(3),
        heightPct: +heightPct.toFixed(3),
    };
}

export function defaultLabelFor(layer: Layer): string {
    if (layer.name) return layer.name;
    switch (layer.type) {
        case 'text':
            return layer.content.default || 'Text';
        case 'shape':
            switch (layer.shape) {
                case 'ellipse':
                    return 'Ellipse';
                case 'line':
                    return 'Line';
                case 'arrow':
                    return 'Arrow';
                case 'polygon':
                    return 'Polygon';
                case 'star':
                    return 'Star';
                case 'path':
                    return 'Path';
                default:
                    return 'Rectangle';
            }
        case 'background':
            return 'Background';
        case 'image':
            return 'Image';
        case 'frame':
            return 'Frame';
        case 'group':
            // GroupLayer.name is required, but early return above already
            // handled truthy names — this keeps the switch exhaustive and
            // fills in 'Group' for the empty-string edge case.
            return layer.name || 'Group';
        case 'callout':
            return 'Callout';
    }
}
