<template>
  <div class="admin-members">
    <!-- Page Header -->
    <div class="page-header">
      <div class="header-row">
        <div>
          <h1>Members</h1>
          <p>Manage members, contributions, and access</p>
        </div>
        <div class="header-actions">
          <button class="btn" @click="showImportModal = true">
            Import CSV
          </button>
          <button
            class="btn"
            :disabled="selectedMemberIds.length === 0"
            @click="openInviteModal"
          >
            Send Invites ({{ selectedMemberIds.length }})
          </button>
          <button class="btn btn-primary" @click="showCreateModal = true">
            Add Member
          </button>
        </div>
      </div>
    </div>

    <!-- Search / Filter -->
    <div class="filter-bar">
      <div class="field" style="margin-bottom: 0; flex: 1">
        <input v-model="searchQuery" placeholder="Search members..." >
      </div>
      <div class="field" style="margin-bottom: 0">
        <select v-model="circleFilter" aria-label="Filter by circle">
          <option value="">All Circles</option>
          <option value="community">Community</option>
          <option value="founder">Founder</option>
          <option value="practitioner">Practitioner</option>
        </select>
      </div>
      <div class="field" style="margin-bottom: 0">
        <select v-model="statusFilter" aria-label="Filter by status">
          <option value="">All Statuses</option>
          <option
            v-for="(label, value) in STATUS_LABELS"
            :key="value"
            :value="value"
          >{{ label }}</option>
        </select>
      </div>
    </div>

    <!-- Members Table -->
    <div class="table-wrap">
      <div v-if="pending" class="loading-state">
        <div class="spinner" />
        <span>Loading members...</span>
      </div>

      <div v-else-if="error" class="error-state">
        Error loading members: {{ error }}
      </div>

      <table v-else-if="filteredMembers.length">
        <thead>
          <tr>
            <th class="col-check">
              <label class="custom-check" aria-label="Select all members">
                <input
                  type="checkbox"
                  :checked="allVisibleSelected"
                  :indeterminate="!allVisibleSelected && someVisibleSelected"
                  @change="toggleSelectAll"
                >
                <span class="check-mark" />
              </label>
            </th>
            <th class="sortable" @click="toggleSort('name')">Name <span class="sort-ind">{{ sortIndicator('name') }}</span></th>
            <th class="sortable" @click="toggleSort('email')">Email <span class="sort-ind">{{ sortIndicator('email') }}</span></th>
            <th class="sortable" @click="toggleSort('circle')">Circle <span class="sort-ind">{{ sortIndicator('circle') }}</span></th>
            <th class="sortable" @click="toggleSort('contributionAmount')">Contribution <span class="sort-ind">{{ sortIndicator('contributionAmount') }}</span></th>
            <th class="sortable" @click="toggleSort('status')">Status <span class="sort-ind">{{ sortIndicator('status') }}</span></th>
            <th>Invite</th>
            <th>Slack</th>
            <th class="sortable" @click="toggleSort('createdAt')">Joined <span class="sort-ind">{{ sortIndicator('createdAt') }}</span></th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          <tr
            v-for="member in filteredMembers"
            :key="member._id"
            class="selectable-row"
            :class="{ 'row-selected': selectedMemberIds.includes(member._id) }"
            @click="toggleSelect(member._id)"
          >
            <td class="col-check" @click.stop>
              <label class="custom-check" :aria-label="`Select ${member.name}`">
                <input
                  type="checkbox"
                  :checked="selectedMemberIds.includes(member._id)"
                  @change="toggleSelect(member._id)"
                >
                <span class="check-mark" />
              </label>
            </td>
            <td class="col-name">
              <NuxtLink :to="`/admin/members/${member._id}`" class="member-name-link" @click.stop>{{ member.name }}</NuxtLink>
            </td>
            <td class="col-email">{{ member.email }}</td>
            <td>
              <CircleBadge :circle="member.circle" />
            </td>
            <td class="col-mono">${{ member.contributionAmount ?? 0 }}{{ member.billingCadence === 'annual' ? '/yr' : '/mo' }}</td>
            <td>
              <span class="badge status" :class="`status-${member.status || 'pending_payment'}`">{{ statusLabel(member.status) }}</span>
            </td>
            <td>
              <span
                :class="member.inviteEmailSent ? 'status-ok' : 'status-dim'"
              >
                {{ member.inviteEmailSent ? "Sent" : "Not sent" }}
              </span>
            </td>
            <td>
              <span v-if="member.slackInvited" class="status-ok">
                Invited {{ formatDate(member.slackInvitedAt) }}
              </span>
              <span v-else class="status-dim">
                Not yet invited
              </span>
            </td>
            <td class="col-mono col-date">
              {{ formatDate(member.createdAt) }}
            </td>
            <td class="col-actions">
              <NuxtLink :to="`/admin/members/${member._id}`" class="link-btn" @click.stop
                >View</NuxtLink
              >
              <button
                v-if="!member.slackInvited"
                class="link-btn"
                @click.stop="markSlackInvited(member)"
              >
                Mark as Slack invited
              </button>
              <button class="link-btn" @click.stop="editMember(member)">Edit</button>
            </td>
          </tr>
        </tbody>
      </table>

      <div v-else class="empty-state">
        No members found matching your criteria
      </div>
    </div>

    <!-- Create Member Modal -->
    <div
      v-if="showCreateModal"
      class="modal-overlay"
      @click.self="showCreateModal = false"
    >
      <div class="modal">
        <div class="modal-header">
          <h2>Add New Member</h2>
          <button class="modal-close" @click="showCreateModal = false">
            &times;
          </button>
        </div>

        <form class="modal-body" @submit.prevent="createMember">
          <div class="field">
            <label>Name</label>
            <input v-model="newMember.name" placeholder="Full name" required >
          </div>
          <div class="field">
            <label>Email</label>
            <input
              v-model="newMember.email"
              type="email"
              placeholder="email@example.com"
              required
            >
          </div>
          <div class="field">
            <label>Circle</label>
            <select v-model="newMember.circle">
              <option value="community">Community</option>
              <option value="founder">Founder</option>
              <option value="practitioner">Practitioner</option>
            </select>
          </div>
          <div class="field">
            <label>Contribution ({{ newMember.billingCadence === 'annual' ? '$/yr' : '$/mo' }})</label>
            <input v-model.number="newMember.contributionAmount" type="number" min="0" step="1">
          </div>
          <div class="modal-actions">
            <button type="button" class="btn" @click="showCreateModal = false">
              Cancel
            </button>
            <button type="submit" class="btn btn-primary" :disabled="creating">
              {{ creating ? "Creating..." : "Create Member" }}
            </button>
          </div>
        </form>
      </div>
    </div>

    <!-- CSV Import Modal -->
    <div
      v-if="showImportModal"
      class="modal-overlay"
      @click.self="closeImportModal"
    >
      <div class="modal modal-wide">
        <div class="modal-header">
          <h2>Import Members from CSV</h2>
          <button class="modal-close" @click="closeImportModal">&times;</button>
        </div>

        <div class="modal-body">
          <div v-if="!csvRows.length">
            <p class="help-text">
              Upload a CSV with columns:
              <code>name,email,circle,contributionAmount</code>
            </p>
            <p class="help-text" style="margin-bottom: 12px">
              Valid circles: community, founder, practitioner. Contribution: whole number ≥ 0.
            </p>
            <input
              ref="csvFileInput"
              type="file"
              accept=".csv"
              class="file-input"
              @change="handleCsvFile"
            >
          </div>

          <div v-if="csvParseError" class="error-box">
            {{ csvParseError }}
          </div>

          <div v-if="csvRows.length">
            <div class="csv-summary">
              <span
                >{{ csvRows.length }} row{{
                  csvRows.length !== 1 ? "s" : ""
                }}
                parsed.</span
              >
              <span
                v-if="csvValidRows.length !== csvRows.length"
                class="csv-errors"
              >
                {{ csvRows.length - csvValidRows.length }} with errors.
              </span>
              <button class="link-btn" @click="resetCsvImport">
                Choose different file
              </button>
            </div>

            <div class="csv-table-wrap">
              <table class="csv-table">
                <thead>
                  <tr>
                    <th>Status</th>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Circle</th>
                    <th>Contribution</th>
                  </tr>
                </thead>
                <tbody>
                  <tr
                    v-for="(row, i) in csvRows"
                    :key="i"
                    :class="{ 'row-error': row.error }"
                  >
                    <td>
                      <span v-if="row.error" class="status-error">{{
                        row.error
                      }}</span>
                      <span v-else class="status-ok">OK</span>
                    </td>
                    <td>{{ row.name }}</td>
                    <td class="col-email">{{ row.email }}</td>
                    <td>{{ row.circle }}</td>
                    <td>${{ row.contributionAmount }}{{ row.billingCadence === 'annual' ? '/yr' : '/mo' }}</td>
                  </tr>
                </tbody>
              </table>
            </div>

            <div v-if="importResults" class="results-box">
              <strong>Import complete</strong>
              <p class="status-ok">{{ importResults.created }} created</p>
              <p v-if="importResults.failed" class="status-error">
                {{ importResults.failed }} failed
              </p>
              <div v-if="importResults.results?.some((r) => !r.success)">
                <p
                  v-for="fail in importResults.results.filter(
                    (r) => !r.success,
                  )"
                  :key="fail.email"
                  class="status-error"
                  style="font-size: 11px"
                >
                  {{ fail.email }}: {{ fail.error }}
                </p>
              </div>
            </div>
          </div>
        </div>

        <div class="modal-actions">
          <button class="btn" @click="closeImportModal">
            {{ importResults ? "Done" : "Cancel" }}
          </button>
          <button
            v-if="csvValidRows.length && !importResults"
            :disabled="importing"
            class="btn btn-primary"
            @click="submitImport"
          >
            {{
              importing
                ? "Importing..."
                : `Import ${csvValidRows.length} member${csvValidRows.length !== 1 ? "s" : ""}`
            }}
          </button>
        </div>
      </div>
    </div>

    <!-- Edit Member Modal -->
    <div
      v-if="showEditModal"
      class="modal-overlay"
      @click.self="showEditModal = false"
    >
      <div class="modal">
        <div class="modal-header">
          <h2>Edit Member</h2>
          <button class="modal-close" @click="showEditModal = false">
            &times;
          </button>
        </div>

        <form class="modal-body" @submit.prevent="submitEditMember">
          <div class="field">
            <label>Name</label>
            <input v-model="editingMember.name" required >
          </div>
          <div class="field">
            <label>Email</label>
            <input v-model="editingMember.email" type="email" required >
          </div>
          <div class="field">
            <label>Circle</label>
            <select v-model="editingMember.circle">
              <option value="community">Community</option>
              <option value="founder">Founder</option>
              <option value="practitioner">Practitioner</option>
            </select>
          </div>
          <div class="field">
            <label>Contribution ({{ editingMember.billingCadence === 'annual' ? '$/yr' : '$/mo' }})</label>
            <input v-model.number="editingMember.contributionAmount" type="number" min="0" step="1">
          </div>
          <div class="field">
            <label>Status</label>
            <select v-model="editingMember.status">
              <option
                v-for="(label, value) in STATUS_LABELS"
                :key="value"
                :value="value"
              >{{ label }}</option>
            </select>
          </div>
          <div class="modal-actions">
            <button type="button" class="btn" @click="showEditModal = false">
              Cancel
            </button>
            <button type="submit" class="btn btn-primary" :disabled="saving">
              {{ saving ? "Saving..." : "Save Changes" }}
            </button>
          </div>
        </form>
      </div>
    </div>

    <!-- Send Invites Modal -->
    <div
      v-if="showInviteModal"
      class="modal-overlay"
      @click.self="showInviteModal = false"
    >
      <div class="modal modal-wide">
        <div class="modal-header">
          <h2>Send Invite Emails</h2>
          <button class="modal-close" @click="showInviteModal = false">
            &times;
          </button>
        </div>

        <div class="modal-body">
          <p class="help-text">
            Sending to <strong>{{ selectedMemberIds.length }}</strong> member{{
              selectedMemberIds.length !== 1 ? "s" : ""
            }}. Each receives a unique magic login link valid for 48 hours.
          </p>

          <div class="field">
            <label>Email Template</label>
            <textarea v-model="inviteTemplate" rows="12"/>
            <p class="help-text" style="margin-top: 4px">
              Tokens: <code>{name}</code>, <code>{loginLink}</code>,
              <code>{circle}</code>
            </p>
          </div>

          <div v-if="invitePreviewMember" class="field">
            <label>Preview ({{ invitePreviewMember.name }})</label>
            <pre class="preview-box">{{ invitePreviewText }}</pre>
          </div>

          <div v-if="inviteResults" class="results-box">
            <strong>Invites sent</strong>
            <p class="status-ok">{{ inviteResults.sent }} sent</p>
            <p v-if="inviteResults.failed" class="status-error">
              {{ inviteResults.failed }} failed
            </p>
            <div v-if="inviteResults.results?.some((r) => !r.success)">
              <p
                v-for="fail in inviteResults.results.filter((r) => !r.success)"
                :key="fail.email"
                class="status-error"
                style="font-size: 11px"
              >
                {{ fail.email }}: {{ fail.error }}
              </p>
            </div>
          </div>
        </div>

        <div class="modal-actions">
          <button class="btn" @click="showInviteModal = false">
            {{ inviteResults ? "Done" : "Cancel" }}
          </button>
          <button
            v-if="!inviteResults"
            :disabled="sendingInvites"
            class="btn btn-primary"
            @click="submitInvites"
          >
            {{
              sendingInvites
                ? "Sending..."
                : `Send ${selectedMemberIds.length} invite${selectedMemberIds.length !== 1 ? "s" : ""}`
            }}
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { STATUS_LABELS, statusLabel } from "~/config/memberStatus";

