<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { ElButton, ElEmpty, ElProgress, ElTag, ElTooltip } from 'element-plus';
import dayjs from 'dayjs';
import { ArrowDown, QuestionFilled } from '@element-plus/icons-vue';
import { RDMS_OBJECT_DIRECTION_DICT_CODE, RDMS_PROJECT_TYPE_DICT_CODE } from '@/constants/dict';
import { useDict } from '@/hooks/business/dict';
import CurrentUserRoleTags from '@/components/custom/current-user-role-tags.vue';
import { getProjectStatusLabel, getProjectStatusTagType } from '../../shared/project-master-data';

defineOptions({ name: 'ProjectGroupedTable' });

interface Props {
  groups: Api.Project.ProjectGroup[];
  loading?: boolean;
  /** 当前是否"全部"视图（决定暂无项目占位是否渲染） */
  allView: boolean;
  /** 是否渲染方向小节层（可见产品方向数 ≥2） */
  showDirectionLayer: boolean;
  /** 组内默认直出条数（超出收纳） */
  topN: number;
  /** 折叠全部开关（true = 全部产品组折叠；方向小节保持展开） */
  allCollapsed: boolean;
  /** userId -> 昵称，项目经理列兜底回显 */
  managerLabelMap: Map<string, string>;
  /**
   * 拉取某组「展开剩余」的完整项目列表（后端 group-page 仅返前 topN 条）。
   * 由父组件注入：内部按 productId/orphanOnly + 当前状态口径调 page 接口并归一，返回该组全量项目（含前 N 条）。
   */
  fetchMore: (group: Api.Project.ProjectGroup) => Promise<Api.Project.Project[]>;
}

const props = withDefaults(defineProps<Props>(), { loading: false });

interface Emits {
  (e: 'enter', project: Api.Project.Project): void;
  (e: 'create', group: Api.Project.ProjectGroup): void;
}

const emit = defineEmits<Emits>();

