import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from "@/lib/auth";
import { prisma } from '@/lib/prisma';
import { getRandomLocalQuote } from '@/lib/quotes';
import React from 'react';
import {
    Document, Page, Text, View, StyleSheet, Font, renderToBuffer,
} from '@react-pdf/renderer';

// ── Font registration (Oswald from Google Fonts, one-time per process) ───────

let _fontPromise: Promise<void> | null = null;
let _oswaldRegistered = false;  // tracked explicitly — Font has no public getRegisteredFamilies API

function ensureFonts(): Promise<void> {
    if (!_fontPromise) {
        _fontPromise = (async () => {
            try {
                const res = await fetch(
                    'https://fonts.googleapis.com/css2?family=Oswald:wght@400;600;700&display=swap',
                    {
                        headers: { 'User-Agent': 'Mozilla/5.0 (compatible; Node.js)' },
                        signal: AbortSignal.timeout(5000),
                    }
                );
                const css = await res.text();
                const entries: { src: string; fontWeight: number }[] = [];
                const re = /font-weight:\s*(\d+)[\s\S]*?url\((https:\/\/fonts\.gstatic\.com\/[^)'"]+)\)/g;
                let m: RegExpExecArray | null;
                while ((m = re.exec(css)) !== null) {
                    entries.push({ fontWeight: parseInt(m[1]), src: m[2] });
                }
                if (entries.length > 0) {
                    Font.register({ family: 'Oswald', fonts: entries });
                    _oswaldRegistered = true;
                }
            } catch {
                // Falls back to Helvetica-Bold
            }
        })();
    }
    return _fontPromise;
}

// ── Constants ────────────────────────────────────────────────────────────────

// Landscape A4
const PW       = 841.89;
const PH       = 595.28;
const PAD_H    = 20;
const PAD_V    = 14;
const FOOTER_H = 18;

// Fixed structural heights
const DAY_HDR_H  = 44;   // column header: weekday name + date
const ALL_DAY_H  = 40;   // all-day strip (external calendar events)
const NO_TIME_H  = 52;   // anyday strip (user tasks without a time, shows ~4)
const TIME_COL_W = 32;   // left time-label column

// Derived
const CONTENT_H  = PH - PAD_V * 2 - FOOTER_H;    // ≈ 549.28
const USABLE_W   = PW - PAD_H * 2;                // ≈ 801.89
const PAGE_HDR_H = 26;                            // page header strip: week label + motivational quote

// Fixed colours
const WHITE     = '#ffffff';
const BORDER    = '#e2e8f0';
const MUTED     = '#64748b';
const DONE_BG   = '#e2e8f0';
const DONE_TEXT = '#94a3b8';
const STRIPE    = '#f9fafb';
const ALLDAY_BG = '#eff6ff';
const NOTIME_BG = '#fafaf9';

// Lighten a hex color toward white (amount 0–1)
function lighten(hex: string, amount: number): string {
    const h = hex.replace('#', '');
    const r = parseInt(h.slice(0, 2), 16);
    const g = parseInt(h.slice(2, 4), 16);
    const b = parseInt(h.slice(4, 6), 16);
    const lr = Math.round(r + (255 - r) * amount);
    const lg = Math.round(g + (255 - g) * amount);
    const lb = Math.round(b + (255 - b) * amount);
    return `#${lr.toString(16).padStart(2,'0')}${lg.toString(16).padStart(2,'0')}${lb.toString(16).padStart(2,'0')}`;
}

// Map WMO weather code → a distinct color (used as small dot indicator in cells)
function wmoColor(code: number | null): string {
    if (code === null || code === undefined) return '#9ca3af';
    if (code === 0) return '#fbbf24';                          // clear → amber
    if (code <= 3)  return '#9ca3af';                         // partly cloudy → gray
    if (code <= 48) return '#cbd5e1';                         // fog → light slate
    if (code <= 67) return '#3b82f6';                         // rain/drizzle → blue
    if (code <= 77) return '#bfdbfe';                         // snow → pale blue
    if (code <= 82) return '#2563eb';                         // heavy showers → dark blue
    if (code <= 86) return '#93c5fd';                         // snow showers → light blue
    return '#7c3aed';                                          // thunderstorm → purple
}

// ── Weekday label helpers ─────────────────────────────────────────────────────

const WDAY_LONG_DE = ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'];
const WDAY_LONG_EN = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];

// Always use full names — columns are wide enough even at 7 days
function weekdayLabel(d: Date, de: boolean): string {
    return (de ? WDAY_LONG_DE : WDAY_LONG_EN)[d.getDay()].toUpperCase();
}

function dayDateLabel(d: Date, de: boolean): string {
    return d.toLocaleDateString(de ? 'de-DE' : 'en-GB', {
        day: '2-digit', month: 'short', year: 'numeric',
    });
}

// Strip Apple Calendar icon encodings (=h=g, =i=g, etc.) and emoji from
// event titles before PDF rendering. Helvetica cannot render these glyphs and
// they appear as raw escape sequences.  The colored dot/chip already identifies
// the calendar visually, so the icon character carries no extra information.
function pdfTitle(s: string | null | undefined, maxLen = 60): string {
    if (!s) return '';
    return s
        .replace(/(?:=[a-zA-Z])+/g, '')          // Apple CalDAV icon escapes: =h=g, =i=g, =k=g …
        .replace(/[\u{1F000}-\u{1FFFF}]/gu, '')  // emoji (supplementary plane)
        .replace(/[\u{2600}-\u{27BF}]/gu, '')    // misc symbols & dingbats
        .replace(/[\uE000-\uF8FF]/g, '')          // Apple private-use area
        .replace(/[\uFE00-\uFE0F\u200D]/g, '')   // variation selectors / ZWJ
        .trim()
        .slice(0, maxLen);
}

function fmtHour(h: number): string {
    return `${String(h).padStart(2, '0')}:00`;
}

function fmtTime(t: string): string {
    const [h, m] = t.split(':');
    return `${h}:${m}`;
}

function parseTimeMinutes(t: string | null | undefined): number | null {
    if (!t) return null;
    const [h, m] = t.split(':').map(Number);
    return h * 60 + (m || 0);
}

function addDays(d: Date, n: number): Date {
    const r = new Date(d);
    r.setDate(r.getDate() + n);
    return r;
}

function localDateStr(d: Date, tz: string): string {
    return d.toLocaleDateString('en-CA', { timeZone: tz });
}

function groupIntoPages(start: Date, end: Date): Date[][] {
    const pages: Date[][] = [];
    const dow    = start.getDay();
    const offset = dow === 0 ? -6 : 1 - dow;
    let ws = addDays(start, offset);
    ws.setHours(0, 0, 0, 0);
    while (ws <= end) {
        const page: Date[] = [];
        for (let i = 0; i < 7; i++) {
            const d = addDays(ws, i);
            if (d >= start && d <= end) page.push(d);
        }
        if (page.length > 0) pages.push(page);
        ws = addDays(ws, 7);
    }
    return pages;
}

// ── Weather labels ────────────────────────────────────────────────────────────


// ── Types ─────────────────────────────────────────────────────────────────────

type TaskItem = {
    id: string;
    title: string | null;
    startTime: string | null;
    endTime:   string | null;
    completed: boolean;
    isExternal: boolean;   // true = imported from Google/MS/Apple Calendar
    project: { name: string; color: string | null } | null;
};

type DayData = {
    allDay:  TaskItem[];   // all tasks without a startTime (shown in GANZ-TAG strip)
    timed:   TaskItem[];   // tasks with startTime within [startHour, endHour)
};

// Compute a contrasting text color (white or near-black) for a given bg hex
function contrastColor(hex: string): string {
    const h = hex.replace('#', '');
    const r = parseInt(h.slice(0, 2), 16) / 255;
    const g = parseInt(h.slice(2, 4), 16) / 255;
    const b = parseInt(h.slice(4, 6), 16) / 255;
    const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
    return lum > 0.42 ? '#1e293b' : '#ffffff';
}

type WeatherHour = { temp: number | null; code: number | null; };

// User-specific style settings passed to the PDF
type UserStyle = {
    weekdayColor:       string;   // e.g. "#0ea5e9"
    weekendColorSat:    string;   // e.g. "#ffc107"
    weekendColorSun:    string;   // e.g. "#dc2626"
    dateColor:          string;   // e.g. "#888888"
    taskColor:          string;   // default task block color when no project
    headlineFontWeight: string;   // e.g. "900"
    useOswald:          boolean;  // whether Oswald was successfully registered
};

function dayColor(d: Date, isToday: boolean, us: UserStyle): string {
    if (isToday) return '#6366f1';
    const dow = d.getDay();
    if (dow === 6) return us.weekendColorSat;
    if (dow === 0) return us.weekendColorSun;
    return us.weekdayColor;
}

// ── PDF Document ──────────────────────────────────────────────────────────────

type SomedayList = {
    id: string;
    title: string;
    tab: string | null;
    tasks: { id: string; title: string; completed: boolean; project: { name: string; color: string | null } | null }[];
};

function WeekCalendarPDF({
    pages,
    tasksByDay,
    weatherHourly,
    startHour,
    endHour,
    showAllDay,
    showNoTime,
    de,
    userName,
    userStyle,
    somedayLists,
    totalPages,
    quote,
}: {
    pages: Date[][];
    tasksByDay: Map<string, DayData>;
    weatherHourly: Map<string, WeatherHour>;
    startHour: number;
    endHour: number;
    showAllDay: boolean;
    showNoTime: boolean;
    de: boolean;
    userName: string;
    userStyle: UserStyle;
    somedayLists: SomedayList[];
    totalPages: number;
    quote: { text: string; author: string } | null;
}) {
    const todayKey  = new Date().toLocaleDateString('en-CA');
    const locale    = de ? 'de-DE' : 'en-GB';
    const numHours = endHour - startHour;

    // Headline font: Oswald if loaded, else Helvetica-Bold (NOT plain Helvetica)
    const headlineFontFamily = userStyle.useOswald ? 'Oswald' : 'Helvetica-Bold';
    const headlineFW = userStyle.useOswald
        ? (parseInt(userStyle.headlineFontWeight) >= 600 ? 700 : 400)
        : undefined;

    // GRID_H = remaining space after page header, day header, and optional all-day strip
    const GRID_H = CONTENT_H - (PAGE_HDR_H + 8) - DAY_HDR_H - (showAllDay ? ALL_DAY_H : 0);

    const exportedOn = new Date().toLocaleDateString(locale, {
        day: '2-digit', month: 'long', year: 'numeric',
    });

    return React.createElement(Document,
        { title: de ? 'Wochenplan' : 'Week View' },

        ...pages.map((days, pageIdx) => {
            const numDays = days.length;
            const dayColW = (USABLE_W - TIME_COL_W) / numDays;

            // Adaptive font size — full names always, size scales with available column width
            const dayNameSize = numDays <= 2 ? 17 : numDays <= 3 ? 14 : numDays <= 5 ? 11 : 9.5;

            const calendarH = DAY_HDR_H + (showAllDay ? ALL_DAY_H : 0) + GRID_H;

            const firstDay = days[0];
            const lastDay  = days[days.length - 1];
            const sameYear = firstDay.getFullYear() === lastDay.getFullYear();
            const rangeLabel = sameYear
                ? `${firstDay.toLocaleDateString(locale, { day: '2-digit', month: 'short' })} – ${lastDay.toLocaleDateString(locale, { day: '2-digit', month: 'short', year: 'numeric' })}`
                : `${firstDay.toLocaleDateString(locale, { day: '2-digit', month: 'short', year: 'numeric' })} – ${lastDay.toLocaleDateString(locale, { day: '2-digit', month: 'short', year: 'numeric' })}`;

            return React.createElement(Page, {
                key: `p${pageIdx}`,
                size: 'A4',
                orientation: 'landscape',
                style: {
                    fontFamily: 'Helvetica',
                    fontSize: 8,
                    color: '#1e293b',
                    paddingTop: PAD_V,
                    paddingBottom: PAD_V + FOOTER_H,
                    paddingHorizontal: PAD_H,
                    backgroundColor: WHITE,
                    flexDirection: 'column',
                },
            },

            // ── Page header: KW + date range on left, motivational quote on right
            React.createElement(View, {
                style: {
                    flexDirection: 'row',
                    justifyContent: 'space-between',
                    alignItems: 'flex-end',
                    height: PAGE_HDR_H,
                    marginBottom: 8,
                    borderBottomWidth: 1,
                    borderBottomColor: '#e8eaed',
                    paddingBottom: 4,
                },
            },
                React.createElement(Text, {
                    style: {
                        fontFamily: headlineFontFamily,
                        fontWeight: headlineFW,
                        fontSize: 12,
                        color: userStyle.weekdayColor,
                        flexShrink: 0,
                    },
                }, `${de ? 'KW' : 'Wk'} ${getWeekNumber(firstDay)} · ${rangeLabel}`),
                quote ? React.createElement(Text, {
                    style: {
                        fontSize: 7,
                        color: MUTED,
                        flex: 1,
                        textAlign: 'right',
                        paddingLeft: 10,
                        paddingBottom: 1,
                        fontStyle: 'italic',
                    },
                }, `"${quote.text}" — ${quote.author}`) : null,
            ),

            // ── Calendar grid: [time col] [day cols...]
            React.createElement(View, {
                style: {
                    flexDirection: 'row',
                    height: calendarH,
                    borderWidth: 1,
                    borderColor: '#e8eaed',
                    borderRadius: 2,
                    overflow: 'hidden',
                },
            },

                // ── Left time column
                React.createElement(View, {
                    style: {
                        width: TIME_COL_W,
                        flexDirection: 'column',
                        flexShrink: 0,
                        borderRightWidth: 1,
                        borderRightColor: '#ebebeb',
                        backgroundColor: '#fafafa',
                    },
                },
                    // All-day label spacer (top — matches day column order)
                    ...(showAllDay ? [
                        React.createElement(View, {
                            style: {
                                height: ALL_DAY_H,
                                borderBottomWidth: 1,
                                borderBottomColor: '#bfdbfe',
                                backgroundColor: ALLDAY_BG,
                                alignItems: 'flex-end',
                                justifyContent: 'flex-start',
                                paddingRight: 4,
                                paddingTop: 3,
                            },
                        },
                            React.createElement(Text, {
                                style: {
                                    fontSize: 6,
                                    fontFamily: 'Helvetica-Bold',
                                    color: '#3b82f6',
                                    textTransform: 'uppercase',
                                    letterSpacing: 0.3,
                                },
                            }, de ? 'Ganztag' : 'All Day'),
                        ),
                    ] : []),

                    // Spacer matching day header row
                    React.createElement(View, {
                        style: {
                            height: DAY_HDR_H,
                            borderBottomWidth: 1,
                            borderBottomColor: BORDER,
                        },
                    }),

                    // Hour labels — flex rows, aligned with day-column hour rows
                    React.createElement(View, { style: { height: GRID_H, flexDirection: 'column' } },
                        ...Array.from({ length: numHours }, (_, i) =>
                            React.createElement(View, {
                                key: `tl${i}`,
                                style: {
                                    flex: 1,
                                    borderBottomWidth: i < numHours - 1 ? 1 : 0,
                                    borderBottomColor: '#ececec',
                                    alignItems: 'flex-end',
                                    justifyContent: 'flex-start',
                                    paddingRight: 4,
                                    paddingTop: 2,
                                },
                            },
                                React.createElement(Text, {
                                    style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: MUTED, textAlign: 'right' },
                                }, fmtHour(startHour + i)),
                            )
                        ),
                    ),
                ),

                // ── Day columns
                ...days.map((d) => {
                    const dk      = d.toLocaleDateString('en-CA');
                    const isToday = dk === todayKey;
                    const dayData = tasksByDay.get(dk);

                    const allDayTasks = dayData?.allDay || [];
                    const timedTasks  = dayData?.timed  || [];

                    const dayName = weekdayLabel(d, de);
                    const dateStr = dayDateLabel(d, de);

                    return React.createElement(View, {
                        key: dk,
                        style: {
                            flex: 1,
                            flexDirection: 'column',
                            borderRightWidth: 1,
                            borderRightColor: '#ebebeb',
                        },
                    },

                    // ── All-day cell (top — above weekday header, matches webapp layout)
                    ...(showAllDay ? [
                        React.createElement(View, {
                            style: {
                                height: ALL_DAY_H,
                                backgroundColor: ALLDAY_BG,
                                borderBottomWidth: 1,
                                borderBottomColor: '#bfdbfe',
                                paddingHorizontal: 3,
                                paddingTop: 3,
                                overflow: 'hidden',
                            },
                        },
                            ...allDayTasks.slice(0, 5).map(t => {
                                if (t.completed) {
                                    return React.createElement(View, {
                                        key: t.id,
                                        style: { borderRadius: 2, paddingVertical: 1.5, paddingHorizontal: 4, marginBottom: 2, backgroundColor: DONE_BG },
                                    }, React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT } }, pdfTitle(t.title, 36)));
                                }
                                if (t.isExternal) {
                                    // Calendar event: solid color chip with white text + dot indicator (matches webapp style)
                                    const evColor = t.project?.color || '#6366f1';
                                    const evTextColor = contrastColor(evColor);
                                    return React.createElement(View, {
                                        key: t.id,
                                        style: {
                                            flexDirection: 'row', alignItems: 'center',
                                            borderRadius: 2,
                                            paddingVertical: 2, paddingHorizontal: 4, marginBottom: 2,
                                            backgroundColor: evColor,
                                        },
                                    },
                                        // Calendar indicator dot (replaces the 📅 emoji the webapp uses)
                                        React.createElement(View, {
                                            style: { width: 5, height: 5, borderRadius: 2.5, backgroundColor: evTextColor, opacity: 0.7, marginRight: 3, flexShrink: 0 },
                                        }),
                                        React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: evTextColor, flex: 1 } }, pdfTitle(t.title, 36)),
                                    );
                                }
                                // User-created all-day task: left border only, no background fill
                                const accent = t.project?.color || userStyle.taskColor;
                                return React.createElement(View, {
                                    key: t.id,
                                    style: { borderLeftWidth: 2, borderLeftColor: accent, paddingVertical: 1.5, paddingHorizontal: 4, marginBottom: 2 },
                                }, React.createElement(Text, { style: { fontSize: 7.5, color: '#374151' } }, pdfTitle(t.title, 36)));
                            }),
                            allDayTasks.length > 5 ? React.createElement(Text, {
                                style: { fontSize: 6, color: '#3b82f6', marginTop: 1 },
                            }, `+${allDayTasks.length - 5} ${de ? 'weitere' : 'more'}`) : null,
                        ),
                    ] : []),

                    // ── Day header: date first (top), then weekday name (bottom) — matches webapp "above" layout
                    React.createElement(View, {
                        style: {
                            height: DAY_HDR_H,
                            backgroundColor: isToday ? '#e0eeff' : WHITE,
                            borderBottomWidth: 1,
                            borderBottomColor: isToday ? '#bfdbfe' : BORDER,
                            alignItems: 'center',
                            justifyContent: 'center',
                            paddingHorizontal: 3,
                        },
                    },
                        React.createElement(Text, {
                            style: {
                                fontSize: 8,
                                color: dayColor(d, isToday, userStyle),
                                textAlign: 'center',
                                fontFamily: 'Helvetica',
                                lineHeight: 1.2,
                            },
                        }, dateStr),
                        React.createElement(Text, {
                            style: {
                                fontFamily: headlineFontFamily,
                                fontWeight: headlineFW,
                                fontSize: dayNameSize,
                                color: dayColor(d, isToday, userStyle),
                                textAlign: 'center',
                                letterSpacing: 0.5,
                                lineHeight: 1.1,
                                marginTop: 1,
                            },
                        }, dayName),
                    ),

                    // ── Time grid: per-hour rows (agenda/list style, no absolute positioning)
                    React.createElement(View, {
                        style: {
                            height: GRID_H,
                            flexDirection: 'column',
                            backgroundColor: isToday ? '#f9fbff' : WHITE,
                        },
                    },
                        ...Array.from({ length: numHours }, (_, i) => {
                            const h = startHour + i;
                            const hk = `${dk} ${String(h).padStart(2, '0')}`;
                            const hw = weatherHourly.get(hk);
                            const hourTasks = timedTasks.filter(t =>
                                t.startTime != null && parseInt(t.startTime.split(':')[0], 10) === h
                            );

                            return React.createElement(View, {
                                key: `hr${i}`,
                                style: {
                                    flex: 1,
                                    borderBottomWidth: i < numHours - 1 ? 1 : 0,
                                    borderBottomColor: '#ececec',
                                    overflow: 'hidden',
                                    position: 'relative',
                                    paddingTop: 2,
                                    paddingLeft: 3,
                                    paddingRight: hw ? 24 : 3,
                                },
                            },
                                // Per-hour weather: small colored dot + temperature
                                ...(hw && (hw.temp != null || hw.code != null) ? [
                                    React.createElement(View, {
                                        key: 'w',
                                        style: { position: 'absolute', top: 2, right: 2, flexDirection: 'row', alignItems: 'center' },
                                    },
                                        React.createElement(View, {
                                            style: { width: 5, height: 5, borderRadius: 2.5, backgroundColor: wmoColor(hw.code), marginRight: 1.5 },
                                        }),
                                        hw.temp != null ? React.createElement(Text, {
                                            style: { fontSize: 6, color: '#94a3b8' },
                                        }, `${Math.round(hw.temp)}°`) : null,
                                    ),
                                ] : []),

                                // Tasks starting in this hour
                                ...hourTasks.map(t => {
                                    if (t.completed) {
                                        return React.createElement(View, {
                                            key: t.id,
                                            style: { marginBottom: 1, paddingLeft: 1, paddingVertical: 1 },
                                        },
                                            React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT, lineHeight: 1.3, textDecoration: 'line-through' } }, pdfTitle(t.title)),
                                        );
                                    }

                                    if (t.isExternal) {
                                        const evColor = t.project?.color || '#6366f1';
                                        return React.createElement(View, {
                                            key: t.id,
                                            style: {
                                                backgroundColor: lighten(evColor, 0.85),
                                                borderRadius: 2, borderLeftWidth: 2, borderLeftColor: evColor,
                                                paddingLeft: 3, paddingVertical: 1, marginBottom: 1,
                                            },
                                        },
                                            React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, pdfTitle(t.title)),
                                        );
                                    }

                                    const pc = t.project?.color;
                                    return React.createElement(View, {
                                        key: t.id,
                                        style: {
                                            ...(pc ? { borderLeftWidth: 2, borderLeftColor: pc, paddingLeft: 3 } : { paddingLeft: 1 }),
                                            paddingVertical: 1, marginBottom: 1,
                                        },
                                    },
                                        React.createElement(Text, { style: { fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, pdfTitle(t.title)),
                                    );
                                }),
                            );
                        }),
                    ), // end time grid

                    ); // end day column
                }), // end days.map

            ), // end calendar grid

            // ── Footer (absolute)
            React.createElement(View, {
                style: {
                    position: 'absolute',
                    bottom: PAD_V,
                    left: PAD_H,
                    right: PAD_H,
                    flexDirection: 'row',
                    justifyContent: 'space-between',
                    borderTopWidth: 1,
                    borderTopColor: BORDER,
                    paddingTop: 3,
                },
                fixed: true,
            },
                React.createElement(Text, {
                    style: { fontSize: 7, color: '#94a3b8' },
                }, `${de ? 'Wochenplan' : 'Week View'} — ${rangeLabel}${userName ? ` · ${userName}` : ''}`),
                React.createElement(Text, {
                    style: { fontSize: 7, color: '#94a3b8' },
                    render: ({ pageNumber, totalPages: tp }: { pageNumber: number; totalPages: number }) =>
                        `${pageNumber} / ${tp}`,
                }),
            ),

            ); // end Page
        }),

        // ── Someday page (portrait A4, one column per list)
        ...(somedayLists.length > 0 && showNoTime ? [
            React.createElement(Page, {
                key: 'someday',
                size: 'A4',
                orientation: 'landscape',
                style: {
                    fontFamily: 'Helvetica',
                    fontSize: 8,
                    color: '#1e293b',
                    padding: 24,
                    paddingBottom: 24 + 18,
                    backgroundColor: WHITE,
                    flexDirection: 'column',
                },
            },
                // Title
                React.createElement(View, {
                    style: {
                        borderBottomWidth: 1,
                        borderBottomColor: BORDER,
                        paddingBottom: 6,
                        marginBottom: 12,
                        flexDirection: 'row',
                        alignItems: 'flex-end',
                        justifyContent: 'space-between',
                    },
                },
                    React.createElement(Text, {
                        style: {
                            fontFamily: headlineFontFamily,
                            fontWeight: headlineFW,
                            fontSize: 16,
                            color: userStyle.weekdayColor,
                            letterSpacing: 0.5,
                        },
                    }, (de ? 'Irgendwann' : 'Someday').toUpperCase()),
                    React.createElement(Text, {
                        style: { fontSize: 7, color: '#94a3b8' },
                    }, userName),
                ),

                // Group lists by tab, then render tab sections
                ...((() => {
                    // Build ordered tab groups
                    const tabOrder: string[] = [];
                    const tabMap = new Map<string, SomedayList[]>();
                    for (const list of somedayLists) {
                        const tab = list.tab || (de ? 'Allgemein' : 'General');
                        if (!tabMap.has(tab)) { tabMap.set(tab, []); tabOrder.push(tab); }
                        tabMap.get(tab)!.push(list);
                    }

                    const renderList = (list: SomedayList) => React.createElement(View, {
                        key: list.id,
                        style: { width: '31%', marginBottom: 10 },
                    },
                        React.createElement(View, {
                            style: {
                                borderLeftWidth: 3,
                                borderLeftColor: list.tasks[0]?.project?.color || userStyle.weekdayColor,
                                paddingLeft: 5, marginBottom: 4,
                            },
                        },
                            React.createElement(Text, {
                                style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: '#374151', textTransform: 'uppercase', letterSpacing: 0.3 },
                            }, list.title),
                        ),
                        ...list.tasks.slice(0, 18).map(t =>
                            React.createElement(View, {
                                key: t.id,
                                style: { flexDirection: 'row', alignItems: 'flex-start', paddingVertical: 2, paddingHorizontal: 3, marginBottom: 1, borderRadius: 2, backgroundColor: '#f8fafc' },
                            },
                                React.createElement(View, {
                                    style: { width: 5, height: 5, borderRadius: 3, borderWidth: 1, borderColor: t.project?.color || MUTED, marginRight: 4, marginTop: 1.5, flexShrink: 0 },
                                }),
                                React.createElement(Text, {
                                    style: { fontSize: 7, color: '#374151', lineHeight: 1.3, flex: 1 },
                                }, pdfTitle(t.title)),
                            )
                        ),
                        list.tasks.length > 18 ? React.createElement(Text, {
                            style: { fontSize: 6, color: MUTED, marginTop: 1, paddingLeft: 3 },
                        }, `+${list.tasks.length - 18} ${de ? 'weitere' : 'more'}`) : null,
                    );

                    return tabOrder.map(tab =>
                        React.createElement(View, { key: tab },
                            // Tab header
                            React.createElement(View, {
                                style: {
                                    borderBottomWidth: 1, borderBottomColor: BORDER,
                                    marginBottom: 8, marginTop: 4, paddingBottom: 3,
                                },
                            },
                                React.createElement(Text, {
                                    style: { fontFamily: 'Helvetica-Bold', fontSize: 9, color: userStyle.weekdayColor, textTransform: 'uppercase', letterSpacing: 0.5 },
                                }, tab),
                            ),
                            // Lists in row
                            React.createElement(View, {
                                style: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 6 },
                            },
                                ...tabMap.get(tab)!.map(renderList),
                            ),
                        )
                    );
                })()),

                // Footer
                React.createElement(View, {
                    style: {
                        position: 'absolute',
                        bottom: 24, left: 24, right: 24,
                        borderTopWidth: 1, borderTopColor: BORDER,
                        paddingTop: 3,
                        flexDirection: 'row', justifyContent: 'space-between',
                    },
                    fixed: true,
                },
                    React.createElement(Text, {
                        style: { fontSize: 7, color: '#94a3b8' },
                    }, `${de ? 'Irgendwann' : 'Someday'} · ${userName}`),
                    React.createElement(Text, {
                        style: { fontSize: 7, color: '#94a3b8' },
                        render: ({ pageNumber, totalPages: tp }: { pageNumber: number; totalPages: number }) =>
                            `${pageNumber} / ${tp}`,
                    }),
                ),
            ),
        ] : []),
    );
}