definePageMeta({
  layout: "admin",
  middleware: "admin",
});

const toast = useToast();

const {
  data: members,
  pending,
  error,
  refresh,
} = await useFetch("/api/admin/members");

const searchQuery = ref("");
const circleFilter = ref("");
const statusFilter = ref("");
const sortKey = ref("createdAt");
const sortDir = ref("desc");

const toggleSort = (key) => {
  if (sortKey.value === key) {
    sortDir.value = sortDir.value === "asc" ? "desc" : "asc";
  } else {
    sortKey.value = key;
    sortDir.value = key === "createdAt" ? "desc" : "asc";
  }
};
const sortIndicator = (key) => {
  if (sortKey.value !== key) return "";
  return sortDir.value === "asc" ? "▲" : "▼";
};
const showCreateModal = ref(false);
const creating = ref(false);

// Selection
const selectedMemberIds = ref([]);

// CSV import
const showImportModal = ref(false);
const csvFileInput = ref(null);
const csvRows = ref([]);
const csvParseError = ref("");
const importing = ref(false);
const importResults = ref(null);

// Invite
const showInviteModal = ref(false);
const sendingInvites = ref(false);
const inviteResults = ref(null);

const DEFAULT_INVITE_TEMPLATE = `Hi {name},

You've been invited to Ghost Guild.

Sign in here to get started:
{loginLink}

This link expires in 48 hours. After that, you can always request a new login link at https://ghostguild.org/login.

Reply to this email if you have any trouble!`;

