<script setup lang="ts">
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import type { SystemAreaApi } from '#/api/system/area';

import { computed, ref } from 'vue';

import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictLabel, getDictOptions } from '@vben/hooks';
import { useUserStore } from '@vben/stores';
import { formatDate } from '@vben/utils';

import { Button } from 'ant-design-vue';
// @ts-expect-error - 安装 vue3-print-nb 局部指令 v-print
import vPrint from 'vue3-print-nb';

import { getProcessInstancePrintData } from '#/api/bpm/processInstance';
import { getAreaTree } from '#/api/system/area';
import { getSimpleDeptList } from '#/api/system/dept';
import { getSimpleUserList } from '#/api/system/user';
import { decodeFields } from '#/components/form-create';

const userStore = useUserStore();

interface FormFieldItem {
  html: string;
  id: string;
  name: string;
}

interface FormFieldOption {
  label?: string;
  value?: unknown;
}

type FormFieldRule = Record<string, unknown> & {
  field?: string;
  options?: FormFieldOption[];
  props?: Record<string, unknown>;
  title?: string;
  type?: string;
};

type PrintableRecord = Record<string, unknown>;

interface PrintLookupMaps {
  areaMap: Map<string, string>;
  deptMap: Map<string, string>;
  userMap: Map<string, string>;
}

const printData = ref<BpmProcessInstanceApi.ProcessPrintDataRespVO>();
const userName = computed(() => userStore.userInfo?.nickname ?? '');
const printTime = ref(formatDate(new Date(), 'YYYY-MM-DD HH:mm'));
const formFields = ref<FormFieldItem[]>([]);
const printDataMap = ref<Record<string, string>>({});

/** 打印配置 */
const printObj = ref({
  id: 'printDivTag',
  popTitle: '&nbsp;',
  extraCss: '/print.css',
  extraHead: '',
  zIndex: 20_003,
});

const [Modal, modalApi] = useVbenModal({
  closable: true,
  footer: false,
  title: '打印流程',
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      return;
    }

    modalApi.lock();
    try {
      const { processInstanceId } = modalApi.getData<{
        processInstanceId: string;
      }>();
      if (processInstanceId) {
        await fetchPrintData(processInstanceId);
      }
    } finally {
      modalApi.unlock();
    }
  },
});

/** 获取打印数据 */
async function fetchPrintData(id: string) {
  printData.value = await getProcessInstancePrintData(id);
  initPrintDataMap();
  await parseFormFields();
}

/** 解析表单字段 */
async function parseFormFields() {
  if (!printData.value) return;

  const formFieldsObj = decodeFields(
    printData.value.processInstance.processDefinition?.formFields || [],
  ) as unknown as FormFieldRule[];
  const processVariables = printData.value.processInstance.formVariables ?? {};
  const lookupMaps = await loadPrintLookupMaps(formFieldsObj);
  const res: FormFieldItem[] = [];

  for (const item of formFieldsObj) {
    const fieldKey = String(item.field ?? '');
    const id = fieldKey;
    const name = String(item.title ?? fieldKey);
    const variable = processVariables[fieldKey];
    const html = formatPrintField(item, variable, lookupMaps);

    printDataMap.value[fieldKey] = html;
    res.push({ id, name, html });
  }

  formFields.value = res;
}

function getRuleProp(rule: FormFieldRule, key: string) {
  return rule?.[key] ?? rule?.props?.[key];
}

function isPrintableRecord(value: unknown): value is PrintableRecord {
  return typeof value === 'object' && value !== null;
}

function getRecordValue(record: PrintableRecord, key: string) {
  return record[key];
}

function isNotEmptyString(value: string) {
  return value.length > 0;
}

function isEmptyValue(value: unknown) {
  return value === undefined || value === null || value === '';
}

function toValueArray(value: unknown) {
  if (Array.isArray(value)) {
    return value;
  }
  if (isEmptyValue(value)) {
    return [];
  }
  return [value];
}

function tryFormatDate(value: unknown) {
  if (isEmptyValue(value)) {
    return '';
  }
  const formatted = formatDate(value as Date | number | string);
  return formatted === 'Invalid Date' ? String(value) : formatted;
}

function formatDateValue(value: unknown) {
  if (Array.isArray(value)) {
    return value.map((item) => tryFormatDate(item)).join(' ~ ');
  }
  return tryFormatDate(value);
}

