<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import {
  fetchChangeProjectExecutionOwner,
  fetchChangeProjectExecutionStatus,
  fetchCreateProjectExecution,
  fetchCreateProjectExecutionAssignee,
  fetchDeleteProjectExecution,
  fetchGetProjectExecution,
  fetchGetProjectExecutionAssignees,
  fetchGetProjectExecutionPage,
  fetchGetProjectExecutionStatusBoard,
  fetchGetProjectMembers,
  fetchInactiveProjectExecutionAssignee,
  fetchUpdateProjectExecution
} from '@/service/api';
import { useObjectContextStore } from '@/store/modules/object-context';
import { useUIPaginatedTable } from '@/hooks/common/table';
import { useCurrentProject } from '../../shared/use-current-project';
import { useTaskPermissions } from './composables/use-task-permissions';
import ExecutionListPanel from './modules/execution-list-panel.vue';
import ExecutionAssigneeDialog from './modules/execution-assignee-dialog.vue';
import ExecutionOperateDialog from './modules/execution-operate-dialog.vue';
import ObjectDeleteDialog from './modules/object-delete-dialog.vue';
import StatusActionDialog from './modules/status-action-dialog.vue';
import TaskWorkspace 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,
    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 searchParams = reactive(getInitExecutionSearchParams());
const DEFAULT_EXECUTION_STATUS: ExecutionStatusFilter = 'active';
const selectedStatus = ref<ExecutionStatusFilter>(DEFAULT_EXECUTION_STATUS);
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);

const projectId = computed(() => currentObjectId.value || '');

const statusActionTitle = computed(() =>
  statusAction.value ? `执行状态变更：${statusAction.value.actionName}` : '执行状态变更'
);
const buttonCodeSet = computed(() => new Set(objectContextStore.buttonCodes));
const canCreateExecution = computed(() => buttonCodeSet.value.has('project:execution:create'));
const deleteDialogVisible = ref(false);
const { canCreateTopLevelTask } = useTaskPermissions();
// 第 2 类：项目内 RBAC 权限码 OR 执行 owner 字段身份；含 isMutable 状态前置
// 选中的执行 = null 时按钮隐藏（无对象上下文可判）
const canCreateTask = computed(() =>
  selectedExecution.value ? canCreateTopLevelTask(selectedExecution.value) : false
);

function createRequestParams(): Api.Project.ProjectExecutionSearchParams {
  return {
    ...searchParams,
    keyword: searchParams.keyword?.trim() || undefined,
    statusCode: selectedStatus.value || undefined
  };
}

function createStatusBoardParams(): Api.Project.ProjectExecutionStatusBoardParams {
  return {
    keyword: searchParams.keyword?.trim() || undefined,
    executionType: searchParams.executionType,
    ownerId: searchParams.ownerId,
    updateTime: searchParams.updateTime
  };
}

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
});

function syncSelectedExecution() {
  if (!data.value.length) {
    selectedExecution.value = null;
    return;
  }

  selectedExecution.value = data.value.find(item => item.id === selectedExecution.value?.id) || data.value[0];
}

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);
  syncSelectedExecution();
}

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;
}

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

async function handleSearch() {
  await Promise.all([reloadExecutionData(1), loadExecutionStatusBoard()]);
}

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

async function handleStatusChange(status: ExecutionStatusFilter) {
  selectedStatus.value = status;
  await reloadExecutionData(1);
}

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 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;
  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,
        executionDesc: payload.executionDesc
      })
    : await fetchCreateProjectExecution(projectId.value, payload);

  if (!result.error) {
    operateVisible.value = false;
    await Promise.all([
      reloadExecutionData(editingExecution.value ? (searchParams.pageNo ?? 1) : 1),
      loadExecutionStatusBoard()
    ]);
  }
}

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);
    await Promise.all([reloadExecutionData(searchParams.pageNo ?? 1), loadExecutionStatusBoard()]);
  }
}

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);
  }
}

function handleDeleteExecution(row: Api.Project.ProjectExecution) {
  selectedExecution.value = row;
  deleteDialogVisible.value = true;
}

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
  });
  if (error) return;
  window.$message?.success('删除成功');
  deleteDialogVisible.value = false;
  selectedExecution.value = null;
  await Promise.all([reloadExecutionData(1), loadExecutionStatusBoard()]);
}

async function handleExecutionChangedByTask() {
  if (!selectedExecution.value) {
    await Promise.all([reloadExecutionData(searchParams.pageNo ?? 1), loadExecutionStatusBoard()]);
    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()]);
    return;
  }

  await Promise.all([reloadExecutionData(searchParams.pageNo ?? 1), loadExecutionStatusBoard()]);
}

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">
    <ExecutionListPanel
      v-model:search-model="searchParams"
      class="project-execution-page__aside"
      :data="data"
      :loading="loading"
      :pagination="mobilePagination"
      :selected-id="selectedExecution?.id || null"
      :status-board="executionStatusBoard"
      :selected-status="selectedStatus"
      :owner-options="projectMemberOptions"
      :can-create="canCreateExecution"
      @select="selectedExecution = $event"
      @status-change="handleStatusChange"
      @search="handleSearch"
      @reset="handleReset"
      @create="openCreateExecution"
      @edit="openEditExecution"
      @view="openViewExecution"
      @members="openMemberDialog"
      @status-action="openExecutionStatus"
      @delete="handleDeleteExecution"
    />

    <TaskWorkspace
      class="project-execution-page__main"
      :project-id="projectId"
      :execution="selectedExecution"
      :can-create="canCreateTask"
      @execution-changed="handleExecutionChangedByTask"
    />

    <ExecutionOperateDialog
      v-model:visible="operateVisible"
      :mode="operateMode"
      :row-data="editingExecution"
      :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 ?? ''"
      :on-confirm="confirmDeleteExecution"
    />
  </div>

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

<style scoped>
.project-execution-page {
  display: grid;
  min-height: 560px;
  gap: 16px;
  overflow: hidden;
  grid-template-columns: 396px minmax(0, 1fr);
}

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

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