const inviteTemplate = ref(DEFAULT_INVITE_TEMPLATE);

const newMember = reactive({
  name: "",
  email: "",
  circle: "community",
  contributionAmount: 0,
});

const filteredMembers = computed(() => {
  if (!members.value) return [];

  const filtered = members.value.filter((member) => {
    const matchesSearch =
      !searchQuery.value ||
      member.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
      member.email.toLowerCase().includes(searchQuery.value.toLowerCase());

    const matchesCircle =
      !circleFilter.value || member.circle === circleFilter.value;

    const matchesStatus =
      !statusFilter.value || (member.status || "pending_payment") === statusFilter.value;

    return matchesSearch && matchesCircle && matchesStatus;
  });

  const key = sortKey.value;
  const dir = sortDir.value === "asc" ? 1 : -1;
  return [...filtered].sort((a, b) => {
    let av = a[key];
    let bv = b[key];
    if (key === "contributionAmount") {
      av = Number(av) || 0;
      bv = Number(bv) || 0;
    } else if (key === "createdAt") {
      av = av ? new Date(av).getTime() : 0;
      bv = bv ? new Date(bv).getTime() : 0;
    } else if (key === "status") {
      av = a.status || "pending_payment";
      bv = b.status || "pending_payment";
    } else {
      av = (av || "").toString().toLowerCase();
      bv = (bv || "").toString().toLowerCase();
    }
    if (av < bv) return -1 * dir;
    if (av > bv) return 1 * dir;
    return 0;
  });
});

