<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import {
  fetchChangeProjectExecutionOwner,
  fetchChangeProjectExecutionStatus,
  fetchCreateProjectExecution,
  fetchCreateProjectExecutionAssignee,
  fetchDeleteProjectExecution,
  fetchGetProjectExecution,
  fetchGetProjectExecutionAssignees,
  fetchGetProjectExecutionPage,
  fetchGetProjectExecutionStatusBoard,
  fetchGetProjectMembers,
  fetchGetProjectTaskStatusBoardCross,
  fetchInactiveProjectExecutionAssignee,
  fetchPrecheckDeleteProjectExecution,
  fetchUpdateProjectExecution
} from '@/service/api';
import { useObjectContextStore } from '@/store/modules/object-context';
import { useAuthStore } from '@/store/modules/auth';
import { useUIPaginatedTable } from '@/hooks/common/table';
import { useCurrentProject } from '../../shared/use-current-project';
import { useTaskPermissions } from './composables/use-task-permissions';
import { useTaskViewContext } from './composables/use-task-view-context';
import ExecutionAssigneeDialog from './modules/execution-assignee-dialog.vue';
import ExecutionOperateDialog from './modules/execution-operate-dialog.vue';
import ExecutionSection from './modules/execution-section.vue';
import ObjectDeleteDialog from './modules/object-delete-dialog.vue';
import StatusActionDialog from './modules/status-action-dialog.vue';
import TaskWorkspaceComp from './modules/task-workspace.vue';

defineOptions({ name: 'ProjectExecution' });

type ExecutionPageResponse = Awaited<ReturnType<typeof fetchGetProjectExecutionPage>>;
type ExecutionAction = Api.Project.LifecycleAction<Api.Project.ProjectExecutionActionCode>;
type ExecutionStatusFilter = string | null;

function getInitExecutionSearchParams(): Api.Project.ProjectExecutionSearchParams {
  return {
    pageNo: 1,
    pageSize: 10,
    keyword: '',
    executionType: undefined,
    ownerId: undefined,
    statusCode: undefined,
    priority: undefined,
    dueRange: undefined,
    updateTime: undefined
  };
}

function transformExecutionPage(response: ExecutionPageResponse, pageNo: number, pageSize: number) {
  if (!response.error && response.data) {
    return { data: response.data.list, pageNum: pageNo, pageSize, total: response.data.total };
  }
  return { data: [], pageNum: pageNo, pageSize, total: 0 };
}

const { currentObjectId } = useCurrentProject();
const objectContextStore = useObjectContextStore();
const authStore = useAuthStore();

const { context: viewContext, switchToMine, switchToAll } = useTaskViewContext();

// 执行域独立视角:跟任务域 viewContext 完全独立,切换互不影响。
// 用 inline reactive 即可,不抽 composable(只在本页用,YAGNI)。
const executionViewContext = reactive<{ type: 'my' | 'all' }>({ type: 'all' });

function switchExecutionToMine() {
  executionViewContext.type = 'my';
}

function switchExecutionToAll() {
  executionViewContext.type = 'all';
}

const searchParams = reactive(getInitExecutionSearchParams());
// 默认"全部":右侧任务列表也对应"项目全部执行下的我参与/所有任务",不预先把范围收窄
const DEFAULT_EXECUTION_STATUS: ExecutionStatusFilter = null;
const selectedStatus = ref<ExecutionStatusFilter>(DEFAULT_EXECUTION_STATUS);

/** 范围维度:选中的具体执行;为 null 表示"未锚定具体执行",数据来源由 selectedStatus 决定 */
const selectedExecution = ref<Api.Project.ProjectExecution | null>(null);

const projectMembers = ref<Api.Project.ProjectMember[]>([]);
const projectMemberOptions = ref<Api.SystemManage.UserSimple[]>([]);
const operateVisible = ref(false);
const operateMode = ref<'create' | 'edit' | 'view'>('create');
const assigneeDialogVisible = ref(false);
const statusVisible = ref(false);
const editingExecution = ref<Api.Project.ProjectExecution | null>(null);
const editingExecutionAssignees = ref<Api.Project.ExecutionAssignee[]>([]);
const statusExecution = ref<Api.Project.ProjectExecution | null>(null);
const statusAction = ref<ExecutionAction | null>(null);
const executionAssignees = ref<Api.Project.ExecutionAssignee[]>([]);
const assigneeLoading = ref(false);
const executionStatusBoard = ref<Api.Project.StatusBoard | null>(null);

