<script setup lang="tsx">
import { computed, nextTick, reactive, ref, watch } from 'vue';
import { ElButton, ElTag } from 'element-plus';
import dayjs from 'dayjs';
import { useBoolean } from '@sa/hooks';
import { commonStatusRecord, objectTypeRecord, scopeTypeRecord } from '@/constants/business';
import { fetchDeleteRole, fetchGetMenuSimpleList, fetchGetRolePage } from '@/service/api';
import { useUIPaginatedTable } from '@/hooks/common/table';
import BusinessTableActionCell from '@/components/custom/business-table-action-cell';
import { $t } from '@/locales';
import { buildMenuTree } from '@/views/system/shared/menu-tree';
import RoleContextPanel from './modules/role-context-panel.vue';
import RoleOperateDialog from './modules/role-operate-dialog.vue';
import RoleResourcePanel from './modules/role-resource-panel.vue';
import RoleSearch from './modules/role-search.vue';

defineOptions({ name: 'RoleManage' });

function getInitSearchParams(): Api.SystemManage.RoleSearchParams {
  return {
    pageNo: 1,
    pageSize: 10,
    name: undefined,
    code: undefined,
    status: undefined,
    createTime: undefined
  };
}

function transformPageResult(response: Awaited<ReturnType<typeof fetchGetRolePage>>, pageNo: number, pageSize: number) {
  if (!response.error) {
    return {
      data: response.data.list,
      pageNum: pageNo,
      pageSize,
      total: response.data.total
    };
  }

  return {
    data: [],
    pageNum: pageNo,
    pageSize,
    total: 0
  };
}

function formatTime(value?: number | null) {
  if (!value) {
    return '--';
  }

  return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
}

function getStatusTagType(status: Api.SystemManage.CommonStatus): UI.ThemeColor {
  return status === 0 ? 'success' : 'warning';
}

function getStatusLabel(status: Api.SystemManage.CommonStatus) {
  return $t(commonStatusRecord[status]);
}

const searchParams = reactive(getInitSearchParams());
const selectedRoleId = ref<string | null>(null);
const pendingSelectedRoleId = ref<string | null>(null);
const scopeType = ref<Api.SystemManage.ScopeType>('global');
const objectType = ref<Api.SystemManage.ObjectType>('product');

const isObjectScope = computed(() => scopeType.value === 'object');

function getCurrentScopeParams(): Api.SystemManage.ScopeQueryParams {
  if (scopeType.value === 'object') {
    return {
      scopeType: 'object',
      objectType: objectType.value
    };
  }

  return {
    scopeType: 'global'
  };
}

const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagination } = useUIPaginatedTable({
  paginationProps: {
    currentPage: searchParams.pageNo,
    pageSize: searchParams.pageSize
  },
  api: () =>
    fetchGetRolePage({
      ...searchParams,
      ...getCurrentScopeParams()
    }),
  transform: response => transformPageResult(response, searchParams.pageNo, searchParams.pageSize),
  onPaginationParamsChange: params => {
    searchParams.pageNo = params.currentPage ?? 1;
    searchParams.pageSize = params.pageSize ?? 10;
  },
  columns: () => [
    { prop: 'index', type: 'index', label: $t('common.index'), width: 64 },
    { prop: 'name', label: $t('page.system.role.roleName'), minWidth: 160, showOverflowTooltip: true },
    { prop: 'code', label: $t('page.system.role.roleCode'), minWidth: 180, showOverflowTooltip: true },
    {
      prop: 'status',
      label: $t('page.system.role.roleStatus'),
      width: 110,
      align: 'center',
      formatter: row => <ElTag type={getStatusTagType(row.status)}>{getStatusLabel(row.status)}</ElTag>
    },
    { prop: 'sort', label: $t('page.system.role.sort'), width: 90, align: 'center' },
    {
      prop: 'remark',
      label: $t('page.system.role.remark'),
      minWidth: 180,
      showOverflowTooltip: true,
      formatter: row => row.remark || '--'
    },
    {
      prop: 'createTime',
      label: $t('page.system.role.createTime'),
      minWidth: 170,
      formatter: row => formatTime(row.createTime)
    },
    {
      prop: 'operate',
      label: $t('common.operate'),
      width: 196,
      align: 'center',
      fixed: 'right',
      className: 'role-operate-column',
      formatter: row => (
        <BusinessTableActionCell
          actions={[
            {
              key: 'edit',
              label: $t('common.edit'),
              buttonType: 'primary',
              onClick: () => openEdit(row)
            },
            {
              key: 'delete',
              label: $t('common.delete'),
              buttonType: 'danger',
              onClick: () => handleDeleteAction(row)
            }
          ]}
        />
      )
    }
  ]
});

