"use client";

import React from "react";
import Image from "next/image";
import { baseRoms } from "@/data/baseRoms";
import { getScreenshotTutorial } from "@/data/screenshotTutorials";
import HackCard from "@/components/HackCard";
import { createClient } from "@/utils/supabase/client";
import { prepareSubmission, presignPatchAndSaveCovers, confirmPatchUpload, saveHackCovers } from "@/app/submit/actions";
import { presignCoverUpload } from "@/app/hack/actions";
import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import Markdown from "@/components/Markdown/Markdown";
import { RxDragHandleDots2 } from "react-icons/rx";
import { FaDiscord, FaTwitter, FaGithub } from "react-icons/fa6";
import { FiExternalLink } from "react-icons/fi";
import PokeCommunityIcon from "@/components/Icons/PokeCommunityIcon";
import { useAuthContext } from "@/contexts/AuthContext";
import { useBaseRoms } from "@/contexts/BaseRomContext";
import TagSelector from "@/components/Submit/TagSelector";
import BinFile from "rom-patcher-js/rom-patcher-js/modules/BinFile.js";
import Select, { SelectOption, SelectDivider } from "@/components/Primitives/Select";
import BPS from "rom-patcher-js/rom-patcher-js/modules/RomPatcher.format.bps.js";
import { sha1Hex } from "@/utils/hash";
import { platformAccept, setDraftCovers, getDraftCovers, deleteDraftCovers } from "@/utils/idb";
import { slugify, sortOrderedTags } from "@/utils/format";

function SortableCoverItem({ id, index, url, filename, onRemove }: { id: string; index: number; url: string; filename: string; onRemove: () => void }) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
  };
  return (
    <div ref={setNodeRef} style={style} className="rounded-md">
      <div className={`h-16 flex items-center justify-between gap-3 p-2 bg-[var(--surface-2)] ring-1 ring-inset ring-[var(--border)] ${isDragging ? "opacity-60" : ""}`}>
        <div className="flex items-center gap-3">
          <div className="cursor-grab select-none pr-1 text-foreground/60" title="Drag to reorder" {...attributes} {...listeners}>
            <RxDragHandleDots2 size={24} />
          </div>
          <div className="relative h-12 w-20 overflow-hidden rounded">
            <Image src={url} alt={`Cover ${index + 1}`} fill className="object-cover" unoptimized />
          </div>
          <div className="min-w-0">
            <div className="truncate max-w-[260px] text-xs text-foreground/80">{filename}</div>
            {index === 0 && <div className="text-[10px] text-emerald-400/90">Primary</div>}
          </div>
        </div>
        <div className="flex items-center gap-1">
          <button
            type="button"
            onClick={onRemove}
            className="inline-flex h-8 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2 text-xs text-red-600 transition-colors hover:bg-black/5 dark:text-red-300 dark:hover:bg-white/10"
          >
            Remove
          </button>
        </div>
      </div>
    </div>
  );
}

interface HackSubmitFormProps {
  dummy?: boolean;
  isArchive?: boolean;
  permissionFrom?: string;
  customCreator?: string;
}