/**
 * 项目下"全部执行简明列表"。一次性拉取(pageSize=-1),用于:
 * 1. task-search 的「所属执行」下拉(executionOptionsForFilter)
 * 2. 左侧 chip 选择某状态时,前端 filter 出"该状态下的执行 ids"传给右侧任务列表
 */
const allProjectExecutions = ref<Api.Project.ProjectExecution[]>([]);

const allTasksCount = ref(0);
const myTasksCount = ref(0);

// 执行视角 chip 计数:对应当前搜索条件下"我参与的执行" / "所有执行"总数
const executionAllCount = ref(0);
const executionMyCount = ref(0);

// "我参与的"执行视角下的快捷过滤计数(逾期 / 本周到期),仅 my 视角有意义
const executionOverdueCount = ref(0);
const executionThisWeekCount = ref(0);

const projectId = computed(() => currentObjectId.value || '');
const currentUserId = computed(() => authStore.userInfo.userId || '');

const statusActionTitle = computed(() =>
  statusAction.value ? `执行状态变更：${statusAction.value.actionName}` : '执行状态变更'
);
const buttonCodeSet = computed(() => new Set(objectContextStore.buttonCodes));
const canCreateExecution = computed(() => buttonCodeSet.value.has('project:execution:create'));
/**
 * 「所有任务」视角按钮可见度:权限码 project:task:query(基础读权限)。
 * list-all 系列权限码已废弃,统一用 query 系列。常规链路恒为 true,
 * 留判断只是与权限模型对齐,不出现"按钮渲染却 403"的状态。
 */
const showAllPerspective = computed(() => buttonCodeSet.value.has('project:task:query'));

/**
 * 执行视角切换可见度:跟着 project:execution:query(基础读权限)。
 * 用户决策:执行没有独立的 list-all 权限码,有 query 就能在"我参与/所有"间切换。
 * 实际能进项目页的用户必然有 query 码,所以这个 computed 在常规链路里恒为 true,
 * 留判断只是与权限模型对齐,不出现"按钮渲染却 403"的状态。
 */
const showExecutionPerspectiveSwitch = computed(() => buttonCodeSet.value.has('project:execution:query'));

/**
 * 当前左侧锚定的"执行范围"——同时供 task-workspace 的任务列表/状态看板入参,以及视角按钮上的计数。
 *
 * 范围维度拆成三个独立字段下传,由后端组合:
 * - scopedExecutionIds:仅在"锚定到某具体执行"时为 [id];否则 undefined。
 * - scopedExecutionInvolveUserId:执行视角 = my 且未锚定具体执行时 = 当前用户,直接表达"我参与的执行"范围;
 *   all 视角 / 锚定具体执行时 undefined。改用后端 executionInvolveUserId 后,无需再"先拉我参与的执行 ids 再 map 回传",
 *   用户未参与任何执行时后端直接返空(不会再退化成查全部,前端也不必空集合短路)。
 * - scopedExecutionStatusCodesForTasks:仅在左侧选了某状态 chip 时下发 [statusCode];其它场景 undefined。
 *
 * 历史上"按状态过滤"用前端 filter 出 ids 中转的方式实现,会把"对执行无成员权限但对其下任务有 owner/协办权限"
 * 的任务漏掉(详见 docs/debt/跨执行任务接口-按执行状态过滤-契约调整.html)。现改为把状态码直接下传给任务接口,
 * 由后端在任务可见性之上叠加"任务所属执行的状态 ∈ 白名单"过滤。
 */
const scopedExecutionIds = computed<string[] | undefined>(() => {
  if (selectedExecution.value) return [selectedExecution.value.id];
  return undefined;
});

const scopedExecutionInvolveUserId = computed<string | undefined>(() => {
  if (selectedExecution.value) return undefined;
  if (executionViewContext.type === 'my') return currentUserId.value || undefined;
  return undefined;
});

const scopedExecutionStatusCodesForTasks = computed<Api.Project.ProjectExecutionStatusCode[] | undefined>(() => {
  if (selectedExecution.value) return undefined;
  if (!selectedStatus.value) return undefined;
  return [selectedStatus.value as Api.Project.ProjectExecutionStatusCode];
});
const deleteDialogVisible = ref(false);
const deleteExecutionDependentSummary = ref<string | null>(null);
const { canCreateTopLevelTask } = useTaskPermissions();

const canCreateTask = computed(() => {
  if (selectedExecution.value) {
    return canCreateTopLevelTask(selectedExecution.value);
  }

  return buttonCodeSet.value.has('project:task:create');
});

const workspaceTitle = computed(() => {
  if (selectedExecution.value) return selectedExecution.value.executionName || '执行任务';
  return viewContext.type === 'my' ? '我参与的' : '所有任务';
});

