const { useEffect, useLayoutEffect, useRef, useState } = React;
const { Card, Button, Input, Label, Stat, PageHeader, ListTable } = window.KelpUI;

function installAuthAdapter() {
    window.KelpAuth = {
        async getAuthHeaders() {
            const session = await window.KelpCloustanAuth.ensureFreshSession();
            const token = session && window.KelpCloustanAuth.getBearerToken(session);
            if (!token) throw new Error('signed out');
            return { Authorization: 'Bearer ' + token };
        },
    };
}

const RETURN_PATH_KEY = 'kelp.academy.returnPath';

function rememberReturnPath() {
    const path = window.location.pathname + window.location.search;
    if (path && path !== '/' && path !== '/signin') {
        sessionStorage.setItem(RETURN_PATH_KEY, path);
    }
}

function restoreReturnPath() {
    const saved = sessionStorage.getItem(RETURN_PATH_KEY);
    if (!saved) return;
    sessionStorage.removeItem(RETURN_PATH_KEY);
    if (window.location.pathname === '/' || window.location.pathname === '/signin') {
        window.history.replaceState({}, '', saved);
    }
}

function startSignIn() {
    rememberReturnPath();
    window.KelpCloustanAuth.startSignIn();
}

function kelpAppUrl() {
    const host = window.location.hostname;
    if (host === 'localhost' || host === '127.0.0.1') {
        return window.location.protocol + '//' + host + ':8080/';
    }
    const origin = window.KELP_APP_ORIGIN || 'https://app.kelplearn.com';
    return origin.replace(/\/$/, '') + '/';
}

function inviteHref(inviteUrl) {
    try {
        const url = new URL(inviteUrl);
        return window.location.origin + url.pathname + url.search;
    } catch (error) {
        return inviteUrl;
    }
}

async function api(path, options) {
    const opts = options || {};
    let headers;
    try {
        headers = await window.KelpAuth.getAuthHeaders();
    } catch (error) {
        window.KelpAcademySession.redirectIfUnauthorized(401, startSignIn);
        throw error;
    }
    const response = await fetch(window.KELP_API_BASE + path, {
        method: opts.method || 'GET',
        headers: Object.assign({}, headers, opts.headers || {}),
        body: opts.body,
    });
    if (window.KelpAcademySession.redirectIfUnauthorized(response.status, startSignIn)) {
        throw new Error('signin');
    }
    if (response.status === 204) return null;
    const text = await response.text();
    let data = null;
    if (text) {
        try {
            data = JSON.parse(text);
        } catch (parseError) {
            const error = new Error(
                response.status === 404
                    ? 'That academy action is not available on the API yet. Deploy the latest kelp-api.'
                    : (text.slice(0, 160) || 'The server returned a non-JSON response'),
            );
            error.status = response.status;
            throw error;
        }
    }
    if (!response.ok) {
        const error = new Error((data && data.error) || 'Request failed');
        error.status = response.status;
        error.body = data;
        throw error;
    }
    return data;
}

const ACADEMY_SECTION_SLUGS = 'members|classes|shared|analytics|announcements|integrations|settings|billing|entitlements|reports|support';

function academySectionPath(academyId, segment) {
    const base = '/academies/' + encodeURIComponent(academyId);
    return segment ? base + '/' + segment : base;
}

function academySharedAssetPath(academyId, logoR2Key) {
    if (!academyId || !logoR2Key) return null;
    const prefix = 'tenants/' + academyId + '/shared/';
    if (!String(logoR2Key).startsWith(prefix)) return null;
    const relative = String(logoR2Key).slice(prefix.length);
    if (!relative) return null;
    return '/api/academy/' + encodeURIComponent(academyId) + '/shared/' + relative.split('/').map(encodeURIComponent).join('/');
}

async function fetchAcademyAsset(path) {
    const headers = await window.KelpAuth.getAuthHeaders();
    const response = await fetch(window.KELP_API_BASE + path, { method: 'GET', headers });
    if (window.KelpAcademySession.redirectIfUnauthorized(response.status, startSignIn)) {
        throw new Error('signin');
    }
    if (!response.ok) return null;
    return response.blob();
}

function AcademyLogoMark({ academyId, branding, title }) {
    const [src, setSrc] = useState('');
    const logoR2Key = branding && branding.logoR2Key;
    const primary = normalizeHexColor(branding && branding.primaryColor, '#11993a');
    const initial = (String(title || 'A').trim()[0] || 'A').toUpperCase();

    useEffect(() => {
        let objectUrl = '';
        let cancelled = false;
        const assetPath = academySharedAssetPath(academyId, logoR2Key);
        if (!assetPath) {
            setSrc('');
            return undefined;
        }
        fetchAcademyAsset(assetPath).then((blob) => {
            if (cancelled || !blob) return;
            objectUrl = URL.createObjectURL(blob);
            setSrc(objectUrl);
        }).catch(() => {
            if (!cancelled) setSrc('');
        });
        return () => {
            cancelled = true;
            if (objectUrl) URL.revokeObjectURL(objectUrl);
        };
    }, [academyId, logoR2Key]);

    return (
        <div className="mb-4">
            {src ? (
                <img
                    src={src}
                    alt=""
                    className="size-16 rounded-2xl object-cover bg-white border border-gray-100 shadow-sm"
                />
            ) : (
                <div
                    className="size-16 rounded-2xl flex items-center justify-center text-white text-2xl font-bold shadow-sm border border-white/20"
                    style={{ backgroundColor: primary }}
                    aria-hidden
                >
                    {initial}
                </div>
            )}
        </div>
    );
}

function AcademyPageHeader({ academyId, title, subtitle, settings, action }) {
    const branding = settings && settings.branding;
    return (
        <div className="flex items-end justify-between gap-4 mb-6">
            <div>
                <AcademyLogoMark academyId={academyId} branding={branding} title={title} />
                <h1 className="text-2xl font-bold text-gray-900">{title}</h1>
                {subtitle ? <p className="text-sm text-gray-500 mt-1">{subtitle}</p> : null}
            </div>
            {action || null}
        </div>
    );
}

function parseRoute() {
    const path = window.location.pathname;
    const join = path.match(/^\/join\/academy\/([^/]+)/);
    if (join) return { name: 'join', token: decodeURIComponent(join[1]) };
    if (path === '/academies/new') return { name: 'create' };
    const member = path.match(/^\/academies\/([^/]+)\/members\/([^/]+)/);
    if (member) return { name: 'member', academyId: decodeURIComponent(member[1]), userId: decodeURIComponent(member[2]) };
    const section = path.match(new RegExp('^/academies/([^/]+)/(' + ACADEMY_SECTION_SLUGS + ')/?$'));
    if (section) {
        const academyId = decodeURIComponent(section[1]);
        const slug = section[2];
        if (slug === 'shared') return { name: 'shared', academyId };
        return { name: slug, academyId };
    }
    const academy = path.match(/^\/academies\/([^/]+)\/?$/);
    if (academy) return { name: 'dashboard', academyId: decodeURIComponent(academy[1]) };
    if (path === '/signin') return { name: 'signin' };
    return { name: 'academies' };
}

const ACADEMY_IN_APP_ROUTES = new Set([
    'dashboard', 'member', 'members', 'classes', 'shared', 'analytics', 'announcements',
    'integrations', 'settings', 'billing', 'entitlements', 'reports', 'support',
]);

function go(path) {
    window.history.pushState({}, '', path);
    window.dispatchEvent(new PopStateEvent('popstate'));
}

function KelpLogo({ className }) {
    return <img src="/kelplogo.png" alt="Kelp" className={'block w-auto ' + (className || 'h-12')} />;
}

function KelpSpinner({ label }) {
    return (
        <div className="flex flex-col items-center justify-center gap-3" role="status" aria-live="polite" aria-label={label || 'Loading'}>
            <div className="kelp-m3-spinner" aria-hidden="true">
                <svg viewBox="0 0 48 48">
                    <circle className="kelp-m3-spinner__track" cx="24" cy="24" r="20" />
                    <circle className="kelp-m3-spinner__active" cx="24" cy="24" r="20" />
                </svg>
            </div>
            {label ? <span className="text-sm text-gray-500">{label}</span> : null}
        </div>
    );
}

function KelpSpinnerScreen({ error }) {
    return (
        <main className="min-h-screen flex items-center justify-center px-6">
            {error ? <p className="text-sm text-gray-600">{error}</p> : <KelpSpinner />}
        </main>
    );
}

function FeatureTile({ icon, title, description, onClick, onContextMenu, href, selected }) {
    const className = 'relative overflow-hidden flex bg-[#f5f5f5] rounded-3xl text-left shadow-sm hover:shadow-md transition-all group w-full'
        + (selected ? ' ring-2 ring-primary' : '');
    const body = (
        <div className="flex-1 min-w-0 p-5">
            <div className="h-12 w-12 rounded-xl bg-white flex items-center justify-center mb-3 text-gray-800">
                <span className="material-symbols-outlined text-2xl">{icon}</span>
            </div>
            <h3 className="font-bold text-lg text-gray-900 group-hover:text-primary transition-colors">{title}</h3>
            {description ? <p className="text-sm text-gray-500 mt-1">{description}</p> : null}
        </div>
    );
    if (href) return <a className={className} href={href}>{body}</a>;
    return <button type="button" onClick={onClick} onContextMenu={onContextMenu} className={className}>{body}</button>;
}

function AcademyContextMenu({ menu, onClose, onDelete }) {
    if (!menu) return null;
    return (
        <div className="fixed inset-0 z-[75]" onClick={onClose} onContextMenu={(event) => { event.preventDefault(); onClose(); }}>
            <div
                className="absolute min-w-[180px] bg-white border border-gray-200 rounded-2xl shadow-lg py-1"
                style={{ left: menu.x, top: menu.y }}
                onClick={(event) => event.stopPropagation()}
            >
                <button
                    type="button"
                    className="w-full text-left px-4 py-2.5 text-sm font-bold text-gray-900 hover:bg-gray-50"
                    onClick={() => { onClose(); onDelete(menu.academy); }}
                >
                    Delete academy
                </button>
            </div>
        </div>
    );
}

function DeleteAcademy({ academy, onClose, onDeleted }) {
    const [step, setStep] = useState(1);
    const [error, setError] = useState('');
    async function confirm() {
        setError('');
        try {
            await api('/api/academy/' + academy.id, { method: 'DELETE' });
            onDeleted();
            onClose();
        } catch (err) {
            if (err.message !== 'signin') setError(err.message);
        }
    }
    return (
        <Popup title="Delete academy" step={step} steps={2} onClose={onClose}>
            {step === 1 ? (
                <div className="space-y-4">
                    <p className="text-sm text-gray-500">
                        Delete {academy.name}. Members, invites, and shared files for this academy will be removed.
                    </p>
                    <Button type="button" onClick={() => setStep(2)}>Continue</Button>
                </div>
            ) : null}
            {step === 2 ? (
                <div className="space-y-4">
                    <p className="text-sm text-gray-500">This cannot be undone.</p>
                    {error ? <p className="text-sm text-gray-600">{error}</p> : null}
                    <div className="flex gap-3">
                        <Button type="button" onClick={confirm}>Delete academy</Button>
                        <Button type="button" variant="secondary" onClick={() => setStep(1)}>Back</Button>
                    </div>
                </div>
            ) : null}
        </Popup>
    );
}

function Popup({ title, step, steps, onClose, children, brandColor }) {
    const innerRef = useRef(null);
    const [height, setHeight] = useState(0);
    useLayoutEffect(() => {
        const node = innerRef.current;
        if (!node) return undefined;
        const measure = () => setHeight(node.offsetHeight);
        measure();
        const observer = new ResizeObserver(measure);
        observer.observe(node);
        return () => observer.disconnect();
    }, [step, title]);
    return (
        <div className="fixed inset-0 z-[80] flex items-center justify-center p-4">
            <button type="button" className="kelp-popup-backdrop absolute inset-0 bg-black/30" aria-label="Close" onClick={onClose} />
            <div
                className="kelp-popup-panel relative w-full max-w-lg bg-white rounded-3xl shadow-sm overflow-hidden"
                role="dialog"
                aria-modal="true"
                style={{
                    height: height || undefined,
                    ...(brandColor ? { '--kelp-primary': normalizeHexColor(brandColor, '#11993a') } : {}),
                }}
            >
                {brandColor ? (
                    <div
                        className="h-1.5 w-full"
                        style={{ backgroundColor: normalizeHexColor(brandColor, '#11993a') }}
                        aria-hidden
                    />
                ) : null}
                <div ref={innerRef} className="p-6">
                    <div className="flex items-start justify-between gap-3 mb-5">
                        <div>
                            <p className="text-xs text-gray-400">Step {step} of {steps}</p>
                            <h2 className="text-xl font-bold text-gray-900">{title}</h2>
                        </div>
                        <button type="button" className="size-9 rounded-full hover:bg-gray-100 flex items-center justify-center" aria-label="Close" onClick={onClose}>
                            <span className="material-symbols-outlined">close</span>
                        </button>
                    </div>
                    <div key={step} className="kelp-popup-step">{children}</div>
                </div>
            </div>
        </div>
    );
}

const ACADEMY_SIDEBAR_PAGES = [
    { key: 'overview', segment: '', icon: 'dashboard', label: 'Overview', routes: ['dashboard', 'member'] },
    { key: 'members', segment: 'members', icon: 'groups', label: 'Members', routes: ['members'] },
    { key: 'classes', segment: 'classes', icon: 'class', label: 'Classes', routes: ['classes'] },
    { key: 'shared', segment: 'shared', icon: 'folder', label: 'Shared material', routes: ['shared'] },
    { key: 'announcements', segment: 'announcements', icon: 'campaign', label: 'Announcements', routes: ['announcements'] },
    { key: 'analytics', segment: 'analytics', icon: 'insights', label: 'Analytics', routes: ['analytics'] },
    { key: 'reports', segment: 'reports', icon: 'assessment', label: 'Reports', routes: ['reports'] },
    { key: 'integrations', segment: 'integrations', icon: 'extension', label: 'Integrations', routes: ['integrations'] },
    { key: 'billing', segment: 'billing', icon: 'payments', label: 'Billing', routes: ['billing'] },
    { key: 'entitlements', segment: 'entitlements', icon: 'workspace_premium', label: 'Entitlements', routes: ['entitlements'] },
    { key: 'settings', segment: 'settings', icon: 'settings', label: 'Settings', routes: ['settings'] },
    { key: 'support', segment: 'support', icon: 'help', label: 'Support', routes: ['support'] },
    { key: 'kelp', icon: 'open_in_new', label: 'Open Kelp app', external: true },
];