// Selection helpers
const allVisibleSelected = computed(() => {
  if (!filteredMembers.value.length) return false;
  return filteredMembers.value.every((m) =>
    selectedMemberIds.value.includes(m._id),
  );
});

const someVisibleSelected = computed(() => {
  return filteredMembers.value.some((m) =>
    selectedMemberIds.value.includes(m._id),
  );
});

const toggleSelectAll = () => {
  if (allVisibleSelected.value) {
    const visibleIds = new Set(filteredMembers.value.map((m) => m._id));
    selectedMemberIds.value = selectedMemberIds.value.filter(
      (id) => !visibleIds.has(id),
    );
  } else {
    const currentSet = new Set(selectedMemberIds.value);
    for (const m of filteredMembers.value) {
      currentSet.add(m._id);
    }
    selectedMemberIds.value = [...currentSet];
  }
};

const toggleSelect = (id) => {
  const idx = selectedMemberIds.value.indexOf(id);
  if (idx >= 0) {
    selectedMemberIds.value.splice(idx, 1);
  } else {
    selectedMemberIds.value.push(id);
  }
};

// Invite preview
const invitePreviewMember = computed(() => {
  if (!selectedMemberIds.value.length || !members.value) return null;
  return members.value.find((m) => m._id === selectedMemberIds.value[0]);
});