// Week number helper
function getWeekNumber(d: Date): number {
    const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
    const dayNum = date.getUTCDay() || 7;
    date.setUTCDate(date.getUTCDate() + 4 - dayNum);
    const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
    return Math.ceil((((date.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
}

// ── Route handler ─────────────────────────────────────────────────────────────

export async function GET(request: Request) {
    const session = await getServerSession(authOptions);
    if (!session?.user?.email) return new NextResponse('Unauthorized', { status: 401 });

    const { searchParams } = new URL(request.url);
    const startDate   = searchParams.get('startDate');
    const endDate     = searchParams.get('endDate');
    const startHour   = Math.max(0, Math.min(23, parseInt(searchParams.get('startHour') || '8',  10)));
    const endHour     = Math.max(startHour + 1, Math.min(24, parseInt(searchParams.get('endHour') || '18', 10)));
    const lang        = searchParams.get('lang') || 'de';
    const de          = lang === 'de';
    const showAllDay  = searchParams.get('showAllDay') !== '0';
    const showNoTime  = searchParams.get('showNoTime') !== '0';
    const showWeather = searchParams.get('showWeather') === '1';
    const inline      = searchParams.get('inline') === '1';
    // noTimeTabs: comma-separated tab names to include; absent = include all
    const noTimeTabsRaw = searchParams.get('noTimeTabs');
    const noTimeTabFilter: string[] | null = noTimeTabsRaw ? noTimeTabsRaw.split(',').filter(Boolean) : null;

    if (!startDate || !endDate) return new NextResponse('Missing startDate or endDate', { status: 400 });

    try {
        const user = await prisma.user.findUnique({
            where: { email: session.user.email },
            select: {
                id: true, name: true, email: true,
                timezone: true,
                weekdayColor: true, weekendColorSat: true, weekendColorSun: true,
                dateColor: true, taskColor: true, headlineFontWeight: true,
                weatherLat: true, weatherLon: true,
            },
        });
        if (!user) return new NextResponse('User not found', { status: 404 });

        const userTz = (user as any).timezone || 'UTC';

        // Register Oswald font (best effort) — use module-level flag, not Font's internal API
        await ensureFonts();

        const userStyle: UserStyle = {
            weekdayColor:       (user as any).weekdayColor       || '#0ea5e9',
            weekendColorSat:    (user as any).weekendColorSat    || '#ffc107',
            weekendColorSun:    (user as any).weekendColorSun    || '#dc2626',
            dateColor:          (user as any).dateColor           || '#888888',
            taskColor:          (user as any).taskColor           || '#6366f1',
            headlineFontWeight: (user as any).headlineFontWeight  || '900',
            useOswald:          _oswaldRegistered,
        };

        // ── Fetch tasks (expanded range for timezone safety)
        const startDt = new Date(startDate + 'T00:00:00.000Z');
        startDt.setTime(startDt.getTime() - 24 * 60 * 60 * 1000);
        const endDt = new Date(endDate + 'T23:59:59.999Z');
        endDt.setTime(endDt.getTime() + 24 * 60 * 60 * 1000);

        const tasks = await prisma.task.findMany({
            where: {
                userId: user.id,
                deletedAt: null,
                scheduledDate: { gte: startDt, lte: endDt },
            },
            include: { project: { select: { name: true, color: true } } },
            orderBy: [{ scheduledDate: 'asc' }, { startTime: 'asc' }],
        });

        // ── Group by user-local date
        const tasksByDay = new Map<string, DayData>();
        for (const task of tasks) {
            if (!task.scheduledDate) continue;
            const dk = localDateStr(task.scheduledDate, userTz);
            if (dk < startDate || dk > endDate) continue;

            if (!tasksByDay.has(dk)) tasksByDay.set(dk, { allDay: [], timed: [] });
            const d = tasksByDay.get(dk)!;
            const item: TaskItem = {
                id: task.id, title: task.title, startTime: task.startTime,
                endTime: task.endTime ?? null, completed: task.completed,
                isExternal: !!((task as any).externalProvider),
                project: task.project,
            };

            if (!task.startTime) {
                // All date-scheduled tasks with no time → all-day strip
                if (showAllDay) d.allDay.push(item);
            } else {
                const h = parseInt(task.startTime.split(':')[0], 10);
                if (h >= startHour && h < endHour) {
                    d.timed.push(item);
                }
                // Tasks outside the time window are simply not shown in the grid
            }
        }

        // ── Fetch CachedCalendarEvents (Google/MS/Apple Calendar)
        const calEvents = await (prisma as any).cachedCalendarEvent.findMany({
            where: {
                userId: user.id,
                OR: [
                    { startDate: { gte: startDate, lte: endDate } },
                    { startDateTime: { gte: startDt, lte: endDt } },
                ],
            },
            select: {
                id: true, title: true, calendarColor: true,
                startDate: true, startDateTime: true, endDateTime: true, endDate: true,
            },
        });

        for (const ev of calEvents) {
            if (ev.startDate) {
                // All-day external event — may span multiple days (iCal endDate is exclusive)
                if (!showAllDay) continue;
                const evStart = ev.startDate as string;
                // endDate is exclusive in iCalendar; treat null/same as single-day
                const evEndExcl = (ev.endDate as string | null) || '';
                const daysToFill: string[] = [];
                if (evEndExcl > evStart) {
                    // Expand across all days in the span (capped to visible range)
                    let cur = new Date(evStart + 'T00:00:00Z');
                    const stop = new Date(evEndExcl + 'T00:00:00Z');
                    while (cur < stop) {
                        const dk = cur.toLocaleDateString('en-CA');
                        if (dk >= startDate && dk <= endDate) daysToFill.push(dk);
                        cur = new Date(cur.getTime() + 86400000);
                    }
                } else {
                    if (evStart >= startDate && evStart <= endDate) daysToFill.push(evStart);
                }
                const evTitle = (ev.title as string | null) || '';
                const evColor = (ev.calendarColor as string | null) || '#6366f1';
                for (const dk of daysToFill) {
                    if (!tasksByDay.has(dk)) tasksByDay.set(dk, { allDay: [], timed: [] });
                    const arr = tasksByDay.get(dk)!.allDay;
                    // Deduplicate: skip if an entry with the same title already exists in this day
                    if (arr.some(x => (x.title || '').toLowerCase() === evTitle.toLowerCase())) continue;
                    arr.push({
                        id: `cal-${ev.id}-${dk}`, title: ev.title as string | null,
                        startTime: null, endTime: null, completed: false, isExternal: true,
                        project: { name: '', color: evColor },
                    });
                }
            } else if (ev.startDateTime) {
                // Timed external event
                const dk = localDateStr(ev.startDateTime as Date, userTz);
                if (dk < startDate || dk > endDate) continue;
                if (!tasksByDay.has(dk)) tasksByDay.set(dk, { allDay: [], timed: [] });
                const startTimeStr = (ev.startDateTime as Date).toLocaleTimeString('en-GB', {
                    hour: '2-digit', minute: '2-digit', timeZone: userTz,
                });
                const endTimeStr = ev.endDateTime
                    ? (ev.endDateTime as Date).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', timeZone: userTz })
                    : null;
                const h = parseInt(startTimeStr.split(':')[0], 10);
                if (h >= startHour && h < endHour) {
                    tasksByDay.get(dk)!.timed.push({
                        id: `cal-${ev.id}`, title: ev.title as string | null,
                        startTime: startTimeStr, endTime: endTimeStr,
                        completed: false, isExternal: true,
                        project: { name: '', color: (ev.calendarColor as string | null) || '#6366f1' },
                    });
                }
            }
        }

        // Sort timed tasks by start time within each day (user tasks + calendar events merged)
        for (const dayData of tasksByDay.values()) {
            dayData.timed.sort((a, b) => (a.startTime || '').localeCompare(b.startTime || ''));
        }

        // ── Weather (hourly — one entry per hour per day, key = "YYYY-MM-DD HH")
        const weatherHourly = new Map<string, WeatherHour>();
        if (showWeather && (user as any).weatherLat && (user as any).weatherLon) {
            try {
                const url = `https://api.open-meteo.com/v1/forecast?latitude=${(user as any).weatherLat}&longitude=${(user as any).weatherLon}&hourly=temperature_2m,weather_code&start_date=${startDate}&end_date=${endDate}&timezone=auto`;
                const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
                if (res.ok) {
                    const raw = await res.json();
                    const times: string[] = raw.hourly?.time || [];   // "2026-04-13T09:00"
                    const temps: number[] = raw.hourly?.temperature_2m || [];
                    const codes: number[] = raw.hourly?.weather_code || [];
                    times.forEach((t, i) => {
                        const [date, time] = t.split('T');
                        const hour = time ? time.split(':')[0] : '00';
                        weatherHourly.set(`${date} ${hour}`, { temp: temps[i] ?? null, code: codes[i] ?? null });
                    });
                }
            } catch { /* silent */ }
        }

        // ── Fetch someday list tasks (unscheduled, in named lists)
        type SomedayListWithTasks = {
            id: string;
            title: string;
            tab: string | null;
            tasks: { id: string; title: string; completed: boolean; project: { name: string; color: string | null } | null }[];
        };
        let somedayLists: SomedayListWithTasks[] = [];
        if (showNoTime) {
            // Build tab filter condition (noTimeTabFilter encodes null tab as the locale-specific default label)
            const defaultTabLabel = de ? 'Allgemein' : 'General';
            const tabWhere = noTimeTabFilter ? {
                OR: [
                    ...(noTimeTabFilter.includes(defaultTabLabel) ? [{ tab: null }] : []),
                    ...(noTimeTabFilter.filter(t => t !== defaultTabLabel).length > 0
                        ? [{ tab: { in: noTimeTabFilter.filter(t => t !== defaultTabLabel) } }]
                        : []),
                ],
            } : {};

            const raw = await prisma.somedayList.findMany({
                where: { userId: user.id, ...tabWhere },
                orderBy: [{ tab: 'asc' }, { order: 'asc' }],
                select: {
                    id: true,
                    title: true,
                    tab: true,
                    tasks: {
                        where: { deletedAt: null, completed: false },
                        orderBy: { order: 'asc' },
                        select: {
                            id: true, title: true, completed: true,
                            project: { select: { name: true, color: true } },
                        },
                        take: 60,
                    },
                },
            });
            somedayLists = raw.filter(l => l.tasks.length > 0);
        }

        // ── Build pages & render
        const pages = groupIntoPages(
            new Date(startDate + 'T00:00:00'),
            new Date(endDate   + 'T00:00:00'),
        );

        const quoteData = getRandomLocalQuote(de ? 'de' : 'en');
        const quote = quoteData ? { text: quoteData.text, author: quoteData.author } : null;

        const pdfBuffer = await renderToBuffer(
            React.createElement(WeekCalendarPDF, {
                pages, tasksByDay, weatherHourly,
                startHour, endHour, showAllDay, showNoTime, de,
                userName: user.name || user.email || '',
                userStyle,
                somedayLists,
                totalPages: pages.length,
                quote,
            }) as React.ReactElement<any>
        );

        const filename = de
            ? `wochenplan_${startDate}_${endDate}.pdf`
            : `week-view_${startDate}_${endDate}.pdf`;

        return new NextResponse(new Uint8Array(pdfBuffer), {
            headers: {
                'Content-Type': 'application/pdf',
                'Content-Disposition': `${inline ? 'inline' : 'attachment'}; filename="${filename}"`,
            },
        });
    } catch (error) {
        console.error('Week view PDF error:', error);
        return new NextResponse('Internal Server Error', { status: 500 });
    }
}