const { getLabel: getDirectionLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
const { getLabel: getTypeLabel, dictOptions: typeOptions } = useDict(RDMS_PROJECT_TYPE_DICT_CODE);

/** 方向小节：把相邻同方向的产品组切成段；游离组单独成段（不渲染方向行） */
interface DirectionSection {
  key: string;
  directionCode: string;
  orphan: boolean;
  groups: Api.Project.ProjectGroup[];
}

const sections = computed<DirectionSection[]>(() => {
  const list: DirectionSection[] = [];

  for (const group of props.groups) {
    const last = list[list.length - 1];

    if (last && !last.orphan && !group.orphan && last.directionCode === group.directionCode) {
      last.groups.push(group);
    } else {
      list.push({
        key: group.orphan ? 'orphan' : `dir-${group.directionCode}`,
        directionCode: group.orphan ? '' : group.directionCode,
        orphan: group.orphan,
        groups: [group]
      });
    }
  }

  return list;
});

function groupKey(group: Api.Project.ProjectGroup) {
  return group.productId ?? 'orphan';
}

function sectionProjectCount(section: DirectionSection) {
  return section.groups.reduce((sum, group) => sum + group.projectTotal, 0);
}

// === 折叠 / 展开内部态（数据刷新即重置，默认全部展开） ===
const collapsedDirections = ref(new Set<string>());
const collapsedProducts = ref(new Set<string>());
const revealedProducts = ref(new Set<string>());
/** 已拉取的「展开剩余」完整列表缓存：groupKey -> 全量项目（避免重复请求） */
const expandedProjects = ref(new Map<string, Api.Project.Project[]>());
/** 正在拉取「展开剩余」的 groupKey（收纳行 loading 态） */
const expandingKeys = ref(new Set<string>());

watch(
  () => props.groups,
  () => {
    collapsedDirections.value = new Set();
    collapsedProducts.value = props.allCollapsed ? new Set(props.groups.map(groupKey)) : new Set();
    revealedProducts.value = new Set();
    expandedProjects.value = new Map();
    expandingKeys.value = new Set();
  },
  { immediate: true }
);

watch(
  () => props.allCollapsed,
  value => {
    collapsedProducts.value = value ? new Set(props.groups.map(groupKey)) : new Set();
  }
);

function toggleDirection(key: string) {
  const next = new Set(collapsedDirections.value);

  if (next.has(key)) {
    next.delete(key);
  } else {
    next.add(key);
  }

  collapsedDirections.value = next;
}

function toggleProduct(key: string) {
  const next = new Set(collapsedProducts.value);

  if (next.has(key)) {
    next.delete(key);
  } else {
    next.add(key);
  }

  collapsedProducts.value = next;
}

async function toggleReveal(group: Api.Project.ProjectGroup) {
  const key = groupKey(group);

  // 正在拉取剩余时忽略重复点击（page 是 GET、不走全局去重，防双击双发请求）
  if (expandingKeys.value.has(key)) {
    return;
  }

  // 已展开 → 收起（保留已拉取缓存，避免再次请求）
  if (revealedProducts.value.has(key)) {
    const next = new Set(revealedProducts.value);
    next.delete(key);
    revealedProducts.value = next;
    return;
  }

  // 首次展开且后端只返了前 N 条 → 异步拉该组完整列表
  const needFetch = group.projects.length < group.projectTotal && !expandedProjects.value.has(key);

  if (needFetch) {
    expandingKeys.value = new Set(expandingKeys.value).add(key);

    try {
      const full = await props.fetchMore(group);
      expandedProjects.value = new Map(expandedProjects.value).set(key, full);
    } catch {
      window.$message?.error('展开失败，请重试');
      const failNext = new Set(expandingKeys.value);
      failNext.delete(key);
      expandingKeys.value = failNext;
      return;
    }

    const doneNext = new Set(expandingKeys.value);
    doneNext.delete(key);
    expandingKeys.value = doneNext;
  }

  revealedProducts.value = new Set(revealedProducts.value).add(key);
}

// === 组内 Top-N 收纳（所有状态视图生效） ===
function visibleProjects(group: Api.Project.ProjectGroup) {
  const key = groupKey(group);

  if (revealedProducts.value.has(key)) {
    // 拉过剩余用完整缓存；若组内总数本就 ≤ topN（无需拉取）则直接用 group.projects
    return expandedProjects.value.get(key) ?? group.projects;
  }

  return group.projects.slice(0, props.topN);
}

/**
 * group.projects 为后端 group-page 返回的前 topN 条，projectTotal 为组内全量计数。
 * 展开剩余通过注入的 props.fetchMore 异步拉取该组完整列表并缓存（见 toggleReveal）。
 */
function hiddenCount(group: Api.Project.ProjectGroup) {
  return Math.max(group.projectTotal - props.topN, 0);
}

// === 扁平行模型：分组结构铺平后交给 ElTable 渲染（非项目行整行合并单元格） ===
type FlatRowType = 'dir' | 'product' | 'hint-empty' | 'project' | 'more';

interface FlatRow {
  rowType: FlatRowType;
  key: string;
  section?: DirectionSection;
  group?: Api.Project.ProjectGroup;
  project?: Api.Project.Project;
}

// 项目行列数（名称/类型/经理/我的角色/进度/计划周期/状态/更新），非项目行整行合并用
const COLUMN_COUNT = 8;

/** 单个产品组铺平为行：产品行 +（未折叠时）占位/项目/收纳行 */
function buildGroupRows(group: Api.Project.ProjectGroup): FlatRow[] {
  const key = groupKey(group);
  const rows: FlatRow[] = [{ rowType: 'product', key: `prod-${key}`, group }];

  if (collapsedProducts.value.has(key)) {
    return rows;
  }

  if (props.allView && group.projectTotal === 0) {
    rows.push({ rowType: 'hint-empty', key: `hint-empty-${key}`, group });
  }

  for (const project of visibleProjects(group)) {
    rows.push({
      rowType: 'project',
      key: `proj-${project.id}`,
      group,
      project
    });
  }

  if (hiddenCount(group) > 0) {
    rows.push({ rowType: 'more', key: `more-${key}`, group });
  }

  return rows;
}

const flatRows = computed<FlatRow[]>(() => {
  const rows: FlatRow[] = [];

  for (const section of sections.value) {
    if (props.showDirectionLayer && !section.orphan) {
      rows.push({ rowType: 'dir', key: `dir-${section.key}`, section });
    }

    if (!collapsedDirections.value.has(section.key)) {
      for (const group of section.groups) {
        rows.push(...buildGroupRows(group));
      }
    }
  }

  return rows;
});

function getRowKey(row: FlatRow) {
  return row.key;
}

function spanMethod({ row, columnIndex }: { row: FlatRow; columnIndex: number }) {
  if (row.rowType === 'project') {
    return undefined;
  }

  return columnIndex === 0 ? { rowspan: 1, colspan: COLUMN_COUNT } : { rowspan: 0, colspan: 0 };
}

function rowClassName({ row }: { row: FlatRow }) {
  if (row.rowType === 'dir') {
    const collapsed = row.section && collapsedDirections.value.has(row.section.key);
    return `pg-dir-row${collapsed ? ' is-collapsed' : ''}`;
  }

  if (row.rowType === 'product') {
    const collapsed = row.group && collapsedProducts.value.has(groupKey(row.group));
    return `pg-prod-row${collapsed ? ' is-collapsed' : ''}`;
  }

  if (row.rowType === 'hint-empty') {
    return 'pg-hint-row';
  }

  if (row.rowType === 'more') {
    return 'pg-more-row';
  }

  return 'pg-proj-row';
}

function handleRowClick(row: FlatRow) {
  if (row.rowType === 'dir' && row.section) {
    toggleDirection(row.section.key);
    return;
  }

  if (row.rowType === 'product' && row.group) {
    toggleProduct(groupKey(row.group));
    return;
  }

  if (row.rowType === 'more' && row.group) {
    toggleReveal(row.group);
  }
}

// === 回显 ===
interface TypeBadge {
  value: string;
  label: string;
  count: number;
}

function groupTypeBadges(group: Api.Project.ProjectGroup): TypeBadge[] {
  return (typeOptions.value ?? [])
    .filter(option => (group.typeCounts[option.value] ?? 0) > 0)
    .map(option => ({
      value: option.value,
      label: option.label,
      count: group.typeCounts[option.value]
    }));
}

function productManagerLabel(group: Api.Project.ProjectGroup) {
  if (group.managerUserNickname) {
    return group.managerUserNickname;
  }

  if (!group.managerUserId) {
    return '';
  }

  return props.managerLabelMap.get(group.managerUserId) || '';
}

function productMetaLabel(group: Api.Project.ProjectGroup) {
  const manager = productManagerLabel(group);

  return [group.productCode, manager ? `经理 ${manager}` : ''].filter(Boolean).join(' · ');
}

function projectManagerLabel(project: Api.Project.Project) {
  return project.managerUserNickname || props.managerLabelMap.get(project.managerUserId) || '--';
}

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

  return dayjs(value).format('YYYY-MM-DD');
}

