const state = {
  activeTab: "intake",
  snapshot: null,
  query: "",
  isSaving: false,
  selected: {
    intake: null,
    intakeAsset: null,
    characters: null,
    stages: null,
    mechanics: null,
  },
  sidebarCollapsed: false,
};

const releaseBucketOptions = ["unassigned", "OG", "Unlocked", "DLC"];

const meta = {
  intake: {
    kicker: "Intake",
    title: "Discord Intake Review",
    subtitle: "Review imported forum characters, promote the usable art, assign slots, and keep everything private until approved.",
  },
  characters: {
    kicker: "Characters",
    title: "Character Sheets",
    subtitle: "Edit fighter metadata and keep roster entries coherent before art gets applied.",
    endpoint: "/api/admin/characters",
    deleteEndpoint: (slug) => `/api/admin/characters/${encodeURIComponent(slug)}`,
    fields: [
      ["slug", "Slug", "text"],
      ["display_name", "Display Name", "text"],
      ["release_bucket", "Release Round", "select", releaseBucketOptions],
      ["roster_group", "Roster Group", "text"],
      ["archetype", "Archetype", "text"],
      ["franchise", "Franchise", "text"],
      ["status", "Status", "text"],
      ["sort_order", "Sort Order", "number"],
      ["is_public", "Public Entry", "checkbox"],
      ["tags", "Tags", "text"],
      ["description", "Description", "textarea"],
      ["mechanics_notes", "Mechanics Notes", "textarea"],
      ["moveset_notes", "Moveset Notes", "textarea"],
      ["unlock_notes", "Unlock Notes", "textarea"],
    ],
  },
  stages: {
    kicker: "Stages",
    title: "Stage Sheets",
    subtitle: "Track stage legality, scale, gimmicks, and music notes without burying them in code.",
    endpoint: "/api/admin/stages",
    deleteEndpoint: (slug) => `/api/admin/stages/${encodeURIComponent(slug)}`,
    fields: [
      ["slug", "Slug", "text"],
      ["display_name", "Display Name", "text"],
      ["category", "Category", "text"],
      ["legal_status", "Legal Status", "text"],
      ["sort_order", "Sort Order", "number"],
      ["is_public", "Public Entry", "checkbox"],
      ["tags", "Tags", "text"],
      ["description", "Description", "textarea"],
      ["size_notes", "Size Notes", "textarea"],
      ["gimmick_notes", "Gimmick Notes", "textarea"],
      ["music_notes", "Music Notes", "textarea"],
    ],
  },
  mechanics: {
    kicker: "Mechanics",
    title: "Mechanic Specs",
    subtitle: "Keep high-level tuning and target-file references in a single editable place.",
    endpoint: "/api/admin/mechanics",
    deleteEndpoint: (slug) => `/api/admin/mechanics/${encodeURIComponent(slug)}`,
    fields: [
      ["slug", "Slug", "text"],
      ["display_name", "Display Name", "text"],
      ["subsystem", "Subsystem", "text"],
      ["tuning_value", "Tuning Value", "text"],
      ["target_file", "Godot Target", "text"],
      ["sort_order", "Sort Order", "number"],
      ["is_public", "Public Entry", "checkbox"],
      ["summary", "Summary", "textarea"],
      ["notes", "Notes", "textarea"],
    ],
  },
  assets: {
    kicker: "Assets",
    title: "Asset Routing",
    subtitle: "Upload files, set public/apply flags, and route them into the right Godot locations.",
  },
  submissions: {
    kicker: "Submissions",
    title: "Submission Queue",
    subtitle: "Review community contributions before they enter the curated database.",
  },
  apply: {
    kicker: "Apply",
    title: "Godot Publish",
    subtitle: "Write approved assets and generated manifests into the Anime Fighter Pro project.",
  },
};

const statusBar = document.getElementById("statusBar");
const summaryBar = document.getElementById("summaryBar");
const searchInput = document.getElementById("viewSearch");
const saveAllBtn = document.getElementById("saveAllBtn");
const viewKicker = document.getElementById("viewKicker");
const viewTitle = document.getElementById("viewTitle");
const viewSubtitle = document.getElementById("viewSubtitle");

const escapeHtml = (value) =>
  String(value ?? "")
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;");

const formatDate = (value) =>
  value ? new Date(value).toLocaleString([], { dateStyle: "medium", timeStyle: "short" }) : "N/A";

const intakeBucketOptions = ["hero-candidates", "reference", "portrait", "supporting", "motion", "misc"];
const intakeReviewOptions = ["pending_review", "approved", "needs_work", "rejected"];
const intakeSlotOptions = ["", "portrait", "select_art", "unlock_art", "reference_sheet", "gallery_support"];

function setStatus(message, kind = "") {
  statusBar.textContent = message;
  statusBar.className = `status-bar${kind ? ` ${kind}` : ""}`;
}

async function apiRequest(url, options = {}) {
  const response = await fetch(url, options);
  const payload = response.headers.get("content-type")?.includes("application/json") ? await response.json() : null;
  if (!response.ok || (payload && payload.ok === false)) {
    throw new Error(payload?.error || `Request failed: ${response.status}`);
  }
  return payload;
}

function rowId(row) {
  return String(row.id ?? row._draftId ?? row.slug);
}

function sectionRows(section) {
  return state.snapshot?.[section] || [];
}

function findRowById(section, id) {
  return sectionRows(section).find((row) => rowId(row) === String(id)) || null;
}

function normalizeValue(type, value) {
  if (type === "checkbox") return Number(Boolean(value));
  if (type === "number") {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : 0;
  }
  return value ?? "";
}

function importedCharacters() {
  return (state.snapshot?.characters || []).filter(
    (row) => row.source_type === "discord_import" || row.source_thread_id
  );
}