const ACADEMY_PLACEHOLDER_META = {
    classes: {
        title: 'Classes',
        description: 'Organize cohorts and assign material.',
        icon: 'class',
        blurb: 'Create classes, link courses, and assign teachers.',
    },
    analytics: {
        title: 'Analytics',
        description: 'Engagement and learning signals.',
        icon: 'insights',
        blurb: 'Seat usage, activity trends, and mastery estimates across the academy.',
    },
    announcements: {
        title: 'Announcements',
        description: 'Reach students and staff.',
        icon: 'campaign',
        blurb: 'Broadcast messages, schedule posts, and target by class or role.',
    },
    integrations: {
        title: 'Integrations',
        description: 'Connect LMS, SIS, and identity providers.',
        icon: 'extension',
        blurb: 'Google Classroom, Clever, SAML, and webhooks.',
    },
    settings: {
        title: 'Settings',
        description: 'Branding, policies, and academy profile.',
        icon: 'settings',
        blurb: 'Edit locale, domains, invite rules, and support contacts after create.',
    },
    billing: {
        title: 'Billing',
        description: 'Seats, plans, and invoices.',
        icon: 'payments',
        blurb: 'Seat limits, upgrades, and payment history.',
    },
    reports: {
        title: 'Reports',
        description: 'Exportable compliance and progress reports.',
        icon: 'assessment',
        blurb: 'CSV/PDF exports for admins and accreditors.',
    },
    support: {
        title: 'Support',
        description: 'Help for admins and academy contacts.',
        icon: 'help',
        blurb: 'Escalation paths, Kelp docs, and your academy support contact.',
    },
};

function academySidebarActivePath(academyId, routeName) {
    if (!academyId) return '';
    const item = ACADEMY_SIDEBAR_PAGES.find((entry) => {
        if (entry.external) return false;
        return entry.routes && entry.routes.indexOf(routeName) !== -1;
    });
    return academySectionPath(academyId, item ? item.segment : '');
}

function SideBarNavList({ activePath, className, children }) {
    const navListRef = useRef(null);
    const [indicator, setIndicator] = useState({ top: 0, left: 0, width: 0, height: 0 });
    useLayoutEffect(() => {
        const el = navListRef.current;
        if (!el) return;
        const active = el.querySelector('[data-nav-path="' + activePath + '"]');
        if (!active) {
            setIndicator({ top: 0, left: 0, width: 0, height: 0 });
            return;
        }
        const cRect = el.getBoundingClientRect();
        const aRect = active.getBoundingClientRect();
        setIndicator({
            top: aRect.top - cRect.top,
            left: aRect.left - cRect.left,
            width: aRect.width,
            height: aRect.height,
        });
    }, [activePath, children]);
    return (
        <div ref={navListRef} className={'relative flex flex-col gap-1 min-h-0 ' + (className || '')}>
            {indicator.height > 0 ? (
                <span
                    aria-hidden="true"
                    className="academy-sidebar-nav-indicator absolute left-0 top-0 z-0 rounded-2xl border border-[#34a853]/50 bg-[#34a853]/15 shadow-sm will-change-transform pointer-events-none"
                    style={{
                        transform: 'translateY(' + indicator.top + 'px)',
                        left: indicator.left + 'px',
                        width: indicator.width + 'px',
                        height: indicator.height + 'px',
                    }}
                />
            ) : null}
            {children}
        </div>
    );
}

function SideBarNavItem({ navPath, icon, label, active, onClick, onContextMenu }) {
    return (
        <button
            type="button"
            data-nav-path={navPath}
            onClick={onClick}
            onContextMenu={onContextMenu}
            className={'relative z-10 flex items-center gap-4 p-3 rounded-2xl text-sm font-bold text-left w-full transition-colors duration-200 '
                + (active ? 'text-[#166529]' : 'text-gray-500 hover:bg-gray-100')}
        >
            <span className={'material-symbols-outlined text-[26px] shrink-0 ' + (active ? 'font-bold' : '')}>{icon}</span>
            <span className="truncate">{label}</span>
        </button>
    );
}

function academySidebarPageVisible(item, features) {
    if (item.external || !item.segment) return true;
    if (!features) return true;
    const flag = features[item.key];
    if (flag === undefined) return true;
    return flag !== false;
}

function SideBar({ academies, academyId, academyName, academyFeatures, email, routeName, onCreate, onAcademyContextMenu }) {
    const [open, setOpen] = useState(false);
    const inAcademy = Boolean(academyId && ACADEMY_IN_APP_ROUTES.has(routeName));
    const overviewPath = academyId ? academySectionPath(academyId, '') : '';
    const globalActivePath = routeName === 'academies' ? '/' : '';
    const academyActivePath = academySidebarActivePath(academyId, routeName);

    function nav(path) {
        setOpen(false);
        go(path);
    }

    return (
        <React.Fragment>
            <button
                type="button"
                className="lg:hidden fixed top-4 left-4 z-30 size-10 rounded-full bg-white border border-gray-100 shadow-sm flex items-center justify-center"
                aria-label="Open menu"
                onClick={() => setOpen(true)}
            >
                <span className="material-symbols-outlined">menu</span>
            </button>
            {open ? <div className="lg:hidden fixed inset-0 bg-black/30 z-40" onClick={() => setOpen(false)} /> : null}
            <nav className={'fixed lg:sticky top-0 z-50 h-screen w-64 shrink-0 bg-white border-r border-gray-100 flex flex-col py-8 transition-transform duration-300 '
                + (open ? 'translate-x-0' : '-translate-x-full lg:translate-x-0')}>
                <div className="px-6 mb-4 shrink-0">
                    <button type="button" onClick={() => nav(inAcademy ? overviewPath : '/')} aria-label="Kelp Academy">
                        <KelpLogo className="h-10" />
                    </button>
                </div>
                <div className="flex-1 min-h-0 overflow-hidden">
                    <div className={'academy-sidebar-track flex h-full w-[200%] ' + (inAcademy ? 'academy-sidebar-track--academy' : '')}>
                        <div className="academy-sidebar-panel w-1/2 h-full flex flex-col min-h-0 px-4 pb-4 overflow-y-auto">
                            <SideBarNavList activePath={globalActivePath} className="pb-2">
                                <SideBarNavItem
                                    navPath="/"
                                    icon="school"
                                    label="Academies"
                                    active={routeName === 'academies'}
                                    onClick={() => nav('/')}
                                />
                                <SideBarNavItem
                                    navPath="/academies/new"
                                    icon="add"
                                    label="Create academy"
                                    active={false}
                                    onClick={() => { setOpen(false); onCreate(); }}
                                />
                            </SideBarNavList>
                            {(academies || []).length ? (
                                <p className="text-[10px] font-bold uppercase tracking-wide text-gray-400 px-3 pt-3 pb-1">Your academies</p>
                            ) : null}
                            <SideBarNavList activePath={inAcademy ? '' : overviewPath} className="flex-1">
                                {(academies || []).map((academy) => (
                                    <SideBarNavItem
                                        key={academy.id}
                                        navPath={'/academies/' + academy.id}
                                        icon="domain"
                                        label={academy.name}
                                        active={!inAcademy && academyId === academy.id}
                                        onClick={() => nav('/academies/' + academy.id)}
                                        onContextMenu={(event) => onAcademyContextMenu(event, academy)}
                                    />
                                ))}
                            </SideBarNavList>
                        </div>
                        <div className="academy-sidebar-panel w-1/2 h-full flex flex-col min-h-0 px-4 pb-4 overflow-y-auto">
                            <button
                                type="button"
                                onClick={() => nav('/')}
                                className="flex items-center gap-2 text-sm font-bold text-gray-500 hover:text-gray-900 px-3 py-2 mb-2 rounded-2xl hover:bg-gray-100 w-full text-left shrink-0"
                            >
                                <span className="material-symbols-outlined text-xl">arrow_back</span>
                                All academies
                            </button>
                            <p className="text-base font-bold text-gray-900 px-3 pb-3 truncate shrink-0" title={academyName || ''}>
                                {academyName || 'Academy'}
                            </p>
                            <SideBarNavList activePath={academyActivePath} className="flex-1">
                                {ACADEMY_SIDEBAR_PAGES.filter((item) => academySidebarPageVisible(item, academyFeatures)).map((item) => {
                                    if (item.external) {
                                        return (
                                            <SideBarNavItem
                                                key={item.key}
                                                navPath="kelp-app"
                                                icon={item.icon}
                                                label={item.label}
                                                active={false}
                                                onClick={() => { setOpen(false); window.open(kelpAppUrl(), '_blank', 'noopener,noreferrer'); }}
                                            />
                                        );
                                    }
                                    const path = academySectionPath(academyId, item.segment);
                                    const active = item.routes.indexOf(routeName) !== -1;
                                    return (
                                        <SideBarNavItem
                                            key={item.key}
                                            navPath={path}
                                            icon={item.icon}
                                            label={item.label}
                                            active={active}
                                            onClick={() => nav(path)}
                                        />
                                    );
                                })}
                            </SideBarNavList>
                        </div>
                    </div>
                </div>
                <div className="px-6 py-4 text-xs text-gray-400 truncate shrink-0">{email || ''}</div>
            </nav>
        </React.Fragment>
    );
}

function SignIn({ joining }) {
    return (
        <main className="max-w-md mx-auto px-6 py-16">
            <KelpLogo className="h-16 mb-8 mx-auto" />
            <Card>
                <h1 className="text-2xl font-bold text-gray-900">{joining ? 'Join this academy' : 'Academy admin'}</h1>
                <p className="text-sm text-gray-500 mt-2 mb-6">
                    {joining
                        ? 'Sign in to accept the invite. You will come back to this same link.'
                        : 'Sign in with your Clou_Stan account.'}
                </p>
                <Button onClick={startSignIn}>Sign in</Button>
            </Card>
        </main>
    );
}

function Academies({ academies, onCreate, onAcademyContextMenu }) {
    return (
        <main className="max-w-5xl mx-auto px-6 py-10">
            <div className="mb-6">
                <div className="flex items-center justify-between gap-4">
                    <h1 className="text-2xl font-bold text-gray-900">Academies</h1>
                    <button
                        type="button"
                        aria-label="Create academy"
                        onClick={onCreate}
                        className="bg-[#f5f5f5] rounded-2xl shadow-sm hover:shadow-md h-12 w-12 shrink-0 flex items-center justify-center"
                    >
                        <span className="material-symbols-outlined text-xl text-gray-700">add</span>
                    </button>
                </div>
                <p className="text-sm text-gray-500 mt-1">Academies you administer.</p>
            </div>
            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
                {academies.map((academy) => (
                    <FeatureTile
                        key={academy.id}
                        icon="school"
                        title={academy.name}
                        description={'You are ' + academy.role}
                        onClick={() => go('/academies/' + academy.id)}
                        onContextMenu={(event) => onAcademyContextMenu(event, academy)}
                    />
                ))}
            </div>
        </main>
    );
}

function Field({ label, children, hint }) {
    return (
        <div className="block space-y-1.5">
            <span className="text-xs font-semibold text-gray-600">{label}</span>
            {children}
            {hint ? <span className="text-xs text-gray-400 block">{hint}</span> : null}
        </div>
    );
}