function createRequestParams(): Api.Project.ProjectExecutionSearchParams {
  return {
    ...searchParams,
    keyword: searchParams.keyword?.trim() || undefined,
    statusCode: selectedStatus.value || undefined,
    // 执行视角:my → 带当前用户 ID 走"我参与";all → 不传(项目全部)
    involveUserId: executionViewContext.type === 'my' ? currentUserId.value || undefined : undefined
  };
}

function createStatusBoardParams(): Api.Project.ProjectExecutionStatusBoardParams {
  return {
    keyword: searchParams.keyword?.trim() || undefined,
    executionType: searchParams.executionType,
    ownerId: searchParams.ownerId,
    // dueRange 与列表同口径下传,状态 chip 数字随"逾期/本周到期"快捷过滤联动
    dueRange: searchParams.dueRange,
    updateTime: searchParams.updateTime,
    // 执行视角与列表保持同一身份维度,确保状态 chip 数字与列表对得上
    involveUserId: executionViewContext.type === 'my' ? currentUserId.value || undefined : undefined
  };
}

const { data, loading, getDataByPage, mobilePagination } = useUIPaginatedTable<
  ExecutionPageResponse,
  Api.Project.ProjectExecution
>({
  paginationProps: {
    currentPage: searchParams.pageNo,
    pageSize: searchParams.pageSize
  },
  api: () => {
    if (!projectId.value) {
      return Promise.resolve({
        data: { total: 0, list: [] },
        error: null
      } as unknown as ExecutionPageResponse);
    }
    return fetchGetProjectExecutionPage(projectId.value, createRequestParams());
  },
  transform: response => transformExecutionPage(response, searchParams.pageNo ?? 1, searchParams.pageSize ?? 10),
  onPaginationParamsChange: params => {
    searchParams.pageNo = params.currentPage ?? 1;
    searchParams.pageSize = params.pageSize ?? 10;
  },
  columns: () => [{ prop: 'executionName', label: '执行名称', minWidth: 160 }],
  immediate: false
});

const executionOptionsForFilter = computed(() =>
  allProjectExecutions.value.map(item => ({ id: item.id, name: item.executionName }))
);

async function loadProjectMemberOptions() {
  if (!projectId.value) {
    projectMemberOptions.value = [];
    return;
  }

  const { error, data: members } = await fetchGetProjectMembers(projectId.value);

  if (error || !members) {
    projectMembers.value = [];
    projectMemberOptions.value = [];
    return;
  }

  projectMembers.value = members.filter(item => item.status === 0);
  projectMemberOptions.value = projectMembers.value.map(item => ({
    id: item.userId,
    nickname: item.userNickname || item.userId,
    username: null,
    deptName: item.roleName || item.roleCode || null
  }));
}

async function reloadExecutionData(page = searchParams.pageNo ?? 1) {
  await getDataByPage(page);
}

async function loadExecutionStatusBoard() {
  if (!projectId.value) {
    executionStatusBoard.value = null;
    return;
  }
  const { error, data: board } = await fetchGetProjectExecutionStatusBoard(projectId.value, createStatusBoardParams());
  executionStatusBoard.value = error || !board ? null : board;
}

/**
 * 拉取项目下执行简明列表(pageSize=-1,后端"不分页全量"约定见 CLAUDE.md §9)。
 * 按当前执行视角带 involveUserId:
 * - my 视角 → 只拉"我参与的执行",作为任务 scope 和"所属执行"下拉的来源
 * - all 视角 → 项目下全部执行
 */
async function loadAllProjectExecutions() {
  if (!projectId.value) {
    allProjectExecutions.value = [];
    return;
  }
  const { error, data: pageData } = await fetchGetProjectExecutionPage(projectId.value, {
    pageNo: 1,
    pageSize: -1,
    involveUserId: executionViewContext.type === 'my' ? currentUserId.value || undefined : undefined
  });
  allProjectExecutions.value = error || !pageData ? [] : pageData.list;
}

