"use client";

import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useAuth } from "@/hooks/useAuth";
import { useRouter } from "next/navigation";
import { apiFetch, resolveToApiOrigin } from "@/lib/api";
import { useDismissingState } from "@/hooks/useDismissingState";
import { Calendar } from "lucide-react";

// ─── helpers ────────────────────────────────────────────────────────────────

function toLocalDT(input: string | Date | null | undefined): string {
  if (!input) return "";
  const d = new Date(input);
  if (isNaN(d.getTime())) return "";
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  const hh = String(d.getHours()).padStart(2, "0");
  const mm = String(d.getMinutes()).padStart(2, "0");
  return `${y}-${m}-${day}T${hh}:${mm}`;
}

const DT_MIN = "2000-01-01T00:00";
const DT_MAX = "2099-12-31T23:59";

function DTInput({ label, value, onChange, hint, required }: { label: string; value: string; onChange: (v: string) => void; hint?: string; required?: boolean }) {
  return (
    <div>
      <label className="block text-xs text-gray-600 mb-1">{label} {required && <span className="text-red-500">*</span>}</label>
      <input type="datetime-local" min={DT_MIN} max={DT_MAX} required={required}
        className="w-full border rounded px-3 py-2 text-sm"
        value={value} onChange={e => onChange(e.target.value)} />
      {hint && <p className="text-[10px] text-gray-400 mt-0.5">{hint}</p>}
    </div>
  );
}

// ─── types ───────────────────────────────────────────────────────────────────

type FormFieldType = "yes_no" | "text" | "date" | "numeric" | "statement" | "paragraph";
type EventFormDef = { isRequired: boolean; fields: { id?: string; type: FormFieldType; label: string; isRequired?: boolean; order?: number; helpText?: string | null; options?: any }[] };

type Tier = { id?: string; deadline: string; price: string; stockLimit: string; order?: number };

type Variant = {
  id?: string;
  name: string;
  price: string;       // empty → use option base price
  stockLimit: string;
  earlyBirdTiers: Tier[];
};

type OptionDraft = {
  id?: string;
  name: string;
  price: string;
  isMainTicket: boolean;
  stockLimit: string;
  earlyBirdTiers: Tier[];   // option-level (used only when no variants)
  variants: Variant[];
};

type SectionDraft = {
  name: string;
  optionIndices: number[];
};

// ─── sub-components ─────────────────────────────────────────────────────────

function UploadImageButton({ onUploaded, label = "Upload image" }: { onUploaded: (url: string) => void; label?: string }) {
  const { token } = useAuth();
  const [uploading, setUploading] = useState(false);
  const ref = useRef<HTMLInputElement | null>(null);
  return (
    <div className="inline-flex items-center">
      <input ref={ref} type="file" accept="image/*" className="hidden" onChange={async e => {
        const file = e.target.files?.[0];
        if (!file || !token) return;
        setUploading(true);
        try {
          const fd = new FormData();
          fd.append("image", file);
          const res: any = await apiFetch("/api/uploads/event-image", { method: "POST", body: fd, authToken: token });
          if (res?.url || res?.path) onUploaded(res.url || res.path);
        } finally { setUploading(false); if (ref.current) ref.current.value = ""; }
      }} />
      <button type="button" onClick={() => ref.current?.click()} disabled={uploading}
        className="px-3 py-1.5 text-sm rounded border border-brand-200 bg-brand-50 text-brand-700 hover:bg-brand-100 disabled:opacity-50">
        {uploading ? "Uploading…" : label}
      </button>
    </div>
  );
}

function FormBuilder({ value, onChange }: { value: EventFormDef; onChange: (v: EventFormDef) => void }) {
  const fields = value.fields || [];
  const add = (type: FormFieldType) => onChange({ ...value, fields: [...fields, { type, label: "", isRequired: false, helpText: "" }] });
  const upd = (idx: number, patch: any) => { const c = fields.slice(); c[idx] = { ...c[idx], ...patch }; onChange({ ...value, fields: c }); };
  const rem = (idx: number) => { const c = fields.slice(); c.splice(idx, 1); onChange({ ...value, fields: c }); };
  const mov = (idx: number, dir: -1 | 1) => {
    const c = fields.slice(); const t = idx + dir;
    if (t < 0 || t >= c.length) return;
    [c[idx], c[t]] = [c[t], c[idx]]; onChange({ ...value, fields: c });
  };
  const typeLabel = (t: FormFieldType) => ({ text: "Text", numeric: "Number", date: "Date", yes_no: "Yes/No", statement: "Statement", paragraph: "Heading" }[t] || t);
  return (
    <div className="border rounded-lg p-3 bg-gray-50">
      <div className="flex items-center justify-between mb-3">
        <div className="text-sm font-semibold">Registration Form Fields</div>
        <label className="text-xs flex items-center gap-1.5 cursor-pointer">
          <input type="checkbox" checked={!!value.isRequired} onChange={e => onChange({ ...value, isRequired: e.target.checked })} />
          Required before tickets generate
        </label>
      </div>
      {fields.length === 0
        ? <p className="text-xs text-gray-500 italic mb-3">No fields yet.</p>
        : (
          <ul className="space-y-2 mb-3">
            {fields.map((f, idx) => (
              <li key={idx} className="bg-white border rounded p-2">
                <div className="flex items-center gap-1 mb-1.5">
                  <span className="text-[10px] px-1.5 py-0.5 rounded bg-brand-50 text-brand-700 font-medium">{typeLabel(f.type)}</span>
                  <span className="text-xs text-gray-400">#{idx + 1}</span>
                  <div className="flex-1" />
                  <button type="button" className="text-gray-400 hover:text-gray-700 px-1" onClick={() => mov(idx, -1)} disabled={idx === 0}>↑</button>
                  <button type="button" className="text-gray-400 hover:text-gray-700 px-1" onClick={() => mov(idx, 1)} disabled={idx === fields.length - 1}>↓</button>
                  <button type="button" className="text-xs px-2 py-0.5 rounded bg-red-50 text-red-700 border border-red-200" onClick={() => rem(idx)}>Remove</button>
                </div>
                <div className="flex flex-wrap gap-2 mb-1.5">
                  <select className="border rounded px-2 py-1 text-xs" value={f.type} onChange={e => upd(idx, { type: e.target.value })}>
                    <option value="text">Text</option><option value="numeric">Number</option><option value="date">Date</option>
                    <option value="yes_no">Yes/No</option><option value="statement">Statement</option><option value="paragraph">Heading+para</option>
                  </select>
                  {f.type !== "statement" && f.type !== "paragraph" && (
                    <label className="text-xs flex items-center gap-1 cursor-pointer">
                      <input type="checkbox" checked={!!f.isRequired} onChange={e => upd(idx, { isRequired: e.target.checked })} /> Required
                    </label>
                  )}
                </div>
                <input className="border rounded px-2 py-1 text-sm w-full mb-1" placeholder={f.type === "paragraph" ? "Heading text" : "Label"} value={f.label} onChange={e => upd(idx, { label: e.target.value })} />
                {f.type === "paragraph"
                  ? <textarea className="border rounded px-2 py-1 text-xs w-full" rows={2} placeholder="Body text" value={f.helpText || ""} onChange={e => upd(idx, { helpText: e.target.value })} />
                  : f.type !== "statement"
                    ? <input className="border rounded px-2 py-1 text-xs w-full" placeholder="Help text (optional)" value={f.helpText || ""} onChange={e => upd(idx, { helpText: e.target.value })} />
                    : null}
              </li>
            ))}
          </ul>
        )}
      <div className="flex gap-2 flex-wrap">
        <button type="button" className="text-xs px-3 py-1.5 rounded bg-brand-600 text-white hover:bg-brand-700" onClick={() => add("text")}>+ Question</button>
        <button type="button" className="text-xs px-3 py-1.5 rounded bg-gray-200 text-gray-700" onClick={() => add("statement")}>+ Statement</button>
        <button type="button" className="text-xs px-3 py-1.5 rounded bg-gray-200 text-gray-700" onClick={() => add("paragraph")}>+ Heading</button>
      </div>
    </div>
  );
}

// ─── Items & Pricing substep editors ────────────────────────────────────────

