<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, toRef } from 'vue';
import { Calendar, Flag, User } from '@element-plus/icons-vue';
import { formatDateRange, getProgressText, getTaskStatusName, getTaskStatusTagType } from '../shared';
import { useTaskActions } from '../composables/use-task-actions';
import { type BoardBaseParams, useTaskBoardColumns } from '../composables/use-task-board-columns';

defineOptions({ name: 'ProjectExecutionTaskBoardView' });

interface Props {
  projectId: string;
  executionId: string;
  statusBoard: Api.Project.StatusBoard | null;
  baseParams: BoardBaseParams;
}

interface Emits {
  (e: 'detail', row: Api.Project.ProjectTask): void;
  (e: 'edit', row: Api.Project.ProjectTask): void;
  (e: 'report', row: Api.Project.ProjectTask): void;
  (e: 'delete', row: Api.Project.ProjectTask): void;
  (
    e: 'status-action',
    row: Api.Project.ProjectTask,
    action: Api.Project.LifecycleAction<Api.Project.ProjectTaskActionCode> | null
  ): void;
}

const props = defineProps<Props>();
const emit = defineEmits<Emits>();

const { columns, loadMore } = useTaskBoardColumns({
  projectId: toRef(props, 'projectId'),
  executionId: toRef(props, 'executionId'),
  // statusBoard 在此仅作"刷新事件源"：workspace 任何变更后都会重新拉它，ref 引用变即触发本看板重拉首屏
  refreshSignal: toRef(props, 'statusBoard'),
  baseParams: () => props.baseParams
});

// 看板卡片操作按钮（与表格操作列同语义）。
// 兼容 useTaskActions 的"叶子判定"需求：拍平当前已加载的全部任务做集合。
const allLoadedTasks = computed(() => columns.value.flatMap(item => item.tasks));

const { createActions } = useTaskActions(allLoadedTasks, {
  edit: row => emit('edit', row),
  report: row => emit('report', row),
  remove: row => emit('delete', row),
  statusAction: (row, action) => emit('status-action', row, action)
});

// 每个列对应一个底部 sentinel，IntersectionObserver 观测到入视即触发 loadMore。
// IO 的 root 用 null（视口）即可：列的 overflow 裁剪不影响 IO 的 boundingClientRect 判定，
// sentinel 实际是否能"被滚到"由 column 滚动条决定。
const sentinelRegistry = new Map<HTMLElement, string>();
let observer: IntersectionObserver | null = null;

function handleSentinelRef(el: Element | null, statusCode: string) {
  if (el instanceof HTMLElement) {
    sentinelRegistry.set(el, statusCode);
    observer?.observe(el);
    return;
  }
  // el === null：sentinel 卸载（hasMore 变 false）—— 按 statusCode 反查清理，避免 observer 持有失效 DOM
  for (const [existing, code] of sentinelRegistry) {
    if (code === statusCode) {
      observer?.unobserve(existing);
      sentinelRegistry.delete(existing);
      break;
    }
  }
}

onMounted(() => {
  observer = new IntersectionObserver(
    entries => {
      entries.forEach(entry => {
        if (!entry.isIntersecting) return;
        const statusCode = sentinelRegistry.get(entry.target as HTMLElement);
        if (statusCode) {
          loadMore(statusCode);
        }
      });
    },
    { rootMargin: '200px 0px' }
  );
  sentinelRegistry.forEach((_, el) => observer!.observe(el));
});

onBeforeUnmount(() => {
  observer?.disconnect();
  observer = null;
  sentinelRegistry.clear();
});
</script>

