<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
import { useResizeObserver } from '@vueuse/core';
import dayjs from 'dayjs';
import {
  fetchGetProjectListByProductId,
  fetchGetRequirement,
  fetchGetRequirementModuleTree,
  fetchGetUserSimpleList,
  fetchUpdateRequirement
} from '@/service/api';
import { useForm, useFormRules } from '@/hooks/common/form';
import { useDict } from '@/hooks/business/dict';
import BusinessAttachmentUploader from '@/components/custom/business-attachment-uploader.vue';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import BusinessFormSection from '@/components/custom/business-form-section.vue';
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
import DictSelect from '@/components/custom/dict-select.vue';
import ReadonlyField from '@/components/custom/readonly-field.vue';
import RequirementTreePicker, {
  type RequirementTreePickerNode
} from '@/views/project/project/execution/components/requirement-tree-picker.vue';
import MemberSelectOption from './member-select-option.vue';

defineOptions({ name: 'RequirementDetailDialog' });

type DialogMode = 'view' | 'edit';

interface Props {
  mode: DialogMode;
  requirement: Api.Product.Requirement | null;
  productId: string;
  memberOptions: Api.Product.ProductMember[];
  categoryDictCode: string;
  priorityDictCode: string;
}

const props = defineProps<Props>();

interface Emits {
  (e: 'submitted', requirementId?: string): void;
}

const emit = defineEmits<Emits>();

const visible = defineModel<boolean>('visible', {
  default: false
});

const { formRef, validate } = useForm();
const { createRequiredRule } = useFormRules();

const { getLabel: getCategoryLabel } = useDict(() => props.categoryDictCode);
const { getLabel: getPriorityLabel } = useDict(() => props.priorityDictCode);

const attachmentUploaderRef = ref<InstanceType<typeof BusinessAttachmentUploader> | null>(null);
const richTextEditorRef = ref<InstanceType<typeof BusinessRichTextEditor> | null>(null);

interface Model {
  title: string;
  description: string | null;
  attachments: Api.Project.AttachmentItem[];
  reviewRequired: number;
  moduleId: string | null;
  category: string;
  priority: string | null;
  sourceBizCode: string;
  expectedTime: string | null;
  proposerId: string;
  proposerNickname: string;
  currentHandlerUserId: string;
  currentHandlerUserNickname: string;
  implementProjectId: string | null;
  sort: number;
  lastStatusReason: string;
}

const loading = ref(false);
const submitting = ref(false);
const moduleTree = ref<Api.Product.RequirementModule[]>([]);
const projectOptions = ref<Api.Project.Project[]>([]);
const allUserOptions = ref<Api.SystemManage.UserSimple[]>([]);
const model = ref<Model>(createDefaultModel());

const isViewMode = computed(() => props.mode === 'view');
const isEditMode = computed(() => props.mode === 'edit');
const dialogTitle = computed(() => {
  if (isViewMode.value) {
    return '查看需求';
  }

  return '编辑需求';
});

const memberUserOptions = computed(() => {
  return props.memberOptions.filter(m => m.status === 0);
});

const memberLabelMap = computed(() => {
  return new Map(memberUserOptions.value.map(item => [String(item.userId), item.userNickname]));
});

const allUserLabelMap = computed(() => {
  return new Map(allUserOptions.value.map(item => [String(item.id), item.nickname]));
});

const moduleLabelMap = computed(() => {
  const map = new Map<string | undefined, string>();

  function traverse(modules: Api.Product.RequirementModule[]) {
    for (const module of modules) {
      map.set(module.id, module.moduleName);
      if (module.children?.length) {
        traverse(module.children);
      }
    }
  }

  traverse(moduleTree.value);
  return map;
});

const projectOptionsMap = computed(() => {
  return new Map(projectOptions.value.map(item => [String(item.id), item.projectName]));
});

const moduleTreeOptions = computed<RequirementTreePickerNode[]>(() => mapModuleTree(moduleTree.value));

const reviewRequiredOptions = [
  { label: '不需要', value: 0 },
  { label: '需要', value: 1 }
];

function buildExpectedTimeShortcut(text: string, mutator: (date: Date) => void) {
  return {
    text,
    value: () => {
      const date = new Date();
      mutator(date);
      return date;
    }
  };
}