/** 计划周期回显：两端完整日期，缺失端以 ? 占位 */
function formatPlannedRange(project: Api.Project.Project) {
  const start = project.plannedStartDate ? dayjs(project.plannedStartDate).format('YYYY-MM-DD') : '';
  const end = project.plannedEndDate ? dayjs(project.plannedEndDate).format('YYYY-MM-DD') : '';

  if (!start && !end) {
    return '--';
  }

  return `${start || '?'} ~ ${end || '?'}`;
}

/** 仅进行态视为可逾期；已完成/作废/归档不再标逾期 */
const OVERDUE_ELIGIBLE_STATUS: Api.Project.ProjectStatusCode[] = ['pending', 'active', 'paused'];

function overdueDays(project: Api.Project.Project) {
  if (!project.plannedEndDate || !OVERDUE_ELIGIBLE_STATUS.includes(project.statusCode)) {
    return 0;
  }

  const days = dayjs().startOf('day').diff(dayjs(project.plannedEndDate).startOf('day'), 'day');

  return Math.max(days, 0);
}
</script>

<template>
  <ElTable
    v-loading="loading"
    class="project-grouped-table"
    height="100%"
    :data="flatRows"
    :row-key="getRowKey"
    :span-method="spanMethod"
    :row-class-name="rowClassName"
    @row-click="handleRowClick"
  >
    <ElTableColumn label="项目名称" min-width="300" align="left">
      <template #default="{ row }">
        <div v-if="row.rowType === 'dir'" class="pg-dir-line">
          <ElIcon class="pg-toggle"><ArrowDown /></ElIcon>
          <span class="pg-dir-chip"></span>
          <span class="pg-dir-name">
            {{ getDirectionLabel(row.section.directionCode, row.section.directionCode || '--') }}
          </span>
          <span class="pg-dir-meta">
            {{ row.section.groups.length }} 个产品 · {{ sectionProjectCount(row.section) }} 个项目
          </span>
        </div>

        <div v-else-if="row.rowType === 'product'" class="pg-prod-line">
          <ElIcon class="pg-toggle"><ArrowDown /></ElIcon>
          <span class="pg-prod-icon" :class="{ 'pg-prod-icon--orphan': row.group.orphan }">
            <ElIcon v-if="row.group.orphan"><QuestionFilled /></ElIcon>
            <icon-material-symbols-package-2 v-else />
          </span>
          <span class="pg-prod-name" :class="{ 'pg-prod-name--orphan': row.group.orphan }">
            {{ row.group.productName }}
          </span>
          <span v-if="productMetaLabel(row.group)" class="pg-prod-code">{{ productMetaLabel(row.group) }}</span>
          <span v-if="row.group.orphan" class="pg-prod-code">未挂产品</span>
          <span v-for="badge in groupTypeBadges(row.group)" :key="badge.value" class="pg-badge">
            {{ badge.label }} {{ badge.count }}
          </span>
          <ElButton
            v-if="!row.group.orphan"
            link
            type="primary"
            class="pg-add-link"
            @click.stop="emit('create', row.group)"
          >
            <template #icon>
              <icon-ic-round-plus class="text-icon" />
            </template>
            新增
          </ElButton>
        </div>

        <div v-else-if="row.rowType === 'hint-empty'" class="pg-hint">
          该产品暂无项目 ——
          <ElButton link type="primary" @click.stop="emit('create', row.group)">新增项目</ElButton>
        </div>

        <div v-else-if="row.rowType === 'project'" class="pg-proj-name">
          <span class="pg-proj-icon">
            <icon-ic-round-folder />
          </span>
          <div class="pg-proj-main">
            <ElButton link type="primary" class="pg-proj-link" @click="emit('enter', row.project)">
              {{ row.project.projectName }}
            </ElButton>
            <div class="pg-sub-code">{{ row.project.projectCode }}</div>
          </div>
        </div>

        <div v-else class="pg-more-line">
          <template v-if="expandingKeys.has(groupKey(row.group))">
            <span class="pg-more-link">加载中…</span>
          </template>
          <template v-else-if="!revealedProducts.has(groupKey(row.group))">
            <span class="pg-more-link">
              <ElIcon class="pg-more-icon"><ArrowDown /></ElIcon>
              还有 {{ hiddenCount(row.group) }} 个项目，展开查看
            </span>
            <span class="pg-more-hint">组内默认只显示前 {{ topN }} 条，按最近更新排序</span>
          </template>
          <span v-else class="pg-more-link">
            <ElIcon class="pg-more-icon pg-more-icon--up"><ArrowDown /></ElIcon>
            收起
          </span>
        </div>
      </template>
    </ElTableColumn>

    <ElTableColumn label="项目类型" width="110" align="center" show-overflow-tooltip>
      <template #default="{ row }">
        <template v-if="row.rowType === 'project'">{{ getTypeLabel(row.project.projectType, '--') }}</template>
      </template>
    </ElTableColumn>

    <ElTableColumn label="项目经理" width="100" align="center" show-overflow-tooltip>
      <template #default="{ row }">
        <template v-if="row.rowType === 'project'">{{ projectManagerLabel(row.project) }}</template>
      </template>
    </ElTableColumn>

    <ElTableColumn label="我的身份" min-width="130" align="center">
      <template #default="{ row }">
        <CurrentUserRoleTags v-if="row.rowType === 'project'" :roles="row.project.currentUserRoles" />
      </template>
    </ElTableColumn>

    <ElTableColumn label="进度" min-width="200">
      <template #default="{ row }">
        <div v-if="row.rowType === 'project'" class="pg-progress">
          <ElProgress
            :percentage="row.project.progressRate ?? 0"
            :status="(row.project.progressRate ?? 0) >= 100 ? 'success' : undefined"
            :stroke-width="16"
            text-inside
          />
        </div>
      </template>
    </ElTableColumn>

    <ElTableColumn label="计划周期" min-width="210" align="center">
      <template #default="{ row }">
        <div v-if="row.rowType === 'project'">
          <span class="pg-muted">{{ formatPlannedRange(row.project) }}</span>
          <div v-if="overdueDays(row.project) > 0" class="pg-overdue">已逾期 {{ overdueDays(row.project) }} 天</div>
        </div>
      </template>
    </ElTableColumn>

    <ElTableColumn label="状态" width="90" align="center">
      <template #default="{ row }">
        <ElTooltip
          v-if="row.rowType === 'project'"
          :content="row.project.lastStatusReason || ''"
          :disabled="!row.project.lastStatusReason"
          placement="top"
        >
          <ElTag :type="getProjectStatusTagType(row.project.statusCode)">
            {{ getProjectStatusLabel(row.project.statusCode) }}
          </ElTag>
        </ElTooltip>
      </template>
    </ElTableColumn>

    <ElTableColumn label="最近更新" width="110" align="center">
      <template #default="{ row }">
        <span v-if="row.rowType === 'project'" class="pg-muted">{{ formatDate(row.project.updateTime) }}</span>
      </template>
    </ElTableColumn>

    <template #empty>
      <ElEmpty description="当前筛选条件下暂无项目" />
    </template>
  </ElTable>