async function loadCrossExecutionCounts() {
  if (!projectId.value || !currentUserId.value) {
    myTasksCount.value = 0;
    allTasksCount.value = 0;
    return;
  }

  // 视角按钮计数 = 当前左侧 chip / 执行 锚定范围内的"我参与/所有"总数。
  // my 执行视角下范围用 executionInvolveUserId 表达,我未参与任何执行时后端直接返空 → 计数自然为 0,无需空集合短路。
  const scopedIds = scopedExecutionIds.value;
  const scopedInvolveUserId = scopedExecutionInvolveUserId.value;
  const scopedStatusCodes = scopedExecutionStatusCodesForTasks.value;

  const scopeParams: Pick<
    Api.Project.ProjectTaskCrossStatusBoardParams,
    'executionIds' | 'executionInvolveUserId' | 'executionStatusCodes'
  > = {};
  if (scopedIds !== undefined) scopeParams.executionIds = scopedIds;
  if (scopedInvolveUserId !== undefined) scopeParams.executionInvolveUserId = scopedInvolveUserId;
  if (scopedStatusCodes !== undefined) scopeParams.executionStatusCodes = scopedStatusCodes;

  const [allRes, myRes] = await Promise.all([
    fetchGetProjectTaskStatusBoardCross(projectId.value, scopeParams),
    fetchGetProjectTaskStatusBoardCross(projectId.value, { ...scopeParams, involveUserId: currentUserId.value })
  ]);

  allTasksCount.value = allRes.error || !allRes.data ? 0 : allRes.data.total;
  myTasksCount.value = myRes.error || !myRes.data ? 0 : myRes.data.total;
}

/**
 * 执行视角 chip 计数:对当前搜索条件(关键字 / 类型 / 负责人 / 更新时间)叠加,并发拉两次:
 * - 不带 involveUserId → "所有"格子的总数
 * - 带 involveUserId = 当前用户 → "我参与的"格子的总数
 *
 * 跟 loadCrossExecutionCounts 对称,只是接口换成执行的 status-board。
 */
async function loadExecutionPerspectiveCounts() {
  if (!projectId.value || !currentUserId.value) {
    executionAllCount.value = 0;
    executionMyCount.value = 0;
    return;
  }

  const baseParams: Api.Project.ProjectExecutionStatusBoardParams = {
    keyword: searchParams.keyword?.trim() || undefined,
    executionType: searchParams.executionType,
    ownerId: searchParams.ownerId,
    updateTime: searchParams.updateTime
  };

  const [allRes, myRes] = await Promise.all([
    fetchGetProjectExecutionStatusBoard(projectId.value, baseParams),
    fetchGetProjectExecutionStatusBoard(projectId.value, { ...baseParams, involveUserId: currentUserId.value })
  ]);

  executionAllCount.value = allRes.error || !allRes.data ? 0 : allRes.data.total;
  executionMyCount.value = myRes.error || !myRes.data ? 0 : myRes.data.total;
}

/**
 * "逾期 / 本周到期"快捷过滤计数:仅"我参与的"执行视角有意义,非 my 视角直接清 0。
 * 复用 page 接口 pageSize=1 读 total(口径同任务侧 loadQuickFilterCounts),
 * 用 involveUserId=me + 基础搜索条件(关键字/类型/更新时间),不叠加 statusCode 或已选中的 dueRange——
 * 计数表达的是该范围内的总量指示,不随当前选中的快捷过滤变化。
 */
async function loadExecutionDueCounts() {
  if (!projectId.value || !currentUserId.value || executionViewContext.type !== 'my') {
    executionOverdueCount.value = 0;
    executionThisWeekCount.value = 0;
    return;
  }

  const baseParams: Api.Project.ProjectExecutionSearchParams = {
    pageNo: 1,
    pageSize: 1,
    keyword: searchParams.keyword?.trim() || undefined,
    executionType: searchParams.executionType,
    updateTime: searchParams.updateTime,
    involveUserId: currentUserId.value
  };

  const [overdueRes, thisWeekRes] = await Promise.all([
    fetchGetProjectExecutionPage(projectId.value, { ...baseParams, dueRange: 'overdue' }),
    fetchGetProjectExecutionPage(projectId.value, { ...baseParams, dueRange: 'thisWeek' })
  ]);

  executionOverdueCount.value = overdueRes.error || !overdueRes.data ? 0 : overdueRes.data.total;
  executionThisWeekCount.value = thisWeekRes.error || !thisWeekRes.data ? 0 : thisWeekRes.data.total;
}

/** 执行视角 chip 计数 + 快捷过滤计数统一刷新入口(各操作后调它,避免两处计数散落) */
async function refreshExecutionCounts() {
  await Promise.all([loadExecutionPerspectiveCounts(), loadExecutionDueCounts()]);
}

/**
 * 刷新"我参与的执行"集合(供「所属执行」下拉 / 左侧执行列表来源) + 跨执行任务计数。
 *
 * 任务 scope 已改用 executionInvolveUserId 直接表达"我参与的执行",不再依赖 allProjectExecutions,
 * 故两者无顺序依赖,可并行。
 */