function normalizeHexColor(raw, fallback) {
    const base = fallback || '#11993a';
    let next = String(raw || '').trim();
    if (!next.startsWith('#')) next = '#' + next;
    if (/^#[0-9a-fA-F]{6}$/.test(next)) return next.toLowerCase();
    return base;
}

function clampColor(n, min, max) {
    return Math.min(max, Math.max(min, n));
}

function hexToRgb(hex) {
    const normalized = normalizeHexColor(hex, '#000000').slice(1);
    return {
        r: parseInt(normalized.slice(0, 2), 16),
        g: parseInt(normalized.slice(2, 4), 16),
        b: parseInt(normalized.slice(4, 6), 16),
    };
}

function rgbToHex(r, g, b) {
    const part = (n) => clampColor(Math.round(n), 0, 255).toString(16).padStart(2, '0');
    return ('#' + part(r) + part(g) + part(b)).toLowerCase();
}

function rgbToHsv(r, g, b) {
    const rn = r / 255;
    const gn = g / 255;
    const bn = b / 255;
    const max = Math.max(rn, gn, bn);
    const min = Math.min(rn, gn, bn);
    const delta = max - min;
    let h = 0;
    const v = max;
    const s = max === 0 ? 0 : delta / max;
    if (delta !== 0) {
        if (max === rn) h = ((gn - bn) / delta + (gn < bn ? 6 : 0)) / 6;
        else if (max === gn) h = ((bn - rn) / delta + 2) / 6;
        else h = ((rn - gn) / delta + 4) / 6;
    }
    return { h: h * 360, s, v };
}

function hsvToRgb(h, s, v) {
    const hue = ((h % 360) + 360) % 360;
    const c = v * s;
    const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
    const m = v - c;
    let rp = 0;
    let gp = 0;
    let bp = 0;
    if (hue < 60) { rp = c; gp = x; }
    else if (hue < 120) { rp = x; gp = c; }
    else if (hue < 180) { gp = c; bp = x; }
    else if (hue < 240) { gp = x; bp = c; }
    else if (hue < 300) { rp = x; bp = c; }
    else { rp = c; bp = x; }
    return { r: (rp + m) * 255, g: (gp + m) * 255, b: (bp + m) * 255 };
}

function hexToHsv(hex) {
    const { r, g, b } = hexToRgb(hex);
    return rgbToHsv(r, g, b);
}

function hsvToHex(hsv) {
    const { r, g, b } = hsvToRgb(hsv.h, hsv.s, hsv.v);
    return rgbToHex(r, g, b);
}

const KELP_COLOR_PRESETS = ['#11993a', '#0d7a2f', '#166529', '#0ea5e9', '#6366f1', '#a855f7', '#e11d48', '#f97316', '#111827'];

function CustomColorPicker({ value, onChange }) {
    const [hsv, setHsv] = useState(() => hexToHsv(normalizeHexColor(value, '#11993a')));
    const svRef = useRef(null);
    const hueRef = useRef(null);
    const dragSv = useRef(false);
    const dragHue = useRef(false);

    useEffect(() => {
        setHsv(hexToHsv(normalizeHexColor(value, '#11993a')));
    }, [value]);

    function emit(next) {
        setHsv(next);
        onChange(hsvToHex(next));
    }

    function pickSv(clientX, clientY) {
        const node = svRef.current;
        if (!node) return;
        const rect = node.getBoundingClientRect();
        const s = clampColor((clientX - rect.left) / rect.width, 0, 1);
        const v = 1 - clampColor((clientY - rect.top) / rect.height, 0, 1);
        emit({ h: hsv.h, s, v });
    }

    function pickHue(clientX) {
        const node = hueRef.current;
        if (!node) return;
        const rect = node.getBoundingClientRect();
        const t = clampColor((clientX - rect.left) / rect.width, 0, 1);
        emit({ h: t * 360, s: hsv.s, v: hsv.v });
    }

    useEffect(() => {
        function onPointerMove(event) {
            if (dragSv.current) pickSv(event.clientX, event.clientY);
            if (dragHue.current) pickHue(event.clientX);
        }
        function onPointerUp() {
            dragSv.current = false;
            dragHue.current = false;
        }
        window.addEventListener('pointermove', onPointerMove);
        window.addEventListener('pointerup', onPointerUp);
        return () => {
            window.removeEventListener('pointermove', onPointerMove);
            window.removeEventListener('pointerup', onPointerUp);
        };
    });

    const hex = hsvToHex(hsv);
    const hueStrip = 'hsl(' + Math.round(hsv.h) + ', 100%, 50%)';

    return (
        <div className="p-3 space-y-3">
            <div
                ref={svRef}
                className="kelp-color-sv relative h-40 w-full rounded-2xl cursor-crosshair touch-none overflow-hidden border border-black/10"
                style={{ backgroundColor: hueStrip }}
                onPointerDown={(event) => {
                    dragSv.current = true;
                    event.currentTarget.setPointerCapture(event.pointerId);
                    pickSv(event.clientX, event.clientY);
                }}
            >
                <div className="absolute inset-0 bg-gradient-to-r from-white to-transparent" />
                <div className="absolute inset-0 bg-gradient-to-t from-black to-transparent" />
                <div
                    className="absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
                    style={{
                        left: (hsv.s * 100) + '%',
                        top: ((1 - hsv.v) * 100) + '%',
                        backgroundColor: hex,
                    }}
                />
            </div>
            <div
                ref={hueRef}
                className="kelp-color-hue relative h-3 w-full rounded-full cursor-pointer touch-none border border-black/10"
                onPointerDown={(event) => {
                    dragHue.current = true;
                    event.currentTarget.setPointerCapture(event.pointerId);
                    pickHue(event.clientX);
                }}
            >
                <div
                    className="absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
                    style={{ left: (hsv.h / 360 * 100) + '%', backgroundColor: hueStrip }}
                />
            </div>
            <div className="flex flex-wrap gap-2">
                {KELP_COLOR_PRESETS.map((preset) => (
                    <button
                        key={preset}
                        type="button"
                        aria-label={'Preset ' + preset}
                        className={'size-8 rounded-full border-2 transition-transform hover:scale-105 '
                            + (preset === hex ? 'border-gray-900 ring-2 ring-primary/30' : 'border-white shadow-sm')}
                        style={{ backgroundColor: preset }}
                        onClick={() => emit(hexToHsv(preset))}
                    />
                ))}
            </div>
        </div>
    );
}

function DropdownSelect({ value, onChange, options, placeholder, disabled, className }) {
    const [open, setOpen] = useState(false);
    const [closing, setClosing] = useState(false);
    const rootRef = useRef(null);
    const showMenu = open || closing;
    const selected = (options || []).find((option) => option.value === value);

    function requestClose() {
        if (!open && !closing) return;
        setOpen(false);
        setClosing(true);
    }

    function requestToggle() {
        if (disabled) return;
        if (open) requestClose();
        else {
            setClosing(false);
            setOpen(true);
        }
    }

    useEffect(() => {
        if (!open) return undefined;
        function onDoc(event) {
            if (rootRef.current && !rootRef.current.contains(event.target)) requestClose();
        }
        document.addEventListener('mousedown', onDoc);
        document.addEventListener('touchstart', onDoc);
        return () => {
            document.removeEventListener('mousedown', onDoc);
            document.removeEventListener('touchstart', onDoc);
        };
    }, [open]);

    return (
        <div ref={rootRef} className={'relative w-full ' + (className || '')}>
            <button
                type="button"
                disabled={disabled}
                aria-haspopup="listbox"
                aria-expanded={open}
                onClick={requestToggle}
                className={'w-full flex items-center justify-between gap-2 px-3 py-2.5 border-2 rounded-xl text-sm text-left bg-white transition-colors '
                    + (disabled ? 'opacity-50 cursor-not-allowed border-gray-200 ' : 'border-gray-200 hover:border-gray-300 focus:border-primary focus:outline-none ')}
            >
                <span className={selected ? 'text-gray-900 font-medium' : 'text-gray-400'}>
                    {selected ? selected.label : (placeholder || 'Choose…')}
                </span>
                <span
                    className={'material-symbols-outlined text-xl text-gray-500 transition-transform duration-200 '
                        + (open ? 'rotate-180' : '')}
                >
                    expand_more
                </span>
            </button>
            {showMenu ? (
                <div
                    role="listbox"
                    className={'kelp-dropdown-panel kelp-dropdown-panel--origin-left absolute z-50 left-0 right-0 mt-2 rounded-2xl bg-white shadow-xl border border-gray-100 py-1 overflow-hidden max-h-64 overflow-y-auto '
                        + (closing ? 'kelp-dropdown-panel--closing ' : '')}
                    onAnimationEnd={(event) => {
                        if (closing && event.animationName === 'kelp-dropdown-out') setClosing(false);
                    }}
                >
                    {(options || []).map((option, index) => {
                        const active = option.value === value;
                        return (
                            <button
                                key={option.value}
                                type="button"
                                role="option"
                                aria-selected={active}
                                style={{ animationDelay: index * 24 + 'ms' }}
                                className={'kelp-dropdown-option w-full flex items-center justify-between gap-2 px-4 py-2.5 text-sm text-left transition-colors '
                                    + (active
                                        ? 'bg-primary/10 text-primary font-bold'
                                        : 'text-gray-700 hover:bg-primary/5 hover:text-primary font-medium')}
                                onClick={() => {
                                    onChange(option.value);
                                    requestClose();
                                }}
                            >
                                <span>{option.label}</span>
                                {active ? <span className="material-symbols-outlined text-base">check</span> : null}
                            </button>
                        );
                    })}
                </div>
            ) : null}
        </div>
    );
}

const LOCALE_OPTIONS = [
    { value: 'en-US', label: 'English (United States)' },
    { value: 'en-GB', label: 'English (United Kingdom)' },
    { value: 'es-US', label: 'Spanish (United States)' },
    { value: 'es-ES', label: 'Spanish (Spain)' },
    { value: 'fr-FR', label: 'French (France)' },
    { value: 'de-DE', label: 'German (Germany)' },
    { value: 'pt-BR', label: 'Portuguese (Brazil)' },
    { value: 'zh-CN', label: 'Chinese (Simplified)' },
    { value: 'zh-HK', label: 'Chinese (Hong Kong)' },
    { value: 'en-HK', label: 'English (Hong Kong)' },
    { value: 'ja-JP', label: 'Japanese (Japan)' },
    { value: 'ko-KR', label: 'Korean (Korea)' },
];

const TIMEZONE_OPTIONS = [
    { value: 'Pacific/Honolulu', label: 'Honolulu (HST)' },
    { value: 'America/Anchorage', label: 'Anchorage (AKST)' },
    { value: 'America/Los_Angeles', label: 'Los Angeles (PT)' },
    { value: 'America/Denver', label: 'Denver (MT)' },
    { value: 'America/Chicago', label: 'Chicago (CT)' },
    { value: 'America/New_York', label: 'New York (ET)' },
    { value: 'America/Toronto', label: 'Toronto (ET)' },
    { value: 'America/Sao_Paulo', label: 'São Paulo (BRT)' },
    { value: 'America/Mexico_City', label: 'Mexico City (CST)' },
    { value: 'UTC', label: 'UTC' },
    { value: 'Europe/London', label: 'London (GMT/BST)' },
    { value: 'Europe/Paris', label: 'Paris (CET)' },
    { value: 'Europe/Berlin', label: 'Berlin (CET)' },
    { value: 'Europe/Madrid', label: 'Madrid (CET)' },
    { value: 'Africa/Johannesburg', label: 'Johannesburg (SAST)' },
    { value: 'Asia/Dubai', label: 'Dubai (GST)' },
    { value: 'Asia/Kolkata', label: 'India (IST)' },
    { value: 'Asia/Bangkok', label: 'Bangkok (ICT)' },
    { value: 'Asia/Hong_Kong', label: 'Hong Kong (HKT)' },
    { value: 'Asia/Shanghai', label: 'Shanghai (CST)' },
    { value: 'Asia/Tokyo', label: 'Tokyo (JST)' },
    { value: 'Asia/Seoul', label: 'Seoul (KST)' },
    { value: 'Australia/Sydney', label: 'Sydney (AEST)' },
    { value: 'Pacific/Auckland', label: 'Auckland (NZST)' },
];

const ACADEMY_TYPE_OPTIONS = [
    { value: 'k12', label: 'K–12 academy' },
    { value: 'district', label: 'District academy' },
    { value: 'college', label: 'College / university academy' },
    { value: 'tutoring', label: 'Tutoring academy' },
    { value: 'corporate', label: 'Corporate training academy' },
    { value: 'other', label: 'Other academy' },
];

const INVITE_POLICY_OPTIONS = [
    { value: 'owner', label: 'Owners only' },
    { value: 'owner_admin', label: 'Owners and admins' },
    { value: 'owner_admin_teacher', label: 'Owners, admins, and teachers' },
];

const INVITE_ROLE_OPTIONS = [
    { value: 'student', label: 'Student' },
    { value: 'teacher', label: 'Teacher' },
];

const SHARED_UPLOAD_OPTIONS = [
    { value: 'staff', label: 'Teachers and admins' },
    { value: 'admin_only', label: 'Admins only' },
];

function ColorField({ label, value, onChange }) {
    const [open, setOpen] = useState(false);
    const [closing, setClosing] = useState(false);
    const rootRef = useRef(null);
    const raw = String(value || '');
    const swatch = normalizeHexColor(raw, '#11993a');
    const textValue = raw.startsWith('#') ? raw : swatch;
    const showMenu = open || closing;

    function requestClose() {
        if (!open && !closing) return;
        setOpen(false);
        setClosing(true);
    }

    function requestToggle() {
        if (open) requestClose();
        else {
            setClosing(false);
            setOpen(true);
        }
    }

    useEffect(() => {
        if (!open) return undefined;
        function onDoc(event) {
            if (rootRef.current && !rootRef.current.contains(event.target)) requestClose();
        }
        document.addEventListener('mousedown', onDoc);
        document.addEventListener('touchstart', onDoc);
        return () => {
            document.removeEventListener('mousedown', onDoc);
            document.removeEventListener('touchstart', onDoc);
        };
    }, [open]);

    function onHexInput(event) {
        let next = event.target.value.trim();
        if (next && !next.startsWith('#')) next = '#' + next;
        next = next.slice(0, 7);
        if (!/^#[0-9a-fA-F]{0,6}$/i.test(next)) return;
        if (next.length === 7) onChange(next.toLowerCase());
        else onChange(next);
    }
    function onHexBlur(event) {
        onChange(normalizeHexColor(event.target.value, swatch));
    }
    return (
        <Field label={label}>
            <div ref={rootRef} className="relative">
                <div className="flex items-center gap-3">
                    <button
                        type="button"
                        aria-label={'Open ' + label + ' picker'}
                        aria-expanded={open}
                        onClick={requestToggle}
                        className="size-11 shrink-0 rounded-xl border-2 border-gray-200 bg-white hover:border-gray-300 shadow-sm overflow-hidden"
                    >
                        <span className="block size-full" style={{ backgroundColor: swatch }} />
                    </button>
                    <Input
                        value={textValue}
                        onChange={onHexInput}
                        onBlur={onHexBlur}
                        placeholder="#11993a"
                        spellCheck={false}
                        autoComplete="off"
                        className="font-mono"
                    />
                </div>
                {showMenu ? (
                    <div
                        className={'kelp-dropdown-panel kelp-dropdown-panel--origin-left absolute z-50 left-0 right-0 sm:right-auto sm:w-72 mt-2 rounded-2xl bg-white shadow-xl border border-gray-100 overflow-hidden '
                            + (closing ? 'kelp-dropdown-panel--closing ' : '')}
                        onAnimationEnd={(event) => {
                            if (closing && event.animationName === 'kelp-dropdown-out') setClosing(false);
                        }}
                    >
                        <CustomColorPicker value={swatch} onChange={onChange} />
                    </div>
                ) : null}
            </div>
        </Field>
    );
}

function normalizeEmailDomain(raw) {
    let domain = String(raw || '').trim().toLowerCase().replace(/^@+/, '');
    if (!domain || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/.test(domain)) return null;
    return domain.slice(0, 120);
}

function EmailDomainAllowlistField({ domains, onChange }) {
    const [addOpen, setAddOpen] = useState(false);
    const [draft, setDraft] = useState('');
    const [error, setError] = useState('');
    const list = Array.isArray(domains) ? domains : [];

    function closeAdd() {
        setAddOpen(false);
        setDraft('');
        setError('');
    }

    function addDomain() {
        const domain = normalizeEmailDomain(draft);
        if (!domain) {
            setError('Enter a valid domain like academy.edu');
            return;
        }
        if (list.includes(domain)) {
            setError('That domain is already on the list.');
            return;
        }
        onChange([...list, domain]);
        closeAdd();
    }

    function removeDomain(domain) {
        onChange(list.filter((entry) => entry !== domain));
    }

    return (
        <section>
            <div className="flex items-start justify-between gap-3">
                <div>
                    <Label>Allowed email domains</Label>
                    <p className="text-sm text-gray-500 mt-1">Leave empty to allow any domain. Add domains to restrict who can be invited.</p>
                </div>
                <button
                    type="button"
                    aria-label="Add email domain"
                    onClick={() => setAddOpen(true)}
                    className="bg-[#f5f5f5] rounded-3xl shadow-sm hover:shadow-md h-12 w-12 shrink-0 flex items-center justify-center"
                >
                    <span className="material-symbols-outlined text-gray-700">add</span>
                </button>
            </div>
            <div className="mt-3">
                <ListTable
                    columns={['Domain', '']}
                    rows={list.map((domain) => (
                        <div key={domain} className="grid grid-cols-2 gap-3 items-center px-4 py-3">
                            <span className="text-sm text-gray-900">{domain}</span>
                            <div className="flex justify-end">
                                <Button type="button" variant="secondary" onClick={() => removeDomain(domain)}>Remove</Button>
                            </div>
                        </div>
                    ))}
                    empty="No domain restrictions yet."
                />
            </div>
            {addOpen ? (
                <Popup title="Add email domain" step={1} steps={1} onClose={closeAdd}>
                    <div className="space-y-4">
                        <p className="text-sm text-gray-500">People must use an address on this domain to be invited.</p>
                        <Input
                            value={draft}
                            onChange={(event) => { setDraft(event.target.value); setError(''); }}
                            onKeyDown={(event) => {
                                if (event.key === 'Enter') {
                                    event.preventDefault();
                                    addDomain();
                                }
                            }}
                            placeholder="academy.edu"
                            autoFocus
                            spellCheck={false}
                        />
                        {error ? <p className="text-sm text-red-700/80">{error}</p> : null}
                        <div className="flex gap-3">
                            <Button type="button" onClick={addDomain}>Add domain</Button>
                            <Button type="button" variant="secondary" onClick={closeAdd}>Cancel</Button>
                        </div>
                    </div>
                </Popup>
            ) : null}
        </section>
    );
}

const CREATE_ACADEMY_DEFAULTS = {
    name: '',
    shortName: '',
    slug: '',
    schoolType: 'k12',
    timezone: 'America/New_York',
    locale: 'en-US',
    primaryColor: '#11993a',
    secondaryColor: '#0d7a2f',
    accentColor: '#11993a',
    tagline: '',
    logoDataUrl: '',
    seatLimit: '',
    whoCanInvite: 'owner_admin',
    defaultInviteRole: 'student',
    emailDomains: [],
    sharedUploadPolicy: 'staff',
    dataRetentionNote: '',
    managedAck: false,
    supportName: '',
    supportEmail: '',
    featureClasses: true,
    featureAnnouncements: true,
    featureAnalytics: true,
    featureReports: true,
    featureIntegrations: true,
};

function buildAcademyCreatePayload(form) {
    const body = {
        name: form.name.trim(),
        settings: {
            shortName: form.shortName.trim(),
            schoolType: form.schoolType,
            timezone: form.timezone.trim(),
            locale: form.locale.trim(),
            branding: {
                primaryColor: form.primaryColor,
                secondaryColor: form.secondaryColor,
                accentColor: form.accentColor,
                tagline: form.tagline.trim(),
                logoDataUrl: form.logoDataUrl || undefined,
            },
            invites: {
                whoCanInvite: form.whoCanInvite,
                defaultRole: form.defaultInviteRole,
            },
            students: {
                managedAccountsOnly: true,
                emailDomainAllowlist: form.emailDomains,
            },
            sharedMaterial: { uploadPolicy: form.sharedUploadPolicy },
            dataRetention: { note: form.dataRetentionNote.trim() },
            features: {
                classes: form.featureClasses,
                announcements: form.featureAnnouncements,
                analytics: form.featureAnalytics,
                reports: form.featureReports,
                integrations: form.featureIntegrations,
            },
            support: {
                name: form.supportName.trim(),
                email: form.supportEmail.trim(),
            },
        },
    };
    if (form.slug.trim()) body.id = form.slug.trim();
    if (form.seatLimit) body.seatLimit = Number(form.seatLimit);
    return body;
}

function academyTypeLabel(value) {
    const labels = {
        k12: 'K–12 academy',
        district: 'District academy',
        college: 'College academy',
        tutoring: 'Tutoring academy',
        corporate: 'Corporate academy',
        other: 'Academy',
    };
    return labels[value] || 'Academy';
}

const CREATE_STEP_META = [
    { title: 'Academy identity', lead: 'Name your academy and set locale defaults.', icon: 'domain' },
    { title: 'Brand', lead: 'Colors and a short line your community will recognize.', icon: 'palette' },
    { title: 'Logo', lead: 'Optional mark for your academy. Skip if you do not have one yet.', icon: 'image' },
    { title: 'Membership', lead: 'Seat limits and who can invite people.', icon: 'group_add' },
    { title: 'Students and files', lead: 'Email rules and shared material policy.', icon: 'policy' },
    { title: 'Support contact', lead: 'Who students reach when they need help.', icon: 'support_agent' },
    { title: 'Capabilities', lead: 'Turn on the academy areas you plan to use.', icon: 'tune' },
    { title: 'Review', lead: 'Confirm everything, then launch.', icon: 'fact_check' },
];

const CREATE_FEATURE_TOGGLES = [
    { key: 'featureClasses', icon: 'class', title: 'Classes', description: 'Cohorts and assignments.' },
    { key: 'featureAnnouncements', icon: 'campaign', title: 'Announcements', description: 'Broadcast to staff and students.' },
    { key: 'featureAnalytics', icon: 'insights', title: 'Analytics', description: 'Engagement and learning signals.' },
    { key: 'featureReports', icon: 'assessment', title: 'Reports', description: 'Exports for admins and compliance.' },
    { key: 'featureIntegrations', icon: 'extension', title: 'Integrations', description: 'LMS, SIS, and SSO connections.' },
];

function CreateFeatureToggle({ icon, title, description, checked, onChange }) {
    return (
        <label className={'flex gap-4 p-4 rounded-3xl cursor-pointer transition-shadow '
            + (checked ? 'bg-[#34a853]/10 ring-2 ring-primary' : 'bg-[#f5f5f5] hover:shadow-sm')}>
            <input type="checkbox" className="sr-only" checked={checked} onChange={(e) => onChange(e.target.checked)} />
            <span className="material-symbols-outlined text-[1.75rem] text-gray-600 shrink-0">{icon}</span>
            <span className="min-w-0">
                <span className="block text-sm font-bold text-gray-900">{title}</span>
                <span className="block text-sm text-gray-500 mt-0.5">{description}</span>
            </span>
            <span className="material-symbols-outlined text-primary shrink-0 ml-auto">{checked ? 'check_circle' : 'radio_button_unchecked'}</span>
        </label>
    );
}

const LOGO_ACCEPT = 'image/png,image/svg+xml,image/webp,image/jpeg';
const LOGO_MAX_BYTES = 512000;

function LogoDropZone({ logoDataUrl, onFile, onClear, error }) {
    const [dragOver, setDragOver] = useState(false);
    const inputRef = useRef(null);

    function pickFiles(fileList) {
        const file = fileList && fileList[0];
        if (file) onFile(file);
    }

    function onDragOver(event) {
        event.preventDefault();
        setDragOver(true);
    }

    function onDragLeave(event) {
        event.preventDefault();
        setDragOver(false);
    }

    function onDrop(event) {
        event.preventDefault();
        setDragOver(false);
        pickFiles(event.dataTransfer.files);
    }

    return (
        <div className="space-y-3">
            <div
                role="button"
                tabIndex={0}
                onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') inputRef.current && inputRef.current.click(); }}
                onDragOver={onDragOver}
                onDragLeave={onDragLeave}
                onDrop={onDrop}
                onClick={() => inputRef.current && inputRef.current.click()}
                className={'academy-logo-drop rounded-3xl border-2 border-dashed p-8 sm:p-10 text-center cursor-pointer transition-colors '
                    + (dragOver ? 'academy-logo-drop--active' : 'border-gray-200 bg-white hover:border-gray-300 hover:bg-[#fafafa]')}
            >
                <input
                    ref={inputRef}
                    type="file"
                    accept={LOGO_ACCEPT}
                    className="sr-only"
                    onChange={(event) => pickFiles(event.target.files)}
                />
                {logoDataUrl ? (
                    <div className="flex flex-col items-center gap-4" onClick={(event) => event.stopPropagation()}>
                        <img src={logoDataUrl} alt="Academy logo preview" className="max-h-40 max-w-full rounded-2xl object-contain shadow-sm" />
                        <div className="flex flex-wrap justify-center gap-2">
                            <Button type="button" variant="secondary" onClick={() => inputRef.current && inputRef.current.click()}>Replace</Button>
                            <Button type="button" variant="ghost" onClick={onClear}>Remove</Button>
                        </div>
                    </div>
                ) : (
                    <div className="pointer-events-none flex flex-col items-center gap-3">
                        <span className="material-symbols-outlined text-5xl text-gray-300">upload_file</span>
                        <p className="text-base font-bold text-gray-900">Drop your logo here</p>
                        <p className="text-sm text-gray-500 max-w-xs">or click to browse · PNG, SVG, WebP, or JPEG · up to 512 KB</p>
                    </div>
                )}
            </div>
            {error ? <p className="text-sm text-red-700/80">{error}</p> : null}
        </div>
    );
}

function CreateAcademyPreview({ form }) {
    const primary = normalizeHexColor(form.primaryColor, '#11993a');
    const secondary = normalizeHexColor(form.secondaryColor, '#0d7a2f');
    const name = form.name.trim() || 'Your academy';
    const short = form.shortName.trim();
    const initial = (name.replace(/\s+/g, ' ').trim()[0] || 'K').toUpperCase();
    return (
        <div className="academy-create-preview-card">
            <div className="rounded-[1.75rem] overflow-hidden bg-white shadow-[0_32px_80px_-24px_rgba(0,0,0,0.22)] border border-black/[0.06]">
                <div
                    className="h-44 w-full"
                    style={{ background: 'linear-gradient(135deg, ' + primary + ', ' + secondary + ')' }}
                />
                <div className="px-8 pb-8 relative -mt-16">
                    {form.logoDataUrl ? (
                        <img src={form.logoDataUrl} alt="" className="size-28 rounded-3xl border-[5px] border-white object-cover bg-white shadow-md" />
                    ) : (
                        <div
                            className="size-28 rounded-3xl border-[5px] border-white flex items-center justify-center text-white text-4xl font-bold shadow-md"
                            style={{ backgroundColor: primary }}
                        >
                            {initial}
                        </div>
                    )}
                    <h3 className="text-2xl font-bold text-gray-900 mt-5 tracking-tight">{name}</h3>
                    {short ? <p className="text-base text-gray-500 mt-1">{short}</p> : null}
                    {form.tagline ? <p className="text-base text-gray-600 mt-4 leading-relaxed">&ldquo;{form.tagline}&rdquo;</p> : null}
                    <div className="mt-5 inline-flex text-sm font-semibold px-3 py-1.5 rounded-full" style={{ color: primary, backgroundColor: primary + '1a' }}>
                        {academyTypeLabel(form.schoolType)}
                    </div>
                    {form.supportName && form.supportEmail ? (
                        <p className="text-sm text-gray-500 mt-6 leading-relaxed">
                            Contact your admin: {form.supportName} ({form.supportEmail})
                        </p>
                    ) : null}
                </div>
            </div>
        </div>
    );
}

function dropdownLabel(options, value) {
    const match = (options || []).find((option) => option.value === value);
    return match ? match.label : (value || '—');
}

function ReviewSection({ title, children }) {
    return (
        <div className="bg-[#f5f5f5] rounded-3xl p-5 shadow-sm">
            <h3 className="text-sm font-bold text-gray-900 mb-2">{title}</h3>
            <div>{children}</div>
        </div>
    );
}

function ReviewRow({ label, children }) {
    return (
        <div className="grid grid-cols-1 sm:grid-cols-[8.5rem_1fr] gap-1 sm:gap-3 py-2.5 border-b border-gray-200/70 last:border-0">
            <span className="text-xs font-semibold text-gray-500">{label}</span>
            <div className="text-sm text-gray-900 min-w-0">{children}</div>
        </div>
    );
}

function ReviewColorSwatch({ label, hex }) {
    const color = normalizeHexColor(hex, '#11993a');
    return (
        <div className="flex items-center gap-3">
            <span
                className="size-10 rounded-xl border-2 border-white shadow-sm shrink-0"
                style={{ backgroundColor: color }}
                aria-hidden
            />
            <div>
                <div className="text-xs font-semibold text-gray-500">{label}</div>
                <div className="font-mono text-sm">{color}</div>
            </div>
        </div>
    );
}

function CreateAcademyReview({ form }) {
    const domains = Array.isArray(form.emailDomains) ? form.emailDomains : [];
    return (
        <div className="space-y-4">
            <ReviewSection title="Academy identity">
                <ReviewRow label="Name">{form.name.trim() || '—'}</ReviewRow>
                <ReviewRow label="Short name">{form.shortName.trim() || '—'}</ReviewRow>
                <ReviewRow label="URL slug">{form.slug.trim() || 'Generated from name'}</ReviewRow>
                <ReviewRow label="Academy type">{academyTypeLabel(form.schoolType)}</ReviewRow>
                <ReviewRow label="Locale">{dropdownLabel(LOCALE_OPTIONS, form.locale)}</ReviewRow>
                <ReviewRow label="Time zone">{dropdownLabel(TIMEZONE_OPTIONS, form.timezone)}</ReviewRow>
            </ReviewSection>
            <ReviewSection title="Brand">
                <ReviewRow label="Colors">
                    <div className="space-y-3">
                        <ReviewColorSwatch label="Primary" hex={form.primaryColor} />
                        <ReviewColorSwatch label="Secondary" hex={form.secondaryColor} />
                        <ReviewColorSwatch label="Accent" hex={form.accentColor} />
                    </div>
                </ReviewRow>
                <ReviewRow label="Tagline">{form.tagline.trim() ? '“' + form.tagline.trim() + '”' : '—'}</ReviewRow>
            </ReviewSection>
            <ReviewSection title="Logo">
                <ReviewRow label="File">
                    {form.logoDataUrl ? (
                        <img
                            src={form.logoDataUrl}
                            alt="Logo preview"
                            className="max-h-16 max-w-[8rem] rounded-xl object-contain bg-white border border-gray-200 p-1"
                        />
                    ) : 'None'}
                </ReviewRow>
            </ReviewSection>
            <ReviewSection title="Membership">
                <ReviewRow label="Seat limit">{form.seatLimit ? form.seatLimit : 'No limit'}</ReviewRow>
                <ReviewRow label="Invites">{dropdownLabel(INVITE_POLICY_OPTIONS, form.whoCanInvite)}</ReviewRow>
                <ReviewRow label="Default role">{dropdownLabel(INVITE_ROLE_OPTIONS, form.defaultInviteRole)}</ReviewRow>
            </ReviewSection>
            <ReviewSection title="Students and files">
                <ReviewRow label="Email domains">
                    {domains.length ? (
                        <ul className="space-y-1">
                            {domains.map((domain) => (
                                <li key={domain} className="font-mono text-sm">{domain}</li>
                            ))}
                        </ul>
                    ) : 'Any domain allowed'}
                </ReviewRow>
                <ReviewRow label="Shared uploads">{dropdownLabel(SHARED_UPLOAD_OPTIONS, form.sharedUploadPolicy)}</ReviewRow>
                <ReviewRow label="Data retention">{form.dataRetentionNote.trim() || '—'}</ReviewRow>
                <ReviewRow label="Managed accounts">{form.managedAck ? 'Confirmed' : 'Not confirmed'}</ReviewRow>
            </ReviewSection>
            <ReviewSection title="Support contact">
                <ReviewRow label="Name">{form.supportName.trim() || '—'}</ReviewRow>
                <ReviewRow label="Email">{form.supportEmail.trim() || '—'}</ReviewRow>
            </ReviewSection>
            <ReviewSection title="Capabilities">
                <ReviewRow label="Enabled">
                    <ul className="space-y-1 text-sm">
                        {CREATE_FEATURE_TOGGLES.filter((item) => form[item.key]).map((item) => (
                            <li key={item.key}>{item.title}</li>
                        ))}
                        {!CREATE_FEATURE_TOGGLES.some((item) => form[item.key]) ? 'None selected' : null}
                    </ul>
                </ReviewRow>
            </ReviewSection>
        </div>
    );
}

const KELP_ACADEMY_CONTACT_EMAIL = 'hello@cloustan.org';

function AcademySizeGatePopup({ onClose, onSelect }) {
    const [view, setView] = useState('pick');
    const mailSubject = encodeURIComponent('Kelp Academies — 500+ students');
    const mailHref = 'mailto:' + KELP_ACADEMY_CONTACT_EMAIL + '?subject=' + mailSubject;

    return (
        <div className="fixed inset-0 z-[80] flex items-center justify-center p-4">
            <button type="button" className="kelp-popup-backdrop absolute inset-0 bg-black/30" aria-label="Close" onClick={onClose} />
            <div
                className="kelp-popup-panel relative w-full max-w-lg bg-white rounded-3xl shadow-sm p-6"
                role="dialog"
                aria-modal="true"
                aria-labelledby="academy-size-gate-title"
            >
                <div className="flex items-start justify-between gap-3 mb-4">
                    <div>
                        <h2 id="academy-size-gate-title" className="text-xl font-bold text-gray-900">
                            {view === 'contact' ? 'Contact us' : 'How large is your academy?'}
                        </h2>
                        <p className="text-sm text-gray-500 mt-2">
                            {view === 'contact'
                                ? 'For more than 500 students, we’ll help you configure seats, onboarding, and support.'
                                : 'Self-serve setup works for up to 500 students. Larger programs start with a quick note to our team.'}
                        </p>
                    </div>
                    <button type="button" className="size-9 rounded-full hover:bg-gray-100 flex items-center justify-center shrink-0" aria-label="Close" onClick={onClose}>
                        <span className="material-symbols-outlined">close</span>
                    </button>
                </div>
                {view === 'pick' ? (
                    <div className="grid gap-3 sm:grid-cols-2">
                        <FeatureTile
                            icon="groups"
                            title="1–500 students"
                            description="Continue setup here with up to 500 seats."
                            onClick={() => onSelect('under-500')}
                        />
                        <FeatureTile
                            icon="mail"
                            title="500+ students"
                            description="Contact us for a large academy."
                            onClick={() => setView('contact')}
                        />
                    </div>
                ) : (
                    <div className="space-y-4">
                        <div className="bg-[#f5f5f5] rounded-3xl p-5 text-sm text-gray-600 leading-relaxed">
                            Email <span className="font-mono text-gray-900">{KELP_ACADEMY_CONTACT_EMAIL}</span> and tell us your academy name, estimated student count, and timeline. We’ll reply with next steps.
                        </div>
                        <div className="flex flex-wrap gap-3">
                            <a className="kelp-btn inline-flex items-center justify-center" href={mailHref}>
                                Email Kelp
                            </a>
                            <Button type="button" variant="secondary" onClick={() => setView('pick')}>Back</Button>
                        </div>
                    </div>
                )}
            </div>
        </div>
    );
}

function CreateAcademy({ onClose, onCreated }) {
    const [sizeTier, setSizeTier] = useState(null);
    const [step, setStep] = useState(1);
    const [form, setForm] = useState({ ...CREATE_ACADEMY_DEFAULTS });
    const [error, setError] = useState('');
    const [logoError, setLogoError] = useState('');
    const [submitting, setSubmitting] = useState(false);
    const steps = CREATE_STEP_META.length;
    const meta = CREATE_STEP_META[step - 1];
    const primary = normalizeHexColor(form.primaryColor, '#11993a');
    const progress = Math.round((step / steps) * 100);

    function setField(key, value) {
        setForm((prev) => ({ ...prev, [key]: value }));
    }
    function chooseSizeTier(tier) {
        if (tier !== 'under-500') return;
        setSizeTier(tier);
        setForm((prev) => ({ ...prev, seatLimit: '500' }));
    }
    function loadLogoFile(file) {
        if (!file) return;
        const okTypes = LOGO_ACCEPT.split(',');
        if (!okTypes.includes(file.type)) {
            setLogoError('Use PNG, SVG, WebP, or JPEG.');
            return;
        }
        if (file.size > LOGO_MAX_BYTES) {
            setLogoError('Logo must be 512 KB or smaller.');
            return;
        }
        setLogoError('');
        const reader = new FileReader();
        reader.onload = () => setField('logoDataUrl', String(reader.result || ''));
        reader.readAsDataURL(file);
    }
    function goStep(next) {
        setError('');
        setStep(next);
        window.scrollTo({ top: 0, behavior: 'smooth' });
    }
    async function submit() {
        setError('');
        if (!form.managedAck) {
            setError('Confirm that students will use managed Kelp accounts.');
            return;
        }
        setSubmitting(true);
        try {
            const created = await api('/api/academy', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(buildAcademyCreatePayload(form)),
            });
            onCreated(created);
        } catch (err) {
            if (err.message !== 'signin') setError(err.message);
        } finally {
            setSubmitting(false);
        }
    }

    function stepBody() {
        if (step === 1) {
            return (
                <form className="space-y-5" onSubmit={(event) => { event.preventDefault(); goStep(2); }}>
                    <p className="text-sm text-gray-500 leading-relaxed">Your personal Kelp account stays your home. This academy is a separate space for your organization.</p>
                    <Field label="Official academy name">
                        <Input value={form.name} onChange={(e) => setField('name', e.target.value)} required />
                    </Field>
                    <Field label="Short name or acronym">
                        <Input value={form.shortName} onChange={(e) => setField('shortName', e.target.value)} placeholder="Optional" />
                    </Field>
                    <Field label="Preferred URL slug" hint="Optional. Leave blank to generate from the name.">
                        <Input value={form.slug} onChange={(e) => setField('slug', e.target.value)} placeholder="lincoln-academy" />
                    </Field>
                    <div className="grid sm:grid-cols-2 gap-4">
                        <Field label="Academy type">
                            <DropdownSelect
                                value={form.schoolType}
                                onChange={(v) => setField('schoolType', v)}
                                options={ACADEMY_TYPE_OPTIONS}
                            />
                        </Field>
                        <Field label="Locale">
                            <DropdownSelect
                                value={form.locale}
                                onChange={(v) => setField('locale', v)}
                                options={LOCALE_OPTIONS}
                            />
                        </Field>
                    </div>
                    <Field label="Time zone">
                        <DropdownSelect
                            value={form.timezone}
                            onChange={(v) => setField('timezone', v)}
                            options={TIMEZONE_OPTIONS}
                        />
                    </Field>
                    <CreateAcademyActions onBack={null} continueLabel="Continue" />
                </form>
            );
        }
        if (step === 2) {
            return (
                <form className="space-y-5" onSubmit={(event) => { event.preventDefault(); goStep(3); }}>
                    <ColorField label="Primary brand color" value={form.primaryColor} onChange={(v) => setField('primaryColor', v)} />
                    <ColorField label="Secondary color" value={form.secondaryColor} onChange={(v) => setField('secondaryColor', v)} />
                    <ColorField label="Accent color" value={form.accentColor} onChange={(v) => setField('accentColor', v)} />
                    <Field label="Tagline or motto">
                        <Input value={form.tagline} onChange={(e) => setField('tagline', e.target.value)} placeholder="Optional" />
                    </Field>
                    <CreateAcademyActions onBack={() => goStep(1)} continueLabel="Continue" />
                </form>
            );
        }
        if (step === 3) {
            return (
                <form className="space-y-5" onSubmit={(event) => { event.preventDefault(); goStep(4); }}>
                    <LogoDropZone
                        logoDataUrl={form.logoDataUrl}
                        onFile={loadLogoFile}
                        onClear={() => { setField('logoDataUrl', ''); setLogoError(''); }}
                        error={logoError}
                    />
                    <CreateAcademyActions onBack={() => goStep(2)} continueLabel="Continue" />
                </form>
            );
        }
        if (step === 4) {
            return (
                <form className="space-y-5" onSubmit={(event) => { event.preventDefault(); goStep(5); }}>
                    <Field label="Seat limit" hint="Leave empty for no limit.">
                        <Input value={form.seatLimit} onChange={(e) => setField('seatLimit', e.target.value)} placeholder="Unlimited" />
                    </Field>
                    <Field label="Who can send invites">
                        <DropdownSelect
                            value={form.whoCanInvite}
                            onChange={(v) => setField('whoCanInvite', v)}
                            options={INVITE_POLICY_OPTIONS}
                        />
                    </Field>
                    <Field label="Default role for new invites">
                        <DropdownSelect
                            value={form.defaultInviteRole}
                            onChange={(v) => setField('defaultInviteRole', v)}
                            options={INVITE_ROLE_OPTIONS}
                        />
                    </Field>
                    <CreateAcademyActions onBack={() => goStep(3)} continueLabel="Continue" />
                </form>
            );
        }
        if (step === 5) {
            return (
                <form className="space-y-5" onSubmit={(event) => { event.preventDefault(); goStep(6); }}>
                    <EmailDomainAllowlistField
                        domains={form.emailDomains}
                        onChange={(list) => setField('emailDomains', list)}
                    />
                    <Field label="Who can upload shared material">
                        <DropdownSelect
                            value={form.sharedUploadPolicy}
                            onChange={(v) => setField('sharedUploadPolicy', v)}
                            options={SHARED_UPLOAD_OPTIONS}
                        />
                    </Field>
                    <Field label="Data retention notes" hint="Internal policy summary for your team.">
                        <Input value={form.dataRetentionNote} onChange={(e) => setField('dataRetentionNote', e.target.value)} placeholder="Optional" />
                    </Field>
                    <label className="flex items-start gap-3 text-sm text-gray-600 bg-[#f5f5f5] rounded-2xl p-4">
                        <input type="checkbox" checked={form.managedAck} onChange={(e) => setField('managedAck', e.target.checked)} className="mt-0.5 size-4 accent-[var(--create-brand)]" />
                        <span>Students will use managed Kelp accounts provisioned by this academy (not personal Kelp homes).</span>
                    </label>
                    <CreateAcademyActions onBack={() => goStep(4)} continueLabel="Continue" />
                </form>
            );
        }
        if (step === 6) {
            return (
                <form className="space-y-5" onSubmit={(event) => { event.preventDefault(); goStep(7); }}>
                    <p className="text-sm text-gray-500">Shown to students as &quot;Contact your admin.&quot;</p>
                    <Field label="Support contact name">
                        <Input value={form.supportName} onChange={(e) => setField('supportName', e.target.value)} required />
                    </Field>
                    <Field label="Support contact email">
                        <Input type="email" value={form.supportEmail} onChange={(e) => setField('supportEmail', e.target.value)} required />
                    </Field>
                    <CreateAcademyActions onBack={() => goStep(5)} continueLabel="Continue" />
                </form>
            );
        }
        if (step === 7) {
            return (
                <form className="space-y-5" onSubmit={(event) => { event.preventDefault(); goStep(8); }}>
                    <p className="text-sm text-gray-500 leading-relaxed">
                        Choose which sections appear in your academy sidebar. You can change these later in Settings.
                    </p>
                    <div className="space-y-3">
                        {CREATE_FEATURE_TOGGLES.map((item) => (
                            <CreateFeatureToggle
                                key={item.key}
                                icon={item.icon}
                                title={item.title}
                                description={item.description}
                                checked={form[item.key]}
                                onChange={(value) => setField(item.key, value)}
                            />
                        ))}
                    </div>
                    <CreateAcademyActions onBack={() => goStep(6)} continueLabel="Continue" />
                </form>
            );
        }
        return (
            <div className="space-y-5 pb-4">
                <CreateAcademyReview form={form} />
                {error ? <p className="text-sm text-red-700/80">{error}</p> : null}
                <CreateAcademyActions
                    onBack={() => goStep(7)}
                    continueLabel={submitting ? 'Creating…' : 'Create academy'}
                    onContinue={submit}
                    continueType="button"
                    continueDisabled={submitting}
                />
            </div>
        );
    }

    if (!sizeTier) {
        return (
            <div className="min-h-screen bg-[#fafafa]">
                <AcademySizeGatePopup onClose={onClose} onSelect={chooseSizeTier} />
            </div>
        );
    }

    return (
        <div
            className="academy-create min-h-screen lg:h-screen lg:max-h-screen lg:overflow-hidden flex flex-col lg:flex-row bg-[#fafafa]"
            style={{ '--create-brand': primary }}
        >
            <aside className="academy-create-rail lg:w-[17.5rem] shrink-0 border-b lg:border-b-0 lg:border-r border-gray-200/80 bg-white lg:h-full flex flex-col">
                <div className="px-5 pt-5 pb-4 flex items-center justify-between gap-3">
                    <button
                        type="button"
                        onClick={onClose}
                        aria-label="Back to academies"
                        className="size-11 rounded-full border-2 border-gray-200 bg-white hover:bg-gray-50 flex items-center justify-center text-gray-700"
                    >
                        <span className="material-symbols-outlined text-xl">arrow_back</span>
                    </button>
                </div>
                <nav className="px-3 pt-1 pb-5 lg:pb-8 flex lg:flex-col gap-1 overflow-x-auto lg:overflow-visible" aria-label="Create academy steps">
                    {CREATE_STEP_META.map((item, index) => {
                        const n = index + 1;
                        const active = n === step;
                        const done = n < step;
                        return (
                            <button
                                key={item.title}
                                type="button"
                                disabled={n > step}
                                onClick={() => { if (n < step) goStep(n); }}
                                className={'academy-create-step flex items-center gap-3 rounded-2xl px-3 py-2.5 text-left min-w-[12rem] lg:min-w-0 '
                                    + (active ? 'academy-create-step--active' : done ? 'academy-create-step--done' : 'opacity-50')}
                            >
                                <span className={'academy-create-step-dot flex items-center justify-center size-8 rounded-full text-xs font-bold shrink-0 '
                                    + (active || done ? 'text-white' : 'bg-gray-100 text-gray-500')}>
                                    {done ? <span className="material-symbols-outlined text-base">check</span> : n}
                                </span>
                                <span className="min-w-0">
                                    <span className="block text-sm font-bold text-gray-900 truncate">{item.title}</span>
                                    <span className="block text-xs text-gray-500 truncate lg:hidden xl:block">{item.lead}</span>
                                </span>
                            </button>
                        );
                    })}
                </nav>
                <div className="mt-auto px-5 pb-6 hidden lg:block">
                    <div className="h-1.5 rounded-full bg-gray-100 overflow-hidden">
                        <div className="h-full rounded-full transition-all duration-300" style={{ width: progress + '%', backgroundColor: primary }} />
                    </div>
                    <p className="text-xs text-gray-400 mt-2">Step {step} of {steps}</p>
                </div>
            </aside>

            <div className="flex-1 flex flex-col xl:flex-row min-w-0 min-h-0 overflow-hidden">
                <main className="academy-create-main flex-1 min-h-0 overflow-y-auto overscroll-contain px-5 sm:px-8 lg:px-12 py-8 lg:py-12">
                    <div className="max-w-xl">
                        <div className="flex items-center gap-3 mb-2">
                            <span className="material-symbols-outlined text-[2rem] sm:text-4xl text-gray-400 shrink-0">{meta.icon}</span>
                            <h1 className="text-3xl sm:text-4xl font-bold text-gray-900 tracking-tight">{meta.title}</h1>
                        </div>
                        <p className="text-base text-gray-500 mt-2 mb-8 leading-relaxed">{meta.lead}</p>
                        <div key={step} className="academy-create-panel">{stepBody()}</div>
                    </div>
                </main>
                <aside className="academy-create-preview-pane hidden xl:flex xl:w-[min(36rem,42vw)] shrink-0 items-center justify-center px-10 py-12">
                    <CreateAcademyPreview form={form} />
                </aside>
            </div>

        </div>
    );
}

function CreateAcademyActions({ onBack, continueLabel, onContinue, continueType, continueDisabled }) {
    return (
        <div className="academy-create-actions flex flex-wrap items-center gap-3 pt-2">
            {onBack ? (
                <button
                    type="button"
                    onClick={onBack}
                    aria-label="Previous step"
                    className="size-11 shrink-0 rounded-full border-2 border-gray-200 bg-white hover:bg-gray-50 flex items-center justify-center text-gray-700"
                >
                    <span className="material-symbols-outlined">arrow_back</span>
                </button>
            ) : null}
            <Button type={continueType || 'submit'} onClick={onContinue} disabled={continueDisabled}>{continueLabel}</Button>
        </div>
    );
}

const INVITE_BATCH_MAX = 200;
const INVITE_CSV_ACCEPT = '.csv,text/csv,text/plain';

function parseInviteBatchText(batch) {
    return String(batch || '')
        .split('\n')
        .map((line) => line.trim())
        .filter(Boolean)
        .map((line) => {
            const [email, role] = line.split(/[,;\s]+/);
            return { email: String(email || '').trim(), role: (role || 'student').trim().toLowerCase() || 'student' };
        })
        .filter((row) => row.email);
}

function parseCsvRowCells(line) {
    const cells = [];
    let i = 0;
    const text = String(line || '');
    while (i < text.length) {
        if (text[i] === '"') {
            let cell = '';
            i += 1;
            while (i < text.length) {
                if (text[i] === '"' && text[i + 1] === '"') {
                    cell += '"';
                    i += 2;
                    continue;
                }
                if (text[i] === '"') {
                    i += 1;
                    break;
                }
                cell += text[i];
                i += 1;
            }
            cells.push(cell.trim());
            if (text[i] === ',') i += 1;
            continue;
        }
        const comma = text.indexOf(',', i);
        if (comma === -1) {
            cells.push(text.slice(i).trim());
            break;
        }
        cells.push(text.slice(i, comma).trim());
        i = comma + 1;
    }
    return cells;
}

function parseInviteCsvText(text) {
    const rows = String(text || '')
        .split(/\r?\n/)
        .map((line) => line.trim())
        .filter(Boolean)
        .map(parseCsvRowCells);
    if (!rows.length) return [];
    let start = 0;
    const firstCell = String(rows[0][0] || '').trim().toLowerCase();
    if (!firstCell.includes('@') && (firstCell === 'email' || firstCell === 'e-mail' || firstCell === 'email address')) start = 1;
    const members = [];
    for (let r = start; r < rows.length; r += 1) {
        const email = String(rows[r][0] || '').trim();
        if (!email) continue;
        const roleRaw = String(rows[r][1] || 'student').trim().toLowerCase();
        members.push({ email, role: roleRaw || 'student' });
    }
    return members;
}

function capInviteMembers(members) {
    const list = Array.isArray(members) ? members : [];
    if (list.length <= INVITE_BATCH_MAX) return { members: list, truncated: false };
    return { members: list.slice(0, INVITE_BATCH_MAX), truncated: true };
}

function InviteCsvDropZone({ fileName, memberCount, onFile, onClear }) {
    const [dragOver, setDragOver] = useState(false);
    const inputRef = useRef(null);

    function pickFiles(fileList) {
        const file = fileList && fileList[0];
        if (file) onFile(file);
    }

    return (
        <div
            role="button"
            tabIndex={0}
            onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') inputRef.current && inputRef.current.click(); }}
            onDragOver={(event) => { event.preventDefault(); setDragOver(true); }}
            onDragLeave={(event) => { event.preventDefault(); setDragOver(false); }}
            onDrop={(event) => {
                event.preventDefault();
                setDragOver(false);
                pickFiles(event.dataTransfer.files);
            }}
            onClick={() => inputRef.current && inputRef.current.click()}
            className={'rounded-3xl border-2 border-dashed p-8 text-center cursor-pointer transition-colors '
                + (dragOver ? 'border-primary bg-primary/5' : 'border-gray-200 bg-white hover:border-gray-300 hover:bg-[#fafafa]')}
        >
            <input
                ref={inputRef}
                type="file"
                accept={INVITE_CSV_ACCEPT}
                className="sr-only"
                onChange={(event) => pickFiles(event.target.files)}
            />
            {fileName ? (
                <div className="flex flex-col items-center gap-3" onClick={(event) => event.stopPropagation()}>
                    <span className="material-symbols-outlined text-5xl text-primary">table_rows</span>
                    <p className="font-bold text-gray-900">{fileName}</p>
                    <p className="text-sm text-gray-500">{memberCount} {memberCount === 1 ? 'person' : 'people'} ready to invite</p>
                    <div className="flex flex-wrap justify-center gap-2">
                        <Button type="button" variant="secondary" onClick={() => inputRef.current && inputRef.current.click()}>Replace file</Button>
                        <Button type="button" variant="ghost" onClick={onClear}>Remove</Button>
                    </div>
                </div>
            ) : (
                <div className="pointer-events-none flex flex-col items-center gap-3">
                    <span className="material-symbols-outlined text-5xl text-gray-300">upload_file</span>
                    <p className="font-bold text-gray-900">Drop a CSV file</p>
                    <p className="text-sm text-gray-500 max-w-sm">Columns: email, role (optional). Header row optional.</p>
                </div>
            )}
        </div>
    );
}

function InviteMembers({ academyId, onClose, onDone }) {
    const [step, setStep] = useState(1);
    const [mode, setMode] = useState('');
    const [personRole, setPersonRole] = useState('student');
    const [personEmail, setPersonEmail] = useState('');
    const [batch, setBatch] = useState('');
    const [batchMembers, setBatchMembers] = useState([]);
    const [csvFileName, setCsvFileName] = useState('');
    const [links, setLinks] = useState([]);
    const [error, setError] = useState('');
    const steps = mode === 'one' ? 4 : 3;

    async function send(members) {
        setError('');
        const capped = capInviteMembers(members);
        if (!capped.members.length) {
            setError('Add at least one email.');
            return;
        }
        if (capped.truncated) {
            setError('Only the first ' + INVITE_BATCH_MAX + ' rows are sent per batch.');
        }
        try {
            const result = await api('/api/academy/' + academyId + '/members/provision', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ members: capped.members }),
            });
            setLinks(result.results || []);
            setStep(steps);
            onDone();
        } catch (err) {
            if (err.message !== 'signin') setError(err.message);
        }
    }

    function readCsvFile(file) {
        setError('');
        if (!file) return;
        const name = String(file.name || '');
        if (!/\.csv$/i.test(name) && file.type && file.type !== 'text/csv' && file.type !== 'text/plain') {
            setError('Upload a .csv file.');
            return;
        }
        if (file.size > 1024 * 1024) {
            setError('CSV must be 1 MB or smaller.');
            return;
        }
        const reader = new FileReader();
        reader.onload = () => {
            const parsed = parseInviteCsvText(String(reader.result || ''));
            if (!parsed.length) {
                setError('No email rows found. Use columns email and role.');
                setCsvFileName('');
                setBatchMembers([]);
                return;
            }
            const capped = capInviteMembers(parsed);
            setCsvFileName(name);
            setBatchMembers(capped.members);
            if (capped.truncated) {
                setError('Only the first ' + INVITE_BATCH_MAX + ' rows will be invited.');
            }
        };
        reader.onerror = () => setError('Could not read that file.');
        reader.readAsText(file);
    }

    function downloadCsvTemplate() {
        const sample = 'email,role\nstudent@academy.edu,student\nteacher@academy.edu,teacher\n';
        const blob = new Blob([sample], { type: 'text/csv;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const anchor = document.createElement('a');
        anchor.href = url;
        anchor.download = 'kelp-academy-invites-template.csv';
        anchor.click();
        URL.revokeObjectURL(url);
    }

    return (
        <Popup title="Invite" step={Math.min(step, steps)} steps={steps} onClose={onClose}>
            {error ? <p className="text-sm text-gray-600 mb-4">{error}</p> : null}
            {step === 1 ? (
                <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
                    <FeatureTile icon="person_add" title="One person" description="Email and a role." onClick={() => { setMode('one'); setStep(2); }} />
                    <FeatureTile icon="group_add" title="A list" description="One email per line." onClick={() => { setMode('list'); setStep(2); }} />
                    <FeatureTile icon="table_rows" title="CSV file" description="Spreadsheet export." onClick={() => { setMode('csv'); setStep(2); setBatchMembers([]); setCsvFileName(''); }} />
                </div>
            ) : null}
            {step === 2 && mode === 'one' ? (
                <div className="space-y-4">
                    <div className="grid grid-cols-1 gap-3">
                        <FeatureTile icon="school" title="Student" description="A managed Kelp account." selected={personRole === 'student'} onClick={() => setPersonRole('student')} />
                        <FeatureTile icon="co_present" title="Teacher" description="Can see members." selected={personRole === 'teacher'} onClick={() => setPersonRole('teacher')} />
                        <FeatureTile icon="admin_panel_settings" title="Admin" description="Can invite people." selected={personRole === 'admin'} onClick={() => setPersonRole('admin')} />
                    </div>
                    <div className="flex gap-3">
                        <Button type="button" onClick={() => setStep(3)}>Next</Button>
                        <Button type="button" variant="secondary" onClick={() => setStep(1)}>Back</Button>
                    </div>
                </div>
            ) : null}
            {step === 2 && mode === 'list' ? (
                <form className="space-y-4" onSubmit={(event) => {
                    event.preventDefault();
                    setBatchMembers(parseInviteBatchText(batch));
                    setStep(3);
                }}>
                    <textarea
                        className="w-full px-3 py-2.5 border-2 border-gray-200 rounded-xl focus:border-primary focus:outline-none text-sm bg-white min-h-[120px]"
                        placeholder={'one per line: email or email, role\nteacher@academy.edu, teacher'}
                        value={batch}
                        onChange={(event) => setBatch(event.target.value)}
                        required
                    />
                    <div className="flex gap-3">
                        <Button type="submit">Next</Button>
                        <Button type="button" variant="secondary" onClick={() => setStep(1)}>Back</Button>
                    </div>
                </form>
            ) : null}
            {step === 2 && mode === 'csv' ? (
                <div className="space-y-4">
                    <InviteCsvDropZone
                        fileName={csvFileName}
                        memberCount={batchMembers.length}
                        onFile={readCsvFile}
                        onClear={() => { setCsvFileName(''); setBatchMembers([]); setError(''); }}
                    />
                    <button type="button" className="text-sm font-bold text-primary hover:underline" onClick={downloadCsvTemplate}>
                        Download template CSV
                    </button>
                    <div className="flex gap-3">
                        <Button type="button" disabled={!batchMembers.length} onClick={() => setStep(3)}>Next</Button>
                        <Button type="button" variant="secondary" onClick={() => setStep(1)}>Back</Button>
                    </div>
                </div>
            ) : null}
            {step === 3 && mode === 'one' ? (
                <form className="space-y-4" onSubmit={(event) => { event.preventDefault(); send([{ email: personEmail.trim(), role: personRole }]); }}>
                    <p className="text-sm text-gray-500">Invite a {personRole}. They get a join link.</p>
                    <Input value={personEmail} onChange={(event) => setPersonEmail(event.target.value)} placeholder="name@academy.edu" type="email" required />
                    <div className="flex gap-3">
                        <Button type="submit">Create invite link</Button>
                        <Button type="button" variant="secondary" onClick={() => setStep(2)}>Back</Button>
                    </div>
                </form>
            ) : null}
            {step === 3 && (mode === 'list' || mode === 'csv') && !links.length ? (
                <div className="space-y-4">
                    <p className="text-sm text-gray-500">{batchMembers.length} people. Students open Kelp. Teachers and admins stay here.</p>
                    {mode === 'csv' && batchMembers.length ? (
                        <ul className="rounded-2xl border border-gray-100 divide-y divide-gray-100 max-h-40 overflow-y-auto text-sm">
                            {batchMembers.slice(0, 8).map((row) => (
                                <li key={row.email} className="px-4 py-2 flex justify-between gap-3">
                                    <span className="truncate text-gray-900">{row.email}</span>
                                    <span className="text-gray-400 shrink-0">{row.role}</span>
                                </li>
                            ))}
                            {batchMembers.length > 8 ? (
                                <li className="px-4 py-2 text-gray-400">+ {batchMembers.length - 8} more</li>
                            ) : null}
                        </ul>
                    ) : null}
                    <div className="flex gap-3">
                        <Button type="button" onClick={() => send(batchMembers)}>Create invite links</Button>
                        <Button type="button" variant="secondary" onClick={() => setStep(2)}>Back</Button>
                    </div>
                </div>
            ) : null}
            {links.length ? (
                <div className="space-y-3">
                    {links.map((link) => (
                        <div key={link.email} className="bg-[#f5f5f5] rounded-3xl p-4">
                            <div className="font-bold text-gray-900">{link.email}</div>
                            <div className="text-sm text-gray-500">{link.status}{link.error ? ' — ' + link.error : ''}</div>
                            {link.inviteUrl ? <a className="text-sm text-gray-700 break-all mt-1 block" href={inviteHref(link.inviteUrl)}>{inviteHref(link.inviteUrl)}</a> : null}
                        </div>
                    ))}
                    <Button type="button" variant="secondary" onClick={onClose}>Done</Button>
                </div>
            ) : null}
        </Popup>
    );
}

const MEMBER_ROLE_META = {
    student: { icon: 'school', label: 'Student' },
    teacher: { icon: 'co_present', label: 'Teacher' },
    admin: { icon: 'admin_panel_settings', label: 'Admin' },
    owner: { icon: 'verified_user', label: 'Owner' },
};

async function storageOwnerIdFromSubject(subject) {
    const enc = new TextEncoder().encode(String(subject || ''));
    const buf = await crypto.subtle.digest('SHA-256', enc);
    const bytes = new Uint8Array(buf);
    const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join('');
    return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '').slice(0, 32);
}