function importedAssets() {
  return (state.snapshot?.assets || []).filter((row) => row.source_type === "discord_import" || row.source_attachment_id);
}

function importedAssetsFor(slug) {
  return importedAssets().filter((asset) => asset.entity_slug === slug);
}

function matchesQuery(row) {
  const q = state.query.trim().toLowerCase();
  if (!q) return true;
  return JSON.stringify(row).toLowerCase().includes(q);
}

function filteredRows(section) {
  return sectionRows(section).filter((row) => matchesQuery(row));
}

function filteredImportedCharacters() {
  return importedCharacters().filter((row) => matchesQuery(row));
}

function ensureSelection(section) {
  const rows = filteredRows(section);
  const current = state.selected[section];
  if (rows.some((row) => rowId(row) === current)) return;
  state.selected[section] = rows.length ? rowId(rows[0]) : null;
}

function ensureIntakeSelection() {
  const rows = filteredImportedCharacters();
  const current = state.selected.intake;
  if (rows.some((row) => row.slug === current)) return;
  state.selected.intake = rows[0]?.slug || null;
}

function selectedRow(section) {
  ensureSelection(section);
  return filteredRows(section).find((row) => rowId(row) === state.selected[section]) || null;
}

function selectedIntakeCharacter() {
  ensureIntakeSelection();
  return filteredImportedCharacters().find((row) => row.slug === state.selected.intake) || null;
}

function ensureIntakeAssetSelection(characterSlug) {
  const assets = importedAssetsFor(characterSlug);
  const current = state.selected.intakeAsset;
  if (assets.some((asset) => String(asset.id) === String(current))) return;
  state.selected.intakeAsset = assets[0]?.id ?? null;
}

function selectedIntakeAsset(characterSlug) {
  ensureIntakeAssetSelection(characterSlug);
  return importedAssetsFor(characterSlug).find((asset) => String(asset.id) === String(state.selected.intakeAsset)) || null;
}

function markDirty(message = "Unsaved changes pending. Autosave runs every minute.") {
  if (!state.isSaving) setStatus(message);
}

function updateLocalRow(section, id, field, rawValue) {
  const row = findRowById(section, id);
  const fieldMeta = meta[section].fields.find(([name]) => name === field);
  if (!row || !fieldMeta) return;
  if (!row._draftId && row._originalSlug === undefined) row._originalSlug = row.slug || "";
  row[field] = normalizeValue(fieldMeta[2], rawValue);
  row._dirty = true;
  markDirty();
}

function updateLocalAsset(id, field, rawValue) {
  const row = sectionRows("assets").find((entry) => String(entry.id) === String(id));
  if (!row) return;
  if (field === "is_public" || field === "is_approved" || field === "is_primary") {
    row[field] = Number(Boolean(rawValue));
  } else if (field === "quality_score") {
    row[field] = Number(rawValue) || 0;
  } else {
    row[field] = rawValue ?? "";
  }
  row._dirty = true;
  markDirty();
}

function updateLocalSubmission(id, field, rawValue) {
  const row = sectionRows("submissions").find((entry) => String(entry.id) === String(id));
  if (!row) return;
  row[field] = rawValue ?? "";
  row._dirty = true;
  markDirty();
}

function payloadForRow(section, row) {
  const payload = {};
  for (const [field, , type] of meta[section].fields) {
    payload[field] = normalizeValue(type, row[field]);
  }
  return payload;
}

async function persistRow(section, row, id = rowId(row)) {
  const payload = payloadForRow(section, row);
  const originalSlug = row._draftId ? "" : row._originalSlug ?? row.slug ?? "";
  const result = await apiRequest(meta[section].endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  if (originalSlug && payload.slug && payload.slug !== originalSlug) {
    await apiRequest(meta[section].deleteEndpoint(originalSlug), { method: "DELETE" });
  }

  const rows = sectionRows(section);
  const index = rows.findIndex((entry) => rowId(entry) === String(id));
  if (index >= 0) rows[index] = result.row;
  else rows.unshift(result.row);

  if (state.selected[section] === String(id)) state.selected[section] = rowId(result.row);
  return result.row;
}

async function persistAssetReview(row) {
  const result = await apiRequest(`/api/admin/assets/${row.id}/review`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      entity_type: row.entity_type,
      entity_slug: row.entity_slug,
      slot: row.slot,
      label: row.label,
      notes: row.notes,
      is_public: row.is_public,
      is_approved: row.is_approved,
      review_status: row.review_status,
      quality_bucket: row.quality_bucket,
      quality_score: row.quality_score,
      is_primary: row.is_primary,
    }),
  });
  const rows = sectionRows("assets");
  const index = rows.findIndex((entry) => String(entry.id) === String(row.id));
  if (index >= 0) rows[index] = result.asset;
}