const invitePreviewText = computed(() => {
  if (!invitePreviewMember.value) return "";
  return inviteTemplate.value
    .replace(/\{name\}/g, invitePreviewMember.value.name)
    .replace(
      /\{loginLink\}/g,
      "https://ghostguild.org/api/auth/verify?token=...",
    )
    .replace(/\{circle\}/g, invitePreviewMember.value.circle);
});

const formatDate = (dateString) => {
  return new Date(dateString).toLocaleDateString();
};

// --- Create Member ---
const createMember = async () => {
  creating.value = true;
  try {
    await $fetch("/api/admin/members", {
      method: "POST",
      body: newMember,
    });

    showCreateModal.value = false;
    Object.assign(newMember, {
      name: "",
      email: "",
      circle: "community",
      contributionAmount: 0,
    });

    await refresh();
    toast.add({ title: "Member created", color: "success" });
  } catch (err) {
    console.error("Failed to create member:", err);
    toast.add({
      title: "Failed to create member",
      description: err.data?.statusMessage || err.message,
      color: "error",
    });
  } finally {
    creating.value = false;
  }
};

// --- CSV Import ---
const VALID_CIRCLES = ["community", "founder", "practitioner"];

const handleCsvFile = (event) => {
  const file = event.target.files[0];
  if (!file) return;

  csvParseError.value = "";
  csvRows.value = [];

  const reader = new FileReader();
  reader.onload = (e) => {
    const text = e.target.result;
    parseCsv(text);
  };
  reader.readAsText(file);
};

const parseCsv = (text) => {
  const lines = text.split(/\r?\n/).filter((l) => l.trim());
  if (lines.length < 2) {
    csvParseError.value =
      "CSV must have a header row and at least one data row.";
    return;
  }

  const header = lines[0].split(",").map((h) => h.trim().toLowerCase());
  const nameIdx = header.indexOf("name");
  const emailIdx = header.indexOf("email");
  const circleIdx = header.indexOf("circle");
  const amountIdx = header.indexOf("contributionamount");

  if (nameIdx === -1 || emailIdx === -1 || circleIdx === -1 || amountIdx === -1) {
    csvParseError.value = `Missing required columns. Found: ${header.join(", ")}. Need: name, email, circle, contributionAmount`;
    return;
  }

  const seenEmails = new Set();
  const rows = [];

  for (let i = 1; i < lines.length; i++) {
    const cols = lines[i].split(",").map((c) => c.trim());
    const name = cols[nameIdx] || "";
    const email = (cols[emailIdx] || "").toLowerCase();
    const circle = (cols[circleIdx] || "").toLowerCase();
    const rawAmount = cols[amountIdx] || "";
    const contributionAmount = Number(rawAmount);

    let error = null;
    if (!name) error = "Missing name";
    else if (!email || !email.includes("@")) error = "Invalid email";
    else if (!VALID_CIRCLES.includes(circle))
      error = `Invalid circle: ${circle}`;
    else if (!Number.isInteger(contributionAmount) || contributionAmount < 0)
      error = `Invalid contribution: ${rawAmount}`;
    else if (seenEmails.has(email)) error = "Duplicate email in CSV";

    if (!error) seenEmails.add(email);

    rows.push({ name, email, circle, contributionAmount, error });
  }

  csvRows.value = rows;
};

