<script setup>
/**
 * Roles — server-driven B2CDataTable + export (Spatie CRUD).
 */
import BaseAlert from "../../components/base/BaseAlert.vue";
import BaseButton from "../../components/base/BaseButton.vue";
import BaseIndex from "../../components/base/BaseIndex.vue";
import BaseInputText from "../../components/base/BaseInputText.vue";
import BaseModal from "../../components/base/BaseModal.vue";
import BaseMultiSelect from "../../components/base/BaseMultiSelect.vue";
import BaseTag from "../../components/base/BaseTag.vue";
import B2CDataTable from "../../components/common/B2CDataTable.vue";

import { mergeDataTableColumns } from "../../composables/mergeDataTableColumns.js";
import { useAuth } from "../../composables/useAuth";
import { api, ensureCsrf } from "../../services/api";
import {
  fetchDataTable,
  getDataTableColumns,
} from "../../services/dataTableService.js";

import { computed, nextTick, onMounted, ref } from "vue";

const { permissions } = useAuth();

const canCreate = computed(() =>
  (permissions.value ?? []).includes("roles.create"),
);

const canEdit = computed(() => (permissions.value ?? []).includes("roles.edit"));

const canDelete = computed(() =>
  (permissions.value ?? []).includes("roles.delete"),
);

const RESOURCE_KEY = "roles";

const rows = ref([]);
const permCatalog = ref([]);

const loading = ref(false);
const error = ref("");
const dialogVisible = ref(false);
const dialogMode = ref("create");
const editingRole = ref(null);

const draftName = ref("");
const draftPerms = ref([]);

const saving = ref(false);
const validation = ref({});

const pagination = ref({ page: 1, limit: 10 });
const sort = ref([{ field: "name", order: 1 }]);

const totalRecords = ref(0);
const totalAllRecords = ref(0);

const tableBackendState = ref({
  globalSearch: "",
  filters: {},
  visibleColumns: [],
  columnOrder: [],
});

/** @type {import('vue').Ref<Array<Record<string, unknown>>>} */
const apiColumnsRaw = ref([]);

const columns = computed(() =>
  mergeDataTableColumns(apiColumnsRaw.value, {
    permissions: { sortable: false },
  }),
);

function defaultVisibleKeys() {
  return columns.value
    .filter((c) => c.defaultVisible !== false)
    .map((c) => c.key);
}

function effectiveVisibleColumns() {
  const v = tableBackendState.value.visibleColumns;
  if (Array.isArray(v) && v.length > 0) return [...v];

  return defaultVisibleKeys();
}

let loadTimer = null;
let fetchSeq = 0;

function criteriaSignature(gs, filters) {
  return JSON.stringify({
    globalSearch: gs,
    filters: filters ?? {},
  });
}

function scheduleLoad() {
  if (loadTimer) clearTimeout(loadTimer);
  loadTimer = setTimeout(() => {
    loadTimer = null;
    load();
  }, 400);
}

function sortFieldApi(field) {
  const allowed = new Set([
    "name",
    "users_count",
    "guard_name",
    "created_at",
    "updated_at",
    "permissions_count",
    "id",
  ]);

  return allowed.has(field) ? field : "name";
}

async function load() {
  const seq = ++fetchSeq;
  loading.value = true;
  error.value = "";
  try {
    await ensureCsrf();
    const s0 = sort.value?.[0];
    const sf = sortFieldApi(s0?.field ?? "name");
    const payload = {
      pagination: { ...pagination.value },
      sort:
        Array.isArray(sort.value) && sort.value.length
          ? [...sort.value]
          : [{ field: sf, order: 1 }],
      visibleColumns: effectiveVisibleColumns(),
      columnOrder:
        tableBackendState.value.columnOrder?.length > 0
          ? [...tableBackendState.value.columnOrder]
          : effectiveVisibleColumns(),
      filters: { ...(tableBackendState.value.filters ?? {}) },
      globalSearch: String(tableBackendState.value.globalSearch ?? "").trim(),
    };

    const data = await fetchDataTable(RESOURCE_KEY, payload);
    if (seq !== fetchSeq) return;

    rows.value = data.data ?? [];
    totalRecords.value = data.meta?.total ?? 0;
    totalAllRecords.value = data.meta?.totalAll ?? data.meta?.total ?? 0;
  } catch (e) {
    if (seq !== fetchSeq) return;
    error.value =
      e?.response?.data?.message ?? "Could not load roles or permissions.";
    rows.value = [];
    totalRecords.value = 0;
    totalAllRecords.value = 0;
  } finally {
    if (seq === fetchSeq) loading.value = false;
  }
}