async function refreshTaskScopeAndCounts() {
  await Promise.all([loadAllProjectExecutions(), loadCrossExecutionCounts()]);
}

async function refreshPageData() {
  await Promise.all([
    loadProjectMemberOptions(),
    reloadExecutionData(),
    loadExecutionStatusBoard(),
    refreshExecutionCounts(),
    refreshTaskScopeAndCounts()
  ]);
}

async function handleExecutionStatusFilter(status: ExecutionStatusFilter) {
  selectedStatus.value = status;
  // 状态 chip 是"按状态范围浏览":意味着不再锚定具体执行,清掉 selectedExecution
  if (selectedExecution.value) selectedExecution.value = null;
  // 左侧范围切换默认回「所有任务」视角,符合"在该范围内看全部"的主用法
  switchToAll();
  await reloadExecutionData(1);
}

async function handleExecutionDueRangeChange(range: Api.Project.ProjectExecutionDueRange | null) {
  // 再点已选中的 chip → 取消(回到不限截止时间)
  searchParams.dueRange = range ?? undefined;
  // dueRange 影响列表与状态看板(状态 chip 数字随之联动);快捷过滤计数是范围总量,不随选中变,无需重算
  await Promise.all([reloadExecutionData(1), loadExecutionStatusBoard()]);
}

async function handleExecutionSearch() {
  // 视角 chip 数字依赖搜索条件(keyword/executionType/ownerId/updateTime),搜索后需同步刷新
  await Promise.all([reloadExecutionData(1), refreshExecutionCounts()]);
}

async function handleExecutionResetSearch() {
  Object.assign(searchParams, getInitExecutionSearchParams());
  selectedStatus.value = DEFAULT_EXECUTION_STATUS;
  if (selectedExecution.value) selectedExecution.value = null;
  await Promise.all([reloadExecutionData(1), loadExecutionStatusBoard(), refreshExecutionCounts()]);
}

async function getExecutionDetail(row: Api.Project.ProjectExecution) {
  if (!projectId.value) return row;
  const result = await fetchGetProjectExecution(projectId.value, row.id);
  return result.error || !result.data ? row : result.data;
}

function handleSelectExecution(row: Api.Project.ProjectExecution) {
  // 锚定具体执行,默认回「所有任务」视角
  selectedExecution.value = row;
  switchToAll();
}

function handleSelectPerspective(type: 'my' | 'all') {
  // 只切身份维度,保留范围(具体执行 / 状态 chip)
  if (type === 'my') switchToMine();
  else switchToAll();
}

async function handleSelectExecutionPerspective(type: 'my' | 'all') {
  if (executionViewContext.type === type) return;

  if (type === 'my') switchExecutionToMine();
  else switchExecutionToAll();

  // 切视角时:清掉锚定执行(避免"我参与"下锚定执行不在新范围内的空高亮);
  // 状态 chip 回"全部"(回到该视角下的项目级总览);
  // 然后重拉所有跟执行视角相关的数据。
  // 同步清空"我参与的执行"旧集合(供「所属执行」下拉),避免切视角瞬间下拉仍显示上一视角的执行,
  // 随后 loadAllProjectExecutions 按新视角重填。任务 scope 已改用 executionInvolveUserId(同步随
  // executionViewContext.type 变),不再有"读到旧执行 ids 算出陈旧 scope"的竞态。
  allProjectExecutions.value = [];
  selectedExecution.value = null;
  selectedStatus.value = DEFAULT_EXECUTION_STATUS;
  // 快捷过滤(逾期/本周到期)仅"我参与的"视角可见,切视角时一并清掉,避免残留过滤被带到下一视角
  searchParams.dueRange = undefined;
  searchParams.pageNo = 1;

  // 视角切换 → "我参与的执行"集合本身变了 → 任务 scope/cross counts 都要重算
  await Promise.all([
    reloadExecutionData(1),
    loadExecutionStatusBoard(),
    refreshExecutionCounts(),
    refreshTaskScopeAndCounts()
  ]);
}

function openCreateExecution() {
  editingExecution.value = null;
  editingExecutionAssignees.value = [];
  operateMode.value = 'create';
  operateVisible.value = true;
}

async function openEditExecution(row: Api.Project.ProjectExecution) {
  const detail = await getExecutionDetail(row);
  if (!detail.allowEdit) {
    window.$message?.warning('当前执行状态不允许编辑');
    return;
  }
  editingExecution.value = detail;
  const assigneeResult = await fetchGetProjectExecutionAssignees(projectId.value, detail.id);
  editingExecutionAssignees.value = assigneeResult.error || !assigneeResult.data ? [] : assigneeResult.data;
  operateMode.value = 'edit';
  operateVisible.value = true;
}

