@router.get("/wizard/options")
async def wizard_options(db: AsyncSession = Depends(get_db),
                         user: User = Depends(get_current_user)):
    """All selectable entities for the 12-step wizard in one call."""
    lists = (await db.execute(select(RecipientList).where(RecipientList.user_id == user.id)
                              .order_by(RecipientList.id.desc()))).scalars().all()
    subjects = (await db.execute(select(SubjectLine).where(SubjectLine.user_id == user.id)
                                 .order_by(SubjectLine.id.desc()))).scalars().all()
    templates = (await db.execute(select(EmailTemplate).where(EmailTemplate.user_id == user.id)
                                  .order_by(EmailTemplate.id.desc()))).scalars().all()
    accounts = (await db.execute(select(GmailAccount).where(GmailAccount.user_id == user.id,
                                                            GmailAccount.status == "authorized",
                                                            GmailAccount.is_active == True))).scalars().all()  # noqa: E712
    phones = (await db.execute(select(CompanyPhoneNumber).where(CompanyPhoneNumber.user_id == user.id,
                                                                CompanyPhoneNumber.is_active == True))).scalars().all()  # noqa: E712
    attachments = (await db.execute(select(Attachment).where(Attachment.user_id == user.id)
                                    .order_by(Attachment.id.desc()))).scalars().all()

    async def valid_count(list_id: int) -> int:
        return (await db.execute(select(func.count()).select_from(Recipient)
                                 .where(Recipient.list_id == list_id,
                                        Recipient.is_valid == True))).scalar() or 0  # noqa: E712

    list_out = []
    for rl in lists:
        list_out.append({"id": rl.id, "name": rl.name, "row_count": rl.row_count,
                         "valid_count": await valid_count(rl.id)})

    return {
        "lists": list_out,
        "subjects": [{"id": s.id, "subject": s.subject[:160]} for s in subjects],
        "templates": [{"id": t.id, "name": t.name} for t in templates],
        "accounts": [{"id": a.id, "email": a.email, "sends_today": a.sends_today,
                      "daily_limit": a.daily_limit, "sending_method": a.sending_method}
                     for a in accounts],
        "phones": [{"id": p.id, "phone": p.phone, "label": p.label} for p in phones],
        "attachments": [{"id": a.id, "name": a.name, "kind": a.kind,
                         "filename": a.filename} for a in attachments],
        "limits": {"max_recipients": cfg.MAX_RECIPIENTS_PER_CAMPAIGN,
                   "max_accounts": cfg.MAX_GMAIL_ACCOUNTS_PER_CAMPAIGN,
                   "max_subjects": cfg.MAX_SUBJECTS},
    }


@router.post("/validate-step")
async def validate_step(payload: dict, db: AsyncSession = Depends(get_db),
                        user: User = Depends(get_current_user)):
    """Wizard step validation between steps (step 3, 6, 9, 11 checks)."""
    step = int(payload.get("step", 0))
    data = payload.get("data", {})
    errors = []

    if step == 1:
        if not (data.get("name") or "").strip():
            errors.append("Campaign name is required")
    if step == 2:
        if not data.get("recipient_list_id"):
            errors.append("Select a recipient list")
    if step == 3:
        ids = data.get("subject_set_ids") or []
        if not ids:
            errors.append("Select at least one subject line")
        if len(ids) > cfg.MAX_SUBJECTS:
            errors.append(f"Max {cfg.MAX_SUBJECTS} subject variations")
        strategy = data.get("subject_rotation")
        if strategy not in VALID_ENUMS["subject_rotation"]:
            errors.append("Invalid subject rotation strategy")
    if step == 4:
        ids = data.get("template_ids") or []
        if not ids:
            errors.append("Select at least one template")
        mode = data.get("template_sequence_mode")
        if mode not in VALID_ENUMS["template_sequence_mode"]:
            errors.append("Invalid template sequence mode")
    if step == 5:
        mode = data.get("attach_mode", "none")
        if mode not in VALID_ENUMS["attach_mode"]:
            errors.append("Invalid attachment mode")
        elif mode != "none" and not (data.get("attach_pdf_template_id") or data.get("attachment_id")):
            errors.append("Select a template/attachment for PDF or file mode")
    if step == 6:
        vc = data.get("variable_config") or {}
        try:
            if "amount_min" in vc and "amount_max" in vc:
                if float(vc["amount_min"]) > float(vc["amount_max"]):
                    errors.append("Amount min must be <= max")
            if "shipping_min" in vc and "shipping_max" in vc:
                if float(vc["shipping_min"]) > float(vc["shipping_max"]):
                    errors.append("Shipping min must be <= max")
        except (TypeError, ValueError):
            errors.append("Ranges must be numeric")
    if step == 7:
        if not data.get("account_ids"):
            errors.append("Select at least one Gmail account")
        elif len(data["account_ids"]) > cfg.MAX_GMAIL_ACCOUNTS_PER_CAMPAIGN:
            errors.append(f"Max {cfg.MAX_GMAIL_ACCOUNTS_PER_CAMPAIGN} accounts")
    if step == 8:
        strategy = data.get("rotation_strategy")
        if strategy not in VALID_ENUMS["rotation_strategy"]:
            errors.append("Invalid rotation strategy")
        if strategy == "weighted":
            weights = data.get("rotation_weights") or {}
            if not weights:
                errors.append("Weighted rotation requires weights per account")
    if step == 9:
        try:
            mpa = int(data.get("max_per_account", 200))
            if not (1 <= mpa <= 2000):
                errors.append("max_per_account must be 1-2000")
        except (TypeError, ValueError):
            errors.append("max_per_account must be an integer")
        try:
            si = float(data.get("send_interval_seconds", 1.0))
            if not (0.5 <= si <= 3600):
                errors.append("send_interval_seconds must be 0.5-3600")
        except (TypeError, ValueError):
            errors.append("send_interval_seconds must be numeric")
    if step == 10:
        mode = data.get("phone_selection")
        if mode not in VALID_ENUMS["phone_selection"]:
            errors.append("Invalid phone selection mode")
    if step == 11:
        if not data.get("unsubscribe_enabled", True):
            errors.append("Unsubscribe handling must stay enabled (compliance requirement)")

    return {"step": step, "valid": not errors, "errors": errors}
===NAV
158:const NAV = [
160:  { id: "index",  ico: "◧", label: "Dashboard", href: "/" },
161:  { id: "campaigns", ico: "✦", label: "Campaigns", href: "/campaigns" },
163:  { id: "gmail-accounts", ico: "✉", label: "Gmail Accounts", href: "/gmail-accounts" },
164:  { id: "recipients", ico: "☰", label: "Recipients", href: "/recipients" },
165:  { id: "send-test", ico: "➤", label: "Send Test", href: "/send-test" },
166:  { id: "logs", ico: "≣", label: "Logs", href: "/logs" },
168:  { id: "subjects", ico: "T", label: "Subject Lines", href: "/subjects" },
169:  { id: "templates", ico: "❑", label: "HTML Templates", href: "/templates" },
170:  { id: "saved-templates", ico: "❒", label: "Saved Templates", href: "/saved-templates" },
171:  { id: "attachments", ico: "◫", label: "Attachments", href: "/attachments" },
172:  { id: "phones", ico: "☏", label: "Company Phones", href: "/phones" },
174:  { id: "suppression", ico: "⊘", label: "Suppression List", href: "/suppression" },
175:  { id: "settings", ico: "⚙", label: "Settings", href: "/settings" },
191:    nav.append(el("a", { class: "nav-link" + (active ? " active" : ""), href: item.href },