function memberIsViewer(member, viewerEmail, viewerUserId) {
    if (!member) return false;
    if (viewerUserId && member.userId && member.userId === viewerUserId) return true;
    if (!viewerEmail) return false;
    const memberEmail = String(member.email || '').trim().toLowerCase();
    const viewer = String(viewerEmail || '').trim().toLowerCase();
    return Boolean(memberEmail && viewer && memberEmail === viewer);
}

function YouBadge() {
    return (
        <span className="inline-flex items-center gap-1 text-xs font-bold text-primary bg-primary/10 px-2 py-0.5 rounded-full shrink-0">
            <span className="material-symbols-outlined text-sm">account_circle</span>
            You
        </span>
    );
}

function MemberMenuActionButton({ icon, label, current, danger, readOnly, onClick }) {
    const className = 'w-full flex items-center gap-4 text-left bg-[#f5f5f5] rounded-3xl px-5 py-4 font-bold '
        + (readOnly ? 'cursor-default ' : 'transition-shadow hover:shadow-md ')
        + (current ? 'ring-2 ring-primary ' : '');
    const body = (
        <>
            <span className={'material-symbols-outlined text-[1.75rem] shrink-0 ' + (danger ? 'text-red-600' : 'text-gray-600')}>{icon}</span>
            <span className="flex-1 min-w-0">{label}</span>
            {current ? (
                <span className="material-symbols-outlined text-primary shrink-0" aria-label="Current role">check_circle</span>
            ) : null}
        </>
    );
    if (readOnly) return <div className={className}>{body}</div>;
    return (
        <button type="button" onClick={onClick} className={className + ' text-gray-900'}>
            {body}
        </button>
    );
}