async function openViewExecution(row: Api.Project.ProjectExecution) {
  const detail = await getExecutionDetail(row);
  editingExecution.value = detail;
  const assigneeResult = await fetchGetProjectExecutionAssignees(projectId.value, detail.id);
  editingExecutionAssignees.value = assigneeResult.error || !assigneeResult.data ? [] : assigneeResult.data;
  operateMode.value = 'view';
  operateVisible.value = true;
}

async function openMemberDialog(row: Api.Project.ProjectExecution) {
  selectedExecution.value = await getExecutionDetail(row);
  assigneeDialogVisible.value = true;
  await loadExecutionAssignees(selectedExecution.value.id);
}

async function openExecutionStatus(row: Api.Project.ProjectExecution, action: ExecutionAction | null) {
  const detail = await getExecutionDetail(row);
  const targetAction = action || detail.availableActions[0] || null;
  if (!targetAction) {
    window.$message?.warning('当前执行暂无可用状态操作');
    return;
  }
  statusExecution.value = detail;
  statusAction.value = targetAction;

  // 完成动作:二次确认后直接提交(完成无需填原因,但需让用户确认这一状态变更)
  if (targetAction.actionCode === 'complete') {
    try {
      await window.$messageBox?.confirm(`确定要完成执行“${detail.executionName}”吗？`, '完成确认', {
        confirmButtonText: '完成执行',
        cancelButtonText: '取消',
        type: 'warning'
      });
    } catch {
      return;
    }
    await handleExecutionStatusSubmit(null);
    return;
  }

  // 其他非必填原因的动作(开始/暂停/恢复)直接提交,不弹原因弹层
  if (!targetAction.needReason) {
    await handleExecutionStatusSubmit(null);
    return;
  }

  statusVisible.value = true;
}

async function loadExecutionAssignees(executionId: string) {
  if (!projectId.value) {
    executionAssignees.value = [];
    return;
  }
  assigneeLoading.value = true;
  try {
    const { error, data: assignees } = await fetchGetProjectExecutionAssignees(projectId.value, executionId);
    executionAssignees.value = error || !assignees ? [] : assignees;
  } finally {
    assigneeLoading.value = false;
  }
}

async function handleExecutionSubmit(payload: Api.Project.SaveProjectExecutionParams) {
  if (!projectId.value) return;
  const result = editingExecution.value
    ? await fetchUpdateProjectExecution(projectId.value, editingExecution.value.id, {
        executionName: payload.executionName,
        executionType: payload.executionType,
        projectRequirementId: payload.projectRequirementId,
        plannedStartDate: payload.plannedStartDate,
        plannedEndDate: payload.plannedEndDate,
        priority: payload.priority,
        executionDesc: payload.executionDesc
      })
    : await fetchCreateProjectExecution(projectId.value, payload);

  if (!result.error) {
    operateVisible.value = false;
    // 执行集合变化 → 视角 chip 数字 + 任务 scope/cross counts 都要刷
    await Promise.all([
      reloadExecutionData(editingExecution.value ? (searchParams.pageNo ?? 1) : 1),
      loadExecutionStatusBoard(),
      refreshExecutionCounts(),
      refreshTaskScopeAndCounts()
    ]);
  }
}

async function handleChangeOwner(payload: Api.Project.ChangeExecutionOwnerParams) {
  if (!projectId.value || !selectedExecution.value) return;
  const result = await fetchChangeProjectExecutionOwner(projectId.value, selectedExecution.value.id, payload);
  if (!result.error) {
    selectedExecution.value = await getExecutionDetail(selectedExecution.value);
    // 改 owner 会影响"我参与的"身份判定,视角 chip + 任务 scope/cross counts 都要刷
    await Promise.all([
      reloadExecutionData(searchParams.pageNo ?? 1),
      loadExecutionStatusBoard(),
      refreshExecutionCounts(),
      refreshTaskScopeAndCounts()
    ]);
  }
}

async function handleExecutionStatusSubmit(reason: string | null) {
  if (!projectId.value || !statusExecution.value || !statusAction.value) return;
  const result = await fetchChangeProjectExecutionStatus(projectId.value, statusExecution.value.id, {
    actionCode: statusAction.value.actionCode,
    reason
  });
  if (!result.error) {
    statusVisible.value = false;
    await Promise.all([reloadExecutionData(searchParams.pageNo ?? 1), loadExecutionStatusBoard()]);
  }
}