async function persistSubmission(row) {
  await apiRequest(`/api/admin/submissions/${row.id}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      status: row.status ?? "pending",
      admin_notes: row.admin_notes ?? "",
    }),
  });
}

function hasDirtyChanges() {
  if (!state.snapshot) return false;
  return ["characters", "stages", "mechanics", "assets", "submissions"].some((section) =>
    sectionRows(section).some((row) => row._dirty)
  );
}

async function saveAllChanges({ silentWhenClean = false, fromAutosave = false } = {}) {
  if (state.isSaving || !state.snapshot) return false;

  const dirtyRecords = ["characters", "stages", "mechanics"].flatMap((section) =>
    sectionRows(section)
      .filter((row) => row._dirty)
      .map((row) => ({ section, row, id: rowId(row) }))
  );
  const dirtyAssets = sectionRows("assets").filter((row) => row._dirty);
  const dirtySubmissions = sectionRows("submissions").filter((row) => row._dirty);

  if (!dirtyRecords.length && !dirtyAssets.length && !dirtySubmissions.length) {
    if (!silentWhenClean) setStatus("Nothing to save.", "success");
    return false;
  }

  state.isSaving = true;
  saveAllBtn.disabled = true;
  setStatus(fromAutosave ? "Autosaving changes..." : "Saving all changes...");

  try {
    for (const { section, row, id } of dirtyRecords) await persistRow(section, row, id);
    for (const row of dirtyAssets) await persistAssetReview(row);
    for (const row of dirtySubmissions) await persistSubmission(row);
    await loadSnapshot({ preserveSelection: true });
    const savedCount = dirtyRecords.length + dirtyAssets.length + dirtySubmissions.length;
    setStatus(`${fromAutosave ? "Autosave" : "Save all"} complete. Saved ${savedCount} item${savedCount === 1 ? "" : "s"}.`, "success");
    return true;
  } catch (error) {
    setStatus(error.message, "error");
    return false;
  } finally {
    state.isSaving = false;
    saveAllBtn.disabled = false;
  }
}

function setViewHeader() {
  const current = meta[state.activeTab];
  viewKicker.textContent = current.kicker;
  viewTitle.textContent = current.title;
  viewSubtitle.textContent = current.subtitle;
}

function renderSummary() {
  const counts = state.snapshot?.counts || {};
  const cards = [
    ["Imported Characters", counts.imported_characters || 0],
    ["Imported Assets", counts.imported_assets || 0],
    ["Review Pending", importedCharacters().filter((row) => row.review_status === "pending_review").length],
    ["Approved Intake", importedCharacters().filter((row) => row.review_status === "approved").length],
    ["Public Characters", (state.snapshot?.characters || []).filter((row) => row.is_public).length],
    ["Import Runs", counts.import_runs || 0],
  ];
  summaryBar.innerHTML = cards
    .map(
      ([label, value]) => `
        <article class="summary-card">
          <span class="summary-label">${escapeHtml(label)}</span>
          <strong class="summary-value">${escapeHtml(value)}</strong>
        </article>
      `
    )
    .join("");
}

function blankRow(section) {
  const row = { _draftId: crypto.randomUUID(), _dirty: true, sort_order: 0, is_public: 1 };
  for (const [field, , type] of meta[section].fields) {
    if (!(field in row)) row[field] = type === "checkbox" ? 0 : "";
  }
  return row;
}

function renderField(field, label, type, value, options = []) {
  if (type === "checkbox") {
    return `
      <label class="field toggle-field">
        <input data-field="${field}" type="checkbox" ${value ? "checked" : ""} />
        <span>${escapeHtml(label)}</span>
      </label>
    `;
  }
  if (type === "select") {
    return `
      <label class="field">
        <span>${escapeHtml(label)}</span>
        <select data-field="${field}">
          ${options
            .map((option) => `<option value="${escapeHtml(option)}" ${String(value ?? "") === String(option) ? "selected" : ""}>${escapeHtml(option)}</option>`)
            .join("")}
        </select>
      </label>
    `;
  }
  if (type === "textarea") {
    return `
      <label class="field field-full">
        <span>${escapeHtml(label)}</span>
        <textarea data-field="${field}" rows="5">${escapeHtml(value)}</textarea>
      </label>
    `;
  }
  return `
    <label class="field">
      <span>${escapeHtml(label)}</span>
      <input data-field="${field}" type="${type === "number" ? "number" : "text"}" value="${escapeHtml(value)}" />
    </label>
  `;
}

function renderEditorSection(section) {
  const container = document.getElementById(`tab-${section}`);
  const rows = filteredRows(section);
  ensureSelection(section);
  const current = selectedRow(section);

  container.innerHTML = `
    <div class="editor-layout">
      <section class="list-panel">
        <div class="panel-header">
          <div>
            <h3>${escapeHtml(meta[section].title)}</h3>
            <p>${rows.length} shown</p>
          </div>
          <button class="primary-button" data-add-row="${section}" type="button">New</button>
        </div>
        <div class="list-scroll">
          ${
            rows.length
              ? rows
                  .map(
                    (row) => `
                      <button class="record-card ${rowId(row) === state.selected[section] ? "active" : ""}" data-select-row="${section}" data-row-id="${rowId(row)}" type="button">
                        <strong>${escapeHtml(row.display_name || row.slug || "Untitled")}</strong>
                        <span>${escapeHtml(row.slug || "draft")}</span>
                        <small>${escapeHtml(row.status || row.category || row.subsystem || "")}</small>
                      </button>
                    `
                  )
                  .join("")
              : `<div class="empty-state">No matching records.</div>`
          }
        </div>
      </section>

      <section class="detail-panel">
        ${
          current
            ? `
              <div class="panel-header">
                <div>
                  <h3>${escapeHtml(current.display_name || current.slug || "New Record")}</h3>
                  <p>${escapeHtml(current.slug || "unsaved draft")}</p>
                </div>
                <div class="pill-row">
                  ${current.is_public ? '<span class="pill">Public</span>' : '<span class="pill muted">Private</span>'}
                  ${current.status ? `<span class="pill muted">${escapeHtml(current.status)}</span>` : ""}
                </div>
              </div>
              <form class="detail-form" data-section="${section}" data-original-slug="${escapeHtml(current._originalSlug ?? current.slug ?? "")}" data-row-id="${rowId(current)}">
                <div class="field-grid">
                  ${meta[section].fields.map(([field, label, type, options]) => renderField(field, label, type, current[field] ?? "", options)).join("")}
                </div>
                <div class="form-actions">
                  <button class="primary-button" type="submit">Save</button>
                  <button class="danger-button" data-delete-row="${section}" type="button">Delete</button>
                </div>
              </form>
            `
            : `<div class="empty-state">Select a record to edit it.</div>`
        }
      </section>
    </div>
  `;
}

function renderAssetPreview(asset) {
  if (asset.mime_type?.startsWith("image/")) {
    return `<img src="${asset.file_url}" alt="${escapeHtml(asset.label || asset.slot || asset.original_filename)}" />`;
  }
  if (asset.mime_type?.startsWith("audio/")) {
    return `<audio controls src="${asset.file_url}"></audio>`;
  }
  if (asset.mime_type?.startsWith("video/")) {
    return `<video controls src="${asset.file_url}"></video>`;
  }
  return `<a class="secondary-button" href="${asset.file_url}" target="_blank" rel="noreferrer">Open File</a>`;
}

function renderIntakeAssetInspector(asset) {
  return `
    <article class="info-card intake-asset-card intake-inspector ${asset._dirty ? "dirty-card" : ""}">
      <div class="panel-header compact">
        <div>
          <strong>${escapeHtml(asset.original_filename || asset.stored_filename)}</strong>
          <p>${escapeHtml(asset.review_status || "pending_review")} / ${escapeHtml(asset.quality_bucket || "unbucketed")}</p>
        </div>
        <div class="pill-row">
          ${asset.is_approved ? '<span class="pill">Approved</span>' : '<span class="pill muted">Unapproved</span>'}
          <span class="pill muted">Score ${escapeHtml(asset.quality_score || 0)}</span>
        </div>
      </div>
      <div class="media-frame">${renderAssetPreview(asset)}</div>
      <div class="pill-row pill-row-tight intake-quickbar">
        <button class="small-button" data-quick-bucket="hero-candidates" data-asset-id="${asset.id}" type="button">Hero</button>
        <button class="small-button" data-quick-bucket="reference" data-asset-id="${asset.id}" type="button">Reference</button>
        <button class="small-button" data-quick-bucket="supporting" data-asset-id="${asset.id}" type="button">Support</button>
        <button class="small-button" data-quick-review="approved" data-asset-id="${asset.id}" type="button">Approve</button>
        <button class="small-button" data-quick-review="rejected" data-asset-id="${asset.id}" type="button">Reject</button>
      </div>
      <div class="field-grid compact-grid">
        <label class="field">
          <span>Bucket</span>
          <select data-asset-field="quality_bucket" data-asset-id="${asset.id}">
            ${intakeBucketOptions.map((value) => `<option value="${value}" ${asset.quality_bucket === value ? "selected" : ""}>${value}</option>`).join("")}
          </select>
        </label>
        <label class="field">
          <span>Review</span>
          <select data-asset-field="review_status" data-asset-id="${asset.id}">
            ${intakeReviewOptions.map((value) => `<option value="${value}" ${asset.review_status === value ? "selected" : ""}>${value}</option>`).join("")}
          </select>
        </label>
        <label class="field">
          <span>Slot</span>
          <select data-asset-field="slot" data-asset-id="${asset.id}">
            ${intakeSlotOptions.map((value) => `<option value="${value}" ${asset.slot === value ? "selected" : ""}>${value || "unassigned"}</option>`).join("")}
          </select>
        </label>
        <label class="field">
          <span>Label</span>
          <input data-asset-field="label" data-asset-id="${asset.id}" type="text" value="${escapeHtml(asset.label || "")}" />
        </label>
        <label class="field toggle-field">
          <input data-asset-field="is_primary" data-asset-id="${asset.id}" type="checkbox" ${asset.is_primary ? "checked" : ""} />
          <span>Primary Pick</span>
        </label>
        <label class="field toggle-field">
          <input data-asset-field="is_public" data-asset-id="${asset.id}" type="checkbox" ${asset.is_public ? "checked" : ""} />
          <span>Public</span>
        </label>
        <label class="field toggle-field">
          <input data-asset-field="is_approved" data-asset-id="${asset.id}" type="checkbox" ${asset.is_approved ? "checked" : ""} />
          <span>Approved for Apply</span>
        </label>
        <label class="field field-full">
          <span>Notes</span>
          <textarea data-asset-field="notes" data-asset-id="${asset.id}" rows="3">${escapeHtml(asset.notes || "")}</textarea>
        </label>
      </div>
      <div class="pill-row muted-row">
        <span>${escapeHtml(asset.mime_type || "file")}</span>
        <span>${escapeHtml(asset.source_message_id || "")}</span>
      </div>
      <div class="form-actions">
        <a class="secondary-button" href="${asset.file_url}" target="_blank" rel="noreferrer">Open</a>
      </div>
    </article>
  `;
}

function renderIntakeStripCard(asset, isActive) {
  return `
    <button class="intake-strip-card ${isActive ? "active" : ""} ${asset._dirty ? "dirty-card" : ""}" data-intake-asset-select="${asset.id}" type="button">
      <div class="media-frame intake-strip-media">${renderAssetPreview(asset)}</div>
      <div class="intake-strip-meta">
        <strong>${escapeHtml(asset.original_filename || asset.stored_filename)}</strong>
        <span>${escapeHtml(asset.quality_bucket || "unbucketed")}</span>
        <span>${escapeHtml(asset.review_status || "pending_review")}</span>
      </div>
    </button>
  `;
}

function renderIntake() {
  const container = document.getElementById("tab-intake");
  const characters = filteredImportedCharacters();
  ensureIntakeSelection();
  const current = selectedIntakeCharacter();
  const importRuns = state.snapshot?.import_runs || [];
  const currentAssets = current ? importedAssetsFor(current.slug) : [];
  const bucketCounts = Object.fromEntries(
    intakeBucketOptions.map((bucket) => [bucket, currentAssets.filter((asset) => (asset.quality_bucket || "misc") === bucket).length])
  );
  const activeAsset = current ? selectedIntakeAsset(current.slug) : null;

  container.innerHTML = `
    <div class="intake-layout intake-layout-compact">
      <section class="list-panel intake-list-panel">
        <div class="panel-header">
          <div>
            <h3>Imported Characters</h3>
            <p>${characters.length} shown</p>
          </div>
        </div>
        <div class="summary-stack compact-stack">
          ${importRuns.slice(0, 2).map((run) => `<article class="info-card compact-card"><strong>Run #${escapeHtml(run.id)}</strong><p>${escapeHtml(formatDate(run.created_at))}</p><p>${escapeHtml(run.summary.total_threads || 0)} threads, ${escapeHtml(run.summary.upserted_assets || 0)} assets.</p></article>`).join("")}
        </div>
        <div class="list-scroll">
          ${characters.length ? characters.map((row) => `
            <button class="record-card ${row.slug === state.selected.intake ? "active" : ""}" data-intake-select="${row.slug}" type="button">
              <strong>${escapeHtml(row.display_name)}</strong>
              <span>${escapeHtml(row.slug)}</span>
              <small>${escapeHtml(row.release_bucket || "unassigned")} · ${escapeHtml(row.review_status || "pending_review")} / ${(importedAssetsFor(row.slug) || []).length} assets</small>
            </button>
          `).join("") : `<div class="empty-state">No imported characters match the current filter.</div>`}
        </div>
      </section>

      <section class="detail-panel intake-detail-panel">
        ${current ? `
          <div class="panel-header">
            <div>
              <h3>${escapeHtml(current.display_name)}</h3>
              <p>${escapeHtml(current.slug)} · ${escapeHtml(current.source_thread_id || "")}</p>
            </div>
            <div class="pill-row">
              <span class="pill ${current.review_status === "approved" ? "" : "muted"}">${escapeHtml(current.review_status || "pending_review")}</span>
              ${current.is_public ? '<span class="pill">Public</span>' : '<span class="pill muted">Private</span>'}
            </div>
          </div>
          <div class="field-grid">
            <label class="field">
              <span>Display Name</span>
              <input data-intake-character-field="display_name" data-character-id="${rowId(current)}" type="text" value="${escapeHtml(current.display_name || "")}" />
            </label>
            <label class="field">
              <span>Roster Group</span>
              <input data-intake-character-field="roster_group" data-character-id="${rowId(current)}" type="text" value="${escapeHtml(current.roster_group || "")}" />
            </label>
            <label class="field">
              <span>Release Round</span>
              <select data-intake-character-field="release_bucket" data-character-id="${rowId(current)}">
                ${releaseBucketOptions.map((value) => `<option value="${value}" ${String(current.release_bucket || "unassigned") === value ? "selected" : ""}>${value}</option>`).join("")}
              </select>
            </label>
            <label class="field">
              <span>Review Status</span>
              <select data-intake-character-field="review_status" data-character-id="${rowId(current)}">
                ${intakeReviewOptions.map((value) => `<option value="${value}" ${current.review_status === value ? "selected" : ""}>${value}</option>`).join("")}
              </select>
            </label>
            <label class="field toggle-field">
              <input data-intake-character-field="is_public" data-character-id="${rowId(current)}" type="checkbox" ${current.is_public ? "checked" : ""} />
              <span>Public Catalog Visibility</span>
            </label>
            <label class="field">
              <span>Tags</span>
              <input data-intake-character-field="tags" data-character-id="${rowId(current)}" type="text" value="${escapeHtml(current.tags || "")}" />
            </label>
            <label class="field field-full">
              <span>Description</span>
              <textarea data-intake-character-field="description" data-character-id="${rowId(current)}" rows="4">${escapeHtml(current.description || "")}</textarea>
            </label>
            <label class="field field-full">
              <span>Imported Notes</span>
              <textarea data-intake-character-field="moveset_notes" data-character-id="${rowId(current)}" rows="5">${escapeHtml(current.moveset_notes || "")}</textarea>
            </label>
          </div>
          <div class="pill-row">
            ${current.source_url ? `<a class="secondary-button" href="${escapeHtml(current.source_url)}" target="_blank" rel="noreferrer">Open Source Thread</a>` : ""}
            <span class="pill muted">${currentAssets.length} imported assets</span>
          </div>
          <div class="intake-bucket-summary">
            ${intakeBucketOptions.map((bucket) => `<span class="pill ${bucket === (activeAsset?.quality_bucket || "") ? "" : "muted"}">${escapeHtml(bucket)} · ${escapeHtml(bucketCounts[bucket] || 0)}</span>`).join("")}
          </div>
          <section class="info-card intake-strip-shell">
            <div class="panel-header compact">
              <div>
                <h4>Asset Strip</h4>
                <p>Slide sideways through the imported art. One focused inspector stays open below.</p>
              </div>
            </div>
            <div class="intake-strip" role="list">
              ${currentAssets.length ? currentAssets.map((asset) => renderIntakeStripCard(asset, String(asset.id) === String(state.selected.intakeAsset))).join("") : `<div class="empty-state">No imported assets for this character.</div>`}
            </div>
          </section>
          <div class="intake-inspector-shell">
            ${activeAsset ? renderIntakeAssetInspector(activeAsset) : `<div class="empty-state">Select an asset from the strip to inspect it.</div>`}
          </div>
        ` : `<div class="empty-state">Select an imported character to review it.</div>`}
      </section>
    </div>
  `;
}

function renderAssets() {
  const container = document.getElementById("tab-assets");
  const assets = filteredRows("assets");
  const entitySlugs = [
    ...(state.snapshot?.characters || []).map((row) => row.slug),
    ...(state.snapshot?.stages || []).map((row) => row.slug),
  ];

  container.innerHTML = `
    <div class="split-layout">
      <section class="detail-panel">
        <div class="panel-header">
          <div>
            <h3>Upload Asset</h3>
            <p>Attach a file to a known slot and decide whether it is public and/or approved.</p>
          </div>
        </div>
        <form id="assetUploadForm" class="detail-form">
          <div class="field-grid">
            <label class="field">
              <span>Entity Type</span>
              <select name="entity_type">
                <option value="character">Character</option>
                <option value="stage">Stage</option>
                <option value="shared">Shared</option>
              </select>
            </label>
            <label class="field">
              <span>Entity Slug</span>
              <input name="entity_slug" type="text" list="entitySlugList" required />
            </label>
            <label class="field">
              <span>Slot</span>
              <input name="slot" type="text" list="slotList" required />
            </label>
            <label class="field">
              <span>Label</span>
              <input name="label" type="text" />
            </label>
            <label class="field field-full">
              <span>Notes</span>
              <textarea name="notes" rows="4"></textarea>
            </label>
            <label class="field">
              <span>File</span>
              <input name="file" type="file" required />
            </label>
            <label class="field toggle-field">
              <input name="is_public" type="checkbox" />
              <span>Public asset</span>
            </label>
            <label class="field toggle-field">
              <input name="is_approved" type="checkbox" />
              <span>Approved for apply</span>
            </label>
          </div>
          <div class="form-actions">
            <button class="primary-button" type="submit">Upload</button>
          </div>
        </form>
        <datalist id="entitySlugList">
          ${entitySlugs.map((slug) => `<option value="${escapeHtml(slug)}"></option>`).join("")}
        </datalist>
        <datalist id="slotList">
          <option value="spritesheet"></option>
          <option value="portrait"></option>
          <option value="select_art"></option>
          <option value="unlock_art"></option>
          <option value="reference_sheet"></option>
          <option value="gallery_support"></option>
          <option value="stock_icon"></option>
          <option value="voice_bank"></option>
          <option value="theme_music"></option>
          <option value="preview_art"></option>
          <option value="background_art"></option>
          <option value="platform_art"></option>
          <option value="geometry_ref"></option>
          <option value="music"></option>
        </datalist>
      </section>

      <section class="grid-panel">
        <div class="panel-header">
          <div>
            <h3>Assets</h3>
            <p>${assets.length} shown</p>
          </div>
        </div>
        <div class="card-grid">
          ${assets.length ? assets.map((asset) => `
            <article class="info-card">
              <div class="panel-header compact">
                <div>
                  <strong>${escapeHtml(asset.label || asset.slot || asset.original_filename)}</strong>
                  <p>${escapeHtml(asset.entity_type)} / ${escapeHtml(asset.entity_slug)} / ${escapeHtml(asset.slot || "unassigned")}</p>
                </div>
                <div class="pill-row">
                  ${asset.source_type === "discord_import" ? '<span class="pill muted">Import</span>' : ""}
                  ${asset.is_public ? '<span class="pill">Public</span>' : ""}
                  ${asset.is_approved ? '<span class="pill">Approved</span>' : ""}
                </div>
              </div>
              <div class="media-frame">${renderAssetPreview(asset)}</div>
              <p>${escapeHtml(asset.notes || "No notes.")}</p>
              <div class="form-actions">
                <a class="secondary-button" href="${asset.file_url}" target="_blank" rel="noreferrer">Open</a>
                <button class="danger-button" data-delete-asset="${asset.id}" type="button">Delete</button>
              </div>
            </article>
          `).join("") : `<div class="empty-state">No matching assets.</div>`}
        </div>
      </section>
    </div>
  `;
}

function renderSubmissions() {
  const container = document.getElementById("tab-submissions");
  const rows = filteredRows("submissions");
  container.innerHTML = `
    <div class="grid-panel">
      <div class="panel-header">
        <div>
          <h3>Submission Queue</h3>
          <p>${rows.length} shown</p>
        </div>
      </div>
      <div class="card-grid">
        ${rows.length ? rows.map((row) => `
          <article class="info-card" data-submission-id="${row.id}">
            <div class="panel-header compact">
              <div>
                <strong>${escapeHtml(row.title)}</strong>
                <p>${escapeHtml(row.entity_type)} / ${escapeHtml(row.entity_slug || "unassigned")} / ${escapeHtml(row.slot || "general")}</p>
              </div>
              <span class="pill ${row.status === "approved" ? "" : "muted"}">${escapeHtml(row.status)}</span>
            </div>
            <p>${escapeHtml(row.description || "No description.")}</p>
            <div class="pill-row">
              ${row.submitter_name ? `<span class="pill muted">${escapeHtml(row.submitter_name)}</span>` : ""}
              ${row.submitter_contact ? `<span class="pill muted">${escapeHtml(row.submitter_contact)}</span>` : ""}
              <span class="pill muted">${escapeHtml(formatDate(row.created_at))}</span>
            </div>
            ${row.file_url ? `<a class="secondary-button" href="${row.file_url}" target="_blank" rel="noreferrer">Open Uploaded File</a>` : ""}
            ${row.link_url ? `<a class="secondary-button" href="${escapeHtml(row.link_url)}" target="_blank" rel="noreferrer">Open Reference Link</a>` : ""}
            <label class="field">
              <span>Status</span>
              <select data-submission-field="status">
                ${["pending", "needs_review", "approved", "rejected"].map((value) => `<option value="${value}" ${row.status === value ? "selected" : ""}>${value}</option>`).join("")}
              </select>
            </label>
            <label class="field field-full">
              <span>Admin Notes</span>
              <textarea data-submission-field="admin_notes" rows="4">${escapeHtml(row.admin_notes || "")}</textarea>
            </label>
            <div class="form-actions">
              <button class="primary-button" data-save-submission="${row.id}" type="button">Save</button>
              <button class="danger-button" data-delete-submission="${row.id}" type="button">Delete</button>
            </div>
          </article>
        `).join("") : `<div class="empty-state">No matching submissions.</div>`}
      </div>
    </div>
  `;
}

function renderApply() {
  const container = document.getElementById("tab-apply");
  const runs = state.snapshot?.apply_runs || [];
  const latest = runs[0]?.summary;
  const pendingImported = importedCharacters().filter((row) => row.review_status !== "approved").length;
  const approvedImportedAssets = importedAssets().filter((asset) => asset.is_approved && asset.slot && asset.review_status === "approved").length;

  container.innerHTML = `
    <div class="split-layout">
      <section class="detail-panel">
        <div class="panel-header">
          <div>
            <h3>Current Publish State</h3>
            <p>Approved assets get copied into <code>assets/content</code>; data gets written into <code>assets/data/generated</code>.</p>
          </div>
        </div>
        <div class="summary-stack">
          <article class="info-card compact-card">
            <strong>Approved Assets</strong>
            <p>${escapeHtml((state.snapshot?.assets || []).filter((asset) => asset.is_approved).length)}</p>
          </article>
          <article class="info-card compact-card">
            <strong>Pending Imported Characters</strong>
            <p>${escapeHtml(pendingImported)}</p>
          </article>
          <article class="info-card compact-card">
            <strong>Approved Intake Slots</strong>
            <p>${escapeHtml(approvedImportedAssets)}</p>
          </article>
          <article class="info-card compact-card">
            <strong>Latest Apply</strong>
            <p>${latest ? `${latest.copied_assets} copied, ${latest.removed_stale_assets || 0} cleaned, ${latest.missing_assets} missing.` : "No apply run yet."}</p>
          </article>
        </div>
      </section>

      <section class="grid-panel">
        <div class="panel-header">
          <div>
            <h3>Apply History</h3>
            <p>${runs.length} recorded</p>
          </div>
        </div>
        <div class="card-grid">
          ${runs.length ? runs.map((run) => `
            <article class="info-card compact-card">
              <strong>Run #${escapeHtml(run.id)}</strong>
              <p>${escapeHtml(formatDate(run.created_at))}</p>
              <div class="pill-row">
                <span class="pill">${escapeHtml(run.summary.copied_assets)} copied</span>
                <span class="pill muted">${escapeHtml(run.summary.removed_stale_assets || 0)} cleaned</span>
                <span class="pill muted">${escapeHtml(run.summary.missing_assets)} missing</span>
              </div>
            </article>
          `).join("") : `<div class="empty-state">No apply runs yet.</div>`}
        </div>
      </section>
    </div>
  `;
}

function renderAll() {
  setViewHeader();
  renderSummary();
  renderIntake();
  renderEditorSection("characters");
  renderEditorSection("stages");
  renderEditorSection("mechanics");
  renderAssets();
  renderSubmissions();
  renderApply();
  applySidebarState();
  syncTabs();
  bindActions();
}

function applySidebarState() {
  document.querySelector(".admin-layout")?.classList.toggle("sidebar-collapsed", state.sidebarCollapsed);
}

function syncTabs() {
  document.querySelectorAll(".sidebar-tab").forEach((button) => {
    button.classList.toggle("active", button.dataset.tab === state.activeTab);
  });
  document.querySelectorAll(".tab-panel").forEach((panel) => {
    panel.classList.toggle("active", panel.id === `tab-${state.activeTab}`);
  });
  setViewHeader();
}

async function loadSnapshot({ preserveSelection = false } = {}) {
  const previousSelection = { ...state.selected };
  setStatus("Loading admin data...");
  state.snapshot = await apiRequest("/api/admin/bootstrap");
  if (preserveSelection) {
    state.selected = previousSelection;
  }
  renderAll();
  setStatus("Admin data loaded.", "success");
}

function bindActions() {
  document.querySelectorAll(".sidebar-tab").forEach((button) => {
    button.onclick = () => {
      state.activeTab = button.dataset.tab;
      syncTabs();
    };
  });

  const sidebarToggle = document.getElementById("sidebarToggleBtn");
  if (sidebarToggle) {
    sidebarToggle.onclick = () => {
      state.sidebarCollapsed = !state.sidebarCollapsed;
      applySidebarState();
    };
  }

  document.querySelectorAll("[data-intake-select]").forEach((button) => {
    button.onclick = () => {
      state.selected.intake = button.dataset.intakeSelect;
      state.selected.intakeAsset = null;
      renderIntake();
      bindActions();
      syncTabs();
    };
  });

  document.querySelectorAll("[data-intake-asset-select]").forEach((button) => {
    button.onclick = () => {
      state.selected.intakeAsset = button.dataset.intakeAssetSelect;
      renderIntake();
      bindActions();
      syncTabs();
    };
  });

  document.querySelectorAll("[data-intake-character-field]").forEach((element) => {
    const syncField = () => {
      updateLocalRow("characters", element.dataset.characterId, element.dataset.intakeCharacterField, element.type === "checkbox" ? element.checked : element.value);
    };
    element.oninput = syncField;
    element.onchange = syncField;
  });

  document.querySelectorAll("[data-asset-field]").forEach((element) => {
    const syncField = () => {
      updateLocalAsset(element.dataset.assetId, element.dataset.assetField, element.type === "checkbox" ? element.checked : element.value);
    };
    element.oninput = syncField;
    element.onchange = syncField;
  });

  document.querySelectorAll("[data-quick-bucket]").forEach((button) => {
    button.onclick = () => {
      updateLocalAsset(button.dataset.assetId, "quality_bucket", button.dataset.quickBucket);
      state.selected.intakeAsset = button.dataset.assetId;
      renderIntake();
      bindActions();
      syncTabs();
    };
  });

  document.querySelectorAll("[data-quick-review]").forEach((button) => {
    button.onclick = () => {
      updateLocalAsset(button.dataset.assetId, "review_status", button.dataset.quickReview);
      if (button.dataset.quickReview === "rejected") {
        updateLocalAsset(button.dataset.assetId, "is_approved", 0);
      }
      state.selected.intakeAsset = button.dataset.assetId;
      renderIntake();
      bindActions();
      syncTabs();
    };
  });

  document.querySelectorAll("[data-add-row]").forEach((button) => {
    button.onclick = () => {
      const section = button.dataset.addRow;
      const draft = blankRow(section);
      state.snapshot[section] = [draft, ...state.snapshot[section]];
      state.selected[section] = rowId(draft);
      renderEditorSection(section);
      bindActions();
      syncTabs();
    };
  });

  document.querySelectorAll("[data-select-row]").forEach((button) => {
    button.onclick = () => {
      const section = button.dataset.selectRow;
      state.selected[section] = button.dataset.rowId;
      renderEditorSection(section);
      bindActions();
      syncTabs();
    };
  });

  document.querySelectorAll("[data-section]").forEach((form) => {
    const section = form.dataset.section;
    const currentId = form.dataset.rowId;

    form.querySelectorAll("[data-field]").forEach((element) => {
      const syncField = () => {
        updateLocalRow(section, currentId, element.dataset.field, element.type === "checkbox" ? element.checked : element.value);
      };
      element.oninput = syncField;
      element.onchange = syncField;
    });

    form.onsubmit = async (event) => {
      event.preventDefault();
      try {
        state.isSaving = true;
        saveAllBtn.disabled = true;
        setStatus("Saving record...");
        const row = findRowById(section, currentId);
        if (!row) throw new Error("Record not found.");
        await persistRow(section, row, currentId);
        await loadSnapshot({ preserveSelection: true });
        setStatus("Record saved.", "success");
      } catch (error) {
        setStatus(error.message, "error");
      } finally {
        state.isSaving = false;
        saveAllBtn.disabled = false;
      }
    };
  });

  document.querySelectorAll("[data-delete-row]").forEach((button) => {
    button.onclick = async () => {
      const form = button.closest("[data-section]");
      const section = form.dataset.section;
      const originalSlug = form.dataset.originalSlug;
      const currentId = form.dataset.rowId;
      if (!originalSlug) {
        state.snapshot[section] = state.snapshot[section].filter((row) => rowId(row) !== currentId);
        state.selected[section] = null;
        renderEditorSection(section);
        bindActions();
        return;
      }
      try {
        await apiRequest(meta[section].deleteEndpoint(originalSlug), { method: "DELETE" });
        state.selected[section] = null;
        await loadSnapshot();
      } catch (error) {
        setStatus(error.message, "error");
      }
    };
  });

  const uploadForm = document.getElementById("assetUploadForm");
  if (uploadForm) {
    uploadForm.onsubmit = async (event) => {
      event.preventDefault();
      try {
        await apiRequest("/api/admin/assets", { method: "POST", body: new FormData(uploadForm) });
        uploadForm.reset();
        await loadSnapshot({ preserveSelection: true });
        state.activeTab = "assets";
        syncTabs();
      } catch (error) {
        setStatus(error.message, "error");
      }
    };
  }

  document.querySelectorAll("[data-delete-asset]").forEach((button) => {
    button.onclick = async () => {
      try {
        await apiRequest(`/api/admin/assets/${button.dataset.deleteAsset}`, { method: "DELETE" });
        await loadSnapshot({ preserveSelection: true });
      } catch (error) {
        setStatus(error.message, "error");
      }
    };
  });

  document.querySelectorAll("[data-save-submission]").forEach((button) => {
    const card = button.closest("[data-submission-id]");
    const submissionId = button.dataset.saveSubmission;

    card.querySelectorAll("[data-submission-field]").forEach((element) => {
      const syncField = () => updateLocalSubmission(submissionId, element.dataset.submissionField, element.value);
      element.oninput = syncField;
      element.onchange = syncField;
    });

    button.onclick = async () => {
      try {
        state.isSaving = true;
        saveAllBtn.disabled = true;
        setStatus("Saving submission...");
        const row = sectionRows("submissions").find((entry) => String(entry.id) === String(submissionId));
        if (!row) throw new Error("Submission not found.");
        await persistSubmission(row);
        await loadSnapshot({ preserveSelection: true });
        setStatus("Submission saved.", "success");
      } catch (error) {
        setStatus(error.message, "error");
      } finally {
        state.isSaving = false;
        saveAllBtn.disabled = false;
      }
    };
  });

  document.querySelectorAll("[data-delete-submission]").forEach((button) => {
    button.onclick = async () => {
      try {
        await apiRequest(`/api/admin/submissions/${button.dataset.deleteSubmission}`, { method: "DELETE" });
        await loadSnapshot({ preserveSelection: true });
      } catch (error) {
        setStatus(error.message, "error");
      }
    };
  });
}

searchInput.addEventListener("input", () => {
  state.query = searchInput.value;
  renderAll();
});

saveAllBtn.addEventListener("click", async () => {
  await saveAllChanges();
});

document.getElementById("refreshBtn").addEventListener("click", async () => {
  await loadSnapshot({ preserveSelection: true });
});

document.getElementById("applyBtn").addEventListener("click", async () => {
  setStatus("Applying approved data into the Godot project...");
  try {
    const result = await apiRequest("/api/admin/apply", { method: "POST" });
    setStatus(
      `Apply complete. Copied ${result.summary.copied_assets}, cleaned ${result.summary.removed_stale_assets || 0}, missing ${result.summary.missing_assets}.`,
      "success"
    );
    await loadSnapshot({ preserveSelection: true });
    state.activeTab = "apply";
    syncTabs();
  } catch (error) {
    setStatus(error.message, "error");
  }
});

window.setInterval(() => {
  if (document.visibilityState === "visible" && hasDirtyChanges()) {
    saveAllChanges({ silentWhenClean: true, fromAutosave: true });
  }
}, 60_000);

loadSnapshot().catch((error) => {
  setStatus(error.message, "error");
});