const csvValidRows = computed(() => csvRows.value.filter((r) => !r.error));

const resetCsvImport = () => {
  csvRows.value = [];
  csvParseError.value = "";
  importResults.value = null;
  if (csvFileInput.value) csvFileInput.value.value = "";
};

const closeImportModal = () => {
  showImportModal.value = false;
  if (importResults.value) {
    resetCsvImport();
    refresh();
  }
};

const submitImport = async () => {
  importing.value = true;
  try {
    const payload = csvValidRows.value.map(
      ({ name, email, circle, contributionAmount }) => ({
        name,
        email,
        circle,
        contributionAmount,
      }),
    );

    const result = await $fetch("/api/admin/members/import", {
      method: "POST",
      body: { members: payload },
    });

    importResults.value = result;
    toast.add({
      title: `Imported ${result.created} member${result.created !== 1 ? "s" : ""}`,
      description: result.failed ? `${result.failed} failed` : undefined,
      color: result.failed ? "warning" : "success",
    });
  } catch (err) {
    console.error("Import failed:", err);
    toast.add({
      title: "Import failed",
      description: err.data?.statusMessage || err.message,
      color: "error",
    });
  } finally {
    importing.value = false;
  }
};

// --- Send Invites ---
const openInviteModal = () => {
  inviteResults.value = null;
  inviteTemplate.value = DEFAULT_INVITE_TEMPLATE;
  showInviteModal.value = true;
};

const submitInvites = async () => {
  sendingInvites.value = true;
  try {
    const result = await $fetch("/api/admin/members/invite", {
      method: "POST",
      body: {
        memberIds: selectedMemberIds.value,
        emailTemplate: inviteTemplate.value,
      },
    });

    inviteResults.value = result;
    await refresh();
    selectedMemberIds.value = [];

    toast.add({
      title: `Sent ${result.sent} invite${result.sent !== 1 ? "s" : ""}`,
      description: result.failed ? `${result.failed} failed` : undefined,
      color: result.failed ? "warning" : "success",
    });
  } catch (err) {
    console.error("Failed to send invites:", err);
    toast.add({
      title: "Failed to send invites",
      description: err.data?.statusMessage || err.message,
      color: "error",
    });
  } finally {
    sendingInvites.value = false;
  }
};

// --- Existing actions ---
const markSlackInvited = async (member) => {
  try {
    const res = await $fetch(
      `/api/admin/members/${member._id}/slack-status`,
      {
        method: "PATCH",
        body: { slackInvited: true },
      },
    );
    const idx = members.value.findIndex((m) => m._id === member._id);
    if (idx !== -1) members.value[idx] = { ...members.value[idx], ...res.member };
    toast.add({ title: "Marked as Slack invited", color: "success" });
  } catch (err) {
    toast.add({
      title: "Failed to mark Slack invited",
      description: err.data?.statusMessage || err.message,
      color: "error",
    });
  }
};

// --- Edit Member ---
const showEditModal = ref(false);
const saving = ref(false);
const editingMemberId = ref(null);
const editingMember = reactive({
  name: "",
  email: "",
  circle: "community",
  contributionAmount: 0,
  billingCadence: "monthly",
  status: "pending_payment",
});

const editMember = (member) => {
  editingMemberId.value = member._id;
  Object.assign(editingMember, {
    name: member.name,
    email: member.email,
    circle: member.circle,
    contributionAmount: member.contributionAmount ?? 0,
    billingCadence: member.billingCadence || "monthly",
    status: member.status || "pending_payment",
  });
  showEditModal.value = true;
};

