import { avatarUrl, firstText, resolveMediaUrl } from "./media";

export type AnyRow = Record<string, any>;

export function arr<T = AnyRow>(value: any): T[] {
  if (Array.isArray(value)) return value;
  if (Array.isArray(value?.records)) return value.records;
  if (Array.isArray(value?.items)) return value.items;
  if (Array.isArray(value?.content)) return value.content;
  if (Array.isArray(value?.list)) return value.list;
  if (Array.isArray(value?.data)) return value.data;
  return [];
}

export { firstText };

export function formatDate(value?: string | number | null) {
  if (!value) return "";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
}

export function formatDateTime(value?: string | number | null) {
  if (!value) return "";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return String(value);
  return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}`;
}

function pad2(value: number) {
  return value < 10 ? `0${value}` : `${value}`;
}

function firstNumber(...values: any[]) {
  const value = firstText(...values);
  if (!value) return undefined;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : undefined;
}

function toBoolean(value: any) {
  if (value === true || value === false) return value;
  const text = firstText(value).toLowerCase();
  return ["true", "1", "yes"].includes(text);
}

function splitTextList(value: any) {
  if (Array.isArray(value)) {
    return value
      .map((item) => (typeof item === "string" ? item : firstText(item?.name, item?.label, item?.value)))
      .map((item) => item.trim())
      .filter(Boolean);
  }
  return firstText(value)
    .split(/[、,，/]/)
    .map((item) => item.trim())
    .filter(Boolean);
}

function numberList(value: any): number[] {
  if (Array.isArray(value)) {
    return value.map((item) => Number(item)).filter((item) => Number.isFinite(item));
  }
  return firstText(value)
    .split(",")
    .map((item) => Number(item.trim()))
    .filter((item) => Number.isFinite(item));
}

function jsonObject(value: any): AnyRow {
  if (!value) return {};
  if (typeof value === "object" && !Array.isArray(value)) {
    const rawValue = (value as AnyRow).value;
    if (typeof rawValue === "string") return jsonObject(rawValue);
    return value as AnyRow;
  }
  if (typeof value !== "string" || !value.trim()) return {};
  try {
    const parsed = JSON.parse(value);
    return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
  } catch {
    return {};
  }
}

function looksLikeHtml(value: any) {
  return /<\/?[a-z][\s\S]*>/i.test(firstText(value));
}

function stripHtmlText(value: any) {
  return firstText(value)
    .replace(/<script[\s\S]*?<\/script>/gi, " ")
    .replace(/<style[\s\S]*?<\/style>/gi, " ")
    .replace(/<[^>]*>/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function normalizeContentBlock(block: AnyRow): AnyRow | null {
  const type = firstText(block.type, block.blockType, block.block_type, block.kind).toLowerCase();
  if (!type) return null;

  if (["text", "paragraph", "rich_text", "markdown"].includes(type)) {
    const content = firstText(block.content, block.text, block.value, block.markdown, block.html);
    return content ? { ...block, type: "text", content } : null;
  }

  if (["html", "legacy_html"].includes(type)) {
    const content = firstText(block.content, block.html, block.value, block.body);
    return content ? { ...block, type: "html", content } : null;
  }

  const metadata = jsonObject(block.metadata);
  if (type === "image") {
    const originalUrl = firstText(block.url, block.src, block.originalUrl, block.original_url, block.fileUrl, block.file_url, metadata.originalUrl, metadata.original_url, metadata.url);
    const displayUrl = firstText(block.webpUrl, block.webp_url, block.thumbnailUrl, block.thumbnail_url, originalUrl);
    if (!displayUrl) return null;
    return {
      ...block,
      type: "image",
      url: resolveMediaUrl(displayUrl),
      src: resolveMediaUrl(displayUrl),
      originalUrl: resolveMediaUrl(originalUrl),
      thumbnailUrl: resolveMediaUrl(firstText(block.thumbnailUrl, block.thumbnail_url, metadata.thumbnailUrl, metadata.thumbnail_url, metadata.coverUrl, metadata.cover_url)),
      webpUrl: resolveMediaUrl(firstText(block.webpUrl, block.webp_url, metadata.webpUrl, metadata.webp_url)),
      alt: firstText(block.alt, block.altText, block.alt_text, block.caption),
      caption: firstText(block.caption, block.title, block.description),
      width: firstNumber(block.width, metadata.width),
      height: firstNumber(block.height, metadata.height),
    };
  }

  if (type === "video") {
    const videoUrls = jsonObject(block.videoUrls ?? block.video_urls ?? metadata.videoUrls ?? metadata.video_urls);
    const originalUrl = firstText(block.url, block.src, block.originalUrl, block.original_url, block.fileUrl, block.file_url, metadata.originalUrl, metadata.original_url, metadata.url);
    const playableUrl = firstText(block.playableUrl, block.playable_url, videoUrls["720p"], videoUrls["480p"], videoUrls.original, originalUrl);
    if (!playableUrl) return null;
    return {
      ...block,
      type: "video",
      url: resolveMediaUrl(playableUrl),
      src: resolveMediaUrl(playableUrl),
      originalUrl: resolveMediaUrl(originalUrl),
      poster: resolveMediaUrl(firstText(block.poster, block.coverUrl, block.cover_url, block.thumbnailUrl, block.thumbnail_url, metadata.coverUrl, metadata.cover_url, metadata.poster)),
      duration: firstNumber(block.duration, block.durationSeconds, block.duration_seconds, metadata.duration),
      caption: firstText(block.caption, block.title, block.description),
    };
  }

  if (type === "audio") {
    const url = firstText(block.url, block.src, block.originalUrl, block.original_url, block.fileUrl, block.file_url, metadata.originalUrl, metadata.url);
    if (!url) return null;
    return {
      ...block,
      type: "audio",
      url: resolveMediaUrl(url),
      src: resolveMediaUrl(url),
      duration: firstNumber(block.duration, block.durationSeconds, block.duration_seconds, metadata.duration),
      caption: firstText(block.caption, block.title, block.description),
    };
  }

  return null;
}

function normalizeParsedContentBlocks(parsed: any): AnyRow[] {
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && firstText(parsed.type, parsed.blockType, parsed.block_type, parsed.kind)) {
    const block = normalizeContentBlock(parsed);
    return block ? [block] : [];
  }
  const source = Array.isArray(parsed) ? parsed : arr(parsed?.blocks ?? parsed?.items ?? parsed?.content ?? parsed?.body);
  return source
    .map((item) => (item && typeof item === "object" ? normalizeContentBlock(item) : null))
    .filter((item): item is AnyRow => Boolean(item));
}

function normalizeContentBlocks(value: any): AnyRow[] {
  if (Array.isArray(value)) return normalizeParsedContentBlocks(value);
  if (value && typeof value === "object") return normalizeParsedContentBlocks(value);
  if (typeof value !== "string") return [];
  const text = value.trim();
  if (!text.startsWith("[") && !text.startsWith("{")) return [];
  try {
    return normalizeParsedContentBlocks(JSON.parse(text));
  } catch {
    return [];
  }
}

function textFromBlocks(blocks: AnyRow[]) {
  return blocks
    .filter((block) => ["text", "html"].includes(firstText(block.type)))
    .map((block) => (firstText(block.type) === "html" ? stripHtmlText(block.content) : firstText(block.content, block.text, block.value)))
    .filter(Boolean)
    .join("\n");
}

function hasMediaBlock(blocks: AnyRow[]) {
  return blocks.some((block) => {
    const type = firstText(block.type).toLowerCase();
    if (["image", "video", "audio"].includes(type)) return true;
    return type === "html" && /<(img|video|audio|iframe)\b/i.test(firstText(block.content));
  });
}

function mediaBlockFromRow(row: AnyRow): AnyRow | null {
  const type = firstText(row.mediaType, row.media_type, row.type).toLowerCase();
  if (!["image", "video", "audio"].includes(type)) return null;
  const role = firstText(row.role).toLowerCase();
  const isCover = row.isCover === true || row.is_cover === true || role === "cover";
  if (isCover) return null;

  const metadata = jsonObject(row.metadata);
  const videoUrls = jsonObject(row.videoUrls ?? row.video_urls ?? metadata.videoUrls ?? metadata.video_urls);
  const originalUrl = firstText(row.url, row.src, row.originalUrl, row.original_url, metadata.originalUrl, metadata.url);
  const playableUrl = type === "video" ? firstText(videoUrls["720p"], videoUrls["480p"], videoUrls.original, originalUrl) : originalUrl;
  if (!playableUrl) return null;

  const common = {
    type,
    url: resolveMediaUrl(playableUrl),
    src: resolveMediaUrl(playableUrl),
    originalUrl: resolveMediaUrl(originalUrl),
    fileId: firstNumber(row.fileId, row.file_id),
    caption: firstText(row.caption, row.altText, row.alt_text),
  };

  if (type === "image") {
    return {
      ...common,
      thumbnailUrl: resolveMediaUrl(firstText(row.thumbnailUrl, row.thumbnail_url, metadata.thumbnailUrl, metadata.coverUrl)),
      webpUrl: resolveMediaUrl(firstText(row.webpUrl, row.webp_url, metadata.webpUrl)),
      alt: firstText(row.altText, row.alt_text, row.caption),
      width: firstNumber(row.width, metadata.width),
      height: firstNumber(row.height, metadata.height),
    };
  }

  if (type === "video") {
    return {
      ...common,
      poster: resolveMediaUrl(firstText(row.poster, row.coverUrl, row.cover_url, row.thumbnailUrl, row.thumbnail_url, metadata.coverUrl, metadata.poster)),
      duration: firstNumber(row.duration, row.durationSeconds, row.duration_seconds, metadata.duration),
      resolution: firstText(row.resolution, metadata.resolution),
    };
  }

  return {
    ...common,
    duration: firstNumber(row.duration, row.durationSeconds, row.duration_seconds, metadata.duration),
  };
}

function mediaBlocksFrom(row: AnyRow) {
  return arr(row.medias ?? row.mediaItems ?? row.media_items)
    .map(mediaBlockFromRow)
    .filter((block): block is AnyRow => Boolean(block));
}

export function normalizeContent(row: AnyRow) {
  const type = firstText(row.type).toLowerCase();
  const legacyHtmlBlocks = firstText(row.body_format, row.bodyFormat).toLowerCase() === "legacy_html" && looksLikeHtml(row.body)
    ? [{ type: "html", content: firstText(row.body) }]
    : [];
  const bodyParsedBlocks = normalizeContentBlocks(row.bodyParsed);
  const bodyJsonBlocks = normalizeContentBlocks(row.bodyJson).length > 0 ? normalizeContentBlocks(row.bodyJson) : normalizeContentBlocks(row.body_json);
  const bodyBlocks = normalizeContentBlocks(row.body);
  const blocks = legacyHtmlBlocks.length > 0
    ? legacyHtmlBlocks
    : bodyParsedBlocks.length > 0
      ? bodyParsedBlocks
      : bodyJsonBlocks.length > 0
        ? bodyJsonBlocks
        : bodyBlocks.length > 0
          ? bodyBlocks
          : normalizeContentBlocks(row.content);
  const supplementalMediaBlocks = mediaBlocksFrom(row);
  const renderBlocks = supplementalMediaBlocks.length > 0 && !hasMediaBlock(blocks) ? [...blocks, ...supplementalMediaBlocks] : blocks;
  const blockText = textFromBlocks(renderBlocks);
  const rawText = firstText(row.body, row.content, row.description, row.summary);
  const category = firstText(row.category, row.category_name, type === "learning" ? row.source_name : undefined, row.type, "综合");
  const image = resolveMediaUrl(firstText(row.cover_image_url, row.coverImageUrl, row.coverUrl, row.image, row.image_url, row.thumbnail));
  const status = firstText(row.status, row.auditStatus, row.audit_status, "pending");
  const source = firstText(row.source_name, row.sourceName, row.source, row.branch_name, row.branchNames, "大连市新联会");
  const id = Number(firstText(row.id, row.contentId, row.content_id)) || 0;

  return {
    ...row,
    id,
    contentId: id,
    content_id: id,
    title: firstText(row.title, row.name),
    summary: firstText(row.summary, row.excerpt, row.description, blockText, renderBlocks.length > 0 ? "" : rawText).slice(0, 120),
    content: renderBlocks.length > 0 ? blockText : rawText,
    blocks: renderBlocks,
    bodyParsed: renderBlocks,
    category,
    tags: splitTextList(row.tags).concat(splitTextList(firstText(row.tagNames, row.tag_names, row.labels, row.label_names))).filter((item, index, list) => list.indexOf(item) === index),
    date: formatDate(firstText(row.published_at, row.publishedAt, row.submissionDate, row.submission_date, row.created_at, row.createdAt, row.updated_at, row.updatedAt, row.date)),
    publishedAt: firstText(row.publishedAt, row.published_at),
    createdAt: firstText(row.createdAt, row.created_at),
    updatedAt: firstText(row.updatedAt, row.updated_at),
    source,
    sourceName: source,
    image,
    coverImageUrl: image,
    views: Number(row.view_count ?? row.views ?? row.viewCount ?? 0),
    status,
    statusText: firstText(row.statusText, row.status_text),
    reviewReason: firstText(row.reviewReason, row.review_reason, row.auditComment, row.audit_comment),
    canResubmit: toBoolean(row.canResubmit ?? row.can_resubmit),
    can_resubmit: toBoolean(row.can_resubmit ?? row.canResubmit),
    branchIds: numberList(row.branchIds ?? row.branch_ids),
    branchNames: Array.isArray(row.branchNames)
      ? row.branchNames.map((item: any) => firstText(item)).filter(Boolean)
      : splitTextList(firstText(row.branchNames, row.branch_names)),
    raw: row,
  };
}

export function normalizeActivity(row: AnyRow) {
  const startsAt = firstText(row.startsAt, row.starts_at, row.startTime, row.start_time, row.time);
  const endsAt = firstText(row.endsAt, row.ends_at, row.endTime, row.end_time);
  const registered = Number(row.registered_count ?? row.registration_count ?? row.enrolled ?? row.registered ?? row.registeredCount ?? row.display_registration_count ?? row.displayRegistrationCount ?? 0);
  const image = resolveMediaUrl(firstText(row.cover_image_url, row.coverImageUrl, row.coverUrl, row.image, row.image_url));
  const id = Number(firstText(row.id, row.activityId, row.activity_id)) || firstText(row.id, row.activityId, row.activity_id);
  const title = firstText(row.title, row.name, row.activity_title, row.activityTitle);
  return {
    ...row,
    id,
    activityId: id,
    activity_id: id,
    title,
    activityTitle: title,
    activity_title: title,
    startsAt,
    starts_at: firstText(row.starts_at, startsAt),
    endsAt,
    ends_at: firstText(row.ends_at, endsAt),
    time: endsAt ? `${formatDateTime(startsAt)} - ${formatDateTime(endsAt)}` : formatDateTime(startsAt),
    location: firstText(row.location, row.address, "待定"),
    registered,
    registrationCount: registered,
    registeredCount: registered,
    displayRegistrationCount: registered,
    total: Number(row.capacity ?? row.total ?? row.quota ?? 0),
    capacity: Number(row.capacity ?? row.total ?? row.quota ?? 0),
    image,
    coverImageUrl: image,
    status: firstText(row.status, "recruiting"),
    statusText: firstText(row.statusText, row.status_text),
    reviewReason: firstText(row.reviewReason, row.review_reason),
    canResubmit: toBoolean(row.canResubmit ?? row.can_resubmit),
    can_resubmit: toBoolean(row.can_resubmit ?? row.canResubmit),
    registrationStatus: firstText(row.registrationStatus, row.registration_status),
    registrationMessage: firstText(row.registrationMessage, row.registration_message),
    canRegister: toBoolean(row.canRegister ?? row.can_register),
    canCheckin: toBoolean(row.canCheckin ?? row.can_checkin),
    isFull: toBoolean(row.isFull ?? row.is_full),
    registrationDeadlinePassed: toBoolean(row.registrationDeadlinePassed ?? row.registration_deadline_passed),
    checkinMessage: firstText(row.checkinMessage, row.checkin_message),
    branchIds: numberList(row.branchIds ?? row.branch_ids),
    branchNames: Array.isArray(row.branchNames) ? row.branchNames.map((item: any) => firstText(item)).filter(Boolean) : splitTextList(firstText(row.branchNames, row.branch_names)),
    participantScope: firstText(row.participant_scope, row.participantScope, "branch_members"),
    registerDeadline: firstText(row.registerDeadline, row.register_deadline),
    register_deadline: firstText(row.register_deadline, row.registerDeadline),
    description: firstText(row.description, row.content, row.summary),
    content: firstText(row.description, row.content, row.summary),
    branchName: firstText(row.branchName, row.branch_name, row.branch, row.source),
    branch_name: firstText(row.branch_name, row.branchName, row.branch, row.source),
    source: firstText(row.source, row.branch_name, "大连市新联会"),
    views: Number(row.views ?? row.view_count ?? 0),
    columnId: firstNumber(row.columnId, row.column_id) ?? null,
    columnName: firstText(row.columnName, row.column_name) || null,
    raw: row,
  };
}

export function normalizeMutualAid(row: AnyRow) {
  const postType = firstText(row.postType, row.post_type, row.type);
  const type = postType === "need" || postType === "demand" ? "need" : "resource";
  const contactVisible = row.contactVisible === true || row.contact_visible === true;
  const publisherName = firstText(row.publisherName, row.publisher_name, row.author, row.contact_name, row.user_name, row.created_by_name, "会员");
  const tags = splitTextList(row.tags).length > 0 ? splitTextList(row.tags) : splitTextList(firstText(row.tagsText, row.tags_text, row.tag_names, row.category));
  return {
    ...row,
    id: Number(firstText(row.id, row.postId, row.post_id)) || firstText(row.id, row.postId, row.post_id),
    title: firstText(row.title, row.name),
    tags,
    tagsText: firstText(row.tagsText, row.tags_text, tags.join("、")),
    tags_text: firstText(row.tags_text, row.tagsText, tags.join("、")),
    type,
    postType: type,
    post_type: type,
    category: firstText(row.category, type === "resource" ? "资源供给" : "合作需求"),
    date: formatDate(firstText(row.created_at, row.createdAt, row.updated_at, row.updatedAt, row.date)),
    views: Number(row.views ?? row.view_count ?? 0),
    source: firstText(row.branch_name, row.branchName, row.source, "大连市新联会"),
    content: firstText(row.description, row.content, row.body),
    description: firstText(row.description, row.content, row.body),
    status: firstText(row.status),
    statusText: firstText(row.statusText, row.status_text),
    reviewReason: firstText(row.reviewReason, row.review_reason),
    canResubmit: toBoolean(row.canResubmit ?? row.can_resubmit),
    can_resubmit: toBoolean(row.can_resubmit ?? row.canResubmit),
    branchId: firstNumber(row.branchId, row.branch_id),
    branchName: firstText(row.branchName, row.branch_name),
    publisherName,
    publisher_name: publisherName,
    publisherTitle: firstText(row.publisherTitle, row.publisher_title, row.branch_name, row.company, row.unit, "会员"),
    publisher_title: firstText(row.publisher_title, row.publisherTitle, row.branch_name, row.company, row.unit, "会员"),
    publisherAvatarUrl: resolveMediaUrl(firstText(row.publisherAvatarUrl, row.publisher_avatar_url, row.avatarUrl, row.avatar_url, row.avatar)),
    contactVisible,
    contact_visible: contactVisible,
    contactPhone: contactVisible ? firstText(row.contactPhone, row.contact_phone, row.phone) : "",
    contactWechat: contactVisible ? firstText(row.contactWechat, row.contact_wechat, row.wechat) : "",
    contactRequests: arr(row.contactRequests ?? row.contact_requests),
    contactRequestStatus: firstText(row.contactRequestStatus, row.contact_request_status),
    raw: row,
  };
}

export function normalizeServiceProject(row: AnyRow) {
  const image = resolveMediaUrl(firstText(row.cover_image_url, row.coverImageUrl, row.coverUrl, row.image, row.image_url));
  return {
    ...row,
    id: Number(firstText(row.id, row.projectId, row.project_id)) || firstText(row.id, row.projectId, row.project_id),
    title: firstText(row.title, row.name),
    time: firstText(row.time, row.starts_at, row.startsAt, row.created_at, row.createdAt) ? formatDateTime(firstText(row.time, row.starts_at, row.startsAt, row.created_at, row.createdAt)) : "长期服务",
    location: firstText(row.location, row.address, "待定"),
    memberCount: Number(row.memberCount ?? row.member_count ?? row.experts ?? 0),
    member_count: Number(row.member_count ?? row.memberCount ?? row.experts ?? 0),
    description: firstText(row.description, row.content, row.service_target, row.serviceTarget, row.summary),
    status: firstText(row.status, "ongoing"),
    date: formatDate(firstText(row.created_at, row.createdAt, row.updated_at, row.updatedAt, row.starts_at, row.startsAt)),
    branchName: firstText(row.branchName, row.branch_name, row.branch, row.source),
    branch_name: firstText(row.branch_name, row.branchName, row.branch, row.source),
    views: Number(row.views ?? row.view_count ?? 0),
    image,
    coverImageUrl: image,
    serviceTarget: firstText(row.serviceTarget, row.service_target, row.target),
    service_target: firstText(row.service_target, row.serviceTarget, row.target),
    startsAt: firstText(row.startsAt, row.starts_at),
    starts_at: firstText(row.starts_at, row.startsAt),
    endsAt: firstText(row.endsAt, row.ends_at),
    ends_at: firstText(row.ends_at, row.endsAt),
    branchId: firstNumber(row.branchId, row.branch_id),
    statusText: firstText(row.statusText, row.status_text),
    reviewReason: firstText(row.reviewReason, row.review_reason),
    canResubmit: toBoolean(row.canResubmit ?? row.can_resubmit),
    can_resubmit: toBoolean(row.can_resubmit ?? row.canResubmit),
    raw: row,
  };
}

export function normalizeServiceTeam(row: AnyRow) {
  const title = firstText(row.name, row.title);
  const image = resolveMediaUrl(firstText(row.cover_image_url, row.coverImageUrl, row.logo_url, row.logoUrl, row.image, row.avatar_url, row.avatarUrl));
  return {
    ...row,
    id: Number(firstText(row.id, row.teamId, row.team_id)) || firstText(row.id, row.teamId, row.team_id),
    title,
    name: title,
    date: formatDate(firstText(row.created_at, row.createdAt, row.updated_at, row.updatedAt)),
    source: firstText(row.branch_name, row.branchName, row.source, "大连市新联会"),
    views: Number(row.views ?? row.view_count ?? 0),
    image,
    coverImageUrl: image,
    tags: splitTextList(row.tags).length > 0 ? splitTextList(row.tags) : splitTextList(firstText(row.tags_text, row.tagsText, row.tag_names, row.category)),
    tagsText: firstText(row.tagsText, row.tags_text),
    introduction: firstText(row.description, row.introduction, row.summary),
    description: firstText(row.description, row.introduction, row.summary),
    memberCount: Number(row.memberCount ?? row.member_count ?? row.members?.length ?? 0),
    member_count: Number(row.member_count ?? row.memberCount ?? row.members?.length ?? 0),
    members: arr(row.members).map((member: AnyRow) => ({
      ...member,
      id: firstText(member.id, member.user_id, member.member_id),
      name: firstText(member.name, member.display_name),
      title: firstText(member.title, member.position, member.unit),
      organization: firstText(member.organization, member.unit, member.branch_name),
      avatar: avatarUrl(firstText(member.avatar_url, member.avatarUrl), firstText(member.name, member.display_name)),
      role: firstText(member.role_name, member.role, "成员"),
    })),
    raw: row,
  };
}

export function normalizeMember(row: AnyRow) {
  const id = firstText(row.user_id, row.userId, row.id, row.member_id);
  const numericId = Number(id) || 0;
  const name = firstText(row.display_name, row.displayName, row.name, row.nickname, `会员${id}`);
  const professions = splitTextList(firstText(row.tag_names, row.tagNames, row.label_names, row.labelNames, row.expertise, row.category));
  const relationshipStatus = firstText(row.relationship_status, row.relationshipStatus, row.friendship_status, row.friendshipStatus, row.friendStatus, row.status, "none");
  const status = row.isFriend === true || row.is_friend === true
    ? "friend"
    : relationshipStatus === "pending_sent" || relationshipStatus === "pending_received"
      ? "pending"
      : relationshipStatus === "accepted"
        ? "friend"
        : relationshipStatus;
  const avatar = avatarUrl(firstText(row.avatar_url, row.avatarUrl, row.avatar), name);
  return {
    ...row,
    id: numericId,
    userId: Number(firstText(row.userId, row.user_id, row.id)) || numericId,
    user_id: Number(firstText(row.user_id, row.userId, row.id)) || numericId,
    displayName: name,
    display_name: name,
    name,
    title: firstText(row.position, row.title, row.unit, "会员"),
    company: firstText(row.unit, row.company, row.organization),
    organization: firstText(row.branch_names, row.branchNames, row.branch_name, row.branchName, row.organization, "大连市新联会"),
    professions,
    tags: professions,
    tag_names: professions.join("、"),
    branch_names: firstText(row.branch_names, row.branchNames, row.branch_name, row.branchName),
    avatar,
    avatarUrl: avatar,
    avatar_url: avatar,
    bio: firstText(row.bio, row.intro, row.introduction, row.unit, row.position, "暂无简介"),
    status,
    friendshipStatus: status,
    friendship_status: status,
    category: firstText(row.category, professions[0], "会员"),
    phone: firstText(row.phone),
    wechat: firstText(row.wechat),
    raw: row,
  };
}

export function normalizeSurvey(row: AnyRow) {
  const image = resolveMediaUrl(firstText(row.cover_image_url, row.coverImageUrl, row.coverUrl, row.image, row.image_url));
  return {
    ...row,
    id: Number(firstText(row.id, row.survey_id)) || firstText(row.id, row.survey_id),
    title: firstText(row.title, row.name),
    deadline: formatDate(firstText(row.ends_at, row.endsAt, row.deadline, row.end_time, row.updated_at, row.updatedAt)),
    image,
    coverImageUrl: image,
    status: firstText(row.status, "ongoing"),
    description: firstText(row.description, row.summary),
    views: Number(row.views ?? row.view_count ?? 0),
    canSubmit: row.canSubmit === true || row.can_submit === true,
    can_submit: row.can_submit === true || row.canSubmit === true,
    submitMessage: firstText(row.submitMessage, row.submit_message),
    submit_message: firstText(row.submit_message, row.submitMessage),
    hasSubmitted: row.hasSubmitted === true || row.has_submitted === true,
    has_submitted: row.has_submitted === true || row.hasSubmitted === true,
    submitCount: Number(row.submitCount ?? row.submit_count ?? 0),
    lastSubmittedAt: firstText(row.lastSubmittedAt, row.last_submitted_at),
    allowMultipleSubmit: row.allowMultipleSubmit === true || row.allow_multiple_submit === true,
    startsAt: firstText(row.startsAt, row.starts_at),
    starts_at: firstText(row.starts_at, row.startsAt),
    endsAt: firstText(row.endsAt, row.ends_at),
    ends_at: firstText(row.ends_at, row.endsAt),
    raw: row,
  };
}

export function normalizeQuestion(row: AnyRow) {
  const typeText = firstText(row.type, row.question_type);
  const type = typeText.includes("multi") || typeText === "multiple" ? "multiple" : typeText.includes("text") ? "text" : "single";
  return {
    ...row,
    id: Number(firstText(row.id, row.question_id)) || firstText(row.id, row.question_id),
    question: firstText(row.question, row.title, row.content),
    type,
    options: Array.isArray(row.options)
      ? row.options.map((option: any, index: number) =>
          typeof option === "string"
            ? { id: index + 1, label: option }
            : { ...option, id: Number(firstText(option.id, option.option_id, option.value, index + 1)), label: firstText(option.label, option.text, option.value) }
        )
      : [],
    raw: row,
  };
}

export function normalizeBranch(row: AnyRow) {
  const homeSubtitle = firstText(row.homeSubtitle, row.home_subtitle, row.subtitle);
  return {
    ...row,
    id: Number(row.id),
    name: firstText(row.name, row.branch_name),
    shortName: firstText(row.shortName, row.short_name, row.name, row.branch_name),
    homeSubtitle,
    home_subtitle: homeSubtitle,
    subtitle: homeSubtitle,
    type: firstText(row.type),
    level: firstText(row.level),
    raw: row,
  };
}