function MemberMenu({ member, viewerEmail, viewerUserId, members, onClose, onRole, onRemove, onTransferOwnership }) {
    const [step, setStep] = useState(1);
    const [choice, setChoice] = useState('');
    const [successor, setSuccessor] = useState(null);
    const [error, setError] = useState('');
    const isSelf = memberIsViewer(member, viewerEmail, viewerUserId);
    const isSelfOwner = isSelf && member.role === 'owner';
    const successors = (members || []).filter((row) => row.userId !== member.userId);
    const popupSteps = isSelfOwner ? 3 : 2;

    async function confirm() {
        setError('');
        try {
            if (choice === 'remove') await onRemove();
            else if (choice === 'transfer' && successor) await onTransferOwnership(successor.userId);
            else await onRole(choice);
            onClose();
        } catch (err) {
            if (err.message !== 'signin') setError(err.message);
        }
    }

    return (
        <Popup title={member.email || 'Member'} step={Math.min(step, popupSteps)} steps={popupSteps} onClose={onClose}>
            {isSelf ? (
                <div className="mb-4">
                    <YouBadge />
                </div>
            ) : null}
            {step === 1 && isSelfOwner ? (
                <div className="space-y-3">
                    <MemberMenuActionButton
                        icon={MEMBER_ROLE_META.owner.icon}
                        label={MEMBER_ROLE_META.owner.label}
                        current
                        readOnly
                    />
                    <p className="text-sm text-gray-500 px-1">You cannot remove yourself or change your role here. Transfer ownership to someone else first.</p>
                    <MemberMenuActionButton
                        icon="swap_horiz"
                        label="Transfer ownership"
                        onClick={() => { setChoice('transfer'); setStep(2); }}
                    />
                </div>
            ) : null}
            {step === 1 && isSelf && !isSelfOwner ? (
                <p className="text-sm text-gray-500">You cannot change your own role or remove yourself. Ask an academy owner for help.</p>
            ) : null}
            {step === 1 && !isSelf ? (
                <div className="space-y-3">
                    {['student', 'teacher', 'admin', 'owner'].map((role) => {
                        const meta = MEMBER_ROLE_META[role];
                        const isCurrent = member.role === role;
                        return (
                            <MemberMenuActionButton
                                key={role}
                                icon={meta.icon}
                                label={isCurrent ? meta.label : 'Make ' + meta.label.toLowerCase()}
                                current={isCurrent}
                                onClick={isCurrent ? undefined : () => { setChoice(role); setStep(2); }}
                                readOnly={isCurrent}
                            />
                        );
                    })}
                    {member.role !== 'owner' ? (
                        <MemberMenuActionButton
                            icon="person_remove"
                            label="Remove"
                            danger
                            onClick={() => { setChoice('remove'); setStep(2); }}
                        />
                    ) : null}
                </div>
            ) : null}
            {step === 2 && choice === 'transfer' ? (
                <div className="space-y-3">
                    <p className="text-sm text-gray-500">Choose who should become the new owner. You will become an admin.</p>
                    {successors.length === 0 ? (
                        <p className="text-sm text-gray-500 bg-[#f5f5f5] rounded-3xl px-5 py-4">Invite another staff member first, then transfer ownership.</p>
                    ) : null}
                    {successors.map((row) => (
                        <MemberMenuActionButton
                            key={row.userId}
                            icon="person"
                            label={row.email || row.userId}
                            onClick={() => { setSuccessor(row); setStep(3); }}
                        />
                    ))}
                    <Button type="button" variant="secondary" onClick={() => { setStep(1); setChoice(''); }}>Back</Button>
                </div>
            ) : null}
            {step === 2 && choice !== 'transfer' ? (
                <div className="space-y-4">
                    <p className="text-sm text-gray-500">
                        {choice === 'remove' ? 'Remove this person from the academy.' : 'Change their role to ' + choice + '.'}
                    </p>
                    {error ? <p className="text-sm text-gray-600">{error}</p> : null}
                    <div className="flex gap-3">
                        <Button type="button" onClick={confirm}>{choice === 'remove' ? 'Remove' : 'Change role'}</Button>
                        <Button type="button" variant="secondary" onClick={() => setStep(1)}>Back</Button>
                    </div>
                </div>
            ) : null}
            {step === 3 && choice === 'transfer' && successor ? (
                <div className="space-y-4">
                    <p className="text-sm text-gray-500">
                        Make <span className="font-bold text-gray-900">{successor.email || successor.userId}</span> the owner? You will become an admin.
                    </p>
                    {error ? <p className="text-sm text-gray-600">{error}</p> : null}
                    <div className="flex gap-3">
                        <Button type="button" onClick={confirm}>Transfer ownership</Button>
                        <Button type="button" variant="secondary" onClick={() => setStep(2)}>Back</Button>
                    </div>
                </div>
            ) : null}
        </Popup>
    );
}