const submitEditMember = async () => {
  saving.value = true;
  try {
    await $fetch(`/api/admin/members/${editingMemberId.value}`, {
      method: "PUT",
      body: {
        name: editingMember.name,
        email: editingMember.email,
        circle: editingMember.circle,
        contributionAmount: editingMember.contributionAmount,
        status: editingMember.status,
      },
    });
    showEditModal.value = false;
    await refresh();
    toast.add({ title: "Member updated", color: "success" });
  } catch (err) {
    toast.add({
      title: "Failed to update member",
      description: err.data?.statusMessage || err.message,
      color: "error",
    });
  } finally {
    saving.value = false;
  }
};
</script>

<style scoped>
.admin-members {}

/* ---- PAGE HEADER ---- */
.page-header {
  padding: 28px 28px 20px;
  border-bottom: 1px dashed var(--border);
}

.header-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
}

.page-header h1 {
  font-family: "Brygada 1918", serif;
  font-size: 24px;
  font-weight: 500;
  color: var(--text-bright);
  line-height: 1.2;
  margin-bottom: 4px;
}

.page-header p {
  font-size: 12px;
  color: var(--text-dim);
}

.header-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

/* ---- FILTER BAR ---- */
.filter-bar {
  display: flex;
  gap: 12px;
  padding: 16px 28px;
  border-bottom: 1px dashed var(--border);
}

/* ---- TABLE ---- */
.table-wrap {
  padding: 0 28px 28px;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

thead th {
  text-align: left;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 14px 10px 10px;
  border-bottom: 1px dashed var(--border-d);
  font-weight: normal;
}

tbody tr {
  border-bottom: 1px dashed var(--border);
  transition: background 0.1s;
}

tbody tr:hover {
  background: var(--surface);
}

tbody td {
  padding: 10px;
  color: var(--text);
  vertical-align: middle;
}

.col-check {
  width: 40px;
  padding-left: 12px;
  padding-right: 4px;
}

.selectable-row {
  cursor: pointer;
}

.row-selected {
  background: var(--surface);
}

/* ---- CUSTOM CHECKBOX ---- */
.custom-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: relative;
  width: 16px;
  height: 16px;
}

.custom-check input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.check-mark {
  width: 14px;
  height: 14px;
  border: 1px solid var(--border);
  background: var(--input-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.15s, background 0.15s;
}

.custom-check:hover .check-mark {
  border-color: var(--candle);
}

.custom-check input:checked + .check-mark {
  background: var(--candle);
  border-color: var(--candle);
}

.custom-check input:checked + .check-mark::after {
  content: "";
  width: 4px;
  height: 8px;
  border: solid var(--bg);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg) translateY(-1px);
}

.custom-check input:indeterminate + .check-mark {
  background: var(--candle);
  border-color: var(--candle);
}

.custom-check input:indeterminate + .check-mark::after {
  content: "";
  width: 8px;
  height: 0;
  border-bottom: 1.5px solid var(--bg);
}

.col-name {
  font-weight: 500;
  color: var(--text-bright);
}

.member-name-link {
  color: var(--text-bright);
  font-weight: 500;
  text-decoration: none;
}

.member-name-link:hover {
  color: var(--candle);
  text-decoration: underline;
}

.col-email {
  color: var(--text-dim);
  font-size: 11px;
}

.col-mono {
  font-variant-numeric: tabular-nums;
}

.col-date {
  font-size: 11px;
  color: var(--text-faint);
}

.col-actions {
  white-space: nowrap;
}

/* ---- LINK BUTTON ---- */
.link-btn {
  background: none;
  border: none;
  color: var(--candle);
  cursor: pointer;
  font-family: "Commit Mono", monospace;
  font-size: 11px;
  padding: 2px 6px;
}

.link-btn:hover {
  text-decoration: underline;
}

/* ---- STATUS INDICATORS ---- */
.status-ok {
  color: var(--green);
  font-size: 11px;
}

.status-dim {
  color: var(--text-faint);
  font-size: 11px;
}

.status-error {
  color: var(--ember);
  font-size: 11px;
}