/** Substep 0 — bare option rows */
function OptionsEditor({ options, onChange, required }: { options: OptionDraft[]; onChange: (o: OptionDraft[]) => void; required?: boolean }) {
  const upd = (i: number, patch: Partial<OptionDraft>) => { const c = options.slice(); c[i] = { ...c[i], ...patch }; onChange(c); };
  const rem = (i: number) => { const c = options.slice(); c.splice(i, 1); onChange(c); };
  const add = () => {
    const hasMain = options.some(o => o.isMainTicket);
    onChange([...options, { name: "", price: "", isMainTicket: !hasMain, stockLimit: "0", earlyBirdTiers: [], variants: [] }]);
  };

  return (
    <div className="space-y-2">
      {options.map((opt, i) => {
        const priceMissing = required && opt.price.trim() === "";
        return (
          <div key={i} className="border rounded-lg p-3 bg-white space-y-2">
            <div className="flex flex-wrap items-end gap-2">
              <div className="flex-1 min-w-36">
                <label className="block text-[10px] text-gray-500 mb-0.5">Option name</label>
                <input className="border rounded px-2 py-1.5 text-sm w-full" placeholder="e.g. Ticket, Hoodie, Meal"
                  value={opt.name} onChange={e => upd(i, { name: e.target.value })} />
              </div>
              <div>
                <label className="block text-[10px] text-gray-500 mb-0.5">Base price (R) {required && <span className="text-red-500">*</span>}</label>
                <input type="number" step="1" min="0" required={required}
                  className={`border rounded px-2 py-1.5 text-sm w-28 ${priceMissing ? "border-red-300 bg-red-50" : ""}`}
                  value={opt.price} onChange={e => upd(i, { price: e.target.value })} />
              </div>
              <div>
                <label className="block text-[10px] text-gray-500 mb-0.5">Stock (0=∞)</label>
                <input type="number" step="1" min="0" className="border rounded px-2 py-1.5 text-sm w-24"
                  value={opt.stockLimit} onChange={e => upd(i, { stockLimit: e.target.value })} />
              </div>
              <div className="flex items-center gap-3 pb-1">
                <label className="text-xs flex items-center gap-1 cursor-pointer whitespace-nowrap">
                  <input type="checkbox" checked={opt.isMainTicket} onChange={e => upd(i, { isMainTicket: e.target.checked })} /> Main ticket
                </label>
                {options.length > 1 && (
                  <button type="button" className="text-xs px-2 py-1 rounded bg-red-50 text-red-700 border border-red-200 hover:bg-red-100 whitespace-nowrap" onClick={() => rem(i)}>Remove</button>
                )}
              </div>
            </div>
            {priceMissing && <p className="text-[10px] text-red-500">Price is required — enter 0 for a free option.</p>}
          </div>
        );
      })}
      <button type="button" className="text-xs px-3 py-1.5 rounded bg-brand-600 text-white hover:bg-brand-700" onClick={add}>+ Add option</button>
    </div>
  );
}

/** Substep 1 — variants per option */
function VariantsEditor({ options, onChange }: { options: OptionDraft[]; onChange: (o: OptionDraft[]) => void }) {
  const updVariants = (optIdx: number, variants: Variant[]) => {
    const c = options.slice(); c[optIdx] = { ...c[optIdx], variants }; onChange(c);
  };

  if (options.length === 0) return <p className="text-sm text-gray-500">Add options first.</p>;

  return (
    <div className="space-y-4">
      {options.map((opt, optIdx) => {
        const variants = opt.variants;
        const addVariant = () => updVariants(optIdx, [...variants, { name: "", price: "", stockLimit: "0", earlyBirdTiers: [] }]);
        const updVariant = (vi: number, patch: Partial<Variant>) => {
          const c = variants.slice(); c[vi] = { ...c[vi], ...patch }; updVariants(optIdx, c);
        };
        const remVariant = (vi: number) => {
          const c = variants.slice(); c.splice(vi, 1); updVariants(optIdx, c);
        };
        return (
          <div key={optIdx} className="border rounded-lg overflow-hidden">
            <div className="px-3 py-2 bg-gray-50 border-b text-sm font-medium text-gray-700">{opt.name || <span className="italic text-gray-400">Unnamed option</span>}</div>
            <div className="p-3 space-y-2">
              {variants.length === 0
                ? <p className="text-xs text-gray-400 italic">No variants — uses base price (R{opt.price || "0"}).</p>
                : variants.map((v, vi) => (
                  <div key={vi} className="flex flex-wrap items-end gap-2 bg-white border rounded p-2">
                    <div className="flex-1 min-w-28">
                      <label className="block text-[10px] text-gray-500 mb-0.5">Variant name</label>
                      <input className="border rounded px-2 py-1 text-xs w-full" placeholder="e.g. Adult, Child, Large"
                        value={v.name} onChange={e => updVariant(vi, { name: e.target.value })} />
                    </div>
                    <div>
                      <label className="block text-[10px] text-gray-500 mb-0.5">Price (blank=base)</label>
                      <input type="number" step="1" min="0" className="border rounded px-2 py-1 text-xs w-24" placeholder="(optional)"
                        value={v.price} onChange={e => updVariant(vi, { price: e.target.value })} />
                    </div>
                    <div>
                      <label className="block text-[10px] text-gray-500 mb-0.5">Stock (0=∞)</label>
                      <input type="number" step="1" min="0" className="border rounded px-2 py-1 text-xs w-20"
                        value={v.stockLimit} onChange={e => updVariant(vi, { stockLimit: e.target.value })} />
                    </div>
                    <button type="button" className="text-xs px-2 py-1 rounded bg-red-50 text-red-700 border border-red-200 hover:bg-red-100 self-end" onClick={() => remVariant(vi)}>✕</button>
                  </div>
                ))}
              <button type="button" className="text-xs px-3 py-1.5 rounded bg-brand-600 text-white hover:bg-brand-700" onClick={addVariant}>+ Add variant</button>
            </div>
          </div>
        );
      })}
    </div>
  );
}

/** Single tier row editor */
function TierRow({ tier, onChange, onRemove }: { tier: Tier; onChange: (t: Tier) => void; onRemove: () => void }) {
  return (
    <div className="flex flex-wrap items-end gap-2 bg-white border rounded p-2">
      <div>
        <div className="text-[10px] text-gray-500 mb-0.5">Deadline</div>
        <input type="datetime-local" min={DT_MIN} max={DT_MAX} className="border rounded px-2 py-1 text-xs"
          value={tier.deadline} onChange={e => onChange({ ...tier, deadline: e.target.value })} />
      </div>
      <div>
        <div className="text-[10px] text-gray-500 mb-0.5">Price (R)</div>
        <input type="number" step="1" min="0" className="border rounded px-2 py-1 text-xs w-24"
          value={tier.price} onChange={e => onChange({ ...tier, price: e.target.value })} />
      </div>
      <div>
        <div className="text-[10px] text-gray-500 mb-0.5">Stock (0=∞)</div>
        <input type="number" step="1" min="0" className="border rounded px-2 py-1 text-xs w-20"
          value={tier.stockLimit} onChange={e => onChange({ ...tier, stockLimit: e.target.value })} />
      </div>
      <button type="button" className="text-xs px-2 py-1 rounded bg-red-50 text-red-700 border border-red-200 self-end" onClick={onRemove}>✕</button>
    </div>
  );
}