</template>

<style lang="scss" scoped>
.project-grouped-table {
  --el-table-row-hover-bg-color: rgb(240 249 255 / 55%);

  // 全局 .el-table .cell { padding: 0 } 把内边距清零了，这里恢复本表的呼吸感
  :deep(td.el-table__cell > .cell),
  :deep(th.el-table__cell > .cell) {
    padding: 0 12px;
  }

  :deep(td.el-table__cell) {
    transition: background-color 0.2s ease;
  }

  // 方向节标题行：无底色、上方留白，像章节标题而不是数据行
  :deep(.pg-dir-row > td.el-table__cell) {
    padding: 16px 0 6px;
    background: transparent;
    border-bottom: none;
    cursor: pointer;
  }

  // 产品组行：柔和色带（与左栏卡片同一 slate 语系）
  :deep(.pg-prod-row > td.el-table__cell) {
    padding: 9px 0;
    background: linear-gradient(90deg, rgb(248 250 252 / 98%), rgb(255 255 255 / 90%));
    border-top: 1px solid var(--el-border-color-extra-light);
    cursor: pointer;
  }

  :deep(.pg-prod-row:hover > td.el-table__cell) {
    background: linear-gradient(90deg, rgb(241 245 249 / 98%), rgb(248 250 252 / 92%));
  }

  :deep(.pg-hint-row > td.el-table__cell) {
    padding: 5px 0;
    border-bottom: none;
  }

  :deep(.pg-more-row > td.el-table__cell) {
    padding: 6px 0;
    background: transparent;
    cursor: pointer;
  }

  :deep(.pg-proj-row > td.el-table__cell) {
    border-bottom: 1px solid var(--el-border-color-extra-light);
  }
}