const expectedTimeShortcuts = [
  buildExpectedTimeShortcut('一星期', date => date.setDate(date.getDate() + 7)),
  buildExpectedTimeShortcut('两星期', date => date.setDate(date.getDate() + 14)),
  buildExpectedTimeShortcut('一个月', date => date.setMonth(date.getMonth() + 1)),
  buildExpectedTimeShortcut('三个月', date => date.setMonth(date.getMonth() + 3)),
  buildExpectedTimeShortcut('半年', date => date.setMonth(date.getMonth() + 6)),
  buildExpectedTimeShortcut('一年', date => date.setFullYear(date.getFullYear() + 1))
];

const rules = computed(() => {
  const baseRules: Record<string, App.Global.FormRule[]> = {
    title: isEditMode.value ? [createRequiredRule('请输入需求名称')] : [],
    category: isEditMode.value ? [createRequiredRule('请选择需求类型')] : [],
    priority: isEditMode.value ? [createRequiredRule('请选择优先级')] : [],
    proposerId: isEditMode.value ? [createRequiredRule('请选择提出人')] : [],
    currentHandlerUserId: isEditMode.value ? [createRequiredRule('请选择负责人')] : []
  };

  return baseRules;
});

const leftColRef = ref<HTMLElement>();
const editorHeight = ref<string>('45vh');

const ATTACHMENT_SECTION_RESERVE_PX = 140;

useResizeObserver(leftColRef, entries => {
  const height = entries[0]?.contentRect.height;

  if (height && height > 120) {
    editorHeight.value = `${Math.max(height - 60 - ATTACHMENT_SECTION_RESERVE_PX, 200)}px`;
  }
});

function isEmptyRichText(html: string | null | undefined) {
  if (!html) {
    return true;
  }

  const text = html
    .replace(/<[^>]+>/g, '')
    .replace(/&nbsp;/g, '')
    .trim();

  if (text) {
    return false;
  }

  return !/<img\b/i.test(html);
}

function createDefaultModel(): Model {
  return {
    title: '',
    description: null,
    attachments: [],
    reviewRequired: 0,
    moduleId: null,
    category: '',
    priority: '3',
    sourceBizCode: '',
    expectedTime: null,
    proposerId: '',
    proposerNickname: '',
    currentHandlerUserId: '',
    currentHandlerUserNickname: '',
    implementProjectId: null,
    sort: 0,
    lastStatusReason: ''
  };
}

function mapModuleTree(modules: Api.Product.RequirementModule[]): RequirementTreePickerNode[] {
  return modules
    .filter(item => Boolean(item.id))
    .map(item => ({
      id: item.id || '',
      title: item.moduleName,
      children: item.children?.length ? mapModuleTree(item.children) : undefined
    }));
}

function closeDialog() {
  visible.value = false;
}

async function handleSubmit() {
  await validate();

  if (!props.productId || !props.requirement?.id) {
    return;
  }

  if (attachmentUploaderRef.value?.hasUploading) {
    window.$message?.warning('附件正在上传中，请稍候');
    return;
  }

  const handler = memberUserOptions.value.find(item => item.userId === model.value.currentHandlerUserId);

  submitting.value = true;

  const updatePayload: Api.Product.UpdateRequirementParams = {
    id: props.requirement.id,
    productId: props.productId,
    moduleId: model.value.moduleId || '0',
    reviewRequired: model.value.reviewRequired as Api.Product.RequirementReviewRequired,
    title: model.value.title.trim(),
    description: isEmptyRichText(model.value.description) ? null : (model.value.description ?? null),
    attachments: [...model.value.attachments],
    category: model.value.category,
    priority: Number(model.value.priority) as Api.Product.RequirementPriority,
    sourceBizCode: model.value.sourceBizCode.trim() || null,
    expectedTime: model.value.expectedTime,
    proposerId: model.value.proposerId,
    proposerNickname: model.value.proposerNickname,
    currentHandlerUserId: model.value.currentHandlerUserId,
    currentHandlerUserNickname: handler?.userNickname || model.value.currentHandlerUserNickname,
    implementProjectId: model.value.implementProjectId,
    sort: model.value.sort
  };

  const { error } = await fetchUpdateRequirement(updatePayload);

  submitting.value = false;

  if (error) {
    return;
  }

  await Promise.all([attachmentUploaderRef.value?.commit(), richTextEditorRef.value?.commit()]);

  window.$message?.success('需求更新成功');
  closeDialog();
  emit('submitted', props.requirement.id);
}

async function loadModuleTree() {
  if (!props.productId) {
    moduleTree.value = [];
    return;
  }

  const { error, data } = await fetchGetRequirementModuleTree(props.productId);

  if (error || !data) {
    moduleTree.value = [];
    return;
  }

  moduleTree.value = data;
}