/** Substep 2 — early birds per variant (or per option when no variants) */
function EarlyBirdsEditor({ options, onChange }: { options: OptionDraft[]; onChange: (o: OptionDraft[]) => void }) {
  if (options.length === 0) return <p className="text-sm text-gray-500">Add options first.</p>;

  return (
    <div className="space-y-4">
      {options.map((opt, optIdx) => {
        const hasVariants = opt.variants.length > 0;

        if (!hasVariants) {
          // Option-level tiers
          const updTiers = (tiers: Tier[]) => {
            const c = options.slice(); c[optIdx] = { ...c[optIdx], earlyBirdTiers: tiers }; onChange(c);
          };
          const addTier = () => updTiers([...opt.earlyBirdTiers, { deadline: "", price: "", stockLimit: "0" }]);
          return (
            <div key={optIdx} className="border rounded-lg overflow-hidden">
              <div className="px-3 py-2 bg-gray-50 border-b text-sm font-medium text-gray-700">
                {opt.name || <span className="italic text-gray-400">Unnamed option</span>}
                <span className="ml-2 text-[10px] text-gray-400">base R{opt.price || "0"}</span>
              </div>
              <div className="p-3 space-y-2">
                {opt.earlyBirdTiers.length === 0 && <p className="text-xs text-gray-400 italic">No early-bird tiers.</p>}
                {opt.earlyBirdTiers.map((t, ti) => (
                  <TierRow key={ti} tier={t}
                    onChange={updated => { const c = opt.earlyBirdTiers.slice(); c[ti] = updated; updTiers(c); }}
                    onRemove={() => { const c = opt.earlyBirdTiers.slice(); c.splice(ti, 1); updTiers(c); }} />
                ))}
                <button type="button" className="text-xs px-3 py-1.5 rounded bg-brand-600 text-white hover:bg-brand-700" onClick={addTier}>+ Add tier</button>
              </div>
            </div>
          );
        }

        // Per-variant tiers
        return (
          <div key={optIdx} className="border rounded-lg overflow-hidden">
            <div className="px-3 py-2 bg-gray-50 border-b text-sm font-medium text-gray-700">
              {opt.name || <span className="italic text-gray-400">Unnamed option</span>}
            </div>
            <div className="divide-y">
              {opt.variants.map((v, vi) => {
                const basePrice = v.price !== "" ? v.price : opt.price;
                const updVTiers = (tiers: Tier[]) => {
                  const co = options.slice();
                  const cv = co[optIdx].variants.slice();
                  cv[vi] = { ...cv[vi], earlyBirdTiers: tiers };
                  co[optIdx] = { ...co[optIdx], variants: cv };
                  onChange(co);
                };
                const addTier = () => updVTiers([...v.earlyBirdTiers, { deadline: "", price: "", stockLimit: "0" }]);
                return (
                  <div key={vi} className="p-3 space-y-2">
                    <div className="text-xs font-medium text-gray-600">
                      {v.name || <span className="italic text-gray-400">Unnamed variant</span>}
                      <span className="ml-2 text-gray-400">base R{basePrice || "0"}</span>
                    </div>
                    {v.earlyBirdTiers.length === 0 && <p className="text-[10px] text-gray-400 italic">No tiers for this variant.</p>}
                    {v.earlyBirdTiers.map((t, ti) => (
                      <TierRow key={ti} tier={t}
                        onChange={updated => { const c = v.earlyBirdTiers.slice(); c[ti] = updated; updVTiers(c); }}
                        onRemove={() => { const c = v.earlyBirdTiers.slice(); c.splice(ti, 1); updVTiers(c); }} />
                    ))}
                    <button type="button" className="text-xs px-3 py-1.5 rounded bg-brand-600 text-white hover:bg-brand-700" onClick={addTier}>+ Add tier</button>
                  </div>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ─── Sections editors ─────────────────────────────────────────────────────────

/** Create-mode: draft sections using option indices */
function SectionsDraftEditor({ sections, options, onChange }: {
  sections: SectionDraft[];
  options: OptionDraft[];
  onChange: (s: SectionDraft[]) => void;
}) {
  const [newName, setNewName] = useState("");
  const [newIndices, setNewIndices] = useState<number[]>([]);

  const create = () => {
    if (!newName.trim()) return;
    onChange([...sections, { name: newName.trim(), optionIndices: newIndices }]);
    setNewName(""); setNewIndices([]);
  };
  const del = (i: number) => { const c = sections.slice(); c.splice(i, 1); onChange(c); };

  return (
    <div className="space-y-3">
      {sections.length === 0 && <p className="text-xs text-gray-500">No sections yet.</p>}
      {sections.map((sec, i) => (
        <div key={i} className="border rounded p-3 bg-white flex items-start justify-between gap-3">
          <div>
            <div className="text-sm font-medium">{sec.name}</div>
            <div className="text-xs text-gray-500 mt-0.5">
              {sec.optionIndices.length === 0
                ? "All options"
                : sec.optionIndices.map(idx => options[idx]?.name || `Option ${idx + 1}`).join(", ")}
            </div>
          </div>
          <button type="button" className="text-xs px-2 py-1 rounded bg-red-50 text-red-700 border border-red-200 shrink-0" onClick={() => del(i)}>Delete</button>
        </div>
      ))}
      <div className="border rounded p-3 bg-gray-50">
        <div className="text-xs font-medium mb-2">Create section</div>
        <input className="border rounded px-2 py-1 text-sm w-full mb-2" placeholder="Section name"
          value={newName} onChange={e => setNewName(e.target.value)} />
        {options.length > 0 && (
          <>
            <div className="text-xs text-gray-600 mb-1">Options in this section:</div>
            <div className="flex flex-wrap gap-2 mb-2">
              {options.map((opt, idx) => (
                <label key={idx} className="text-xs flex items-center gap-1 cursor-pointer">
                  <input type="checkbox" checked={newIndices.includes(idx)}
                    onChange={e => setNewIndices(e.target.checked ? [...newIndices, idx] : newIndices.filter(x => x !== idx))} />
                  {opt.name || `Option ${idx + 1}`}
                </label>
              ))}
            </div>
          </>
        )}
        <button type="button" className="text-xs px-3 py-1.5 rounded bg-brand-600 text-white hover:bg-brand-700" onClick={create}>Create section</button>
      </div>
    </div>
  );
}

/** Edit-mode: live sections manager (DB-backed) */
function SectionsManager({ eventId }: { eventId: string }) {
  const { token } = useAuth();
  const [sections, setSections] = useState<any[]>([]);
  const [eventOptions, setEventOptions] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);
  const [newName, setNewName] = useState("");
  const [newOptionIds, setNewOptionIds] = useState<string[]>([]);

  const load = async () => {
    setLoading(true);
    try {
      const [secList, evFull] = await Promise.all([
        apiFetch<any[]>("/api/sections", { authToken: token || undefined }),
        apiFetch<any>(`/api/events/${eventId}`)
      ]);
      setSections((secList || []).filter(s => s.eventId === eventId));
      setEventOptions(evFull?.eventOptions || []);
    } catch {} finally { setLoading(false); }
  };
  useEffect(() => { if (eventId) load(); }, [eventId]);

  const create = async () => {
    if (!newName.trim() || !token) return;
    await apiFetch("/api/sections", { method: "POST", authToken: token, body: { eventId, name: newName.trim(), allowedOptionIds: newOptionIds } });
    setNewName(""); setNewOptionIds([]); load();
  };
  const del = async (id: string) => {
    if (!token) return;
    await apiFetch(`/api/sections/${id}`, { method: "DELETE", authToken: token });
    load();
  };
  const update = async (id: string, name: string, optionIds: string[]) => {
    if (!token) return;
    await apiFetch(`/api/sections/${id}`, { method: "PUT", authToken: token, body: { name, allowedOptionIds: optionIds } });
    load();
  };

  if (loading) return <div className="text-xs text-gray-500">Loading sections…</div>;

  return (
    <div className="space-y-3">
      {sections.length === 0 && <p className="text-xs text-gray-500">No sections yet.</p>}
      {sections.map(sec => (
        <SectionRow key={sec.id} sec={sec} options={eventOptions} onDelete={() => del(sec.id)} onUpdate={update} />
      ))}
      <div className="border rounded p-3 bg-gray-50">
        <div className="text-xs font-medium mb-2">Create section</div>
        <input className="border rounded px-2 py-1 text-sm w-full mb-2" placeholder="Section name" value={newName} onChange={e => setNewName(e.target.value)} />
        <div className="text-xs text-gray-600 mb-1">Options in this section:</div>
        <div className="flex flex-wrap gap-2 mb-2">
          {eventOptions.map(opt => (
            <label key={opt.id} className="text-xs flex items-center gap-1 cursor-pointer">
              <input type="checkbox" checked={newOptionIds.includes(opt.id)}
                onChange={e => setNewOptionIds(e.target.checked ? [...newOptionIds, opt.id] : newOptionIds.filter(id => id !== opt.id))} />
              {opt.name}
            </label>
          ))}
        </div>
        <button type="button" className="text-xs px-3 py-1.5 rounded bg-brand-600 text-white hover:bg-brand-700" onClick={create}>Create section</button>
      </div>
    </div>
  );
}

function SectionRow({ sec, options, onDelete, onUpdate }: any) {
  const [name, setName] = useState(sec.name || "");
  const [optIds, setOptIds] = useState<string[]>((sec.allowedOptions || []).map((o: any) => o.eventOptionId));
  return (
    <div className="border rounded p-3 bg-white">
      <input className="border rounded px-2 py-1 text-sm w-full mb-2" value={name} onChange={e => setName(e.target.value)} />
      <div className="flex flex-wrap gap-2 mb-2">
        {options.map((opt: any) => (
          <label key={opt.id} className="text-xs flex items-center gap-1 cursor-pointer">
            <input type="checkbox" checked={optIds.includes(opt.id)}
              onChange={e => setOptIds(e.target.checked ? [...optIds, opt.id] : optIds.filter(id => id !== opt.id))} />
            {opt.name}
          </label>
        ))}
      </div>
      <div className="flex gap-2">
        <button type="button" className="text-xs px-2 py-1 rounded bg-brand-600 text-white" onClick={() => onUpdate(sec.id, name, optIds)}>Save</button>
        <button type="button" className="text-xs px-2 py-1 rounded bg-red-50 text-red-700 border border-red-200" onClick={onDelete}>Delete</button>
      </div>
    </div>
  );
}

// ─── Attachments ─────────────────────────────────────────────────────────────

function AttachmentsManager({ eventId }: { eventId: string }) {
  const { token } = useAuth();
  const [items, setItems] = useState<any[]>([]);
  const [uploading, setUploading] = useState(false);
  const ref = useRef<HTMLInputElement | null>(null);

  const load = async () => {
    const res = await apiFetch<any[]>(`/api/events/${eventId}/attachments`);
    setItems(Array.isArray(res) ? res : []);
  };
  useEffect(() => { load(); }, [eventId]);

  const upload: React.ChangeEventHandler<HTMLInputElement> = async e => {
    const file = e.target.files?.[0];
    if (!file || !token) return;
    setUploading(true);
    try {
      const fd = new FormData();
      fd.append("file", file);
      await apiFetch(`/api/events/${eventId}/attachments`, { method: "POST", body: fd, authToken: token });
      await load();
    } finally { setUploading(false); if (ref.current) ref.current.value = ""; }
  };

  const del = async (id: string) => {
    if (!token || !confirm("Delete this attachment?")) return;
    await apiFetch(`/api/events/${eventId}/attachments/${id}`, { method: "DELETE", authToken: token });
    await load();
  };

  return (
    <div>
      <div className="flex items-center justify-between mb-2">
        <div className="text-sm font-medium">Attachments</div>
        <div>
          <input ref={ref} type="file" className="hidden" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.csv,.zip" onChange={upload} />
          <button type="button" onClick={() => ref.current?.click()} disabled={uploading} className="text-xs px-2 py-1 rounded border bg-white hover:bg-gray-50">
            {uploading ? "Uploading…" : "Upload file"}
          </button>
        </div>
      </div>
      {items.length === 0
        ? <p className="text-xs text-gray-500">No attachments.</p>
        : (
          <ul className="space-y-1">
            {items.map(it => (
              <li key={it.id} className="flex items-center justify-between text-xs">
                <a href={it.url} target="_blank" rel="noreferrer" className="text-brand-600 hover:underline truncate max-w-xs">{it.originalName}</a>
                <button type="button" className="text-red-600 hover:underline ml-2" onClick={() => del(it.id)}>Delete</button>
              </li>
            ))}
          </ul>
        )}
    </div>
  );
}

// ─── Notification recipients ───────────────────────────────────────────────

type NotifyUser = { id: string; name: string; email: string };

/** Tracks an anchor element's viewport rect while `active`, updating on any scroll
 *  (capture-phase, so it catches scrolling inside the modal's clipped containers too)
 *  or resize. Used to position the search-results dropdown outside the modal's overflow clip. */
function useAnchorRect(anchorRef: React.RefObject<HTMLElement | null>, active: boolean) {
  const [rect, setRect] = useState<{ top: number; left: number; width: number; bottom: number } | null>(null);

  useLayoutEffect(() => {
    if (!active) { setRect(null); return; }
    const update = () => {
      const el = anchorRef.current;
      if (!el) return;
      const r = el.getBoundingClientRect();
      setRect({ top: r.top, left: r.left, width: r.width, bottom: r.bottom });
    };
    update();
    window.addEventListener("scroll", update, true);
    window.addEventListener("resize", update);
    return () => {
      window.removeEventListener("scroll", update, true);
      window.removeEventListener("resize", update);
    };
  }, [anchorRef, active]);

  return rect;
}

/** Presentational picker — pure controlled component, no API persistence of its own.
 *  Used directly for create-mode staging, and wrapped by NotifyRecipientsManager for edit mode. */
function NotifyRecipientsPicker({ selected, onChange, disabled }: { selected: NotifyUser[]; onChange: (next: NotifyUser[]) => void; disabled?: boolean }) {
  const { token } = useAuth();
  const [search, setSearch] = useState("");
  const [results, setResults] = useState<NotifyUser[]>([]);
  const [searching, setSearching] = useState(false);
  const anchorRef = useRef<HTMLDivElement>(null);

  const visibleResults = results.filter(u => !selected.some(s => s.id === u.id));
  const dropdownOpen = visibleResults.length > 0;
  const rect = useAnchorRect(anchorRef, dropdownOpen);

  useEffect(() => {
    if (!search.trim()) { setResults([]); return; }
    let cancelled = false;
    setSearching(true);
    const t = setTimeout(async () => {
      try {
        const res = await apiFetch<any>(`/api/users?search=${encodeURIComponent(search.trim())}&limit=10`, { authToken: token || undefined });
        if (!cancelled) setResults(Array.isArray(res?.data) ? res.data : []);
      } catch {
        if (!cancelled) setResults([]);
      } finally {
        if (!cancelled) setSearching(false);
      }
    }, 300);
    return () => { cancelled = true; clearTimeout(t); };
  }, [search, token]);

  const add = (u: NotifyUser) => {
    if (selected.some(s => s.id === u.id)) return;
    onChange([...selected, u]);
    setSearch(""); setResults([]);
  };
  const remove = (id: string) => onChange(selected.filter(s => s.id !== id));

  return (
    <div className="space-y-3">
      {selected.length === 0 ? (
        <p className="text-xs text-gray-400 italic">No recipients selected — falling back to the event creator.</p>
      ) : (
        <ul className="space-y-1">
          {selected.map(u => (
            <li key={u.id} className="flex items-center justify-between text-sm bg-white border rounded px-3 py-1.5">
              <span>{u.name} <span className="text-xs text-gray-400">{u.email}</span></span>
              <button type="button" className="text-red-600 hover:underline text-xs" onClick={() => remove(u.id)} disabled={disabled}>Remove</button>
            </li>
          ))}
        </ul>
      )}
      <div ref={anchorRef}>
        <input
          className="border rounded px-3 py-2 text-sm w-full"
          placeholder="Search staff by name or email…"
          value={search}
          onChange={e => setSearch(e.target.value)}
          disabled={disabled}
        />
        {searching && <div className="text-xs text-gray-400 mt-1">Searching…</div>}
      </div>
      {dropdownOpen && rect && typeof document !== "undefined" && createPortal(
        <ul
          style={{
            position: "fixed",
            top: rect.bottom + 4,
            left: rect.left,
            width: rect.width,
            maxHeight: Math.max(160, Math.min(360, window.innerHeight - rect.bottom - 16)),
            zIndex: 9999,
          }}
          className="border rounded bg-white shadow-lg overflow-y-auto"
        >
          {visibleResults.map(u => (
            <li key={u.id} className="px-3 py-1.5 text-sm hover:bg-brand-50 cursor-pointer" onClick={() => add(u)}>
              {u.name} <span className="text-xs text-gray-400">{u.email}</span>
            </li>
          ))}
        </ul>,
        document.body
      )}
    </div>
  );
}

/** Edit-mode manager — persists each change via PUT. Uses `initialRecipients` (already
 *  loaded by the parent's GET /api/events/:id call) when available so opening this step
 *  is instant; only falls back to its own (lightweight) GET when that wasn't provided. */
function NotifyRecipientsManager({ eventId, creator, initialRecipients }: { eventId: string; creator?: NotifyUser | null; initialRecipients?: NotifyUser[] | null }) {
  const { token } = useAuth();
  const hasInitial = Array.isArray(initialRecipients);
  const [selected, setSelected] = useState<NotifyUser[]>(initialRecipients || []);
  const [loading, setLoading] = useState(!hasInitial);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useDismissingState<string | null>(null);

  useEffect(() => {
    if (hasInitial) return; // already have the data — skip the network round trip entirely
    let cancelled = false;
    (async () => {
      setLoading(true);
      try {
        const res = await apiFetch<any>(`/api/events/${eventId}/notify-recipients`, { authToken: token || undefined });
        if (!cancelled) setSelected(Array.isArray(res) ? res : []);
      } catch (e: any) {
        if (!cancelled) setError(e?.message || "Failed to load notification recipients");
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [eventId]);

  const persist = async (next: NotifyUser[]) => {
    const prev = selected;
    setSelected(next);
    setSaving(true);
    setError(null);
    try {
      await apiFetch(`/api/events/${eventId}/notify-recipients`, {
        method: "PUT", authToken: token || undefined,
        body: { userIds: next.map(u => u.id) },
      });
    } catch (e: any) {
      setSelected(prev);
      setError(e?.message || "Failed to save notification recipients");
    } finally {
      setSaving(false);
    }
  };

  if (loading) return <div className="text-xs text-gray-500">Loading…</div>;

  return (
    <div className="space-y-3">
      <p className="text-xs text-gray-500">
        Choose who receives new registration, payment, and daily summary emails for this event.
        Leave empty to send them to the event creator{creator ? <> (<strong>{creator.name}</strong>, {creator.email})</> : null} instead.
      </p>
      {error && <div className="p-2 border rounded bg-red-50 text-red-700 text-xs">{error}</div>}
      <NotifyRecipientsPicker selected={selected} onChange={persist} disabled={saving} />
    </div>
  );
}

// ─── steps ───────────────────────────────────────────────────────────────────

const STEPS = ["Basic Details", "Items & Pricing", "Sections", "Form", "Visibility", "Notifications", "Attachments"] as const;
type StepIdx = 0 | 1 | 2 | 3 | 4 | 5 | 6;
const PRICING_SUBSTEPS = ["Options", "Variants", "Early Birds"] as const;
type PricingSubstep = 0 | 1 | 2;

interface EventDraft {
  title: string; description: string; startDate: string; endDate: string;
  registrationDeadline: string; goLiveAt: string; price: string; picture: string;
  redirectUrl: string; isActive: boolean; isHidden: boolean; requiresAuth: boolean;
}

const blankDraft = (): EventDraft => ({
  title: "", description: "", startDate: "", endDate: "",
  registrationDeadline: "", goLiveAt: "", price: "", picture: "",
  redirectUrl: "", isActive: true, isHidden: false, requiresAuth: true,
});

const blankOptions = (): OptionDraft[] => [
  { name: "Ticket", price: "", isMainTicket: true, stockLimit: "0", earlyBirdTiers: [], variants: [] }
];

// ─── EventModal ───────────────────────────────────────────────────────────────

interface EventModalProps {
  mode: "create" | "edit";
  event?: any;
  onClose: () => void;
  onSuccess: () => void;
}

function EventModal({ mode, event: ev, onClose, onSuccess }: EventModalProps) {
  const { token, user } = useAuth();
  const isAdmin = user?.role === "admin";
  const [step, setStep] = useState<StepIdx>(0);
  const [pricingSubstep, setPricingSubstep] = useState<PricingSubstep>(0);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // ── event draft ──
  const [draft, setDraft] = useState<EventDraft>(() => ev ? {
    title: ev.title || "", description: ev.description || "",
    startDate: toLocalDT(ev.startDate), endDate: toLocalDT(ev.endDate),
    registrationDeadline: toLocalDT(ev.registrationDeadline), goLiveAt: toLocalDT(ev.goLiveAt),
    price: String(ev.price ?? ""), picture: ev.picture || "", redirectUrl: ev.redirectUrl || "",
    isActive: ev.isActive !== false, isHidden: !!ev.isHidden, requiresAuth: ev.requiresAuth !== false,
  } : blankDraft());

  // ── options (with per-variant tiers) ──
  const [options, setOptions] = useState<OptionDraft[]>(() => {
    if (!ev?.eventOptions?.length) return blankOptions();
    return ev.eventOptions.map((o: any) => {
      const allTiers: any[] = o.earlyBirdTiers || [];
      return {
        id: o.id,
        name: o.name,
        price: String(o.price ?? ""),
        isMainTicket: !!o.isMainTicket,
        stockLimit: String(o.stockLimit ?? "0"),
        // Option-level tiers only (no variantId)
        earlyBirdTiers: allTiers
          .filter((t: any) => !t.variantId)
          .map((t: any) => ({ id: t.id, deadline: toLocalDT(t.deadline), price: String(t.price ?? ""), stockLimit: String(t.stockLimit ?? "0") })),
        variants: (o.variants || []).map((v: any) => ({
          id: v.id, name: v.name,
          price: v.price !== null && v.price !== undefined ? String(v.price) : "",
          stockLimit: String(v.stockLimit ?? "0"),
          // Variant-level tiers — filter by variantId
          earlyBirdTiers: allTiers
            .filter((t: any) => t.variantId === v.id)
            .map((t: any) => ({ id: t.id, deadline: toLocalDT(t.deadline), price: String(t.price ?? ""), stockLimit: String(t.stockLimit ?? "0") })),
        })),
      };
    });
  });

  // ── sections (staged for create mode) ──
  const [sectionDrafts, setSectionDrafts] = useState<SectionDraft[]>([]);

  // ── attachments (staged for create mode; uploaded after event is created) ──
  const [stagedFiles, setStagedFiles] = useState<File[]>([]);

  // ── notify recipients (staged for create mode; saved after event is created) ──
  const [notifyRecipientDrafts, setNotifyRecipientDrafts] = useState<NotifyUser[]>([]);

  // ── form ──
  const [formDef, setFormDef] = useState<EventFormDef>({ isRequired: false, fields: [] });
  const formLoadedRef = useRef(false);
  useEffect(() => {
    if (mode !== "edit" || !ev?.id || formLoadedRef.current) return;
    apiFetch<any>(`/api/events/${ev.id}`).then(full => {
      const f = full?.form;
      if (f) setFormDef({ isRequired: !!f.isRequired, fields: (f.fields || []).map((x: any) => ({ id: x.id, type: x.type, label: x.label, isRequired: !!x.isRequired, helpText: x.helpText || "" })) });
    }).catch(() => {}).finally(() => { formLoadedRef.current = true; });
  }, [mode, ev?.id]);

  const upd = (patch: Partial<EventDraft>) => setDraft(d => ({ ...d, ...patch }));

  // ── auto-fill first option's price from the event base price (create mode only, until the
  // user directly edits that option's price — then it becomes independent) ──
  const [mainPriceTouched, setMainPriceTouched] = useState(false);
  useEffect(() => {
    if (mode !== "create" || mainPriceTouched) return;
    setOptions(prev => {
      if (prev.length === 0 || prev[0].price === draft.price) return prev;
      const next = prev.slice();
      next[0] = { ...next[0], price: draft.price };
      return next;
    });
  }, [draft.price, mode, mainPriceTouched]);

  const handleOptionsChange = (next: OptionDraft[]) => {
    if (mode === "create" && next[0] && options[0] && next[0].price !== options[0].price) {
      setMainPriceTouched(true);
    }
    setOptions(next);
  };

  const isPriceInvalid = (p: string) => p.trim() === "" || isNaN(parseFloat(p)) || parseFloat(p) < 0;

  // Closed (cashed-up) events reject every mutating edit endpoint server-side — show that
  // upfront and disable Save rather than letting staff fill out the whole form only to have
  // it rejected on submit.
  const isClosed = mode === "edit" && ev?.cashupStatus === "closed";

  // ── save helpers ──

  const saveOptions = async (eventId: string) => {
    if (mode === "edit") {
      for (const opt of options) {
        // Build flat tier array: option-level + all variant tiers
        const allTiers: any[] = [
          ...opt.earlyBirdTiers
            .filter(t => t.deadline && t.price !== "")
            .map((t, i) => ({ id: t.id, deadline: new Date(t.deadline).toISOString(), price: parseFloat(t.price), stockLimit: parseInt(t.stockLimit || "0"), order: i, variantId: null })),
        ];

        const variantsPayload = opt.variants.map((v, vi) => ({
          id: v.id, name: v.name,
          price: v.price !== "" ? parseFloat(v.price) : null,
          stockLimit: parseInt(v.stockLimit || "0"), order: vi,
        }));

        if (opt.id) {
          // Update option + variants first to get/confirm variant IDs
          const updated: any = await apiFetch(`/api/events/options/${opt.id}`, {
            method: "PUT", authToken: token || undefined,
            body: { name: opt.name, price: parseFloat(opt.price || "0"), isMainTicket: opt.isMainTicket, stockLimit: parseInt(opt.stockLimit || "0"), variants: variantsPayload }
          });

          // Build variant tiers with confirmed IDs from response
          const serverVariants: any[] = updated?.variants || [];
          for (let vi = 0; vi < opt.variants.length; vi++) {
            const v = opt.variants[vi];
            const serverVariant = serverVariants[vi] || serverVariants.find((sv: any) => sv.name === v.name);
            if (!serverVariant) continue;
            for (let ti = 0; ti < v.earlyBirdTiers.length; ti++) {
              const t = v.earlyBirdTiers[ti];
              if (!t.deadline || t.price === "") continue;
              allTiers.push({ deadline: new Date(t.deadline).toISOString(), price: parseFloat(t.price), stockLimit: parseInt(t.stockLimit || "0"), order: ti, variantId: serverVariant.id });
            }
          }

          if (allTiers.length > 0 || opt.earlyBirdTiers.length > 0 || opt.variants.some(v => v.earlyBirdTiers.length > 0)) {
            await apiFetch(`/api/events/options/${opt.id}`, {
              method: "PUT", authToken: token || undefined,
              body: { earlyBirdTiers: allTiers }
            });
          }

          // Handle form save (edit only)
          await apiFetch(`/api/events/${eventId}`, {
            method: "PUT", authToken: token || undefined,
            body: { form: { isRequired: !!formDef.isRequired, fields: formDef.fields.filter(f => f.label?.trim()).map((f, i) => ({ type: f.type, label: f.label, isRequired: !!f.isRequired, order: i, helpText: f.helpText || null })) } }
          });
        } else {
          // New option added during edit
          const created: any = await apiFetch(`/api/events/${eventId}/options`, {
            method: "POST", authToken: token || undefined,
            body: { name: opt.name, price: parseFloat(opt.price || "0"), isMainTicket: opt.isMainTicket, stockLimit: parseInt(opt.stockLimit || "0") }
          });
          if (created?.id) {
            const updated: any = await apiFetch(`/api/events/options/${created.id}`, {
              method: "PUT", authToken: token || undefined,
              body: { variants: variantsPayload }
            });
            const serverVariants: any[] = updated?.variants || [];
            for (let vi = 0; vi < opt.variants.length; vi++) {
              const v = opt.variants[vi];
              const sv = serverVariants[vi] || serverVariants.find((x: any) => x.name === v.name);
              if (!sv) continue;
              for (let ti = 0; ti < v.earlyBirdTiers.length; ti++) {
                const t = v.earlyBirdTiers[ti];
                if (!t.deadline || t.price === "") continue;
                allTiers.push({ deadline: new Date(t.deadline).toISOString(), price: parseFloat(t.price), stockLimit: parseInt(t.stockLimit || "0"), order: ti, variantId: sv.id });
              }
            }
            if (allTiers.length > 0) {
              await apiFetch(`/api/events/options/${created.id}`, {
                method: "PUT", authToken: token || undefined, body: { earlyBirdTiers: allTiers }
              });
            }
          }
        }
      }
    } else {
      // Create mode — backend auto-creates a "Main Ticket" option; replace/create from draft
      let createdEvent: any = null;
      try { createdEvent = await apiFetch(`/api/events/${eventId}`); } catch {}
      const existingMain = (createdEvent?.eventOptions || []).find((o: any) => !!o.isMainTicket);
      const uiMainIdx = options.findIndex(o => o.isMainTicket);

      for (let i = 0; i < options.length; i++) {
        const opt = options[i];
        const variantsPayload = opt.variants.map((v, vi) => ({
          name: v.name, price: v.price !== "" ? parseFloat(v.price) : null,
          stockLimit: parseInt(v.stockLimit || "0"), order: vi,
        }));

        let optId: string;

        if (i === uiMainIdx && existingMain) {
          // Update the auto-created main ticket
          await apiFetch(`/api/events/options/${existingMain.id}`, {
            method: "PUT", authToken: token || undefined,
            body: { name: opt.name, price: parseFloat(opt.price || "0"), isMainTicket: true, stockLimit: parseInt(opt.stockLimit || "0") }
          });
          optId = existingMain.id;
        } else {
          const created: any = await apiFetch(`/api/events/${eventId}/options`, {
            method: "POST", authToken: token || undefined,
            body: { name: opt.name, price: parseFloat(opt.price || "0"), isMainTicket: opt.isMainTicket, stockLimit: parseInt(opt.stockLimit || "0") }
          });
          optId = created?.id;
        }

        if (!optId) continue;

        // Save variants, then build tiers with resolved IDs
        const allTiers: any[] = opt.earlyBirdTiers
          .filter(t => t.deadline && t.price !== "")
          .map((t, ti) => ({ deadline: new Date(t.deadline).toISOString(), price: parseFloat(t.price), stockLimit: parseInt(t.stockLimit || "0"), order: ti, variantId: null }));

        if (variantsPayload.length > 0 || opt.variants.some(v => v.earlyBirdTiers.length > 0)) {
          const updated: any = await apiFetch(`/api/events/options/${optId}`, {
            method: "PUT", authToken: token || undefined, body: { variants: variantsPayload }
          });
          const serverVariants: any[] = updated?.variants || [];
          for (let vi = 0; vi < opt.variants.length; vi++) {
            const v = opt.variants[vi];
            const sv = serverVariants[vi] || serverVariants.find((x: any) => x.name === v.name);
            if (!sv) continue;
            for (let ti = 0; ti < v.earlyBirdTiers.length; ti++) {
              const t = v.earlyBirdTiers[ti];
              if (!t.deadline || t.price === "") continue;
              allTiers.push({ deadline: new Date(t.deadline).toISOString(), price: parseFloat(t.price), stockLimit: parseInt(t.stockLimit || "0"), order: ti, variantId: sv.id });
            }
          }
        }

        if (allTiers.length > 0) {
          await apiFetch(`/api/events/options/${optId}`, {
            method: "PUT", authToken: token || undefined, body: { earlyBirdTiers: allTiers }
          });
        }
      }
    }
  };

  const saveStagedSections = async (eventId: string, createdOptions: any[]) => {
    for (const sec of sectionDrafts) {
      if (!sec.name.trim()) continue;
      const optionIds = sec.optionIndices
        .map(idx => createdOptions[idx]?.id)
        .filter(Boolean);
      try {
        await apiFetch("/api/sections", {
          method: "POST", authToken: token || undefined,
          body: { eventId, name: sec.name.trim(), allowedOptionIds: optionIds }
        });
      } catch {}
    }
  };

  const handleSave = async () => {
    if (!draft.title.trim()) { setError("Title is required"); setStep(0); return; }
    if (!draft.startDate || !draft.endDate) { setError("Start and end dates are required"); setStep(0); return; }
    if (mode === "create") {
      if (isPriceInvalid(draft.price)) { setError("Base price is required (enter 0 for a free event)"); setStep(0); return; }
      if (options.some(o => isPriceInvalid(o.price))) { setError("Every option needs a price (enter 0 for a free option)"); setStep(1); setPricingSubstep(0); return; }
    }
    setSaving(true); setError(null);
    try {
      const body: any = {
        title: draft.title.trim(), description: draft.description || undefined,
        startDate: new Date(draft.startDate).toISOString(), endDate: new Date(draft.endDate).toISOString(),
        registrationDeadline: draft.registrationDeadline ? new Date(draft.registrationDeadline).toISOString() : undefined,
        goLiveAt: draft.goLiveAt ? new Date(draft.goLiveAt).toISOString() : undefined,
        price: draft.price ? parseFloat(draft.price) : 0,
        picture: draft.picture || undefined, isHidden: draft.isHidden, requiresAuth: draft.requiresAuth,
        redirectUrl: draft.redirectUrl?.trim().replace(/\s+/g, "-") || undefined,
      };

      if (mode === "edit") {
        body.isActive = draft.isActive;
        await apiFetch(`/api/events/${ev.id}`, { method: "PUT", authToken: token || undefined, body });
        await saveOptions(ev.id);
        // Form is saved inside saveOptions for edit mode; avoid double call
      } else {
        if (formDef.isRequired || formDef.fields.length > 0) {
          body.form = { isRequired: !!formDef.isRequired, fields: formDef.fields.filter(f => f.label?.trim()).map((f, i) => ({ type: f.type, label: f.label, isRequired: !!f.isRequired, order: i, helpText: f.helpText || null })) };
        }
        const created: any = await apiFetch("/api/events", { method: "POST", authToken: token || undefined, body });
        if (created?.id) {
          await saveOptions(created.id);
          // Fetch event to get final option IDs for sections
          const fullEvent: any = await apiFetch(`/api/events/${created.id}`).catch(() => null);
          const createdOptions = fullEvent?.eventOptions || [];
          if (sectionDrafts.length > 0) await saveStagedSections(created.id, createdOptions);
          // Upload staged attachments
          for (const file of stagedFiles) {
            const fd = new FormData();
            fd.append("file", file);
            await apiFetch(`/api/events/${created.id}/attachments`, { method: "POST", body: fd, authToken: token || undefined });
          }
          // Save staged notify recipients
          if (notifyRecipientDrafts.length > 0) {
            await apiFetch(`/api/events/${created.id}/notify-recipients`, {
              method: "PUT", authToken: token || undefined,
              body: { userIds: notifyRecipientDrafts.map(u => u.id) },
            }).catch(() => {});
          }
        }
      }

      onSuccess(); onClose();
    } catch (e: any) {
      setError(e?.message || "Failed to save event");
    } finally {
      setSaving(false);
    }
  };

  // ── footer navigation ──

  const handleBack = () => {
    setError(null);
    if (step === 1 && pricingSubstep > 0) {
      setPricingSubstep((pricingSubstep - 1) as PricingSubstep);
    } else {
      setStep((step - 1) as StepIdx);
      if (step - 1 === 1) setPricingSubstep(2); // entering from right → land on last substep
    }
  };

  const handleNext = () => {
    setError(null);
    if (step === 1 && pricingSubstep < 2) {
      setPricingSubstep((pricingSubstep + 1) as PricingSubstep);
    } else {
      setStep((step + 1) as StepIdx);
      if (step + 1 === 1) setPricingSubstep(0); // entering from left → land on first substep
    }
  };

  const isFirstStep = step === 0;
  const isLastStep = step === STEPS.length - 1;
  const isOnLastSubstep = step !== 1 || pricingSubstep === 2;
  // Basic Details step requires title, start/end dates, and a base price before moving on
  // (mirrors handleSave's own checks). Base price is only compulsory when creating a new event.
  const basicDetailsIncomplete = step === 0 && (!draft.title.trim() || !draft.startDate || !draft.endDate || (mode === "create" && isPriceInvalid(draft.price)));
  // Items & Pricing: every option needs a valid price before leaving the step (checked across
  // all pricing substeps so switching to Variants/Early Birds can't be used to skip the gate).
  const optionsIncomplete = mode === "create" && step === 1 && options.some(o => isPriceInvalid(o.price));
  const nextDisabled = basicDetailsIncomplete || optionsIncomplete;

  // ── render ──

  return (
    <div className="fixed inset-0 z-50 flex items-start justify-center overflow-auto py-6 px-4">
      <div className="absolute inset-0 bg-black/40" onClick={onClose} />
      <div className="relative bg-white rounded-xl shadow-2xl w-full max-w-2xl">
        {/* Header */}
        <div className="flex items-center justify-between px-5 pt-5 pb-3 border-b">
          <h2 className="text-lg font-semibold">{mode === "create" ? "New Event" : "Edit Event"}</h2>
          <button type="button" className="text-gray-400 hover:text-gray-700 text-xl leading-none" onClick={onClose}>×</button>
        </div>

        {/* Step bar */}
        <div className="flex items-center gap-1 px-5 py-3 border-b overflow-x-auto">
          {STEPS.map((s, i) => {
            const clickable = mode === "edit";
            return (
              <button key={i} type="button"
                onClick={clickable ? () => { setStep(i as StepIdx); if (i === 1) setPricingSubstep(0); } : undefined}
                className={`px-3 py-1.5 text-xs rounded border whitespace-nowrap transition-colors ${
                  step === i ? "bg-brand-600 text-white border-brand-600"
                    : clickable ? "bg-white text-gray-700 border-gray-200 hover:bg-gray-50 cursor-pointer"
                    : "bg-white text-gray-400 border-gray-100 cursor-default"
                }`}
              >{s}</button>
            );
          })}
        </div>

        {/* Pricing substep bar */}
        {step === 1 && (
          <div className="flex items-center gap-1 px-5 py-2 border-b bg-gray-50 overflow-x-auto">
            {PRICING_SUBSTEPS.map((s, i) => (
              <button key={i} type="button"
                onClick={() => setPricingSubstep(i as PricingSubstep)}
                className={`px-3 py-1 text-xs rounded border whitespace-nowrap transition-colors ${
                  pricingSubstep === i ? "bg-brand-100 text-brand-700 border-brand-300 font-medium" : "bg-white text-gray-600 border-gray-200 hover:bg-gray-50"
                }`}
              >{s}</button>
            ))}
          </div>
        )}

        <div className="p-5 space-y-4 max-h-[65vh] overflow-y-auto">
          {isClosed && (
            <div className="p-3 border rounded bg-rose-50 text-rose-800 text-sm">
              This event is closed — it can't be edited until it's reopened from the cashup page (admin only).
            </div>
          )}
          {error && <div className="p-3 border rounded bg-red-50 text-red-700 text-sm">{error}</div>}

          {/* ── Step 0: Basic Details ── */}
          {step === 0 && (
            <div className="space-y-3">
              <div>
                <label className="block text-xs text-gray-600 mb-1">Title <span className="text-red-500">*</span></label>
                <input className="w-full border rounded px-3 py-2 text-sm" value={draft.title} onChange={e => upd({ title: e.target.value })} placeholder="Event title" />
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">Description</label>
                <textarea className="w-full border rounded px-3 py-2 text-sm" rows={3} value={draft.description} onChange={e => upd({ description: e.target.value })} />
              </div>
              <div className="grid sm:grid-cols-2 gap-3">
                <DTInput label="Start" required value={draft.startDate} onChange={v => upd({ startDate: v })} />
                <DTInput label="End" required value={draft.endDate} onChange={v => upd({ endDate: v })} />
                <DTInput label="Registration Deadline (optional)" value={draft.registrationDeadline} onChange={v => upd({ registrationDeadline: v })} />
                <DTInput label="Go Live At (optional)" value={draft.goLiveAt} onChange={v => upd({ goLiveAt: v })} hint="Leave blank to show immediately" />
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">Base Price (R) {mode === "create" && <span className="text-red-500">*</span>}</label>
                <input type="number" step="1" min="0" required={mode === "create"} className="w-full border rounded px-3 py-2 text-sm" value={draft.price} onChange={e => upd({ price: e.target.value })} placeholder="0" />
                {mode === "create" && <p className="text-[10px] text-gray-400 mt-0.5">Auto-fills the first ticket option below — enter 0 for a free event.</p>}
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">Picture</label>
                {draft.picture && <img src={resolveToApiOrigin(draft.picture) || undefined} alt="" className="h-20 w-20 object-cover rounded border mb-2" />}
                <div className="flex items-center gap-2">
                  <input className="flex-1 border rounded px-3 py-2 text-sm" placeholder="/uploads/... or https://" value={draft.picture} onChange={e => upd({ picture: e.target.value })} />
                  <UploadImageButton onUploaded={url => upd({ picture: url })} label="Upload" />
                </div>
              </div>
              <div>
                <label className="block text-xs text-gray-600 mb-1">URL Alias (optional)</label>
                <input className="w-full border rounded px-3 py-2 text-sm" placeholder="e.g. camp-2025, movie-night" value={draft.redirectUrl} onChange={e => upd({ redirectUrl: e.target.value })} />
              </div>
            </div>
          )}

          {/* ── Step 1: Items & Pricing (3 substeps) ── */}
          {step === 1 && pricingSubstep === 0 && (
            <div>
              <p className="text-xs text-gray-500 mb-3">Define your ticket types or purchasable items. Variants and early-bird pricing are configured in the next two steps.</p>
              <OptionsEditor options={options} onChange={handleOptionsChange} required={mode === "create"} />
            </div>
          )}
          {step === 1 && pricingSubstep === 1 && (
            <div>
              <p className="text-xs text-gray-500 mb-3">Add variants per option (e.g. Adult / Child, or t-shirt sizes). Leave blank if an option has only one price.</p>
              <VariantsEditor options={options} onChange={setOptions} />
            </div>
          )}
          {step === 1 && pricingSubstep === 2 && (
            <div>
              <p className="text-xs text-gray-500 mb-3">Set early-bird prices per variant (if variants exist) or per option. The cheapest applicable tier is used at registration time.</p>
              <EarlyBirdsEditor options={options} onChange={setOptions} />
            </div>
          )}

          {/* ── Step 2: Sections ── */}
          {step === 2 && (
            <div>
              <p className="text-xs text-gray-500 mb-3">Group ticket options into named sections shown on the registration page (e.g. "Tickets", "Add-ons", "Accommodation").</p>
              {mode === "edit" && ev?.id
                ? <SectionsManager eventId={ev.id} />
                : <SectionsDraftEditor sections={sectionDrafts} options={options} onChange={setSectionDrafts} />
              }
            </div>
          )}

          {/* ── Step 3: Form ── */}
          {step === 3 && (
            <div>
              <p className="text-xs text-gray-500 mb-3">Collect extra information from registrants (e.g. dietary requirements, t-shirt size, emergency contact).</p>
              <FormBuilder value={formDef} onChange={setFormDef} />
            </div>
          )}

          {/* ── Step 4: Visibility ── */}
          {step === 4 && (
            <div className="space-y-3">
              <div className="border rounded-lg p-4 bg-gray-50 space-y-3">
                <div className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1">Visibility & Access</div>
                <label className="flex items-start gap-2 text-sm cursor-pointer">
                  <input type="checkbox" className="mt-0.5" checked={draft.isHidden} onChange={e => upd({ isHidden: e.target.checked })} />
                  <span><strong>Hidden</strong> — not shown in the public event listing; still accessible via direct link</span>
                </label>
                <label className="flex items-start gap-2 text-sm cursor-pointer">
                  <input type="checkbox" className="mt-0.5" checked={!draft.requiresAuth} onChange={e => upd({ requiresAuth: !e.target.checked })} />
                  <span><strong>Allow guest registration</strong> — attendees can register without creating an account</span>
                </label>
                {mode === "edit" && (
                  <label className="flex items-start gap-2 text-sm cursor-pointer">
                    <input type="checkbox" className="mt-0.5" checked={draft.isActive} onChange={e => upd({ isActive: e.target.checked })} />
                    <span><strong>Active</strong> — when unchecked, the event is hidden everywhere including staff views</span>
                  </label>
                )}
              </div>
            </div>
          )}

          {/* ── Step 5: Notifications ── */}
          {step === 5 && (
            <div>
              {mode === "edit" && ev?.id ? (
                <NotifyRecipientsManager eventId={ev.id} creator={ev.createdBy || null} initialRecipients={ev.notifyRecipients} />
              ) : (
                <div className="space-y-3">
                  <p className="text-xs text-gray-500">
                    Choose who receives new registration, payment, and daily summary emails for this event.
                    Leave empty to send them to you (the event creator) instead.
                  </p>
                  <NotifyRecipientsPicker selected={notifyRecipientDrafts} onChange={setNotifyRecipientDrafts} />
                </div>
              )}
            </div>
          )}

          {/* ── Step 6: Attachments ── */}
          {step === 6 && (
            <div>
              <p className="text-xs text-gray-500 mb-3">Upload documents for attendees to download (e.g. waiver forms, info sheets, maps, schedules).</p>
              {mode === "edit" && ev?.id
                ? <AttachmentsManager eventId={ev.id} />
                : (
                  <div>
                    <div className="flex items-center justify-between mb-2">
                      <span className="text-sm font-medium">Files to attach</span>
                      <label className="cursor-pointer text-xs px-2 py-1 rounded border bg-white hover:bg-gray-50">
                        <input
                          type="file"
                          className="hidden"
                          accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.csv,.zip"
                          onChange={e => {
                            const file = e.target.files?.[0];
                            if (file) setStagedFiles(prev => [...prev, file]);
                            e.target.value = "";
                          }}
                        />
                        Add file
                      </label>
                    </div>
                    {stagedFiles.length === 0
                      ? <p className="text-xs text-gray-400">No files added. Files will be uploaded when the event is created.</p>
                      : (
                        <ul className="space-y-1">
                          {stagedFiles.map((f, i) => (
                            <li key={i} className="flex items-center justify-between text-xs">
                              <span className="truncate max-w-xs text-gray-700">{f.name}</span>
                              <button
                                type="button"
                                className="text-red-600 hover:underline ml-2"
                                onClick={() => setStagedFiles(prev => prev.filter((_, j) => j !== i))}
                              >Remove</button>
                            </li>
                          ))}
                        </ul>
                      )
                    }
                  </div>
                )
              }
            </div>
          )}
        </div>

        {/* Footer */}
        <div className="flex items-center justify-between px-5 py-3 border-t bg-gray-50 rounded-b-xl">
          <button type="button" disabled={isFirstStep} onClick={handleBack}
            className="px-3 py-1.5 text-sm rounded border bg-white hover:bg-gray-50 disabled:opacity-40">← Back</button>

          <div className="flex items-center gap-2">
            {mode === "create" ? (
              isLastStep && isOnLastSubstep ? (
                <button type="button" disabled={saving} onClick={handleSave}
                  className="px-5 py-1.5 text-sm rounded bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50 font-medium">
                  {saving ? "Creating…" : "Create Event"}
                </button>
              ) : (
                <button type="button" disabled={nextDisabled} onClick={handleNext}
                  title={basicDetailsIncomplete ? "Fill in title, start, end date, and base price to continue" : optionsIncomplete ? "Every option needs a price before continuing" : undefined}
                  className="px-4 py-1.5 text-sm rounded bg-brand-600 text-white hover:bg-brand-700 font-medium disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-brand-600">Next →</button>
              )
            ) : (
              <>
                {!(isLastStep && isOnLastSubstep) && (
                  <button type="button" onClick={handleNext}
                    className="px-3 py-1.5 text-sm rounded border bg-white hover:bg-gray-50">Next →</button>
                )}
                <button type="button" disabled={saving || isClosed} onClick={handleSave}
                  title={isClosed ? "This event is closed — reopen it first to make changes" : undefined}
                  className="px-5 py-1.5 text-sm rounded bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50 font-medium">
                  {saving ? "Saving…" : "Save Changes"}
                </button>
              </>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── event card ───────────────────────────────────────────────────────────────

function EventCard({ ev, onEdit }: { ev: any; onEdit: () => void }) {
  const isPast = ev.endDate && new Date(ev.endDate) < new Date();
  const isInactive = ev.isActive === false;
  const isClosed = ev.cashupStatus === "closed";
  return (
    <li className="border rounded-lg p-3 bg-white hover:bg-brand-50/40 cursor-pointer transition-colors flex items-start justify-between gap-3" onClick={onEdit}>
      <div className="min-w-0">
        <div className="flex items-center gap-2 flex-wrap">
          <span className="font-medium text-sm">{ev.title}</span>
          {isInactive && <span className="text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-500">Inactive</span>}
          {isPast && !isInactive && <span className="text-[10px] px-1.5 py-0.5 rounded bg-amber-50 text-amber-700">Past</span>}
          {ev.isHidden && <span className="text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-500">Hidden</span>}
          {isClosed && <span className="text-[10px] px-1.5 py-0.5 rounded bg-rose-50 text-rose-700">Closed</span>}
        </div>
        <div className="text-xs text-gray-500 mt-0.5">
          {ev.startDate ? new Date(ev.startDate).toLocaleDateString() : ""}{ev.endDate ? ` – ${new Date(ev.endDate).toLocaleDateString()}` : ""}
          {ev.price != null ? <span className="ml-2">R{Number(ev.price).toFixed(0)}</span> : ""}
        </div>
      </div>
      <span className="text-xs text-brand-600 shrink-0 mt-0.5">Edit →</span>
    </li>
  );
}

// ─── main page ────────────────────────────────────────────────────────────────

export default function ManageEventsPage() {
  const { user, loading, token } = useAuth();
  const router = useRouter();

  const canView = useMemo(() => {
    const role = user?.role;
    return role === "admin" || role === "supervisor";
  }, [user]);

  useEffect(() => {
    if (loading) return;
    if (!user) router.replace("/login");
  }, [user, loading, router]);

  const [events, setEvents] = useState<any[]>([]);
  const [loadingEvents, setLoadingEvents] = useState(false);
  const [error, setError] = useDismissingState<string | null>(null);

  const loadEvents = async () => {
    if (!token) return;
    setLoadingEvents(true);
    try {
      const evs = await apiFetch<any[]>("/api/events/all?includePast=true&includeInactive=true", { authToken: token });
      setEvents(Array.isArray(evs) ? evs.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()) : []);
    } catch (e: any) {
      setError(e?.message || "Failed to load events");
    } finally {
      setLoadingEvents(false);
    }
  };

  useEffect(() => { loadEvents(); }, [user, token]);

  const [modalMode, setModalMode] = useState<"create" | "edit" | null>(null);
  const [editingEvent, setEditingEvent] = useState<any | null>(null);
  const [includePast, setIncludePast] = useState(false);
  const [showInactive, setShowInactive] = useState(false);
  const [showClosed, setShowClosed] = useState(false);
  const [search, setSearch] = useState("");
  const isAdmin = user?.role === "admin";
  const now = Date.now();

  const filtered = useMemo(() => events.filter(ev => {
    if (!showInactive && ev.isActive === false) return false;
    if (!showClosed && ev.cashupStatus === "closed") return false;
    if (!includePast && ev.endDate && new Date(ev.endDate).getTime() < now) return false;
    if (search && !ev.title?.toLowerCase().includes(search.toLowerCase())) return false;
    return true;
  }), [events, includePast, showInactive, showClosed, search, now]);

  const openCreate = () => { setEditingEvent(null); setModalMode("create"); };
  const openEdit = async (ev: any) => {
    try {
      const full = await apiFetch<any>(`/api/events/${ev.id}`, { authToken: token || undefined });
      setEditingEvent(full);
    } catch { setEditingEvent(ev); }
    setModalMode("edit");
  };

  return (
    <div className="max-w-4xl mx-auto w-full p-6">
      <div className="flex items-center justify-between mb-6 flex-wrap gap-3">
        <div className="flex items-center gap-3">
          <div className="w-10 h-10 rounded-xl bg-brand-50 flex items-center justify-center shrink-0">
            <Calendar className="w-5 h-5 text-brand-600" />
          </div>
          <div>
            <h1 className="text-2xl font-semibold text-gray-900">Events</h1>
            <p className="text-sm text-gray-500 mt-0.5">Manage and create events</p>
          </div>
        </div>
        <div className="flex items-center gap-2">
          <button type="button" className="px-4 py-2 text-sm rounded-lg bg-brand-600 text-white hover:bg-brand-700 shadow-sm font-medium" onClick={openCreate}>+ Add Event</button>
          <button className="px-3 py-1.5 text-sm rounded-lg bg-gray-100 hover:bg-gray-200 text-gray-800" onClick={() => router.push("/dashboard")}>Back</button>
        </div>
      </div>

      {!canView && <div className="p-3 border rounded bg-yellow-50 text-yellow-800 text-sm mb-4">You need supervisor or admin access.</div>}
      {error && <div className="p-3 mb-3 border rounded bg-red-50 text-red-700 text-sm">{error}</div>}

      <div className="flex flex-wrap items-center gap-3 mb-4 text-sm">
        <input className="border rounded px-3 py-1.5 text-sm w-48" placeholder="Search by title…" value={search} onChange={e => setSearch(e.target.value)} />
        <label className="flex items-center gap-2 cursor-pointer">
          <input type="checkbox" checked={includePast} onChange={e => setIncludePast(e.target.checked)} /> Include past
        </label>
        <label className={`flex items-center gap-2 cursor-pointer ${!isAdmin ? "opacity-50 cursor-not-allowed" : ""}`}>
          <input type="checkbox" disabled={!isAdmin} checked={showInactive && isAdmin} onChange={e => setShowInactive(e.target.checked)} /> Show inactive
        </label>
        <label className="flex items-center gap-2 cursor-pointer">
          <input type="checkbox" checked={showClosed} onChange={e => setShowClosed(e.target.checked)} /> Show closed
        </label>
        {loadingEvents && <span className="text-xs text-gray-500">Loading…</span>}
      </div>

      <div className="border rounded-xl bg-white shadow-sm p-4">
        {filtered.length === 0
          ? <p className="text-sm text-gray-500">{events.length === 0 ? 'No events yet. Click "+ Add Event" to create one.' : "No events match the current filters."}</p>
          : <ul className="space-y-2">{filtered.map(ev => <EventCard key={ev.id} ev={ev} onEdit={() => openEdit(ev)} />)}</ul>
        }
      </div>

      {modalMode && (
        <EventModal mode={modalMode} event={editingEvent}
          onClose={() => { setModalMode(null); setEditingEvent(null); }}
          onSuccess={loadEvents} />
      )}
    </div>
  );
}