<script setup>
/**
 * Manage users — server-driven B2CDataTable + async CSV export.
 */
import BaseAlert from "../../components/base/BaseAlert.vue";
import BaseButton from "../../components/base/BaseButton.vue";
import BaseCheckbox from "../../components/base/BaseCheckbox.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 { ACTIVE_INACTIVE_BADGES } from "../../constants/badgeStyles.js";
import {
  fetchDataTable,
  getDataTableColumns,
} from "../../services/dataTableService.js";
import { api, ensureCsrf } from "../../services/api";
import { useAuth } from "../../composables/useAuth";

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

const { permissions } = useAuth();

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

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

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

const canAssignRoles = computed(() =>
  (permissions.value ?? []).includes("users.assign-roles"),
);

const RESOURCE_KEY = "users";

const rows = ref([]);
const rolesCatalog = ref([]);
const loading = ref(false);
const error = ref("");
const dialogVisible = ref(false);
const editing = ref(null);

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

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

const draft = ref({
  name: "",
  email: "",
  password: "",
  is_active: true,
  roles: [],
});

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

function defaultVisibleKeys(cols) {
  return (cols ?? [])
    .filter((c) => c.defaultVisible !== false)
    .map((c) => c.key);
}

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

  return defaultVisibleKeys(cols);
}

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

const columns = computed(() => {
  const extras = {
    roles_txt: {
      filterKey: "roles_txt",
      sortable: false,
    },
    is_active: {
      type: "badge",
      filterType: "boolean",
      valueMap: ACTIVE_INACTIVE_BADGES,
      defaultBadgeEntry: ACTIVE_INACTIVE_BADGES.false,
    },
  };
  const merged = mergeDataTableColumns(apiColumnsRaw.value, extras);
  if (!canAssignRoles.value) {
    return merged.filter((c) => c.key !== "roles_txt");
  }
  return merged;
});

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

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, colKeys) {
  const fallback = colKeys.includes("name") ? "name" : colKeys[0] ?? "name";
  return colKeys.includes(field) ? field : fallback;
}