function useAcademyStaffData(academyId) {
    const [data, setData] = useState(null);
    const [error, setError] = useState('');

    async function load() {
        const academy = await api('/api/academy/' + academyId);
        let members = { members: [], invites: [] };
        if (academy.role && academy.role !== 'student') {
            members = await api('/api/academy/' + academyId + '/members');
        }
        setData({ academy, members });
    }

    useEffect(() => { load().catch((err) => { if (err.message !== 'signin') setError(err.message); }); }, [academyId]);

    return { data, error, setError, load };
}

function AcademyMembersWorkspace({ academyId, viewerEmail, viewerUserId }) {
    const { data, error, setError, load } = useAcademyStaffData(academyId);
    const [inviteOpen, setInviteOpen] = useState(false);
    const [menuMember, setMenuMember] = useState(null);
    const [revokeInvite, setRevokeInvite] = useState(null);

    async function changeRole(userId, role) {
        await api('/api/academy/' + academyId + '/members/' + userId, {
            method: 'PATCH',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ role }),
        });
        await load();
    }

    async function removeMember(userId) {
        await api('/api/academy/' + academyId + '/members/' + userId, { method: 'DELETE' });
        await load();
    }

    async function transferOwnership(fromUserId, toUserId) {
        await changeRole(toUserId, 'owner');
        await changeRole(fromUserId, 'admin');
    }

    async function revoke(inviteId) {
        try {
            await api('/api/academy/' + academyId + '/invites/' + inviteId, { method: 'DELETE' });
            await load();
        } catch (err) {
            if (err.message !== 'signin') setError(err.message);
        }
    }

    if (!data) return <KelpSpinnerScreen error={error} />;
    const info = data.academy;
    const members = data.members.members || [];
    const invites = data.members.invites || [];
    if (info.role === 'student') {
        const support = info.settings && info.settings.support;
        return <ManagedKelpAccount academyName={info.academy.name} support={support} />;
    }

    return (
        <div className="space-y-8">
            {error ? <p className="text-sm text-gray-600">{error}</p> : null}
            <section>
                <div className="flex items-center justify-between gap-3">
                    <Label>Members</Label>
                    <button
                        type="button"
                        aria-label="Invite"
                        onClick={() => setInviteOpen(true)}
                        className="bg-[#f5f5f5] rounded-3xl shadow-sm hover:shadow-md h-12 w-12 flex items-center justify-center"
                    >
                        <span className="material-symbols-outlined text-gray-700">add</span>
                    </button>
                </div>
                <div className="mt-3">
                    <ListTable
                        columns={['Person', 'Role', '']}
                        rows={members.map((member) => (
                            <div key={member.userId} className="grid grid-cols-3 gap-3 items-center px-4 py-3">
                                <button type="button" className="text-left text-sm text-gray-900 flex items-center gap-2 min-w-0" onClick={() => go('/academies/' + academyId + '/members/' + member.userId)}>
                                    <span className="truncate">{member.email || member.userId}</span>
                                    {memberIsViewer(member, viewerEmail, viewerUserId) ? <YouBadge /> : null}
                                </button>
                                <span className="text-sm text-gray-500">{member.role}</span>
                                <Button variant="secondary" onClick={() => setMenuMember(member)}>Manage</Button>
                            </div>
                        ))}
                        empty="No members yet."
                    />
                </div>
            </section>
            <section>
                <Label>Pending invites</Label>
                <ul className="mt-3 rounded-2xl border border-gray-200 divide-y divide-gray-100 bg-white">
                    {invites.length === 0 ? <li className="px-4 py-6 text-sm text-gray-500">No pending invites.</li> : null}
                    {invites.map((invite) => (
                        <li key={invite.id} className="px-4 py-3 flex items-center justify-between gap-3">
                            <div>
                                <div className="text-sm text-gray-900">{invite.email}</div>
                                <div className="text-xs text-gray-400">{invite.role}</div>
                            </div>
                            <Button variant="secondary" onClick={() => setRevokeInvite(invite)}>Revoke</Button>
                        </li>
                    ))}
                </ul>
            </section>
            {inviteOpen ? (
                <InviteMembers academyId={academyId} onClose={() => setInviteOpen(false)} onDone={() => load().catch(() => {})} />
            ) : null}
            {menuMember ? (
                <MemberMenu
                    member={menuMember}
                    viewerEmail={viewerEmail}
                    viewerUserId={viewerUserId}
                    members={members}
                    onClose={() => setMenuMember(null)}
                    onRole={(role) => changeRole(menuMember.userId, role)}
                    onRemove={() => removeMember(menuMember.userId)}
                    onTransferOwnership={(toUserId) => transferOwnership(menuMember.userId, toUserId)}
                />
            ) : null}
            {revokeInvite ? (
                <Popup title="Revoke invite" step={1} steps={1} onClose={() => setRevokeInvite(null)}>
                    <div className="space-y-4">
                        <p className="text-sm text-gray-500">Revoke the invite for {revokeInvite.email}. The link will stop working.</p>
                        <div className="flex gap-3">
                            <Button type="button" onClick={() => { const id = revokeInvite.id; setRevokeInvite(null); revoke(id); }}>Revoke</Button>
                            <Button type="button" variant="secondary" onClick={() => setRevokeInvite(null)}>Back</Button>
                        </div>
                    </div>
                </Popup>
            ) : null}
        </div>
    );
}