<template>
  <ElCard class="task-board-card" body-class="task-board-card__body">
    <div class="task-board">
      <section v-for="column in columns" :key="column.statusCode" class="task-board-column">
        <header class="task-board-column__header">
          <strong>{{ column.title }}</strong>
          <ElTag effect="plain" size="small" round>{{ column.total }}</ElTag>
        </header>

        <ElScrollbar class="task-board-column__scrollbar">
          <ElEmpty
            v-if="column.tasks.length === 0 && !column.loading"
            :description="`暂无${column.title}任务`"
            :image-size="64"
          />
          <article
            v-for="task in column.tasks"
            :key="task.id"
            class="task-board-card-item"
            @click="emit('detail', task)"
          >
            <div class="task-board-card-item__top">
              <strong class="task-board-card-item__title">{{ task.taskTitle || '未命名任务' }}</strong>
              <ElTag :type="getTaskStatusTagType(task.statusCode)" effect="light" size="small">
                {{ getTaskStatusName(task) }}
              </ElTag>
            </div>

            <div class="task-board-card-item__meta">
              <span>
                <ElIcon><User /></ElIcon>
                {{ task.ownerNickname || task.ownerId || '未设置负责人' }}
              </span>
              <span>
                <ElIcon><Flag /></ElIcon>
                计划 {{ formatDateRange(task.plannedStartDate, task.plannedEndDate) }}
              </span>
              <span>
                <ElIcon><Calendar /></ElIcon>
                实际 {{ formatDateRange(task.actualStartDate, task.actualEndDate) }}
              </span>
            </div>

            <div class="task-board-card-item__progress">
              <span>进度 {{ getProgressText(task.progressRate) }}</span>
              <ElProgress :percentage="task.progressRate" :stroke-width="6" :show-text="false" />
            </div>

            <div v-if="createActions(task).length" class="task-board-card-item__actions" @click.stop>
              <ElTooltip v-for="action in createActions(task)" :key="action.key" :content="action.tooltip">
                <ElButton link :type="action.type" class="task-action-btn" @click="action.onClick()">
                  <component :is="action.icon" class="text-15px" />
                </ElButton>
              </ElTooltip>
            </div>
          </article>

          <div
            v-if="column.hasMore"
            :ref="el => handleSentinelRef(el as Element | null, column.statusCode)"
            class="task-board-column__sentinel"
          >
            <span v-if="column.loading">加载中…</span>
            <span v-else class="task-board-column__sentinel-idle">下拉加载</span>
          </div>
          <div v-else-if="column.tasks.length > 0" class="task-board-column__footer">已加载全部</div>
        </ElScrollbar>
      </section>
    </div>
  </ElCard>
</template>

<style scoped lang="scss">
.task-board-card {
  min-height: 0;
  flex: 1;
}

:deep(.task-board-card__body) {
  display: flex;
  min-height: 0;
  height: 100%;
}

.task-board {
  display: grid;
  grid-template-columns: repeat(5, minmax(220px, 1fr));
  gap: 12px;
  width: 100%;
  min-height: 0;
  overflow-x: auto;
}

.task-board-column {
  display: flex;
  min-width: 220px;
  min-height: 0;
  flex-direction: column;
  border: 1px solid rgb(226 232 240 / 92%);
  border-radius: 8px;
  background-color: rgb(248 250 252 / 82%);
}

.task-board-column__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 42px;
  padding: 10px 12px;
  border-bottom: 1px solid rgb(226 232 240 / 92%);
  color: rgb(15 23 42 / 94%);
}

.task-board-column__scrollbar {
  min-height: 0;
  flex: 1;
  padding: 10px;
}

.task-board-card-item {
  padding: 10px;
  border: 1px solid rgb(226 232 240 / 92%);
  border-radius: 6px;
  margin-bottom: 10px;
  background-color: #fff;
  cursor: pointer;
  transition:
    border-color 0.16s ease,
    box-shadow 0.16s ease;
}

.task-board-card-item:hover {
  border-color: rgb(148 163 184 / 76%);
  box-shadow: 0 6px 18px rgb(15 23 42 / 8%);
}

.task-board-card-item__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}

.task-board-card-item__title {
  min-width: 0;
  color: rgb(15 23 42 / 94%);
  font-size: 14px;
  line-height: 1.5;
  word-break: break-word;
}

.task-board-card-item__meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
  color: rgb(100 116 139 / 94%);
  font-size: 12px;
}

.task-board-card-item__meta span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.task-board-card-item__progress {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
  color: rgb(71 85 105 / 96%);
  font-size: 12px;
}

.task-board-card-item__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 12px;
  padding-top: 8px;
  border-top: 1px dashed rgb(226 232 240 / 92%);
}

.task-board-card-item__actions :deep(.el-button + .el-button) {
  margin-left: 0;
}

:deep(.task-action-btn) {
  padding: 3px;
  min-width: auto;
  height: auto;
  line-height: 1;
}

.task-board-column__sentinel,
.task-board-column__footer {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px 0;
  color: rgb(148 163 184 / 92%);
  font-size: 12px;
}

.task-board-column__sentinel-idle {
  opacity: 0.6;
}
</style>