export default function HackSubmitForm({
  dummy = false,
  isArchive = false,
  permissionFrom = undefined,
  customCreator = undefined,
}: HackSubmitFormProps) {
  const MAX_COVERS = 10;
  const { profile, user } = useAuthContext();
  const [isHydrating, setIsHydrating] = React.useState(true);
  const [restoredDraft, setRestoredDraft] = React.useState(false);
  const hydratedFromDraftRef = React.useRef(false);
  const draftKey = React.useMemo(() => (user?.id ? `hack-submit/v1/${user.id}` : null), [user?.id]);
  const initialDraftRef = React.useRef<any>(undefined);
  if (initialDraftRef.current === undefined && typeof window !== "undefined") {
    try {
      if (draftKey) {
        const raw = localStorage.getItem(draftKey);
        initialDraftRef.current = raw ? JSON.parse(raw) : {};
      } else {
        initialDraftRef.current = null; // will hydrate later when user is known
      }
    } catch {
      initialDraftRef.current = {};
    }
  }
  const [title, setTitle] = React.useState(() => initialDraftRef.current?.title || "");
  const [summary, setSummary] = React.useState(() => initialDraftRef.current?.summary || "");
  const [description, setDescription] = React.useState(() => initialDraftRef.current?.description || "");
  const [newCoverFiles, setNewCoverFiles] = React.useState<File[]>([]);
  const [coverErrors, setCoverErrors] = React.useState<string[]>([]);
  const [baseRom, setBaseRom] = React.useState(() => initialDraftRef.current?.baseRom || "");
  const [platform, setPlatform] = React.useState<"GB" | "GBC" | "GBA" | "NDS" | "">(() => (initialDraftRef.current?.platform as any) || "");
  const [version, setVersion] = React.useState(() => initialDraftRef.current?.version || "");
  const [language, setLanguage] = React.useState(() => initialDraftRef.current?.language || "");
  const [completionStatus, setCompletionStatus] = React.useState<"Complete" | "Demo" | "Alpha" | "Beta" | "">(() => (initialDraftRef.current?.completionStatus as any) || "");
  const [boxArt, setBoxArt] = React.useState(() => initialDraftRef.current?.boxArt || "");
  const [discord, setDiscord] = React.useState(() => initialDraftRef.current?.discord || "");
  const [twitter, setTwitter] = React.useState(() => initialDraftRef.current?.twitter || "");
  const [pokecommunity, setPokecommunity] = React.useState(() => initialDraftRef.current?.pokecommunity || "");
  const [github, setGithub] = React.useState(() => initialDraftRef.current?.github || "");
  const [verificationContactInfo, setVerificationContactInfo] = React.useState(() => initialDraftRef.current?.verificationContactInfo || "");
  const [tags, setTags] = React.useState<string[]>(() => (Array.isArray(initialDraftRef.current?.tags) ? initialDraftRef.current.tags : []));
  const [showMdPreview, setShowMdPreview] = React.useState<boolean>(() => !!initialDraftRef.current?.showMdPreview);
  const [originalAuthor, setOriginalAuthor] = React.useState<string>(() => {
    // If customCreator is provided, use it; otherwise use draft or empty string
    if (customCreator) return customCreator;
    return initialDraftRef.current?.originalAuthor || "";
  });
  const [patchFile, setPatchFile] = React.useState<File | null>(null);
  const [patchMode, setPatchMode] = React.useState<"bps" | "rom">(() => (initialDraftRef.current?.patchMode === "rom" ? "rom" : "bps"));
  const [genStatus, setGenStatus] = React.useState<"idle" | "generating" | "ready" | "error">("idle");
  const [checksumStatus, setChecksumStatus] = React.useState<"idle" | "validating" | "valid" | "invalid" | "unknown">("idle");
  const [checksumError, setChecksumError] = React.useState<string>("");
  const [genError, setGenError] = React.useState<string>("");
  const [submitting, setSubmitting] = React.useState(false);
  const maxSteps = isArchive ? 3 : 4;
  const [step, setStep] = React.useState<number>(() => {
    const s = initialDraftRef.current?.step;
    return Number.isInteger(s) ? Math.min(maxSteps, Math.max(1, s)) : 1;
  });
  const supabase = createClient();
  const isDummy = !!dummy;

  const titleInputRef = React.useRef<HTMLInputElement | null>(null);
  const versionInputRef = React.useRef<HTMLInputElement | null>(null);
  const screenshotsInputRef = React.useRef<HTMLInputElement | null>(null);
  const patchInputRef = React.useRef<HTMLInputElement | null>(null);
  const modifiedRomInputRef = React.useRef<HTMLInputElement | null>(null);

  const baseRomEntry = React.useMemo(() => baseRoms.find((r) => r.id === baseRom) || null, [baseRom]);
  const baseRomName = baseRomEntry?.name || "";
  const baseRomPlatform = baseRomEntry?.platform;
  const tutorialInfo = React.useMemo(() => getScreenshotTutorial(baseRomPlatform), [baseRomPlatform]);
  const { isLinked, hasPermission, hasCached, importUploadedBlob, ensurePermission, getFileBlob, supported } = useBaseRoms();

  const baseRomReady = baseRom && (hasPermission(baseRom) || hasCached(baseRom));
  const baseRomNeedsPermission = baseRom && isLinked(baseRom) && !baseRomReady;
  const baseRomMissing = baseRom && !isLinked(baseRom) && !hasCached(baseRom);

  const coverPreviews = React.useMemo(() => {
    return newCoverFiles.map((f) => URL.createObjectURL(f));
  }, [newCoverFiles]);

  React.useEffect(() => {
    return () => {
      coverPreviews.forEach((u) => URL.revokeObjectURL(u));
    };
  }, [coverPreviews]);

  // Load persisted screenshot blobs after user/draftKey known
  React.useEffect(() => {
    if (dummy || !draftKey) return;
    (async () => {
      try {
        const files = await getDraftCovers(draftKey);
        if (files && files.length) {
          setNewCoverFiles(files);
          hydratedFromDraftRef.current = true; setRestoredDraft(true);
        }
      } catch {}
    })();
  }, [dummy, draftKey]);

  React.useEffect(() => {
    setPatchFile(null);
    setGenStatus("idle");
    setGenError("");
    patchInputRef.current && (patchInputRef.current.value = "");
    modifiedRomInputRef.current && (modifiedRomInputRef.current.value = "");
  }, [patchMode]);

  // Sync originalAuthor with customCreator if provided
  React.useEffect(() => {
    if (customCreator) {
      setOriginalAuthor(customCreator);
    }
  }, [customCreator]);

  const uploadCovers = async (slug: string) => {
    if (!newCoverFiles || newCoverFiles.length === 0) return [] as string[];
    const urls: string[] = [];
    for (let i = 0; i < newCoverFiles.length; i++) {
      const file = newCoverFiles[i];
      const fileExt = file.name.split('.').pop();
      const path = `${slug}/${Date.now()}-${i}.${fileExt}`;
      const presigned = await presignCoverUpload({ slug, objectKey: path });
      if (!presigned.ok) throw new Error(presigned.error || 'Failed to presign cover upload');
      await fetch(presigned.presignedUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type || 'image/jpeg' } });
      urls.push(path);
    }
    return urls;
  };

  function getAllowedSizesForPlatform(platform: "GB" | "GBC" | "GBA" | "NDS") {
    if (platform === "GB" || platform === "GBC") return [{ w: 160, h: 144 }];
    if (platform === "GBA") return [{ w: 240, h: 160 }];
    return [{ w: 256, h: 192 }, { w: 256, h: 384 }];
  }

  async function validateImageDimensions(file: File, allowed: { w: number; h: number }[]) {
    return new Promise<boolean>((resolve) => {
      const img = document.createElement("img");
      const url = URL.createObjectURL(file);
      img.onload = () => {
        const ok = allowed.some((s) => img.naturalWidth === s.w && img.naturalHeight === s.h);
        URL.revokeObjectURL(url);
        resolve(ok);
      };
      img.onerror = () => {
        URL.revokeObjectURL(url);
        resolve(false);
      };
      img.src = url;
    });
  }
  const overLimit = newCoverFiles.length > MAX_COVERS;

  const removeAt = (index: number) => {
    setNewCoverFiles((prev) => prev.filter((_, i) => i !== index));
  };

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 5 } })
  );

  const onDragEnd = (event: any) => {
    const { active, over } = event;
    if (!over || active.id === over.id) return;
    const ids = newCoverFiles.map((f, i) => `${f.name}-${i}`);
    const oldIndex = ids.indexOf(active.id as string);
    const newIndex = ids.indexOf(over.id as string);
    if (oldIndex === -1 || newIndex === -1) return;
    setNewCoverFiles((prev) => arrayMove(prev, oldIndex, newIndex));
  };

  // Persist draft covers whenever they change
  React.useEffect(() => {
    if (dummy || !draftKey) return;
    (async () => {
      try { await setDraftCovers(draftKey, newCoverFiles); } catch {}
    })();
  }, [dummy, draftKey, newCoverFiles]);

  React.useEffect(() => {
    if (isDummy || isHydrating) return;
    let target: HTMLInputElement | null = null;
    if (step === 1) {
      target = titleInputRef.current;
    } else if (step === 2 && !isArchive) {
      target = versionInputRef.current;
    } else if ((step === 2 && isArchive) || (step === 3 && !isArchive)) {
      target = screenshotsInputRef.current;
    } else if (step === 4 && !isArchive) {
      target = patchInputRef.current;
    }
    if (!target) return;
    const raf1 = requestAnimationFrame(() => {
      const raf2 = requestAnimationFrame(() => {
        if (!target?.disabled) target?.focus();
      });
      // Cleanup nested rAF on effect re-run
      return () => cancelAnimationFrame(raf2);
    });
    return () => cancelAnimationFrame(raf1);
  }, [step, isDummy, isHydrating]);

  const slug = slugify(title || "");

  // Draft load after user is known (covers case where user id wasn't ready at first render)
  React.useEffect(() => {
    if (dummy) { setIsHydrating(false); return; }
    if (!draftKey) return; // wait for user
    try {
      // If we didn't have the draft synchronously, hydrate now
      if (!initialDraftRef.current || Object.keys(initialDraftRef.current || {}).length === 0) {
        const raw = localStorage.getItem(draftKey);
        if (raw) {
          const data = JSON.parse(raw);
          if (data && typeof data === "object") {
            const isEmpty =
              !title && !summary && !description && !baseRom && !platform && !version && !language && !completionStatus && !boxArt && !discord && !twitter && !pokecommunity && !github && !verificationContactInfo && (!tags || tags.length === 0) && !originalAuthor;
            if (isEmpty) {
              let applied = false;
              if (typeof data.title === "string") setTitle(data.title);
              if (typeof data.title === "string") applied = applied || !!data.title;
              if (typeof data.summary === "string") setSummary(data.summary);
              if (typeof data.summary === "string") applied = applied || !!data.summary;
              if (typeof data.description === "string") setDescription(data.description);
              if (typeof data.description === "string") applied = applied || !!data.description;
              if (typeof data.baseRom === "string") setBaseRom(data.baseRom);
              if (typeof data.baseRom === "string") applied = applied || !!data.baseRom;
              if (["GB","GBC","GBA","NDS",""].includes(data.platform)) setPlatform(data.platform);
              if (["GB","GBC","GBA","NDS",""].includes(data.platform)) applied = applied || !!data.platform;
              if (typeof data.version === "string") setVersion(data.version);
              if (typeof data.version === "string") applied = applied || !!data.version;
              if (typeof data.language === "string") setLanguage(data.language);
              if (typeof data.language === "string") applied = applied || !!data.language;
              if (["Complete","Demo","Alpha","Beta",""].includes(data.completionStatus)) setCompletionStatus(data.completionStatus);
              if (["Complete","Demo","Alpha","Beta",""].includes(data.completionStatus)) applied = applied || !!data.completionStatus;
              if (typeof data.boxArt === "string") setBoxArt(data.boxArt);
              if (typeof data.boxArt === "string") applied = applied || !!data.boxArt;
              if (typeof data.discord === "string") setDiscord(data.discord);
              if (typeof data.discord === "string") applied = applied || !!data.discord;
              if (typeof data.twitter === "string") setTwitter(data.twitter);
              if (typeof data.twitter === "string") applied = applied || !!data.twitter;
              if (typeof data.pokecommunity === "string") setPokecommunity(data.pokecommunity);
              if (typeof data.pokecommunity === "string") applied = applied || !!data.pokecommunity;
              if (typeof data.github === "string") setGithub(data.github);
              if (typeof data.github === "string") applied = applied || !!data.github;
              if (typeof data.verificationContactInfo === "string") setVerificationContactInfo(data.verificationContactInfo);
              if (typeof data.verificationContactInfo === "string") applied = applied || !!data.verificationContactInfo;
              if (Array.isArray(data.tags)) setTags(data.tags.filter((t: any) => typeof t === "string"));
              if (Array.isArray(data.tags)) applied = applied || data.tags.length > 0;
              // Only load originalAuthor from draft if customCreator is not provided
              if (!customCreator && typeof data.originalAuthor === "string") {
                setOriginalAuthor(data.originalAuthor);
                applied = applied || !!data.originalAuthor;
              }
              if (data.step && Number.isInteger(data.step)) setStep(Math.min(maxSteps, Math.max(1, data.step)));
              if (typeof data.showMdPreview === "boolean") setShowMdPreview(data.showMdPreview);
              if (data.patchMode === "bps" || data.patchMode === "rom") setPatchMode(data.patchMode);
              if (applied) { hydratedFromDraftRef.current = true; setRestoredDraft(true); }
            }
          }
        }
      }
    } catch {
      // ignore
    } finally {
      setIsHydrating(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [draftKey]);

  // If we synchronously seeded from initialDraftRef, mark as restored
  React.useEffect(() => {
    if (dummy || !draftKey || hydratedFromDraftRef.current) return;
    const d = initialDraftRef.current;
    if (!d || typeof d !== "object") return;
    // Don't count originalAuthor if customCreator is provided
    const hasAny = Boolean(
      d.title || d.summary || d.description || d.baseRom || d.platform || d.version || d.language || d.completionStatus || d.boxArt || d.discord || d.twitter || d.pokecommunity || d.github || d.verificationContactInfo || (Array.isArray(d.tags) && d.tags.length > 0) || (!customCreator && d.originalAuthor)
    );
    if (hasAny) { hydratedFromDraftRef.current = true; setRestoredDraft(true); }
  }, [dummy, draftKey, customCreator]);

  React.useEffect(() => {
    if (dummy || !draftKey || isHydrating) return;
    const handle = setTimeout(() => {
      try {
        const data: any = {
          title,
          summary,
          description,
          baseRom,
          platform,
          version,
          language,
          completionStatus,
          boxArt,
          discord,
          twitter,
          pokecommunity,
          github,
          verificationContactInfo,
          tags,
          step,
          showMdPreview,
          patchMode,
        };
        // Only save originalAuthor if customCreator is not provided
        if (!customCreator) {
          data.originalAuthor = originalAuthor;
        }
        localStorage.setItem(draftKey, JSON.stringify(data));
      } catch {
        // ignore
      }
    }, 400);
    return () => clearTimeout(handle);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    dummy,
    draftKey,
    isHydrating,
    title,
    summary,
    description,
    baseRom,
    platform,
    version,
    language,
    completionStatus,
    boxArt,
    discord,
    twitter,
    pokecommunity,
    github,
    verificationContactInfo,
    tags,
    originalAuthor,
    customCreator,
    step,
    showMdPreview,
    patchMode,
  ]);

  const summaryLimit = 120;
  const summaryTooLong = summary.length > summaryLimit;

  const allowedSizes = platform ? getAllowedSizesForPlatform(platform) : [];

  const urlLike = (s: string) => !s || /^https?:\/\//i.test(s);

  const allSocialValid = [discord, twitter, pokecommunity, github].every((s) => !s || urlLike(s));

  const step1Valid = !!title.trim() && !!platform && !!baseRom.trim() && !!language.trim() && !!completionStatus.trim() && (isArchive ? !!originalAuthor.trim() : true);
  const step2Valid = (isArchive ? true : !!version.trim()) && !!summary.trim() && !summaryTooLong && !!description.trim() && tags.length > 0;
  const step3Valid = (newCoverFiles.length > 0) && !overLimit && coverErrors.length === 0 && (!boxArt.trim() || urlLike(boxArt)) && allSocialValid;
  const isValid = step1Valid && step2Valid && step3Valid && (isArchive ? true : !!patchFile);

  const onSubmit = async () => {
    if (!isValid || submitting) return;
    setSubmitting(true);
    try {
      const fd = new FormData();
      fd.set('title', title);
      fd.set('summary', summary);
      fd.set('description', description);
      fd.set('base_rom', baseRom);
      fd.set('language', language);
      fd.set('completion_status', completionStatus);
      fd.set('version', version);
      if (boxArt) fd.set('box_art', boxArt);
      if (discord) fd.set('discord', discord);
      if (twitter) fd.set('twitter', twitter);
      if (pokecommunity) fd.set('pokecommunity', pokecommunity);
      if (github) fd.set('github', github);
      if (verificationContactInfo) fd.set('verification_contact_info', verificationContactInfo);
      if (tags.length) fd.set('tags', tags.join(','));
      if (isArchive) {
        fd.set('is_archive', 'true');
      }
      if (originalAuthor) {
        fd.set('original_author', originalAuthor);
      }
      if (permissionFrom) {
        fd.set('permission_from', permissionFrom);
      }

      console.log('[HackSubmitForm] Preparing submission...');

      const prepared = await prepareSubmission(fd);
      if (!prepared.ok) throw new Error(prepared.error || 'Failed to prepare');

      console.log('[HackSubmitForm] Uploading covers...');

      const uploadedCoverUrls = await uploadCovers(prepared.slug);

      if (isArchive) {
        // For archives, we don't need patch upload
        const coversSaved = await saveHackCovers({ slug: prepared.slug, coverUrls: uploadedCoverUrls });
        if (!coversSaved.ok) throw new Error(coversSaved.error || 'Failed to save covers');
        try {
          if (draftKey) {
            localStorage.removeItem(draftKey);
            await deleteDraftCovers(draftKey);
          }
        } catch {}
        window.location.href = `/hack/${prepared.slug}`;
      } else {
        console.log('[HackSubmitForm] Getting patch upload URL...');
        const presigned = await presignPatchAndSaveCovers({ slug: prepared.slug, version, coverUrls: uploadedCoverUrls });
        if (!presigned.ok) throw new Error(presigned.error || 'Failed to presign');

        if (patchFile) {
          console.log('[HackSubmitForm] Uploading patch...');
          await fetch(presigned.presignedUrl, { method: 'PUT', body: patchFile, headers: { 'Content-Type': 'application/octet-stream' } });
          const finalized = await confirmPatchUpload({ slug: prepared.slug, objectKey: presigned.objectKey!, version, firstUpload: true, publishAutomatically: true });
          if (!finalized.ok) throw new Error(finalized.error || 'Failed to finalize');
          try {
            if (draftKey) {
              localStorage.removeItem(draftKey);
              await deleteDraftCovers(draftKey);
            }
          } catch {}
          window.location.href = finalized.redirectTo!;
        } else {
          console.log('[HackSubmitForm] No patch file, redirecting to hack page...');
          try {
            if (draftKey) {
              localStorage.removeItem(draftKey);
              await deleteDraftCovers(draftKey);
            }
          } catch {}
          window.location.href = `/hack/${prepared.slug}`;
        }
      }
      console.log('[HackSubmitForm] Submission successful');
    } catch (e: any) {
      console.log('[HackSubmitForm] Submission failed', e);
      alert(e.message ? `There was an error during submission:\n\n===\n${e.message}\n===\n\nYour hack might have only been partially submitted. Try going to your dashboard and see if your hack is listed there. If not, please contact support.` : 'Submission failed');
    } finally {
      setSubmitting(false);
    }
  };

  async function onGrantPermission() {
    if (!baseRom) return;
    await ensurePermission(baseRom, true);
  }

  async function onUploadBaseRom(e: React.ChangeEvent<HTMLInputElement>) {
    try {
      setGenError("");
      const f = e.target.files?.[0];
      if (!f) return;
      const matchedId = await importUploadedBlob(f);
      if (!matchedId) {
        setGenError("That ROM doesn't match any supported base ROM.");
        return;
      }
      if (matchedId !== baseRom) {
        const matchedName = baseRoms.find(r => r.id === matchedId)?.name;
        setGenError(`This ROM matches "${matchedName ?? matchedId}", but the form requires "${baseRomName}".`);
        return;
      }
    } catch {
      setGenError("Failed to import base ROM.");
    }
  }

  async function onUploadModifiedRom(e: React.ChangeEvent<HTMLInputElement>) {
    try {
      setGenStatus("generating");
      setGenError("");
      const mod = e.target.files?.[0] || null;
      if (!mod || !baseRom) {
        setGenStatus("idle");
        return;
      }
      let baseFile = await getFileBlob(baseRom);
      if (!baseFile) {
        setGenStatus("idle");
        setGenError("Base ROM not available.");
        return;
      }
      if (baseRomEntry?.sha1) {
        const hash = await sha1Hex(baseFile);
        if (hash.toLowerCase() !== baseRomEntry.sha1.toLowerCase()) {
          setGenStatus("error");
          setGenError("Selected base ROM hash does not match the chosen base ROM.");
          return;
        }
      }
      const [origBuf, modBuf] = await Promise.all([baseFile.arrayBuffer(), mod.arrayBuffer()]);
      const origBin = new BinFile(origBuf);
      const modBin = new BinFile(modBuf);
      const deltaMode = origBin.fileSize <= 4194304;
      const patch = BPS.buildFromRoms(origBin, modBin, deltaMode);
      const fileName = slug || title || "patch";
      const patchBin = patch.export(fileName);
      const out = new File([patchBin._u8array], `${fileName}.bps`, { type: 'application/octet-stream' });
      setPatchFile(out);
      setGenStatus("ready");
    } catch (err: any) {
      setGenStatus("error");
      setGenError(err?.message || "Failed to generate patch.");
    }
  }

  async function onUploadPatch(e: React.ChangeEvent<HTMLInputElement>) {
    try {
      setChecksumStatus("validating");
      setChecksumError("");

      const patch = e.target.files?.[0] || null;
      if (!patch) {
        setChecksumStatus("idle");
        setChecksumError("");
        setPatchFile(null);
        return;
      }

      if (!baseRomEntry) {
        setChecksumStatus("unknown");
        setChecksumError("A checksum is not available to validate this patch file. Proceed at your own risk, or upload your modified ROM instead.");
        return;
      }

      // Verify that the patch is a valid BPS file for the selected base ROM
      const bps = BPS.fromFile(new BinFile(await patch.arrayBuffer()));
      if (bps.sourceChecksum === 0 || bps.sourceChecksum === undefined) {
        setChecksumStatus("unknown");
        setChecksumError("A checksum is not available to validate this patch file. Proceed at your own risk, or upload your modified ROM instead.");
        return;
      }

      const baseRomChecksum = parseInt(baseRomEntry.crc32, 16);
      if (bps.sourceChecksum !== baseRomChecksum) {
        setChecksumStatus("invalid");
        setChecksumError("Checksum validation failed. The patch file is not compatible with the selected base ROM.");
        return;
      }

      // All checks passed, set the checksum status to valid
      setChecksumStatus("valid");
      setChecksumError("");

      setPatchFile(patch);
    }
    catch (err: any) {
      setChecksumStatus("unknown");
      setChecksumError(err?.message || "Failed to validate patch file.");
    }
  }

  const preview = {
    slug: slug || "preview",
    title: title || "Your hack title",
    author: (isArchive || customCreator) ?
      (originalAuthor || "Unknown") :
      (profile?.username ? `@${profile.username}` : "You"),
    summary: (summary || "Short description, max 100 characters.") as string,
    description: (description || "Write a longer markdown description here.") as string,
    covers: coverPreviews,
    baseRomId: baseRom,
    downloads: 0,
    version: isArchive ? "Archive" : (version || "v0.0.0"),
    tags: sortOrderedTags(tags.map((name, index) => ({ name, order: index + 1 }))),
    ...(boxArt ? { boxArt } : {}),
    socialLinks:
      discord || twitter || pokecommunity || github
        ? { discord: discord || undefined, twitter: twitter || undefined, pokecommunity: pokecommunity || undefined, github: github || undefined }
        : undefined,
    createdAt: new Date().toISOString(),
    patchUrl: "",
    is_archive: isArchive,
  };

  const hasBaseRom = !!baseRom.trim();

  // Before each group of base ROMs of the same category, add a divider with the category name
  const baseRomOptions = React.useMemo<(SelectOption | SelectDivider)[]>(() => {
    let currentCategory = "";
    const options: (SelectOption | SelectDivider)[] = [];
    for (const rom of baseRoms) {
      if (!platform || rom.platform !== platform) continue;
      if (rom.category && rom.category !== currentCategory) {
        currentCategory = rom.category;
        options.push({ type: "divider", label: currentCategory });
      }
      options.push({
        value: rom.id,
        label: `${rom.name.replace('Pokémon ', '')} (${rom.region})`,
        description: rom.sha1,
      });
    }
    return options;
  }, [platform]);

  return (
    <div className="flex flex-col gap-8 lg:flex-row w-full">
      <div className="flex-1">
        <form className="grid gap-5">
          <div className="text-xs italic text-foreground/60">* Required</div>
          {isHydrating && (
            <div className="flex items-center gap-2 text-[13px] text-foreground/70 animate-pulse">
              <span className="inline-block h-2 w-2 rounded-full bg-foreground/50"></span>
              Checking for existing draft…
            </div>
          )}
          {customCreator && permissionFrom && (
            <div className="flex items-center gap-3 rounded-md border border-blue-500/30 bg-blue-500/10 p-3 text-sm text-blue-900 dark:text-blue-100">
              <div className="flex items-center justify-center w-2 h-full">
                <div className="inline-block h-2 w-2 rounded-full bg-blue-400" />
              </div>
              <div className="flex flex-col gap-1">
                <p className="font-semibold">
                  {customCreator === permissionFrom
                    ? `Submitting on behalf of ${customCreator} with their permission.`
                    : `Submitting on behalf of ${customCreator}`}
                </p>
                {customCreator !== permissionFrom && (
                  <p className="text-xs text-blue-800 dark:text-blue-200">
                    You are submitting this hack with permission from {permissionFrom}.
                  </p>
                )}
              </div>
            </div>
          )}
          {!isHydrating && restoredDraft && (
            <div className="rounded-md border border-amber-500/30 bg-amber-500/10 p-3 text-sm text-amber-900 dark:text-amber-100 flex items-center justify-between">
              <div className="flex items-center gap-2">
                <span className="inline-block h-2 w-2 rounded-full bg-amber-400"></span>
                Restored a previously saved draft.
              </div>
              <button
                type="button"
                onClick={async () => {
                  const ok = window.confirm("Clear saved draft? This will remove all saved fields and screenshots.");
                  if (!ok) return;
                  try {
                    if (draftKey) {
                      localStorage.removeItem(draftKey);
                      await deleteDraftCovers(draftKey);
                    }
                  } catch {}
                  // Reset form state
                  setTitle("");
                  setSummary("");
                  setDescription("");
                  setBaseRom("");
                  setPlatform("");
                  setVersion("");
                  setLanguage("");
                  setCompletionStatus("");
                  setBoxArt("");
                  setDiscord("");
                  setTwitter("");
                  setPokecommunity("");
                  setGithub("");
                  setVerificationContactInfo("");
                  setTags([]);
                  setNewCoverFiles([]);
                  setCoverErrors([]);
                  setPatchFile(null);
                  setOriginalAuthor(customCreator || "");
                  setShowMdPreview(false);
                  setStep(1);
                  // Clear file inputs if present
                  if (screenshotsInputRef.current) screenshotsInputRef.current.value = "";
                  if (patchInputRef.current) patchInputRef.current.value = "";
                  if (modifiedRomInputRef.current) modifiedRomInputRef.current.value = "";
                  setRestoredDraft(false);
                }}
                className="inline-flex h-8 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground/80 transition-colors hover:bg-black/5 dark:hover:bg-white/10"
              >
                Clear saved draft
              </button>
            </div>
          )}
          <fieldset disabled={isHydrating} aria-busy={isHydrating} className="grid gap-5">

            {step === 1 && (
              <>
                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Title <span className="text-red-500">*</span></label>
                  {!isDummy ? (
                    <input
                      ref={titleInputRef}
                      value={title}
                      onChange={(e) => setTitle(e.target.value)}
                      className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
                    />
                  ) : (
                    <div
                      role="textbox"
                      aria-disabled
                      className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none"
                    >
                      Your hack title
                    </div>
                  )}
                  <div className="mt-1 text-xs text-foreground/60">URL preview: <span className="text-foreground/80">/hack/{slug || "your-title"}</span></div>
                </div>

                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Platform <span className="text-red-500">*</span></label>
                  {!isDummy ? (
                    <Select
                      value={platform}
                      onChange={(value) => { if ((newCoverFiles.length) > 0) return; setPlatform(value as any); setBaseRom(""); }}
                      disabled={newCoverFiles.length > 0}
                      placeholder="Select platform"
                      options={(["GB","GBC","GBA","NDS"] as const).map(p => ({
                        value: p,
                        label: p,
                      }))}
                    />
                  ) : (
                    <div className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">{platform || ""}</div>
                  )}
                  {newCoverFiles.length > 0 && (
                    <div className="text-xs text-red-500">Please remove all screenshots before changing the platform.</div>
                  )}
                </div>

                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Base ROM <span className="text-red-500">*</span></label>
                  {!isDummy ? (
                    <Select
                      enableFilter
                      value={baseRom}
                      onChange={setBaseRom}
                      disabled={!platform}
                      placeholder={platform ? "Select base rom" : "Select platform first"}
                      options={baseRomOptions}
                    />
                  ) : (
                    <div className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">{baseRoms.find(r=>r.id===baseRom)?.name || baseRom}</div>
                  )}
                  <p className="text-xs text-foreground/60">
                    Missing a base ROM?{" "}
                    <a
                      href="https://github.com/Hackdex-App/hackdex-website/issues/new?template=add-base-rom.yml"
                      target="_blank"
                      rel="noopener noreferrer"
                      className="underline underline-offset-2 hover:text-foreground/80 transition-colors"
                    >
                      Request it on GitHub
                      <FiExternalLink className="inline-block h-3 w-3 ml-1" />
                    </a>
                  </p>
                </div>

                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Language <span className="text-red-500">*</span></label>
                  {!isDummy ? (
                    <Select
                      value={language}
                      onChange={setLanguage}
                      placeholder="Select language"
                      options={['English','Spanish','French','German','Italian','Portuguese','Japanese','Chinese','Korean','Other'].map(l => ({
                        value: l,
                        label: l,
                      }))}
                    />
                  ) : (
                    <div role="textbox" aria-disabled className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">{language}</div>
                  )}
                </div>

                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Completion Status <span className="text-red-500">*</span></label>
                  {!isDummy ? (
                    <Select
                      value={completionStatus}
                      onChange={(value) => setCompletionStatus(value as any)}
                      placeholder="Select completion status"
                      options={['Complete','Demo','Alpha','Beta'].map(s => ({
                        value: s,
                        label: s,
                      }))}
                    />
                  ) : (
                    <div role="textbox" aria-disabled className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">{completionStatus || "Select completion status"}</div>
                  )}
                </div>

                {isArchive && (
                  <div className="grid gap-2">
                    <label className="text-sm text-foreground/80">Original Author <span className="text-red-500">*</span></label>
                    {!isDummy ? (
                      <input
                        value={originalAuthor}
                        onChange={(e) => setOriginalAuthor(e.target.value)}
                        disabled={!!customCreator}
                        placeholder="Name of the original hack creator"
                        className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)] disabled:opacity-50 disabled:cursor-not-allowed"
                      />
                    ) : (
                      <div role="textbox" aria-disabled className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">Original author name</div>
                    )}
                    <div className="text-xs text-foreground/60">The name of the person or team who originally created this hack</div>
                  </div>
                )}
              </>
            )}

            {step === 2 && (
              <>
                {!isArchive && (
                  <div className="grid gap-2">
                    <label className="text-sm text-foreground/80">Version <span className="text-red-500">*</span></label>
                    {!isDummy ? (
                      <input
                        ref={versionInputRef}
                        value={version}
                        onChange={(e) => setVersion(e.target.value)}
                        placeholder="e.g. v1.2.0"
                        className={`h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]`}
                      />
                    ) : (
                      <div role="textbox" aria-disabled className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">v0.1.0</div>
                    )}
                  </div>
                )}

                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Tags <span className="text-red-500">*</span></label>
                  <TagSelector value={tags} onChange={setTags} />
                </div>

                <div className="grid gap-1">
                  <div className="flex items-center justify-between">
                    <label className="text-sm text-foreground/80">Summary <span className="text-red-500">*</span></label>
                    <span className={`text-[11px] ${summaryTooLong ? "text-red-300" : "text-foreground/60"}`}>{summary.length}/{summaryLimit}</span>
                  </div>
                  {!isDummy ? (
                    <input
                      value={summary}
                      onChange={(e) => setSummary(e.target.value)}
                      placeholder="<= 100 characters"
                      className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${summaryTooLong ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
                    />
                  ) : (
                    <div
                      role="textbox"
                      aria-disabled
                      className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center text-foreground/60 select-none ${summaryTooLong ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
                    >
                      Short description, max 100 characters.
                    </div>
                  )}
                </div>

                <div className="grid gap-2">
                  <div className="flex items-center justify-between">
                    <label className="text-sm text-foreground/80">Description <span className="text-red-500">*</span></label>
                    {!isDummy && (
                      <div className="flex items-center gap-1 text-xs">
                        <button type="button" onClick={() => setShowMdPreview(false)} className={`px-2 py-1 rounded ${!showMdPreview ? "bg-[var(--surface-2)] ring-1 ring-[var(--border)]" : "text-foreground/70"}`}>Write</button>
                        <button type="button" onClick={() => setShowMdPreview(true)} className={`px-2 py-1 rounded ${showMdPreview ? "bg-[var(--surface-2)] ring-1 ring-[var(--border)]" : "text-foreground/70"}`}>Preview</button>
                      </div>
                    )}
                  </div>
                  {isDummy ? (
                    <div className="prose max-w-none h-36 rounded-md bg-[var(--surface-2)] px-3 py-2 ring-1 ring-inset ring-[var(--border)] text-foreground/60 select-none">
                      <Markdown headingLevelOffset={1}>{description || "Write a longer markdown description here."}</Markdown>
                    </div>
                  ) : !showMdPreview ? (
                    <textarea
                      rows={14}
                      value={description}
                      onChange={(e) => setDescription(e.target.value)}
                      placeholder="Supports Markdown"
                      className={`rounded-md bg-[var(--surface-2)] px-3 py-2 min-h-[14rem] text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]`}
                    />
                  ) : (
                    <div className={`prose max-w-none rounded-md bg-[var(--surface-2)] min-h-[14rem] px-3 py-2 ring-1 ring-inset ring-[var(--border)] ${description ? "" : "text-foreground/60 text-sm"}`}>
                      <Markdown headingLevelOffset={1}>{description || "Nothing to preview yet."}</Markdown>
                    </div>
                  )}
                </div>
              </>
            )}

            {step === 3 && (
              <>
                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Screenshots <span className="text-red-500">*</span></label>
                  {allowedSizes.length > 0 && (
                    <p className="text-xs text-foreground/60">Upload screenshots of your game. Allowed sizes: <span className="font-bold">{allowedSizes.map((s) => `${s.w}x${s.h}`).join(", ")}</span>.</p>
                  )}
                  {tutorialInfo && (
                    <p className="text-xs text-foreground/60">
                      Need help taking screenshots? Watch our{" "}
                      <a
                        href={tutorialInfo.url}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="inline-flex items-center gap-1 underline underline-offset-2 hover:text-foreground/80 transition-colors"
                      >
                        {tutorialInfo.emulatorName} tutorial
                        <FiExternalLink className="h-3 w-3" />
                      </a>
                    </p>
                  )}
                  <div className="space-y-3">
                    {!isDummy ? (
                      <input
                        ref={screenshotsInputRef}
                        type="file"
                        multiple
                        accept="image/*"
                        onChange={async (e) => {
                          const list = Array.from(e.target.files || []);
                          const allowed = baseRoms.find(r => r.id === baseRom)?.platform;
                          const sizes = allowed ? getAllowedSizesForPlatform(allowed) : [];
                          const accepted: File[] = [];
                          const errors: string[] = [];
                          for (const f of list) {
                            if (sizes.length === 0) { accepted.push(f); continue; }
                            const ok = await validateImageDimensions(f, sizes);
                            if (ok) accepted.push(f); else errors.push(f.name);
                          }
                        setCoverErrors(errors);
                        // Compute next files list and update once
                        const nextFiles = [...newCoverFiles, ...accepted];
                        setNewCoverFiles(nextFiles);
                        }}
                        className={`w-full rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none ${!hasBaseRom ? 'pointer-events-none opacity-50 blur-[1px]' : ''}`}
                      />
                    ) : (
                      <div className="w-full h-14 rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm ring-1 ring-inset ring-[var(--border)] text-foreground/60 select-none">
                        Choose images to upload
                      </div>
                    )}
                    <div className="flex items-center gap-2">
                      {!isDummy ? (
                        <>
                          <button
                            type="button"
                            onClick={() => { setNewCoverFiles([]); try { if (draftKey) deleteDraftCovers(draftKey); } catch {} }}
                            className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground/80 transition-colors hover:bg-black/5 dark:hover:bg-white/10"
                          >
                            Clear
                          </button>
                        </>
                      ) : (
                        <>
                          <button type="button" disabled className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground/70 disabled:opacity-40">
                            Add
                          </button>
                          <button type="button" disabled className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground/60 disabled:opacity-40">
                            Clear
                          </button>
                        </>
                      )}
                    </div>
                    <div className="text-xs text-foreground/60 flex justify-between">
                      <p>Images: <span className={overLimit ? "text-red-300 font-bold" : "text-foreground/60"}>{newCoverFiles.length}</span>/{MAX_COVERS}</p>
                      {overLimit && <p className="text-red-300/80 italic">Remove some to submit.</p>}
                    </div>
                    {coverErrors.length > 0 && (
                      <div className="text-xs text-red-400">
                        Rejected (wrong size): {coverErrors.join(", ")}
                      </div>
                    )}
                    <div className="grid gap-2">
                      {newCoverFiles.length === 0 ? (
                        <p className="text-xs text-foreground/60">No images added yet. Add at least one to preview.</p>
                      ) : (
                        !isDummy ? (
                          <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
                            <SortableContext
                              items={newCoverFiles.map((f, i) => `${f.name}-${i}`)}
                              strategy={verticalListSortingStrategy}
                            >
                              {newCoverFiles.map((f, i) => (
                                <SortableCoverItem
                                  key={`${f.name}-${i}`}
                                  id={`${f.name}-${i}`}
                                  index={i}
                                  filename={f.name}
                                  url={URL.createObjectURL(f)}
                                  onRemove={() => removeAt(i)}
                                />
                              ))}
                            </SortableContext>
                          </DndContext>
                        ) : null
                      )}
                    </div>
                  </div>
                </div>

                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Box art URL</label>
                  {!isDummy ? (
                    <input
                      value={boxArt}
                      onChange={(e) => setBoxArt(e.target.value)}
                      placeholder="https://..."
                      className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${boxArt && !urlLike(boxArt) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
                    />
                  ) : (
                    <div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center text-foreground/60 select-none ${boxArt && !urlLike(boxArt) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>https://...</div>
                  )}
                </div>

                <div className="grid gap-2">
                  <label className="text-sm text-foreground/80">Social links</label>
                  <p className="text-xs text-foreground/60">Use full URLs starting with http:// or https://</p>
                  <div className="grid gap-2">
                    {!isDummy ? (
                      <>
                        <div className="flex items-center gap-2">
                          <div className="flex items-center justify-center w-10 h-11 shrink-0">
                            <FaDiscord size={20} className="text-foreground/70" />
                          </div>
                          <input
                            value={discord}
                            onChange={(e) => setDiscord(e.target.value)}
                            placeholder="Discord invite URL"
                            className={`flex-1 h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${discord && !urlLike(discord) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
                          />
                        </div>
                        <div className="flex items-center gap-2">
                          <div className="flex items-center justify-center w-10 h-11 shrink-0">
                            <FaTwitter size={20} className="text-foreground/70" />
                          </div>
                          <input
                            value={twitter}
                            onChange={(e) => setTwitter(e.target.value)}
                            placeholder="Twitter/X profile URL"
                            className={`flex-1 h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${twitter && !urlLike(twitter) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
                          />
                        </div>
                        <div className="flex items-center gap-2">
                          <div className="flex items-center justify-center w-10 h-11 shrink-0">
                            <PokeCommunityIcon width={20} height={20} color="currentColor" className="text-foreground/70" />
                          </div>
                          <input
                            value={pokecommunity}
                            onChange={(e) => setPokecommunity(e.target.value)}
                            placeholder="PokeCommunity thread URL"
                            className={`flex-1 h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${pokecommunity && !urlLike(pokecommunity) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
                          />
                        </div>
                        <div className="flex items-center gap-2">
                          <div className="flex items-center justify-center w-10 h-11 shrink-0">
                            <FaGithub size={20} className="text-foreground/70" />
                          </div>
                          <input
                            value={github}
                            onChange={(e) => setGithub(e.target.value)}
                            placeholder="GitHub repository URL"
                            className={`flex-1 h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${github && !urlLike(github) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
                          />
                        </div>
                      </>
                    ) : (
                      <>
                        <div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center gap-2 text-foreground/60 select-none ${discord && !urlLike(discord) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>
                          <FaDiscord size={20} className="text-foreground/50" />
                          <span>Discord invite URL</span>
                        </div>
                        <div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center gap-2 text-foreground/60 select-none ${twitter && !urlLike(twitter) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>
                          <FaTwitter size={20} className="text-foreground/50" />
                          <span>Twitter/X profile URL</span>
                        </div>
                        <div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center gap-2 text-foreground/60 select-none ${pokecommunity && !urlLike(pokecommunity) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>
                          <PokeCommunityIcon width={20} height={20} color="currentColor" className="text-foreground/50" />
                          <span>PokeCommunity thread URL</span>
                        </div>
                        <div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center gap-2 text-foreground/60 select-none ${github && !urlLike(github) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>
                          <FaGithub size={20} className="text-foreground/50" />
                          <span>GitHub repository URL</span>
                        </div>
                      </>
                    )}
                  </div>
                </div>

                {!profile?.verified && (
                  <div className="grid gap-2">
                    <label className="text-sm text-foreground/80">Verification Contact Information <span className="text-foreground/60">(Recommended)</span></label>
                    <p className="text-xs text-foreground/60">
                      Help us verify your account by providing contact information from a platform where your message/post history can be verified (e.g., Discord, PokéCommunity). This information will only be visible to admins during the approval process.
                    </p>
                    {!isDummy ? (
                      <textarea
                        value={verificationContactInfo}
                        onChange={(e) => setVerificationContactInfo(e.target.value)}
                        placeholder={`Discord username: @example
I am active in the following servers:
Team Aqua's Hideout, RH Hideout, pret

Here is an invite to my development server:
https://discord.gg/example`}
                        rows={6}
                        className="rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)] resize-y"
                      />
                    ) : (
                      <div className="rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm ring-1 ring-inset ring-[var(--border)] text-foreground/60 select-none min-h-[6rem]">
                        Discord: @example<br />
                        I am active in the following servers: Team Aqua's Hideout, pret, and PokéDev School<br />
                        Here is an invite to my development server: https://discord.gg/example_server_invite
                      </div>
                    )}
                    <p className="text-xs text-foreground/60">Optional but recommended for faster account verification. Please be detailed.</p>
                  </div>
                )}
              </>
            )}

            {step === 4 && !isArchive && (
              <div className="grid gap-3">
                <label className="text-sm text-foreground/80">Provide patch <span className="text-red-500">*</span></label>
                {!isDummy ? (
                  <div className="flex flex-col gap-3">
                    <div className="inline-flex items-center">
                      <button
                        type="button"
                        onClick={() => setPatchMode("bps")}
                        className={`rounded-md rounded-r-none px-3 py-1.5 text-xs border-l-1 border-y-1 ${patchMode === "bps" ? "bg-[var(--surface-2)] border-[var(--border)]" : "text-foreground/70 border-[var(--border)]"}`}
                      >
                        Upload .bps
                      </button>
                      <button
                        type="button"
                        onClick={() => setPatchMode("rom")}
                        className={`rounded-md rounded-l-none px-3 py-1.5 text-xs border-1 ${patchMode === "rom" ? "bg-[var(--surface-2)] border-[var(--border)]" : "text-foreground/70 border-[var(--border)]"}`}
                      >
                        Upload modified ROM (auto-generate .bps)
                      </button>
                    </div>

                    {patchMode === "bps" && (
                      <div className="grid gap-2">
                        <input
                          ref={patchInputRef}
                          onChange={onUploadPatch}
                          type="file"
                          accept=".bps"
                          className="rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm italic text-foreground/50 ring-1 ring-inset ring-[var(--border)] file:bg-black/10 dark:file:bg-[var(--surface-2)] file:text-foreground/80 file:text-sm file:font-medium file:not-italic file:rounded-md file:border-0 file:px-3 file:py-2 file:mr-2 file:cursor-pointer"
                        />
                        <p className="text-xs text-foreground/60">Upload a BPS patch file.</p>
                        {checksumStatus === "validating" && <div className="text-xs text-foreground/70">Validating checksum…</div>}
                        {checksumStatus === "valid" && <div className="text-xs text-emerald-400/90">Checksum valid.</div>}
                        {checksumStatus === "invalid" && !!checksumError && <div className="text-xs text-red-400">{checksumError}</div>}
                        {checksumStatus === "unknown" && !!checksumError && <div className="text-xs text-amber-400/90">{checksumError}</div>}
                      </div>
                    )}

                    {patchMode === "rom" && (
                      <div className="grid gap-3">
                        <div className="rounded-md border border-[var(--border)] p-3 bg-[var(--surface-2)]/50">
                          <div className="text-xs text-foreground/75">Required base ROM</div>
                          <div className="mt-1 text-sm font-medium">{baseRomEntry ? `${baseRomEntry.name} (${baseRomEntry.platform})` : "Select a base ROM in Step 1"}</div>
                          <div className="mt-2 flex flex-wrap items-center gap-2 text-xs">
                            <span className={`rounded-full px-2 py-0.5 ring-1 ${baseRomReady ? "bg-emerald-600/60 text-white ring-emerald-700/80 dark:bg-emerald-500/25 dark:text-emerald-100 dark:ring-emerald-400/90" : baseRomNeedsPermission ? "bg-amber-600/60 text-white ring-amber-700/80 dark:bg-amber-500/50 dark:text-amber-100 dark:ring-amber-400/90" : "bg-red-600/60 text-white ring-red-700/80 dark:bg-red-500/50 dark:text-red-100 dark:ring-red-400/90"}`}>
                              {baseRomReady ? "Ready" : baseRomNeedsPermission ? "Permission needed" : "Base ROM needed"}
                            </span>
                            {baseRomNeedsPermission && (
                              <button type="button" onClick={onGrantPermission} disabled={!supported} className="rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2 py-1 disabled:opacity-60 disabled:cursor-not-allowed">Grant permission</button>
                            )}
                            {baseRomMissing && (
                              <label className="inline-flex items-center gap-2 text-xs text-foreground/80">
                                <input type="file" onChange={onUploadBaseRom} className="rounded-md bg-[var(--surface-2)] px-2 py-1 text-xs ring-1 ring-inset ring-[var(--border)]" />
                                <span>Upload base ROM</span>
                              </label>
                            )}
                          </div>
                          {!!genError && <div className="mt-2 text-xs text-red-400">{genError}</div>}
                        </div>

                        <div className="grid gap-2">
                          <label className="text-sm text-foreground/80">Modified ROM</label>
                          <input
                            ref={modifiedRomInputRef}
                            type="file"
                            accept={baseRomPlatform ? platformAccept(baseRomPlatform) : "*/*"}
                            disabled={!baseRomEntry || !baseRomReady || !baseRomPlatform}
                            onChange={onUploadModifiedRom}
                            className="rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm ring-1 ring-inset ring-[var(--border)] disabled:opacity-50 disabled:cursor-not-allowed"
                          />
                          <p className="text-xs text-foreground/60">We'll generate a .bps patch on-device. No ROMs are uploaded.</p>
                          {genStatus === "generating" && <div className="text-xs text-foreground/70">Generating patch…</div>}
                          {genStatus === "ready" && patchFile && <div className="text-xs text-emerald-400/90">Patch ready: {patchFile.name}</div>}
                          {genStatus === "error" && !!genError && <div className="text-xs text-red-400">{genError}</div>}
                        </div>
                      </div>
                    )}
                  </div>
                ) : (
                  <div className="rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm italic text-foreground/50 ring-1 ring-inset ring-[var(--border)] select-none">Choose file</div>
                )}
              </div>
            )}

            {!isDummy && (
              <div className="flex items-center justify-between gap-3 border-t border-[var(--border)] pt-4 mt-4">
                <button
                  type="button"
                  onClick={() => setStep((s) => Math.max(1, s - 1))}
                  disabled={step === 1 || submitting}
                  className="inline-flex h-11 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-4 text-sm font-semibold text-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 disabled:opacity-50 disabled:cursor-not-allowed"
                >
                  Back
                </button>
                <div className="flex items-center gap-3">
                  <span className="text-sm text-foreground/60">Step {step} of {maxSteps}</span>
                </div>
                {step < maxSteps ? (
                  <button
                    type="button"
                    onClick={() => setStep((s) => Math.min(maxSteps, s + 1))}
                    disabled={
                      submitting ||
                      (step === 1 && !step1Valid) ||
                      (step === 2 && !step2Valid) ||
                      (step === 3 && !step3Valid)
                    }
                    className="inline-flex h-11 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-4 text-sm font-semibold text-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 disabled:opacity-50 disabled:cursor-not-allowed"
                  >
                    Next
                  </button>
                ) : (
                  <button
                    type="button"
                    onClick={onSubmit}
                    disabled={!isValid || submitting}
                    className="shine-wrap btn-premium h-11 min-w-[7.5rem] text-sm font-semibold dark:disabled:opacity-70 disabled:cursor-not-allowed disabled:[box-shadow:0_0_0_1px_var(--border)]"
                  >
                    <span>{submitting ? 'Submitting…' : 'Submit'}</span>
                  </button>
                )}
              </div>
            )}
          </fieldset>
        </form>
      </div>

      <aside className="flex flex-col gap-5 lg:sticky lg:top-20 self-start basis-[360px]">
        <HackCard hack={preview} clickable={false} />
        <div className="card h-max p-5">
          <div className="text-[15px] font-semibold tracking-tight">Submission tips</div>
          <ul className="mt-3 list-disc space-y-2 pl-5 text-sm text-foreground/75">
            <li>Use a reliable image URL (e.g. `imgur`).</li>
            <li>Include the exact expected base ROM name.</li>
            <li>Describe notable features, difficulty, and target players.</li>
          </ul>
        </div>
      </aside>
    </div>
  );
}