function formatPrimitiveValue(value: unknown): string {
  if (isEmptyValue(value)) {
    return '';
  }
  if (Array.isArray(value)) {
    return value
      .map((item) => formatPrimitiveValue(item))
      .filter((s) => isNotEmptyString(s))
      .join(', ');
  }
  if (typeof value === 'boolean') {
    return value ? '是' : '否';
  }
  if (isPrintableRecord(value)) {
    const record = value;
    const displayValue =
      getRecordValue(record, 'label') ??
      getRecordValue(record, 'name') ??
      getRecordValue(record, 'url') ??
      getRecordValue(record, 'value') ??
      JSON.stringify(value);
    return String(displayValue);
  }
  return String(value);
}

function createImageHtml(url: string) {
  const imgEl = document.createElement('img');
  imgEl.setAttribute('src', url);
  imgEl.setAttribute('style', 'max-width: 600px; max-height: 300px;');
  return imgEl.outerHTML;
}

function renderImageListHtml(value: unknown) {
  return toValueArray(value)
    .map((item) => {
      let url: string | undefined;
      if (typeof item === 'string') {
        url = item;
      } else if (isPrintableRecord(item)) {
        const recordUrl = getRecordValue(item, 'url');
        url = recordUrl ? String(recordUrl) : undefined;
      }
      return url ? createImageHtml(String(url)) : '';
    })
    .filter((s) => isNotEmptyString(s))
    .join('<br/>');
}

function createFileLinkHtml(file: unknown) {
  const record = isPrintableRecord(file) ? file : undefined;
  const recordUrl = record ? getRecordValue(record, 'url') : undefined;
  const url = typeof file === 'string' ? file : String(recordUrl ?? '');
  if (!url) {
    return '';
  }
  const linkEl = document.createElement('a');
  linkEl.setAttribute('href', String(url));
  linkEl.setAttribute('target', '_blank');
  linkEl.setAttribute('rel', 'noopener noreferrer');
  const fallbackName = String(url).slice(Math.max(0, String(url).lastIndexOf('/') + 1)) || String(url);
  const recordName = record ? getRecordValue(record, 'name') : undefined;
  linkEl.textContent = recordName ? String(recordName) : fallbackName;
  return linkEl.outerHTML;
}

function renderFileListHtml(value: unknown) {
  return toValueArray(value)
    .map((item) => createFileLinkHtml(item))
    .filter((s) => isNotEmptyString(s))
    .join('<br/>');
}

function mapValuesWithOptions(
  value: unknown,
  options: FormFieldOption[] = [],
) {
  const values = toValueArray(value);
  const labels = values
    .map((item) => {
      const matched = options.find(
        (option) =>
          option?.value === item || String(option?.value ?? '') === String(item),
      );
      return matched?.label ?? String(item);
    })
    .filter((s) => isNotEmptyString(s));
  return labels.join(', ');
}

function flattenAreaTree(
  list: Array<SystemAreaApi.Area & { children?: SystemAreaApi.Area[] }> = [],
  map: Map<string, string> = new Map(),
) {
  list.forEach((item) => {
    if (item.id !== undefined) {
      map.set(String(item.id), item.name);
    }
    if (Array.isArray(item.children) && item.children.length > 0) {
      flattenAreaTree(item.children, map);
    }
  });
  return map;
}

function mapValueWithLabelMap(
  value: unknown,
  labelMap: Map<string, string>,
  separator = ', ',
) {
  const values = toValueArray(value);
  const labels = values
    .map((item) => labelMap.get(String(item)) ?? String(item))
    .filter((s) => isNotEmptyString(s));
  return labels.length > 0 ? labels.join(separator) : formatPrimitiveValue(values);
}

/**
 * 按当前表单字段类型按需加载打印所需的关联数据，并构建查找映射表。
 *
 * @param formFieldsObj - 已解码的表单字段规则列表
 * @returns 打印展示时使用的区域、部门、用户名称映射
 */
async function loadPrintLookupMaps(formFieldsObj: FormFieldRule[]) {
  const hasAreaSelect = formFieldsObj.some((item) => item.type === 'AreaSelect');
  const hasUserSelect = formFieldsObj.some((item) => item.type === 'UserSelect');
  const hasDeptSelect = formFieldsObj.some((item) => item.type === 'DeptSelect');

  const [areaList, userList, deptList] = await Promise.all([
    hasAreaSelect ? getAreaTree() : Promise.resolve([]),
    hasUserSelect ? getSimpleUserList() : Promise.resolve([]),
    hasDeptSelect ? getSimpleDeptList() : Promise.resolve([]),
  ]);

  return {
    areaMap: flattenAreaTree(areaList as Array<SystemAreaApi.Area>),
    deptMap: new Map(
      (deptList ?? []).map((item) => [String(item.id), item.name] as const),
    ),
    userMap: new Map(
      (userList ?? []).map(
        (item) => [String(item.id), item.nickname ?? item.username] as const,
      ),
    ),
  } satisfies PrintLookupMaps;
}