async function handleAddExecutionAssignee(payload: Api.Project.CreateExecutionAssigneeParams) {
  if (!projectId.value || !selectedExecution.value) return;
  const result = await fetchCreateProjectExecutionAssignee(projectId.value, selectedExecution.value.id, payload);
  if (!result.error) {
    await loadExecutionAssignees(selectedExecution.value.id);
  }
}

async function handleInactiveExecutionAssignee(
  assignee: Api.Project.ExecutionAssignee,
  payload: Api.Project.InactiveExecutionAssigneeParams
) {
  if (!projectId.value || !selectedExecution.value) return;
  const result = await fetchInactiveProjectExecutionAssignee(projectId.value, selectedExecution.value.id, {
    assigneeId: assignee.id,
    data: payload
  });
  if (!result.error) {
    await loadExecutionAssignees(selectedExecution.value.id);
  }
}

async function handleDeleteExecution(row: Api.Project.ProjectExecution) {
  if (!projectId.value) return;
  const precheck = await fetchPrecheckDeleteProjectExecution(projectId.value, row.id);
  const canDirectDelete = !precheck.error && precheck.data && !precheck.data.hasDependentData;

  if (!canDirectDelete) {
    selectedExecution.value = row;
    deleteExecutionDependentSummary.value =
      precheck.data && precheck.data.taskCount > 0 ? `下含 ${precheck.data.taskCount} 个任务` : null;
    deleteDialogVisible.value = true;
    return;
  }

  try {
    await window.$messageBox?.confirm(
      `确定要删除执行“${row.executionName}”吗？删除后将不可见，如需恢复请联系管理员。`,
      '删除确认',
      { confirmButtonText: '确认删除', cancelButtonText: '取消', type: 'warning' }
    );
  } catch {
    return;
  }

  const { error } = await fetchDeleteProjectExecution(projectId.value, row.id, {
    executionName: row.executionName,
    confirmText: 'DELETE',
    reason: '无下挂数据，用户已二次确认'
  });
  if (error) {
    await Promise.all([reloadExecutionData(1), loadExecutionStatusBoard()]);
    return;
  }
  window.$message?.success('删除成功');
  selectedExecution.value = null;
  // 删执行 → 执行集合 -1,视角 chip + 任务 scope/cross counts 都要刷
  await Promise.all([
    reloadExecutionData(1),
    loadExecutionStatusBoard(),
    refreshExecutionCounts(),
    refreshTaskScopeAndCounts()
  ]);
}

async function confirmDeleteExecution(payload: { name: string; confirmText: string; reason: string }) {
  if (!projectId.value || !selectedExecution.value) return;
  const { error } = await fetchDeleteProjectExecution(projectId.value, selectedExecution.value.id, {
    executionName: payload.name,
    confirmText: payload.confirmText,
    reason: payload.reason
  });
  // 成功=正常删除；失败=多为打开弹层后对象被并发改状态/删除，错误文案由全局 onError 弹 Toast。
  // 两种情况都关弹层 + 刷新：失败也要让用户离开已失效的弹层、看到最新数据。
  deleteDialogVisible.value = false;
  selectedExecution.value = null;
  if (!error) window.$message?.success('删除成功');
  // 删执行 → 执行集合 -1,视角 chip + 任务 scope/cross counts 都要刷
  await Promise.all([
    reloadExecutionData(1),
    loadExecutionStatusBoard(),
    refreshExecutionCounts(),
    refreshTaskScopeAndCounts()
  ]);
}

async function handleExecutionChangedByTask() {
  if (!selectedExecution.value) {
    await Promise.all([
      reloadExecutionData(searchParams.pageNo ?? 1),
      loadExecutionStatusBoard(),
      refreshExecutionCounts(),
      refreshTaskScopeAndCounts()
    ]);
    return;
  }

  const latestExecution = await getExecutionDetail(selectedExecution.value);
  selectedExecution.value = latestExecution;

  if (selectedStatus.value && latestExecution.statusCode !== selectedStatus.value) {
    selectedStatus.value = latestExecution.statusCode;
    await Promise.all([
      reloadExecutionData(1),
      loadExecutionStatusBoard(),
      refreshExecutionCounts(),
      refreshTaskScopeAndCounts()
    ]);
    return;
  }
  await Promise.all([
    reloadExecutionData(searchParams.pageNo ?? 1),
    loadExecutionStatusBoard(),
    refreshExecutionCounts(),
    refreshTaskScopeAndCounts()
  ]);
}

// 左侧 chip / 执行 选择变化 → 视角按钮上的「我参与的 / 所有任务」计数跟着刷,保持与 task-workspace 任务列表口径一致
watch([scopedExecutionIds, scopedExecutionInvolveUserId, scopedExecutionStatusCodesForTasks], () => {
  loadCrossExecutionCounts();
});