.pg-toggle {
  flex: none;
  color: var(--el-text-color-placeholder);
  font-size: 13px;
  transition: transform 0.2s ease;
}

.is-collapsed .pg-toggle {
  transform: rotate(-90deg);
}

// === 方向节标题 ===
.pg-dir-line {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.pg-dir-chip {
  flex: none;
  width: 4px;
  height: 14px;
  border-radius: 2px;
  background: rgb(14 165 233 / 85%);
}

.pg-dir-name {
  color: rgb(51 65 85 / 96%);
  font-size: 13px;
  font-weight: 700;
}

.pg-dir-meta {
  color: var(--el-text-color-placeholder);
  font-size: 12px;
}

// === 产品组行 ===
.pg-prod-line {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.pg-prod-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: rgb(240 249 255 / 96%);
  color: rgb(2 132 199 / 92%);
  font-size: 14px;
}

.pg-prod-icon--orphan {
  background: rgb(245 243 255 / 96%);
  color: rgb(124 58 237 / 92%);
}

.pg-prod-name {
  color: rgb(15 23 42 / 94%);
  font-size: 14px;
  font-weight: 700;
}

.pg-prod-name--orphan {
  color: var(--el-text-color-secondary);
}

.pg-prod-code {
  color: var(--el-text-color-secondary);
  font-size: 12px;
}

.pg-badge {
  flex: none;
  padding: 1px 9px;
  border: 1px solid rgb(226 232 240 / 90%);
  border-radius: 999px;
  background: rgb(255 255 255 / 88%);
  color: rgb(71 85 105 / 92%);
  font-size: 11.5px;
  font-weight: 600;
}

.pg-add-link {
  margin-left: auto;
  padding: 0;
}

// === 提示行 / 收纳行：统一缩进到产品名起始位 ===
.pg-hint,
.pg-more-line {
  margin-left: 55px;
}

// 项目行：文件夹图标 + 文本块；图标占缩进位（margin 25 + icon22 + gap8 = 55），项目名仍与产品名对齐
.pg-proj-name {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: 25px;
  min-width: 0;
}

// 项目文件夹图标：浅蓝底 + 同色系深字，与产品库存箱图标同一视觉语言（父=箱子 / 子=文件夹）
.pg-proj-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: rgb(240 249 255 / 96%);
  color: rgb(2 132 199 / 92%);
  font-size: 14px;
}