/**
 * 根据表单字段规则和字段值，格式化为适合打印展示的 HTML 字符串。
 *
 * @param rule - 表单字段规则，包含字段类型、选项、props 等配置信息
 * @param value - 该字段在流程实例中的实际值
 * @param lookupMaps - 预加载的查找映射表，包含区域、部门、用户的 id→name 映射
 * @returns 格式化后的 HTML 字符串（纯文本或富文本，取决于字段类型）
 */
function formatPrintField(
  rule: FormFieldRule,
  value: unknown,
  lookupMaps: PrintLookupMaps,
){
  const type = String(rule.type ?? '');

  switch (type) {
    case 'AreaSelect': {
      const separator = String(getRuleProp(rule, 'separator') || '/');
      return mapValueWithLabelMap(value, lookupMaps.areaMap, separator);
    }
    case 'cascader':
    case 'checkbox':
    case 'radio':
    case 'select':
    case 'treeSelect': {
      const options = getRuleProp(rule, 'options');
      return Array.isArray(options) && options.length > 0
        ? mapValuesWithOptions(value, options as FormFieldOption[])
        : formatPrimitiveValue(value);
    }
    case 'date':
    case 'DatePicker':
    case 'datePicker':
    case 'daterange':
    case 'datetime':
    case 'datetimerange':
    case 'month':
    case 'monthrange':
    case 'RangePicker':
    case 'rangePicker':
    case 'TimePicker':
    case 'timePicker':
    case 'TimeRangePicker':
    case 'timeRangePicker': {
      return formatDateValue(value);
    }
    case 'DeptSelect': {
      if (String(getRuleProp(rule, 'returnType')) === 'name') {
        return formatPrimitiveValue(value);
      }
      return mapValueWithLabelMap(value, lookupMaps.deptMap);
    }
    case 'DictSelect': {
      const dictType = getRuleProp(rule, 'dictType');
      if (typeof dictType !== 'string' || !dictType) {
        return formatPrimitiveValue(value);
      }
      const valueTypeMap = {
        bool: 'boolean',
        int: 'number',
        str: 'string',
      } as const;
      const rawValueType = String(getRuleProp(rule, 'valueType') ?? '');
      const valueType =
        (valueTypeMap as Record<string, 'boolean' | 'number' | 'string'>)[rawValueType] ??
        'string';
      const options = getDictOptions(dictType, valueType);
      return mapValuesWithOptions(value, options);
    }
    case 'FileUpload': {
      return renderFileListHtml(value);
    }
    case 'IframeComponent': {
      const propsObj = rule.props;
      const propsUrl =
        isPrintableRecord(propsObj)
          ? String(getRecordValue(propsObj, 'url') ?? '')
          : '';
      const iframeUrl = isEmptyValue(value) ? propsUrl : String(value ?? '');
      return iframeUrl ? createFileLinkHtml(iframeUrl) : '';
    }
    case 'ImagesUpload':
    case 'ImageUpload':
    case 'UploadImg': {
      return renderImageListHtml(value);
    }
    case 'switch': {
      if (isEmptyValue(value)) return '否';
      const checkedVal = getRuleProp(rule, 'checkedValue');
      const isChecked =
        checkedVal !== undefined && checkedVal !== null
          ? value === checkedVal
          : Boolean(value);
      return isChecked ? '是' : '否';
    }
    case 'Tinymce': {
      return isEmptyValue(value) ? '' : String(value);
    }
    case 'UserSelect': {
      if (String(getRuleProp(rule, 'returnType')) === 'name') {
        return formatPrimitiveValue(value);
      }
      return mapValueWithLabelMap(value, lookupMaps.userMap);
    }
    default: {
      return formatPrimitiveValue(value);
    }
  }
}

/** 初始化打印数据映射 */
function initPrintDataMap() {
  if (!printData.value) return;

  printDataMap.value.startUser =
    printData.value.processInstance.startUser?.nickname || '';
  printDataMap.value.startUserDept =
    printData.value.processInstance.startUser?.deptName || '';
  printDataMap.value.processName = printData.value.processInstance.name;
  printDataMap.value.processNum = String(printData.value.processInstance.id ?? '');
  printDataMap.value.startTime = formatDate(
    printData.value.processInstance.startTime,
  );
  printDataMap.value.endTime = formatDate(
    printData.value.processInstance.endTime,
  );
  printDataMap.value.processStatus = String(
    getDictLabel(
      DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
      printData.value.processInstance.status,
    ) ?? '',
  );
  printDataMap.value.printUser = userName.value;
  printDataMap.value.printTime = printTime.value;
}