/* ---- SORTABLE HEADERS ---- */
th.sortable {
  cursor: pointer;
  user-select: none;
}
th.sortable:hover {
  color: var(--candle);
}
.sort-ind {
  display: inline-block;
  width: 10px;
  font-size: 9px;
  color: var(--candle);
  margin-left: 2px;
}

/* ---- MEMBER STATUS BADGES ---- */
.badge.status {
  text-transform: uppercase;
}
.badge.status-active {
  color: var(--green);
  border-color: rgba(58, 107, 58, 0.45);
}
.badge.status-pending_payment {
  color: var(--text-dim);
  border-color: var(--border);
}
.badge.status-suspended {
  color: var(--ember);
  border-color: color-mix(in srgb, var(--ember) 45%, transparent);
}
.badge.status-cancelled {
  color: var(--text-faint);
  border-color: var(--border);
  opacity: 0.7;
}

/* ---- MODALS ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
}

.modal {
  background: var(--bg);
  border: 1px dashed var(--border);
  max-width: 440px;
  width: 100%;
  margin: 16px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}

.modal-wide {
  max-width: 640px;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 24px 16px;
  border-bottom: 1px dashed var(--border);
}

.modal-header h2 {
  font-family: "Brygada 1918", serif;
  font-size: 18px;
  font-weight: 500;
  color: var(--text-bright);
}

.modal-close {
  background: none;
  border: none;
  color: var(--text-faint);
  font-size: 20px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}

.modal-close:hover {
  color: var(--text);
}

.modal-body {
  padding: 20px 24px;
  overflow-y: auto;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 16px 24px;
  border-top: 1px dashed var(--border);
}

/* ---- HELP TEXT ---- */
.help-text {
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.5;
}

.help-text code {
  color: var(--text-bright);
  font-size: 11px;
}

/* ---- FILE INPUT ---- */
.file-input {
  font-size: 12px;
  color: var(--text-dim);
}

/* ---- ERROR / RESULTS BOXES ---- */
.error-box {
  padding: 12px 16px;
  border: 1px dashed var(--ember);
  color: var(--ember);
  font-size: 12px;
  margin-bottom: 12px;
}

.results-box {
  padding: 16px 20px;
  border: 1px dashed var(--border);
  margin-top: 16px;
}

.results-box strong {
  color: var(--text-bright);
  font-size: 13px;
  display: block;
  margin-bottom: 4px;
}

/* ---- CSV TABLE ---- */
.csv-summary {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 12px;
}

.csv-errors {
  color: var(--ember);
}

.csv-table-wrap {
  max-height: 250px;
  overflow: auto;
  border: 1px dashed var(--border);
}

.csv-table {
  font-size: 11px;
}

.csv-table thead th {
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}

.row-error {
  background: color-mix(in srgb, var(--ember) 4%, transparent);
}

/* ---- PREVIEW BOX ---- */
.preview-box {
  font-family: "Commit Mono", monospace;
  font-size: 11px;
  color: var(--text-dim);
  background: var(--surface);
  border: 1px dashed var(--border);
  padding: 16px;
  white-space: pre-wrap;
  overflow: auto;
  max-height: 200px;
  line-height: 1.5;
}

/* ---- STATES ---- */
.loading-state {
  text-align: center;
  padding: 48px 24px;
  color: var(--text-faint);
  font-size: 12px;
}

.error-state {
  text-align: center;
  padding: 48px 24px;
  color: var(--ember);
  font-size: 12px;
}

.empty-state {
  text-align: center;
  padding: 48px 24px;
  color: var(--text-faint);
  font-size: 12px;
}

.spinner {
  width: 24px;
  height: 24px;
  border: 2px dashed var(--candle);
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin: 0 auto 12px;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* ---- RESPONSIVE ---- */
@media (max-width: 768px) {
  .page-header {
    padding: 24px 20px 16px;
  }

  .header-row {
    flex-direction: column;
  }

  .header-actions {
    flex-wrap: wrap;
  }

  .filter-bar {
    flex-direction: column;
    padding: 16px 20px;
  }

  .table-wrap {
    padding: 0 20px 20px;
    overflow-x: auto;
  }

  table {
    min-width: 700px;
  }
}
</style>