function AcademyMembersPage({ academyId, viewerEmail, viewerUserId }) {
    return (
        <main className="max-w-5xl mx-auto px-6 py-10 space-y-8">
            <PageHeader title="Members" subtitle="Roster, roles, and invites" />
            <AcademyMembersWorkspace academyId={academyId} viewerEmail={viewerEmail} viewerUserId={viewerUserId} />
        </main>
    );
}

function formatIsoDate(value) {
    if (!value) return '—';
    try {
        return new Date(value).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
    } catch (error) {
        return value;
    }
}

function AcademyEntitlementsPage({ academyId }) {
    const [data, setData] = useState(null);
    const [error, setError] = useState('');
    useEffect(() => {
        api('/api/academy/' + academyId + '/entitlements')
            .then(setData)
            .catch((err) => { if (err.message !== 'signin') setError(err.message); });
    }, [academyId]);
    if (!data && !error) return <KelpSpinnerScreen />;
    const ai = data && data.aiUsage;
    const rows = (data && data.entitlements) || [];
    const primary = rows[0];
    return (
        <main className="max-w-5xl mx-auto px-6 py-10 space-y-8">
            <PageHeader
                title="Entitlements"
                subtitle="Org plan, seats, and AI usage for this academy."
            />
            {error ? <p className="text-sm text-gray-600">{error}</p> : null}
            {data ? (
                <>
                    <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
                        <div className="bg-[#f5f5f5] rounded-3xl p-5 shadow-sm">
                            <Stat value={data.seatsUsed} label="Seats used" />
                        </div>
                        <div className="bg-[#f5f5f5] rounded-3xl p-5 shadow-sm">
                            <Stat value={data.seatLimit == null ? '∞' : data.seatLimit} label="Seat limit" />
                        </div>
                        <div className="bg-[#f5f5f5] rounded-3xl p-5 shadow-sm">
                            <Stat value={ai ? ai.remaining : '—'} label="AI credits left" />
                        </div>
                    </div>
                    <section className="bg-[#f5f5f5] rounded-3xl p-6 shadow-sm space-y-4">
                        <h2 className="text-sm font-bold text-gray-900">Organization entitlement</h2>
                        {primary ? (
                            <div className="grid sm:grid-cols-2 gap-3 text-sm">
                                <div><span className="text-gray-500">Plan</span><div className="font-bold text-gray-900 capitalize">{primary.plan}</div></div>
                                <div><span className="text-gray-500">Status</span><div className="font-bold text-gray-900 capitalize">{primary.status}</div></div>
                                <div><span className="text-gray-500">Period</span><div className="text-gray-900">{formatIsoDate(primary.periodStart)} – {formatIsoDate(primary.periodEnd)}</div></div>
                                <div><span className="text-gray-500">Source</span><div className="text-gray-900">{primary.source || '—'}</div></div>
                                {primary.customAiLimit != null ? (
                                    <div><span className="text-gray-500">Custom AI limit</span><div className="text-gray-900">{primary.customAiLimit}</div></div>
                                ) : null}
                            </div>
                        ) : (
                            <p className="text-sm text-gray-500">No org entitlement row yet — this academy uses the default free plan until billing is configured.</p>
                        )}
                    </section>
                    {ai ? (
                        <section className="bg-white border border-gray-100 rounded-3xl p-6 space-y-3">
                            <h2 className="text-sm font-bold text-gray-900">AI usage (org pool)</h2>
                            <p className="text-sm text-gray-600">
                                {ai.count} of {ai.limit} credits used this period
                                {!data.meterAvailable ? ' (live meter unavailable — counts may be stale)' : ''}.
                            </p>
                            <div className="h-3 rounded-full bg-gray-100 overflow-hidden">
                                <div
                                    className="h-full bg-primary rounded-full transition-all"
                                    style={{ width: String(Math.min(100, ai.limit ? (ai.count / ai.limit) * 100 : 0)) + '%' }}
                                />
                            </div>
                        </section>
                    ) : null}
                </>
            ) : null}
        </main>
    );
}