/** 获取打印模板 HTML  */
function getPrintTemplateHTML() {
  if (!printData.value?.printTemplateHtml) return '';

  const parser = new DOMParser();
  const doc = parser.parseFromString(
    printData.value.printTemplateHtml,
    'text/html',
  );

  // 替换 mentions
  const mentions = doc.querySelectorAll('[data-w-e-type="mention"]');
  mentions.forEach((item) => {
    const htmlElement = item as HTMLElement;
    const mentionId = JSON.parse(
      decodeURIComponent(htmlElement.dataset.info ?? ''),
    ).id;
    item.innerHTML = printDataMap.value[mentionId] ?? '';
  });

  // 替换流程记录
  const processRecords = doc.querySelectorAll(
    '[data-w-e-type="process-record"]',
  );
  const processRecordTable: Element = document.createElement('table');

  if (processRecords.length > 0) {
    // 构建流程记录 html
    processRecordTable.setAttribute('class', 'w-full border-collapse');

    const headTr = document.createElement('tr');
    const headTd = document.createElement('td');
    headTd.setAttribute('colspan', '2');
    headTd.setAttribute('class', 'border border-black p-1.5 text-center');
    headTd.innerHTML = '流程记录';
    headTr.append(headTd);
    processRecordTable.append(headTr);

    printData.value?.tasks.forEach((item) => {
      const tr = document.createElement('tr');
      const td1 = document.createElement('td');
      td1.setAttribute('class', 'border border-black p-1.5');
      td1.innerHTML = item.name;
      const td2 = document.createElement('td');
      td2.setAttribute('class', 'border border-black p-1.5');
      td2.innerHTML = item.description;
      tr.append(td1);
      tr.append(td2);
      processRecordTable.append(tr);
    });
  }

  processRecords.forEach((item) => {
    item.innerHTML = processRecordTable.outerHTML;
  });

  // 返回 html
  return doc.body.innerHTML;
}
</script>

<template>
  <Modal class="w-2/3">
    <div id="printDivTag" class="break-all">
      <!-- eslint-disable vue/no-v-html  使用自定义打印模板 -->
      <div
        v-if="printData?.printTemplateEnable"
        v-html="getPrintTemplateHTML()"
      ></div>
      <div v-else-if="printData">
        <h2 class="mb-3 text-center text-xl font-bold">
          {{ printData.processInstance.name }}
        </h2>
        <div class="mb-2 flex justify-between text-sm">
          <div>
            {{ `流程编号: ${printData.processInstance.id}` }}
          </div>
          <div>
            {{ `打印人员: ${userName}` }}
          </div>
        </div>
        <table class="mt-3 w-full border-collapse">
          <tbody>
            <tr>
              <td class="w-1/4 border border-black p-1.5">发起人</td>
              <td class="w-1/4 border border-black p-1.5">
                {{ printData.processInstance.startUser?.nickname }}
              </td>
              <td class="w-1/4 border border-black p-1.5">发起时间</td>
              <td class="w-1/4 border border-black p-1.5">
                {{ formatDate(printData.processInstance.startTime) }}
              </td>
            </tr>
            <tr>
              <td class="w-1/4 border border-black p-1.5">所属部门</td>
              <td class="w-1/4 border border-black p-1.5">
                {{ printData.processInstance.startUser?.deptName }}
              </td>
              <td class="w-1/4 border border-black p-1.5">流程状态</td>
              <td class="w-1/4 border border-black p-1.5">
                {{
                  getDictLabel(
                    DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
                    printData.processInstance.status,
                  )
                }}
              </td>
            </tr>
            <tr>
              <td
                class="w-full border border-black p-1.5 text-center"
                colspan="4"
              >
                <h4>表单内容</h4>
              </td>
            </tr>
            <tr v-for="item in formFields" :key="item.id">
              <td class="w-1/5 border border-black p-1.5">
                {{ item.name }}
              </td>
              <td class="w-4/5 border border-black p-1.5" colspan="3">
                <div v-html="item.html"></div>
              </td>
            </tr>
            <tr>
              <td
                class="w-full border border-black p-1.5 text-center"
                colspan="4"
              >
                <h4>流程记录</h4>
              </td>
            </tr>
            <tr v-for="item in printData.tasks" :key="item.id">
              <td class="w-1/5 border border-black p-1.5">
                {{ item.name }}
              </td>
              <td class="w-4/5 border border-black p-1.5" colspan="3">
                {{ item.description }}
                <div v-if="item.signPicUrl && item.signPicUrl.length > 0">
                  <img class="h-10 w-[90px]" :src="item.signPicUrl" alt="" />
                </div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
    <template #footer>
      <div class="flex justify-end gap-2">
        <Button @click="modalApi.close()">取 消</Button>
        <Button v-print="printObj" type="primary">打 印</Button>
      </div>
    </template>
  </Modal>
</template>