watch(
  () => projectId.value,
  async () => {
    selectedExecution.value = null;
    Object.assign(searchParams, getInitExecutionSearchParams());
    selectedStatus.value = DEFAULT_EXECUTION_STATUS;
    await refreshPageData();
  },
  { immediate: true }
);
</script>

<template>
  <div v-if="projectId" class="project-execution-page">
    <div class="project-execution-page__content">
      <aside class="project-execution-page__aside">
        <ExecutionSection
          v-model:search-model="searchParams"
          class="project-execution-page__execution-section"
          :data="data"
          :loading="loading"
          :pagination="mobilePagination"
          :active-execution-id="selectedExecution?.id ?? null"
          :status-board="executionStatusBoard"
          :selected-status="selectedStatus"
          :can-create="canCreateExecution"
          :owner-options="projectMemberOptions"
          :view-context-type="executionViewContext.type"
          :my-count="executionMyCount"
          :all-count="executionAllCount"
          :overdue-count="executionOverdueCount"
          :this-week-count="executionThisWeekCount"
          :show-perspective-switch="showExecutionPerspectiveSwitch"
          @select="handleSelectExecution"
          @status-change="handleExecutionStatusFilter"
          @search="handleExecutionSearch"
          @reset="handleExecutionResetSearch"
          @create="openCreateExecution"
          @edit="openEditExecution"
          @view="openViewExecution"
          @members="openMemberDialog"
          @status-action="openExecutionStatus"
          @delete="handleDeleteExecution"
          @select-perspective="handleSelectExecutionPerspective"
          @due-range-change="handleExecutionDueRangeChange"
        />
      </aside>

      <TaskWorkspaceComp
        class="project-execution-page__main"
        :project-id="projectId"
        :view-context="viewContext"
        :execution="selectedExecution"
        :execution-options="executionOptionsForFilter"
        :scoped-execution-ids="scopedExecutionIds"
        :scoped-execution-involve-user-id="scopedExecutionInvolveUserId"
        :scoped-execution-status-codes="scopedExecutionStatusCodesForTasks"
        :can-create="canCreateTask"
        :title="workspaceTitle"
        :my-count="myTasksCount"
        :all-count="allTasksCount"
        :show-all="showAllPerspective"
        @execution-changed="handleExecutionChangedByTask"
        @select-perspective="handleSelectPerspective"
      />
    </div>

    <ExecutionOperateDialog
      v-model:visible="operateVisible"
      :mode="operateMode"
      :row-data="editingExecution"
      :project-id="projectId"
      :user-options="projectMemberOptions"
      :current-assignees="editingExecutionAssignees"
      @submit="handleExecutionSubmit"
    />

    <ExecutionAssigneeDialog
      v-model:visible="assigneeDialogVisible"
      :execution="selectedExecution"
      :assignees="executionAssignees"
      :user-options="projectMemberOptions"
      :loading="assigneeLoading"
      @add="handleAddExecutionAssignee"
      @inactive="handleInactiveExecutionAssignee"
      @change-owner="handleChangeOwner"
    />

    <StatusActionDialog
      v-model:visible="statusVisible"
      :title="statusActionTitle"
      :action="statusAction"
      @submit="handleExecutionStatusSubmit"
    />

    <ObjectDeleteDialog
      v-model:visible="deleteDialogVisible"
      object-type="execution"
      :object-name="selectedExecution?.executionName ?? ''"
      :dependent-summary="deleteExecutionDependentSummary"
      :on-confirm="confirmDeleteExecution"
    />
  </div>

  <ElEmpty v-else description="未获取到当前项目上下文，请返回项目列表重新选择项目" />
</template>

<style scoped>
.project-execution-page {
  display: flex;
  flex-direction: column;
  min-height: 560px;
  gap: 12px;
  overflow: hidden;
}

.project-execution-page__content {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: 14px;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.project-execution-page__aside {
  display: flex;
  flex-direction: column;
  border: 1px solid rgb(226 232 240 / 92%);
  border-radius: 8px;
  background-color: #fff;
  overflow: hidden;
  min-width: 0;
  min-height: 0;
}

.project-execution-page__execution-section {
  flex: 1;
  min-height: 0;
}

.project-execution-page__main {
  min-width: 0;
  min-height: 0;
}

@media (width <= 1280px) {
  .project-execution-page__content {
    display: flex;
    flex-direction: column;
    overflow: auto;
  }

  .project-execution-page__aside {
    max-height: 480px;
  }
}
</style>