function Dashboard({ academyId, viewerEmail, viewerUserId, onAcademyContextMenu }) {
    const { data, error } = useAcademyStaffData(academyId);
    if (!data) return <KelpSpinnerScreen error={error} />;
    const info = data.academy;
    if (info.role === 'student') {
        const support = info.settings && info.settings.support;
        return <ManagedKelpAccount academyName={info.academy.name} support={support} />;
    }
    return (
        <main
            className="max-w-5xl mx-auto px-6 py-10 space-y-8"
            onContextMenu={(event) => onAcademyContextMenu(event, { id: academyId, name: info.academy.name, role: info.role })}
        >
            <AcademyPageHeader
                academyId={academyId}
                title={info.academy.name}
                settings={info.settings}
                subtitle={'You are ' + info.role}
                action={(
                    <div className="flex flex-wrap gap-2">
                        <Button variant="secondary" onClick={() => go(academySectionPath(academyId, 'members'))}>Members</Button>
                        <Button variant="secondary" onClick={() => go(academySectionPath(academyId, 'shared'))}>Shared material</Button>
                    </div>
                )}
            />
            <div className="grid grid-cols-2 gap-4">
                <div className="bg-[#f5f5f5] rounded-3xl p-5 shadow-sm">
                    <Stat value={info.memberCount} label="Members" />
                </div>
                <div className="bg-[#f5f5f5] rounded-3xl p-5 shadow-sm">
                    <Stat value={info.seatLimit == null ? '∞' : info.seatLimit} label="Seat limit" />
                </div>
            </div>
            {error ? <p className="text-sm text-gray-600">{error}</p> : null}
            <FeatureTile
                icon="groups"
                title="Manage members and invites"
                description="Roster, roles, CSV and link invites."
                onClick={() => go(academySectionPath(academyId, 'members'))}
            />
        </main>
    );
}

function AcademyPlaceholderPage({ academyId, title, description, icon, blurb, extraAction }) {
    return (
        <main className="max-w-5xl mx-auto px-6 py-10 space-y-8">
            <PageHeader title={title} subtitle={description} />
            <div className="bg-[#f5f5f5] rounded-3xl p-10 sm:p-12 text-center shadow-sm">
                <span className="material-symbols-outlined text-6xl text-gray-300">{icon}</span>
                <p className="mt-5 text-base font-bold text-gray-900">Coming soon</p>
                <p className="mt-2 text-sm text-gray-500 max-w-md mx-auto">{blurb}</p>
                {extraAction ? <div className="mt-6 flex justify-center">{extraAction}</div> : null}
            </div>
        </main>
    );
}

function MemberDetail({ academyId, userId }) {
    const [progress, setProgress] = useState(null);
    const [files, setFiles] = useState(null);
    const [error, setError] = useState('');
    useEffect(() => {
        Promise.all([
            api('/api/academy/' + academyId + '/members/' + userId + '/progress'),
            api('/api/academy/' + academyId + '/members/' + userId + '/files'),
        ]).then(([nextProgress, nextFiles]) => {
            setProgress(nextProgress);
            setFiles(nextFiles);
        }).catch((err) => { if (err.message !== 'signin') setError(err.message); });
    }, [academyId, userId]);
    return (
        <main className="max-w-5xl mx-auto px-6 py-10 space-y-8">
            <PageHeader title="Member" subtitle={userId} action={<Button variant="secondary" onClick={() => go(academySectionPath(academyId, 'members'))}>Back</Button>} />
            {error ? <p className="text-sm text-gray-600">{error}</p> : null}
            <section>
                <Label>Progress</Label>
                <ul className="mt-3 rounded-2xl border border-gray-200 divide-y divide-gray-100 bg-white">
                    {(progress && progress.courses || []).length === 0 ? <li className="px-4 py-6 text-sm text-gray-500">No courses yet.</li> : null}
                    {(progress && progress.courses || []).map((course) => (
                        <li key={course.id} className="px-4 py-3">
                            <div className="text-sm font-bold text-gray-900">{course.name || course.id}</div>
                            <div className="text-xs text-gray-400">{(course.subtopics || []).length} subtopics</div>
                        </li>
                    ))}
                </ul>
            </section>
            <section>
                <Label>Files</Label>
                <ul className="mt-3 rounded-2xl border border-gray-200 divide-y divide-gray-100 bg-white">
                    {(files && files.keys || []).length === 0 ? <li className="px-4 py-6 text-sm text-gray-500">No files.</li> : null}
                    {(files && files.keys || []).map((file) => (
                        <li key={file.key} className="px-4 py-3 text-sm text-gray-700">{file.key}</li>
                    ))}
                </ul>
            </section>
        </main>
    );
}

function SharedMaterial({ academyId }) {
    const [keys, setKeys] = useState([]);
    const [error, setError] = useState('');
    async function load() {
        const listing = await api('/api/academy/' + academyId + '/shared/');
        setKeys(listing.keys || []);
    }
    useEffect(() => { load().catch((err) => { if (err.message !== 'signin') setError(err.message); }); }, [academyId]);
    async function upload(event) {
        const file = event.target.files && event.target.files[0];
        if (!file) return;
        setError('');
        try {
            await api('/api/academy/' + academyId + '/shared/files/' + encodeURIComponent(file.name), {
                method: 'PUT',
                headers: { 'Content-Type': file.type || 'application/octet-stream' },
                body: file,
            });
            await load();
        } catch (err) {
            if (err.message !== 'signin') setError(err.message);
        }
    }
    async function remove(key) {
        const relative = key.split('/shared/')[1];
        try {
            await api('/api/academy/' + academyId + '/shared/' + relative, { method: 'DELETE' });
            await load();
        } catch (err) {
            if (err.message !== 'signin') setError(err.message);
        }
    }
    return (
        <main className="max-w-5xl mx-auto px-6 py-10">
            <PageHeader
                title="Shared material"
                subtitle="Teachers and admins can upload. Students can read."
                action={<Button variant="secondary" onClick={() => go(academySectionPath(academyId, ''))}>Back</Button>}
            />
            {error ? <p className="text-sm text-gray-600 mb-4">{error}</p> : null}
            <label className="kelp-btn inline-block mb-6">
                Upload
                <input className="hidden" type="file" onChange={upload} />
            </label>
            <ListTable
                columns={['File', '']}
                rows={keys.map((file) => (
                    <div key={file.key} className="grid grid-cols-2 gap-3 items-center px-4 py-3">
                        <span className="text-sm text-gray-700 break-all">{file.key}</span>
                        <Button variant="secondary" onClick={() => remove(file.key)}>Delete</Button>
                    </div>
                ))}
                empty="No shared files."
            />
        </main>
    );
}

function ManagedKelpAccount({ academyName, support }) {
    const supportLine = support && support.name && support.email
        ? 'Contact your admin: ' + support.name + ' (' + support.email + ').'
        : null;
    return (
        <main className="max-w-lg mx-auto px-6 py-16 space-y-6">
            <KelpLogo className="h-16 mb-8 mx-auto" />
            <FeatureTile
                icon="account_circle"
                title="Your Kelp account is ready"
                description={(academyName ? academyName + ' manages this Kelp account. ' : 'This academy manages this Kelp account. ') + 'Open Kelp to start.'}
                href={kelpAppUrl()}
            />
            {supportLine ? (
                <p className="text-sm text-gray-600 text-center px-2">{supportLine}</p>
            ) : null}
        </main>
    );
}

function Join({ token }) {
    const [message, setMessage] = useState('Accepting invite…');
    useEffect(() => {
        api('/api/academy/invites/accept', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ token }),
        }).then((result) => {
            const membership = result && result.membership;
            const academyId = membership && membership.tenantId;
            if (membership && membership.role === 'student') {
                window.location.assign(kelpAppUrl());
                return;
            }
            if (academyId) {
                go('/academies/' + academyId);
                return;
            }
            setMessage('Joined as ' + (membership && membership.role));
        }).catch((err) => {
            if (err.message === 'signin') return;
            if (err.body && err.body.code === 'personal_account') {
                setMessage('This Kelp account already has a personal home, so this invite cannot attach it. Sign in with the student account the invite was created for.');
                return;
            }
            setMessage(err.message);
        });
    }, [token]);
    return (
        <main className="max-w-lg mx-auto px-6 py-16">
            <KelpLogo className="h-16 mb-8 mx-auto" />
            <FeatureTile icon="school" title="Joining academy" description={message} />
        </main>
    );
}

function App() {
    const [route, setRoute] = useState(parseRoute);
    const [ready, setReady] = useState(false);
    const [signedIn, setSignedIn] = useState(false);
    const [academies, setAcademies] = useState([]);
    const [email, setEmail] = useState('');
    const [viewerUserId, setViewerUserId] = useState('');
    const [academyMenu, setAcademyMenu] = useState(null);
    const [deleteAcademyTarget, setDeleteAcademyTarget] = useState(null);
    const [academyFeatures, setAcademyFeatures] = useState(null);

    function refreshAcademies() {
        return api('/api/academy/mine').then((data) => setAcademies(data.academies || [])).catch(() => {});
    }

    function openAcademyContextMenu(event, academy) {
        if (!academy || academy.role !== 'owner') return;
        event.preventDefault();
        setAcademyMenu({ academy, x: event.clientX, y: event.clientY });
    }

    useEffect(() => {
        installAuthAdapter();
        const onPop = () => setRoute(parseRoute());
        window.addEventListener('popstate', onPop);
        window.KelpCloustanAuth.handleOAuthRedirect(window.location).then(() => {
            restoreReturnPath();
            setRoute(parseRoute());
            return window.KelpCloustanAuth.ensureFreshSession();
        }).then(async (session) => {
            setSignedIn(!!session);
            if (session && session.claims) {
                setEmail(session.claims.email || session.username || '');
                const subject = session.claims.sub
                    || session.claims['cognito:username']
                    || session.username
                    || '';
                if (subject) {
                    try {
                        setViewerUserId(await storageOwnerIdFromSubject(subject));
                    } catch (error) {
                        setViewerUserId('');
                    }
                }
            }
            setReady(true);
        }).catch(() => setReady(true));
        return () => window.removeEventListener('popstate', onPop);
    }, []);

    useEffect(() => {
        if (!signedIn || route.name === 'signin') return;
        api('/api/academy/mine').then((data) => setAcademies(data.academies || [])).catch(() => {});
    }, [signedIn, route.name]);

    useEffect(() => {
        if (!signedIn) return;
        api('/api/me').then((data) => {
            if (data && data.userId) setViewerUserId(data.userId);
        }).catch(() => {});
    }, [signedIn]);

    useEffect(() => {
        const id = route.academyId;
        if (!signedIn || !id) {
            setAcademyFeatures(null);
            return;
        }
        api('/api/academy/' + id).then((payload) => {
            setAcademyFeatures(payload.settings && payload.settings.features ? payload.settings.features : null);
        }).catch(() => setAcademyFeatures(null));
    }, [signedIn, route.academyId, route.name]);

    if (!ready) return <KelpSpinnerScreen />;
    if (!signedIn || route.name === 'signin') return <SignIn joining={route.name === 'join'} />;

    const staffAcademies = academies.filter((academy) => academy.role !== 'student');
    function openCreate() {
        go('/academies/new');
    }
    const studentAcademy = academies.find((academy) => academy.role === 'student');

    if (route.name === 'create') {
        return (
            <CreateAcademy
                onClose={() => go('/')}
                onCreated={(created) => {
                    refreshAcademies();
                    go('/academies/' + created.academy.id);
                }}
            />
        );
    }

    let page = staffAcademies.length === 0 && academies.length
        ? <ManagedKelpAccount academyName={studentAcademy && studentAcademy.name} support={studentAcademy && studentAcademy.support} />
        : <Academies academies={staffAcademies} onCreate={openCreate} onAcademyContextMenu={openAcademyContextMenu} />;
    if (route.name === 'dashboard') page = <Dashboard academyId={route.academyId} viewerEmail={email} viewerUserId={viewerUserId} onAcademyContextMenu={openAcademyContextMenu} />;
    if (route.name === 'members') page = <AcademyMembersPage academyId={route.academyId} viewerEmail={email} viewerUserId={viewerUserId} />;
    if (route.name === 'entitlements') page = <AcademyEntitlementsPage academyId={route.academyId} />;
    if (route.name === 'member') page = <MemberDetail academyId={route.academyId} userId={route.userId} />;
    if (route.name === 'shared') page = <SharedMaterial academyId={route.academyId} />;
    if (ACADEMY_PLACEHOLDER_META[route.name]) {
        const meta = ACADEMY_PLACEHOLDER_META[route.name];
        page = (
            <AcademyPlaceholderPage
                academyId={route.academyId}
                title={meta.title}
                description={meta.description}
                icon={meta.icon}
                blurb={meta.blurb}
                extraAction={route.name === 'members'
                    ? <Button variant="secondary" onClick={() => go(academySectionPath(route.academyId, ''))}>Open overview</Button>
                    : null}
            />
        );
    }
    if (route.name === 'join') page = <Join token={route.token} />;

    const sidebarAcademyId = route.academyId || '';
    const sidebarAcademy = staffAcademies.find((row) => row.id === sidebarAcademyId)
        || academies.find((row) => row.id === sidebarAcademyId);

    return (
        <div className="min-h-screen bg-white lg:flex">
            <SideBar
                academies={staffAcademies}
                academyId={sidebarAcademyId}
                academyName={sidebarAcademy && sidebarAcademy.name}
                academyFeatures={academyFeatures}
                email={email}
                routeName={route.name}
                onCreate={openCreate}
                onAcademyContextMenu={openAcademyContextMenu}
            />
            <div className="min-w-0 flex-1">{page}</div>
            {academyMenu ? (
                <AcademyContextMenu
                    menu={academyMenu}
                    onClose={() => setAcademyMenu(null)}
                    onDelete={(academy) => { setAcademyMenu(null); setDeleteAcademyTarget(academy); }}
                />
            ) : null}
            {deleteAcademyTarget ? (
                <DeleteAcademy
                    academy={deleteAcademyTarget}
                    onClose={() => setDeleteAcademyTarget(null)}
                    onDeleted={() => {
                        const deletedId = deleteAcademyTarget.id;
                        refreshAcademies();
                        if (route.academyId === deletedId) go('/');
                    }}
                />
            ) : null}
        </div>
    );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
