export interface AuditFieldDiff {
  [key: string]: unknown;
  changeType?: string;
  chidData?: Array<Record<string, unknown>>;
  chidField?: Array<Record<string, unknown>>;
  componentType?: string;
  field?: string;
  fieldName?: string;
  jnpfKey?: string;
  masked?: boolean;
  nameModified?: boolean;
  newData?: unknown;
  newDisplay?: unknown;
  oldData?: unknown;
  oldDisplay?: unknown;
  technical?: boolean;
  type?: number | string;
  valueType?: string;
}

export interface AuditFieldDiffParseResult {
  diffs: AuditFieldDiff[];
  parseError: boolean;
}

const SENSITIVE_ENGLISH_FIELD_PATTERN =
  /\b(?:password|passwd|pwd|secret|token|credential|authorization|cookie|passport|mobile|phone|telephone|email)\b|\b(?:id|identity)\s+card\b|\bbank\s+(?:account|card)\b/i;
const SENSITIVE_CHINESE_FIELD_PATTERN = /密码|口令|密钥|令牌|凭证|身份证|证件号|护照|银行卡|银行账号|手机号|联系电话|电话号码|电子邮箱|邮箱/;
const SIGN_REFERENCE_FIELD_PATTERN = /^(?:biz[_\s-]*sign|sign(?:ature)?[_\s-]*(?:id|ref|reference))$/i;
const TECHNICAL_IDENTIFIER_PATTERN = /^[a-z_]\w*$/i;
const UNAVAILABLE_DISPLAY_VALUE = '名称暂不可用';

/** Parse audit diff JSON without exposing malformed or masked field values. */
export function parseAuditFieldDiffs(value: unknown): AuditFieldDiffParseResult {
  if (value === undefined || value === null || value === '') return { diffs: [], parseError: false };

  let parsed = value;
  if (typeof value === 'string') {
    try {
      parsed = JSON.parse(value);
    } catch {
      return { diffs: [], parseError: true };
    }
  }

  if (!Array.isArray(parsed)) return { diffs: [], parseError: true };

  const diffs = parsed
    .filter((item) => isRecord(item))
    .map((item) => {
      const diff: AuditFieldDiff = {
        ...item,
        changeType: toOptionalString(item.changeType),
        chidData: toRecordList(item.chidData),
        chidField: toRecordList(item.chidField),
        componentType: toOptionalString(item.componentType),
        field: toOptionalString(item.field),
        fieldName: toOptionalString(item.fieldName),
        jnpfKey: toOptionalString(item.jnpfKey),
        masked: toBoolean(item.masked),
        nameModified: item.nameModified === true || item.nameModified === 'true',
        technical: toBoolean(item.technical),
        valueType: toOptionalString(item.valueType),
      };

      if (diff.masked) {
        delete diff.oldData;
        delete diff.newData;
        delete diff.oldDisplay;
        delete diff.newDisplay;
      }
      return diff;
    });

  return { diffs, parseError: false };
}

/** Prefer the structured API contract while retaining compatibility with old event rows. */
export function parseAuditEventFieldDiffs(fieldDiffList: unknown, legacyFieldDiffs: unknown): AuditFieldDiffParseResult {
  const source = fieldDiffList === undefined || fieldDiffList === null ? legacyFieldDiffs : fieldDiffList;
  return parseAuditFieldDiffs(source);
}

/** Convert a diff value into bounded plain text for Vue text interpolation. */
export function formatAuditDiffValue(value: unknown): string {
  if (value === undefined || value === null) return '';
  if (typeof value === 'string') return value;
  if (typeof value === 'number' || typeof value === 'bigint') return String(value);
  if (typeof value === 'boolean') return value ? '是' : '否';
  try {
    return JSON.stringify(value);
  } catch {
    return '[无法显示的值]';
  }
}

export function hasAuditDiffValue(value: unknown): boolean {
  return value !== undefined && value !== null && value !== '';
}

/** Prefer backend-resolved display semantics and fall back to the stored raw value. */
export function getAuditDiffDisplayValue(diff: AuditFieldDiff, side: 'new' | 'old'): unknown {
  const displayValue = side === 'old' ? diff.oldDisplay : diff.newDisplay;
  if (displayValue !== undefined && displayValue !== null && !isUnavailableDisplayValue(displayValue)) return displayValue;
  return side === 'old' ? diff.oldData : diff.newData;
}

function isUnavailableDisplayValue(value: unknown): boolean {
  return typeof value === 'string' && value.trim() === UNAVAILABLE_DISPLAY_VALUE;
}

/** Return a business-facing label without falling back to a database identifier. */
export function getSafeAuditDiffFieldLabel(diff: AuditFieldDiff, index = 0): string {
  if (isSignReferenceAuditDiff(diff)) return '电子签名';

  const fieldName = diff.fieldName?.trim();
  const field = diff.field?.trim();
  if (fieldName && (!field || fieldName.toLocaleLowerCase() !== field.toLocaleLowerCase() || !TECHNICAL_IDENTIFIER_PATTERN.test(fieldName))) {
    return fieldName;
  }
  return `未配置字段名称 ${index + 1}`;
}

/** Detect fields whose values should be masked from audit detail views. */
export function isSensitiveAuditDiff(diff: AuditFieldDiff): boolean {
  if (diff.masked) return true;
  const jnpfKey = diff.jnpfKey?.trim().toLocaleLowerCase();
  if (jnpfKey === 'sign' || jnpfKey === 'signature') return true;
  const componentType = diff.componentType?.trim().toLocaleLowerCase();
  if (componentType === 'sign' || componentType === 'signature') return true;
  if (diff.valueType?.trim().toLocaleLowerCase() === 'masked') return true;
  if (isSignReferenceAuditDiff(diff)) return true;
  return [diff.field, diff.fieldName].some((value) => {
    if (typeof value !== 'string') return false;
    const normalizedValue = value.replaceAll(/([a-z\d])([A-Z])/g, '$1 $2').replaceAll(/[^\p{L}\p{N}]+/gu, ' ');
    return SENSITIVE_ENGLISH_FIELD_PATTERN.test(normalizedValue) || SENSITIVE_CHINESE_FIELD_PATTERN.test(normalizedValue);
  });
}

function isSignReferenceAuditDiff(diff: AuditFieldDiff): boolean {
  return [diff.field, diff.fieldName].some((value) => typeof value === 'string' && SIGN_REFERENCE_FIELD_PATTERN.test(value.trim()));
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function toOptionalString(value: unknown): string | undefined {
  return typeof value === 'string' && value ? value : undefined;
}

function toBoolean(value: unknown): boolean | undefined {
  if (value === true || value === 'true') return true;
  if (value === false || value === 'false') return false;
  return undefined;
}

function toRecordList(value: unknown): Array<Record<string, unknown>> | undefined {
  if (!Array.isArray(value)) return undefined;
  const records = value.filter(isRecord);
  return records.length ? records : undefined;
}