.pg-proj-main {
  display: flex;
  flex-direction: column;
  // 贴左：避免 ElButton(link) 被 stretch 拉满后文字居中
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}

.pg-hint {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 12px;
  border: 1px dashed var(--el-border-color);
  border-radius: 8px;
  background: var(--el-fill-color-lighter);
  color: var(--el-text-color-secondary);
  font-size: 13px;
}

.pg-proj-link {
  padding: 0;
}

.pg-sub-code {
  margin-top: 2px;
  color: var(--el-text-color-placeholder);
  font-size: 12px;
  letter-spacing: 0.03em;
}

.pg-progress {
  padding: 0 8px;
}

.pg-muted {
  color: var(--el-text-color-secondary);
}

.pg-overdue {
  margin-top: 2px;
  color: var(--el-color-danger);
  font-size: 12px;
  font-weight: 600;
}

// === 收纳行 ===
.pg-more-line {
  display: flex;
  align-items: center;
  gap: 8px;
}

.pg-more-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--el-color-primary);
  font-size: 12.5px;
  font-weight: 600;
}

.pg-more-icon {
  font-size: 12px;
}

.pg-more-icon--up {
  transform: rotate(180deg);
}

.pg-more-hint {
  color: var(--el-text-color-placeholder);
  font-size: 12px;
}
</style>