const menuTreeLoading = ref(false);
const menuTree = ref<Api.SystemManage.MenuSimple[]>([]);

async function getMenuTreeData() {
  menuTreeLoading.value = true;
  menuTree.value = [];

  const { error, data: menuList } = await fetchGetMenuSimpleList(getCurrentScopeParams());

  menuTreeLoading.value = false;

  if (error) {
    menuTree.value = [];
    return;
  }

  menuTree.value = buildMenuTree(menuList);
}

const currentRole = computed(() => data.value.find(item => item.id === selectedRoleId.value) ?? null);
const currentRoleTotal = computed(() => mobilePagination.value.total || data.value.length);
const currentScopeLabel = computed(() => $t(scopeTypeRecord[scopeType.value]));
const currentObjectTypeLabel = computed(() => {
  return objectType.value ? $t(objectTypeRecord[objectType.value]) : '';
});
const currentContextTagLabel = computed(() => {
  return isObjectScope.value && currentObjectTypeLabel.value
    ? `${currentScopeLabel.value} / ${currentObjectTypeLabel.value}`
    : currentScopeLabel.value;
});
const currentTableTitle = computed(() => {
  return $t(isObjectScope.value ? 'page.system.role.objectRoleTitle' : 'page.system.role.globalRoleTitle');
});

const { bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal } = useBoolean();
const operateType = ref<UI.TableOperateType>('add');
const editingData = ref<Api.SystemManage.Role | null>(null);

function openAdd() {
  operateType.value = 'add';
  editingData.value = null;
  openOperateModal();
}

function openEdit(item: Api.SystemManage.Role) {
  operateType.value = 'edit';
  editingData.value = item;
  openOperateModal();
}

async function handleDelete(item: Api.SystemManage.Role) {
  const { error } = await fetchDeleteRole(item.id);

  if (error) {
    return;
  }

  window.$message?.success($t('common.deleteSuccess'));

  if (selectedRoleId.value === item.id) {
    selectedRoleId.value = null;
  }

  await getData();
}

async function handleDeleteAction(row: Api.SystemManage.Role) {
  try {
    await window.$messageBox?.confirm($t('common.confirmDelete'), $t('common.warning'), {
      confirmButtonText: $t('common.confirm'),
      cancelButtonText: $t('common.cancel'),
      type: 'warning'
    });
  } catch {
    return;
  }

  await handleDelete(row);
}

function resetSearchParams() {
  Object.assign(searchParams, getInitSearchParams());
  getDataByPage(1);
}

function handleSearch() {
  getDataByPage(1);
}

function selectRole(roleId: string) {
  selectedRoleId.value = roleId;
}

function handleRowClick(row: Api.SystemManage.Role) {
  selectRole(row.id);
}

function handleSubmitted(roleId: string) {
  pendingSelectedRoleId.value = roleId;
  closeOperateModal();
  getData();
}

watch(
  data,
  value => {
    if (!value.length) {
      selectedRoleId.value = null;
      pendingSelectedRoleId.value = null;
      return;
    }

    const targetId = pendingSelectedRoleId.value ?? selectedRoleId.value;
    const matched = targetId ? value.find(item => item.id === targetId) : null;

    if (matched) {
      selectedRoleId.value = matched.id;
    } else {
      selectedRoleId.value = value[0].id;
    }

    pendingSelectedRoleId.value = null;
  },
  { immediate: true }
);

watch(scopeType, value => {
  if (value === 'object' && !objectType.value) {
    objectType.value = 'product';
  }
});

let contextChangeToken = 0;