function onTableState(state) {
  const prevCriteria = criteriaSignature(
    tableBackendState.value.globalSearch,
    tableBackendState.value.filters,
  );
  const prevVis = JSON.stringify(tableBackendState.value.visibleColumns ?? []);
  const prevOrder = JSON.stringify(tableBackendState.value.columnOrder ?? []);

  const nextGs = String(state?.globalSearch ?? "");
  const nextFilters =
    state?.filters && typeof state.filters === "object"
      ? { ...state.filters }
      : {};
  const nextVisible = Array.isArray(state.visibleColumns)
    ? [...state.visibleColumns]
    : [];
  const nextOrder = Array.isArray(state.columnOrder)
    ? [...state.columnOrder]
    : [];

  tableBackendState.value = {
    globalSearch: nextGs,
    filters: nextFilters,
    visibleColumns: nextVisible,
    columnOrder: nextOrder,
  };

  const nextCriteria = criteriaSignature(nextGs, nextFilters);
  const nextVis = JSON.stringify(nextVisible);
  const nextOrderStr = JSON.stringify(nextOrder);

  if (nextCriteria !== prevCriteria) {
    pagination.value = { ...pagination.value, page: 1 };
    scheduleLoad();

    return;
  }

  if (nextVis !== prevVis || nextOrderStr !== prevOrder) {
    scheduleLoad();
  }
}

async function bootstrapPermCatalog() {
  try {
    await ensureCsrf();
    const p = await api.get("/api/permissions");
    permCatalog.value = p.data?.data ?? [];
  } catch {
    permCatalog.value = [];
  }
}

onMounted(async () => {
  await nextTick();
  await bootstrapPermCatalog();
  await ensureCsrf();
  try {
    apiColumnsRaw.value = await getDataTableColumns(RESOURCE_KEY);
  } catch {
    apiColumnsRaw.value = [];
  }
  await load();
});

function openCreate() {
  dialogMode.value = "create";
  editingRole.value = null;
  draftName.value = "";
  draftPerms.value = [];
  validation.value = {};
  dialogVisible.value = true;
}

function openEdit(role) {
  dialogMode.value = "edit";
  editingRole.value = role;
  draftName.value = role.name ?? "";
  draftPerms.value = [...(role.permissions ?? [])];
  validation.value = {};
  dialogVisible.value = true;
}

async function saveRole() {
  if (dialogMode.value === "edit" && editingRole.value?.name === "admin") {
    dialogVisible.value = false;

    return;
  }

  saving.value = true;
  validation.value = {};

  try {
    await ensureCsrf();

    if (dialogMode.value === "create") {
      await api.post("/api/roles", {
        name: draftName.value.trim(),
        permissions: draftPerms.value,
      });
    } else if (editingRole.value) {
      await api.put(`/api/roles/${editingRole.value.id}`, {
        name: draftName.value.trim(),
        permissions: draftPerms.value,
      });
    }

    dialogVisible.value = false;
    await load();
  } catch (e) {
    validation.value = e?.response?.data?.errors ?? {};
    if (
      typeof e?.response?.data?.message === "string" &&
      Object.keys(validation.value).length === 0
    ) {
      error.value = e.response.data.message;
    }
  } finally {
    saving.value = false;
  }
}

async function destroyRole(role) {
  if (role.name === "admin") return;

  if (
    typeof window !== "undefined" &&
    !window.confirm(`Remove role "${role.name}"?`)
  ) {
    return;
  }

  try {
    await ensureCsrf();
    await api.delete(`/api/roles/${role.id}`);
    await load();
  } catch (e) {
    error.value =
      e?.response?.data?.message ?? "Can't delete role (still assigned?).";
  }
}
</script>