async function load() {
  const seq = ++fetchSeq;
  loading.value = true;
  error.value = "";
  try {
    await ensureCsrf();

    const cols = columns.value;
    const colKeys = cols.map((c) => c.key);
    const s0 = sort.value?.[0];
    const sf = sortFieldApi(s0?.field ?? "name", colKeys);

    const payload = {
      pagination: { ...pagination.value },
      sort: Array.isArray(sort.value) ? [...sort.value] : [],
      visibleColumns: effectiveVisibleColumns(cols),
      columnOrder:
        tableBackendState.value.columnOrder?.length > 0
          ? [...tableBackendState.value.columnOrder]
          : effectiveVisibleColumns(cols),
      filters: { ...(tableBackendState.value.filters ?? {}) },
      globalSearch: String(tableBackendState.value.globalSearch ?? "").trim(),
    };

    payload.sort = payload.sort?.length
      ? payload.sort
      : [{ field: sf, order: 1 }];

    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 users.";
    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 bootstrapRolesCatalog() {
  try {
    await ensureCsrf();
    const rolesRes = await api.get("/api/roles");
    const raw = rolesRes.data?.data ?? [];
    rolesCatalog.value = Array.isArray(raw)
      ? raw.map((r) => r.name).filter(Boolean)
      : [];
  } catch {
    rolesCatalog.value = [];
  }
}

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

function openCreate() {
  editing.value = null;
  errors.value = {};
  draft.value = {
    name: "",
    email: "",
    password: "",
    is_active: true,
    roles: [],
  };
  dialogVisible.value = true;
}

function openEdit(row) {
  editing.value = row;
  errors.value = {};
  draft.value = {
    name: row.name ?? "",
    email: row.email ?? "",
    password: "",
    is_active: !!row.is_active,
    roles: [...(row.roles ?? [])],
  };
  dialogVisible.value = true;
}

function closeDialog() {
  dialogVisible.value = false;
}

function fieldError(field) {
  const e = errors.value[field];
  return Array.isArray(e) ? e[0] : e ?? "";
}

async function saveUser() {
  saving.value = true;
  errors.value = {};
  try {
    await ensureCsrf();

    /** @type {Record<string, unknown>} */
    const payload = {
      name: draft.value.name.trim(),
      email: draft.value.email.trim(),
      is_active: !!draft.value.is_active,
    };

    if (canAssignRoles.value) {
      payload.roles = [...(draft.value.roles ?? [])];
    }

    if (!editing.value) {
      payload.password = draft.value.password;
      await api.post("/api/users", payload);
      dialogVisible.value = false;
      await load();

      return;
    }

    if (draft.value.password) {
      payload.password = draft.value.password;
    }

    await api.patch(`/api/users/${editing.value.id}`, payload);
    dialogVisible.value = false;
    await load();
  } catch (e) {
    errors.value =
      e?.response?.data?.errors ?? e?.response?.data?.validation ?? {};
    if (!Object.keys(errors.value).length) {
      errors.value.form = [e?.response?.data?.message ?? "Save failed."];
    }
  } finally {
    saving.value = false;
  }
}

async function removeUser(row) {
  if (
    typeof window !== "undefined" &&
    !window.confirm(`Delete ${row.email}? This cannot be undone.`)
  ) {
    return;
  }
  try {
    await ensureCsrf();
    await api.delete(`/api/users/${row.id}`);
    await load();
  } catch (e) {
    error.value = e?.response?.data?.message ?? "Could not delete user.";
  }
}
</script>

<template>
  <BaseIndex flush hide-header>
    <BaseAlert
      v-if="errors.form?.[0]"
      severity="error"
      class="rounded-2xl mb-3"
    >
      {{ errors.form?.[0] }}
    </BaseAlert>

    <B2CDataTable
      table-key="settings.users"
      :columns="columns"
      :rows="rows"
      :loading="loading"
      server-mode
      :total-records="totalRecords"
      :total-all-records="totalAllRecords"
      :pagination="pagination"
      :sort="sort"
      resource-key="users"
      :enable-export="true"
      :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="user"
      resource-label-plural="users"
      @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"
          >
            Users
          </h1>
          <p
            class="text-sm text-surface-600 dark:text-surface-400 mt-1.5 mb-0 max-w-3xl"
          >
            Invite and manage dashboard users inside your organisation.
          </p>
        </div>
      </template>
      <template #controls-left>
        <BaseButton
          v-if="canCreate"
          variant="primary"
          icon="plus"
          label="Add user"
          class="shrink-0"
          @click="openCreate"
        />
      </template>

      <template v-if="canAssignRoles" #cell-roles_txt="{ row }">
        <div class="flex flex-wrap gap-1 max-w-md">
          <BaseTag
            v-for="r in row.roles ?? []"
            :key="r"
            :value="r"
            severity="secondary"
          />
        </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="Edit user"
            @click="openEdit(row)"
          />
          <BaseButton
            v-if="canDelete"
            type="button"
            variant="danger"
            icon="trash"
            rounded
            text
            size="small"
            class="shrink-0"
            :icon-only="true"
            aria-label="Delete user"
            @click="removeUser(row)"
          />
        </div>
      </template>
    </B2CDataTable>

    <BaseModal
      v-model:visible="dialogVisible"
      :title="editing ? 'Edit user' : 'New user'"
      width="32rem"
      class="w-full max-w-lg"
      :dismissable-mask="true"
      @hide="closeDialog"
    >
      <form class="flex flex-col gap-4 mt-4" @submit.prevent="saveUser">
        <BaseInputText
          id="uv-name"
          v-model.trim="draft.name"
          fluid
          label="Name"
          required
          :error="fieldError('name')"
        />
        <BaseInputText
          id="uv-email"
          v-model.trim="draft.email"
          fluid
          type="email"
          label="Email"
          required
          :error="fieldError('email')"
        />
        <BaseInputText
          id="uv-password"
          v-model="draft.password"
          fluid
          type="password"
          :description="editing ? 'Leave blank to keep current.' : ''"
          :label="editing ? 'Password (optional)' : 'Password'"
          :required="!editing"
          :error="fieldError('password')"
        />

        <BaseCheckbox
          id="uv-active"
          v-model="draft.is_active"
          :binary="true"
          label="Active"
        />

        <div v-if="canAssignRoles" class="flex flex-col gap-2">
          <BaseMultiSelect
            v-model="draft.roles"
            label="Roles"
            :options="rolesCatalog"
            display="chip"
            placeholder="Roles"
            class="w-full"
            :error="fieldError('roles')"
          />
        </div>

        <div class="flex justify-end gap-3 pt-2">
          <BaseButton
            variant="secondary"
            outlined
            icon="cancel"
            type="button"
            @click="closeDialog"
          >
            Cancel
          </BaseButton>
          <BaseButton
            variant="primary"
            icon="check"
            type="submit"
            :loading="saving"
          >
            Save
          </BaseButton>
        </div>
      </form>
    </BaseModal>
  </BaseIndex>
</template>