async function loadProjectOptions() {
  if (!props.productId) {
    projectOptions.value = [];
    return;
  }

  const { error, data } = await fetchGetProjectListByProductId(props.productId);

  if (error || !data) {
    projectOptions.value = [];
    return;
  }

  projectOptions.value = data;
}

function transformRequirementData(data: Api.Product.Requirement): typeof model.value {
  return {
    title: data.title || '',
    description: data.description || null,
    attachments: data.attachments ? [...data.attachments] : [],
    reviewRequired: data.reviewRequired ?? 0,
    moduleId: data.moduleId || null,
    category: data.category || '',
    priority: data.priority === null || data.priority === undefined ? null : String(data.priority),
    sourceBizCode: data.sourceBizCode || '',
    expectedTime: formatExpectedTime(data.expectedTime),
    proposerId: data.proposerId || '',
    proposerNickname: data.proposerNickname || '',
    currentHandlerUserId: data.currentHandlerUserId || '',
    currentHandlerUserNickname: data.currentHandlerUserNickname || '',
    implementProjectId: data.implementProjectId || null,
    sort: data.sort ?? 0,
    lastStatusReason: data.lastStatusReason || ''
  };
}

function formatExpectedTime(value?: string | number[] | null): string | null {
  if (!value) {
    return null;
  }

  if (Array.isArray(value)) {
    const [year, month, day] = value;
    return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
  }

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

async function loadRequirementDetail() {
  if (!props.productId || !props.requirement?.id) {
    return;
  }

  loading.value = true;

  const { error, data } = await fetchGetRequirement(props.requirement.id, props.productId);

  loading.value = false;

  if (error || !data) {
    return;
  }

  model.value = transformRequirementData(data);
}

async function loadAllUsers() {
  const { error, data } = await fetchGetUserSimpleList();
  if (!error && data) {
    allUserOptions.value = data;
  }
}

watch(
  () => visible.value,
  async value => {
    if (!value) {
      return;
    }

    await Promise.all([loadModuleTree(), loadProjectOptions(), loadAllUsers()]);

    if (props.requirement?.id) {
      await loadRequirementDetail();
    } else {
      model.value = createDefaultModel();
    }

    await nextTick();
    attachmentUploaderRef.value?.initSession();
    richTextEditorRef.value?.initSession();
    formRef.value?.clearValidate();
  }
);
</script>

<template>
  <BusinessFormDialog
    v-model="visible"
    :title="dialogTitle"
    width="1100px"
    max-body-height="78vh"
    :loading="loading"
    :confirm-loading="submitting"
    @confirm="handleSubmit"
  >
    <template v-if="isViewMode" #footer="{ close }">
      <ElButton type="primary" @click="close">关闭</ElButton>
    </template>

    <ElForm ref="formRef" :model="model" :rules="rules" label-position="top" :validate-on-rule-change="false">
      <div class="requirement-operate-dialog__grid">
        <div ref="leftColRef" class="requirement-operate-dialog__col-left">
          <BusinessFormSection title="需求信息">
            <ElFormItem label="需求名称" prop="title">
              <ReadonlyField :value="model.title" />
            </ElFormItem>

            <ElFormItem label="模块">
              <template v-if="isViewMode">
                <ReadonlyField :value="moduleLabelMap.get(model.moduleId || undefined) || '--'" />
              </template>
              <RequirementTreePicker
                v-else
                v-model="model.moduleId"
                :data="moduleTreeOptions"
                placeholder="搜索或选择所属模块"
              />
            </ElFormItem>

            <ElFormItem label="是否需要评审">
              <ReadonlyField :value="reviewRequiredOptions.find(opt => opt.value === model.reviewRequired)?.label" />
            </ElFormItem>

            <ElFormItem label="优先级" prop="priority">
              <template v-if="isViewMode">
                <ReadonlyField
                  :value="model.priority !== null ? getPriorityLabel(String(model.priority)) || '--' : '--'"
                />
              </template>
              <DictSelect
                v-else
                v-model="model.priority"
                :dict-code="priorityDictCode"
                placeholder="请选择优先级"
                show-remark
              />
            </ElFormItem>

            <ElFormItem label="需求类型" prop="category">
              <ReadonlyField :value="getCategoryLabel(model.category) || '--'" />
            </ElFormItem>

            <ElFormItem label="来源业务编号">
              <ReadonlyField v-if="isViewMode" :value="model.sourceBizCode || '--'" />
              <ElInput
                v-else
                v-model="model.sourceBizCode"
                clearable
                maxlength="128"
                placeholder="请输入来源业务编号"
              />
            </ElFormItem>

            <ElFormItem label="提出人" prop="proposerId">
              <ReadonlyField :value="allUserLabelMap.get(model.proposerId) || '--'" />
            </ElFormItem>

            <ElFormItem label="负责人" prop="currentHandlerUserId">
              <template v-if="isViewMode">
                <ReadonlyField :value="memberLabelMap.get(model.currentHandlerUserId) || '--'" />
              </template>
              <ElSelect
                v-else
                v-model="model.currentHandlerUserId"
                class="w-full"
                filterable
                placeholder="请选择负责人"
              >
                <ElOption
                  v-for="item in memberUserOptions"
                  :key="item.userId"
                  :label="item.userNickname"
                  :value="item.userId"
                >
                  <MemberSelectOption :nickname="item.userNickname" :role-name="item.roleName" />
                </ElOption>
              </ElSelect>
            </ElFormItem>

            <ElFormItem v-if="isViewMode" label="关联项目">
              <ReadonlyField :value="projectOptionsMap.get(model.implementProjectId || '') || '--'" />
            </ElFormItem>

            <ElFormItem label="预期完成时间">
              <ReadonlyField v-if="isViewMode" :value="model.expectedTime || '--'" />
              <ElDatePicker
                v-else
                v-model="model.expectedTime"
                type="date"
                value-format="YYYY-MM-DD"
                placeholder="请选择预期完成时间"
                :shortcuts="expectedTimeShortcuts"
                class="requirement-operate-dialog__date-picker"
              />
            </ElFormItem>

            <!--            <ElFormItem label="排序值">-->
            <!--              <template v-if="isViewMode">-->
            <!--                <ReadonlyField :value="model.sort" />-->
            <!--              </template>-->
            <!--              <ElInputNumber-->
            <!--                v-else-->
            <!--                v-model="model.sort"-->
            <!--                class="w-full"-->
            <!--                :min="0"-->
            <!--                :max="9999"-->
            <!--                placeholder="请输入排序值"-->
            <!--              />-->
            <!--            </ElFormItem>-->

            <ElFormItem v-if="isViewMode && model.lastStatusReason" label="状态变更原因">
              <div class="requirement-operate-dialog__readonly-textarea">{{ model.lastStatusReason }}</div>
            </ElFormItem>
          </BusinessFormSection>
        </div>

        <div class="requirement-operate-dialog__col-right">
          <BusinessFormSection title="需求内容">
            <ElFormItem class="requirement-operate-dialog__desc-item">
              <BusinessRichTextEditor
                ref="richTextEditorRef"
                v-model="model.description"
                :disabled="isViewMode"
                :height="editorHeight"
                upload-directory="requirement"
                :placeholder="isViewMode && isEmptyRichText(model.description) ? '--' : '请输入需求内容'"
              />
            </ElFormItem>
          </BusinessFormSection>

          <BusinessFormSection title="附件">
            <ElFormItem class="requirement-operate-dialog__attachment-item">
              <BusinessAttachmentUploader
                ref="attachmentUploaderRef"
                v-model="model.attachments"
                directory="requirement"
                :disabled="isViewMode"
              />
            </ElFormItem>
          </BusinessFormSection>
        </div>
      </div>
    </ElForm>
  </BusinessFormDialog>
</template>

<style scoped>
.requirement-operate-dialog__grid {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: 24px;
  align-items: start;
}

.requirement-operate-dialog__col-left,
.requirement-operate-dialog__col-right {
  min-width: 0;
}

.requirement-operate-dialog__col-right {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.requirement-operate-dialog__desc-item,
.requirement-operate-dialog__attachment-item {
  margin-bottom: 0;
}

.requirement-operate-dialog__readonly-textarea {
  box-sizing: border-box;
  width: 100%;
  min-height: 65px;
  padding: 8px 12px;
  border-radius: 4px;
  background: linear-gradient(180deg, rgb(241 245 249 / 98%), rgb(226 232 240 / 94%)), rgb(241 245 249);
  box-shadow: 0 0 0 1px rgb(203 213 225 / 96%) inset;
  color: rgb(51 65 85 / 96%);
  font-size: 14px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-all;
}

@media (width <= 1024px) {
  .requirement-operate-dialog__grid {
    grid-template-columns: 1fr;
  }
}

:deep(.requirement-operate-dialog__date-picker.el-date-editor.el-input) {
  width: 100%;
}
</style>