watch([scopeType, objectType], async ([nextScope, nextObject], [prevScope, prevObject]) => {
  if (nextScope === prevScope && nextObject === prevObject) {
    return;
  }

  contextChangeToken += 1;
  const token = contextChangeToken;
  await nextTick();

  if (token !== contextChangeToken) {
    return;
  }

  Object.assign(searchParams, getInitSearchParams());
  selectedRoleId.value = null;
  pendingSelectedRoleId.value = null;
  editingData.value = null;
  closeOperateModal();
  await getMenuTreeData();
  await getDataByPage(1);
});

getMenuTreeData();
</script>

<template>
  <div class="min-h-560px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
    <RoleContextPanel
      v-model:scope-type="scopeType"
      v-model:object-type="objectType"
      :total="currentRoleTotal"
      :loading="loading || menuTreeLoading"
    />

    <div
      class="role-page-content gap-16px overflow-hidden xl:grid xl:grid-cols-[minmax(0,1fr)_360px] lt-xl:flex lt-xl:flex-col lt-xl:overflow-auto"
    >
      <div class="flex-col-stretch gap-16px xl:min-h-0">
        <RoleSearch v-model:model="searchParams" @reset="resetSearchParams" @search="handleSearch" />

        <ElCard class="card-wrapper xl:flex-1-hidden" body-class="role-table-card-body">
          <template #header>
            <div class="role-card-header">
              <div class="min-w-0 flex-1">
                <div class="flex flex-wrap items-center gap-8px">
                  <p class="truncate text-16px font-600">{{ currentTableTitle }}</p>
                  <ElTag effect="plain" :type="isObjectScope ? 'success' : 'primary'">
                    {{ currentContextTagLabel }}
                  </ElTag>
                  <ElTag effect="plain">{{ currentRoleTotal }}</ElTag>
                </div>
              </div>
              <TableHeaderOperation
                v-model:columns="columnChecks"
                :disabled-delete="true"
                :loading="loading"
                @refresh="getData"
              >
                <template #default>
                  <ElButton plain type="primary" @click="openAdd">
                    <template #icon>
                      <icon-ic-round-plus class="text-icon" />
                    </template>
                    {{ $t('common.add') }}
                  </ElButton>
                </template>
              </TableHeaderOperation>
            </div>
          </template>

          <div class="flex-1">
            <ElTable
              v-loading="loading"
              height="100%"
              border
              row-key="id"
              highlight-current-row
              :data="data"
              :current-row-key="selectedRoleId ?? undefined"
              @row-click="handleRowClick"
            >
              <ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col" />
            </ElTable>
          </div>

          <div class="mt-20px flex justify-end">
            <ElPagination
              v-if="mobilePagination.total"
              layout="total,prev,pager,next,sizes"
              v-bind="mobilePagination"
              @current-change="mobilePagination['current-change']"
              @size-change="mobilePagination['size-change']"
            />
          </div>
        </ElCard>
      </div>

      <div class="flex-col-stretch xl:min-h-0">
        <RoleResourcePanel :role="currentRole" :menu-tree="menuTree" :loading="menuTreeLoading" />
      </div>

      <RoleOperateDialog
        v-model:visible="operateVisible"
        :operate-type="operateType"
        :row-data="editingData"
        :scope-type="scopeType"
        :object-type="objectType"
        @submitted="handleSubmitted"
      />
    </div>
  </div>
</template>

<style lang="scss" scoped>
:deep(.role-page-content) {
  flex: 1;
  min-height: 0;
}

:deep(.role-table-card-body) {
  height: calc(100% - 56px);
  display: flex;
  flex-direction: column;
}

:deep(.el-table__row.current-row > td.el-table__cell) {
  background-color: rgb(64 158 255 / 8%);
}

// 修复固定列穿透问题：给操作列添加不透明背景
:deep(.role-operate-column) {
  background-color: var(--el-bg-color, #ffffff) !important;
}
// 选中行时操作列保持背景色
:deep(.el-table__row.current-row) {
  .role-operate-column {
    background-color: var(--el-bg-color, #ffffff) !important;
  }

  td.el-table__cell.is-fixed-right {
    background-color: var(--el-bg-color, #ffffff) !important;
  }
}

.role-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

@media (width <= 768px) {
  .role-card-header {
    flex-direction: column;
    align-items: stretch;
  }
}
</style>