<template>
  <BaseIndex flush hide-header>
    <B2CDataTable
      table-key="settings.roles"
      :columns="columns"
      :rows="rows"
      :loading="loading"
      server-mode
      resource-key="roles"
      :enable-export="true"
      :total-records="totalRecords"
      :total-all-records="totalAllRecords"
      :pagination="pagination"
      :sort="sort"
      :persist-to-server="true"
      :paginator="true"
      :rows-per-page="pagination.limit"
      :rows-per-page-options="[10, 25, 50]"
      :show-actions="canEdit || canDelete"
      search-placeholder="Zoeken…"
      :table-error="error"
      resource-label-singular="rol"
      resource-label-plural="rollen"
      @update:pagination="
        pagination = $event;
        load();
      "
      @update:sort="
        sort = $event;
        load();
      "
      @update:state="onTableState($event)"
    >
      <template #toolbar-title>
        <div>
          <h1
            class="text-2xl font-semibold tracking-tight text-surface-950 dark:text-surface-0 m-0"
          >
            Rollen
          </h1>
          <p
            class="text-sm text-surface-600 dark:text-surface-400 mt-1.5 mb-0 max-w-3xl"
          >
            Define roles and connect them to abilities (Spatie).
          </p>
        </div>
      </template>
      <template #controls-left>
        <BaseButton
          v-if="canCreate"
          variant="primary"
          icon="plus"
          label="Nieuwe rol"
          class="shrink-0"
          @click="openCreate"
        />
      </template>

      <template #cell-permissions="{ row }">
        <div class="flex flex-wrap gap-1 max-w-xl">
          <BaseTag
            v-for="perm in (row.permissions ?? []).slice(0, 12)"
            :key="`${row.id}:${perm}`"
            severity="secondary"
            :value="String(perm)"
          />
          <span
            v-if="(row.permissions ?? []).length > 12"
            class="text-xs text-surface-500 px-2"
          >
            (+{{ (row.permissions ?? []).length - 12 }} more)
          </span>
        </div>
      </template>

      <template v-if="canEdit || canDelete" #actions="{ row }">
        <div class="flex flex-nowrap items-center justify-end gap-1">
          <BaseButton
            v-if="canEdit"
            type="button"
            variant="secondary"
            icon="pencil"
            rounded
            text
            size="small"
            class="shrink-0"
            :icon-only="true"
            aria-label="Rol bewerken"
            @click="openEdit(row)"
          />
          <BaseButton
            v-if="canDelete && row.name !== 'admin'"
            type="button"
            variant="danger"
            icon="trash"
            rounded
            text
            size="small"
            class="shrink-0"
            :icon-only="true"
            aria-label="Rol verwijderen"
            @click="destroyRole(row)"
          />
        </div>
      </template>
    </B2CDataTable>

    <BaseModal
      v-model:visible="dialogVisible"
      :title="dialogMode === 'create' ? 'Nieuwe rol' : 'Rol bewerken'"
      width="42rem"
      class="w-full max-w-2xl"
    >
      <form class="flex flex-col gap-4 mt-2" @submit.prevent="saveRole">
        <BaseInputText
          v-if="dialogMode === 'create' || editingRole?.name !== 'admin'"
          v-model.trim="draftName"
          label="Rol naam"
          required
          fluid
          :disabled="dialogMode === 'edit' && editingRole?.name === 'admin'"
        />

        <div
          v-if="dialogMode === 'edit' && editingRole?.name === 'admin'"
          class="text-sm text-surface-500"
        >
          De built-in rol <strong>admin</strong> heeft vaste rechten via seed —
          naam en rechtenlijst worden niet gewijzigd via deze dialoog.
        </div>

        <template
          v-if="
            dialogMode === 'create' ||
            (dialogMode === 'edit' && editingRole?.name !== 'admin')
          "
        >
          <BaseMultiSelect
            v-model="draftPerms"
            label="Permissions"
            :filter="true"
            :options="permCatalog"
            display="chip"
            placeholder="Kies rechten"
            class="w-full max-h-[20rem]"
            :error="
              validation.permissions
                ? (
                    Array.isArray(validation.permissions)
                      ? validation.permissions.join(' ')
                      : String(validation.permissions)
                  )
                : ''
            "
          />
        </template>

        <BaseAlert v-if="validation.name" severity="error">
          {{
            Array.isArray(validation.name) ? validation.name[0] : validation.name
          }}
        </BaseAlert>

        <div class="flex justify-end gap-2 pt-4">
          <BaseButton
            variant="secondary"
            outlined
            icon="cancel"
            type="button"
            @click="dialogVisible = false"
          >
            Annuleren
          </BaseButton>
          <BaseButton
            v-if="dialogMode === 'edit' && editingRole?.name === 'admin'"
            variant="secondary"
            outlined
            icon="cancel"
            type="button"
            @click="dialogVisible = false"
          >
            Sluiten
          </BaseButton>
          <BaseButton
            v-if="dialogMode !== 'edit' || editingRole?.name !== 'admin'"
            variant="primary"
            icon="check"
            type="submit"
            :loading="saving"
          >
            Opslaan
          </BaseButton>
        </div>
      </form>
    </BaseModal>
  </BaseIndex>
</template>
