type AppDataRecord = Record<string, unknown>;

interface SkipReason {
  key: string;
  id?: string;
  reason: string;
}

interface ProductRow {
  id: string;
  name: string;
  description: string;
  createdAt?: string;
  updatedAt?: string;
}

interface ProjectRow {
  id: string;
  productId: string;
  name: string;
  description: string;
  createdAt?: string;
  updatedAt?: string;
}

interface VersionRow {
  id: string;
  productId: string;
  projectId?: string;
  name: string;
  description: string;
  releaseDate?: string;
  createdAt?: string;
  updatedAt?: string;
}

interface UserRow {
  id: string;
  email: string;
  name: string;
  avatar?: string;
  createdAt?: string;
  updatedAt?: string;
}

interface RequirementRow {
  id: string;
  productId: string;
  projectId?: string;
  versionId?: string;
  code: string;
  title: string;
  description: string;
  status: string;
  priority: number;
  type?: string;
  sourceType?: string;
  sourceTarget?: string;
  platform?: string;
  creatorId?: string;
  createdAt?: string;
  updatedAt?: string;
}

interface TaskCategoryRow {
  id: string;
  name: string;
  code?: string;
  group: string;
  isSystem: boolean;
  createdAt?: string;
  updatedAt?: string;
}

interface VersionPlanRow {
  id: string;
  versionId: string;
  productId: string;
  projectId?: string;
  type: string;
  title: string;
  status: string;
  ownerId?: string;
  expectedStartAt?: string;
  expectedEndAt?: string;
  actualStartAt?: string;
  completedAt?: string;
  resultUrl?: string;
  requirementCoverage: unknown[];
  logs: unknown[];
  createdAt?: string;
  updatedAt?: string;
}

interface DevTaskRow {
  id: string;
  versionId: string;
  productId: string;
  projectId: string;
  requirementId?: string;
  requirementProductId?: string;
  categoryId?: string;
  code: string;
  title: string;
  description: string;
  status: string;
  priority: number;
  assigneeId?: string;
  creatorId?: string;
  isBlocked: boolean;
  blockReason?: string;
  expectedStartAt?: string;
  expectedEndAt?: string;
  startDate?: string;
  completedAt?: string;
  estimateHours?: number;
  aiEstimateHours?: number;
  references: unknown[];
  aiDraft: boolean;
  aiDraftAt?: string;
  createdAt?: string;
  updatedAt?: string;
}

interface TestCaseRow {
  id: string;
  versionId: string;
  productId: string;
  projectId: string;
  requirementId?: string;
  requirementProductId?: string;
  categoryId?: string;
  code: string;
  title: string;
  description: string;
  status: string;
  roundNo: number;
  priority: number;
  assigneeId?: string;
  creatorId?: string;
  plannedTestAt?: string;
  plannedEndAt?: string;
  startedAt?: string;
  completedAt?: string;
  estimateHours?: number;
  aiEstimateHours?: number;
  references: unknown[];
  aiDraft: boolean;
  aiDraftAt?: string;
  createdAt?: string;
  updatedAt?: string;
}

interface BugRow {
  id: string;
  versionId: string;
  productId: string;
  projectId: string;
  testCaseId?: string;
  testCaseVersionId?: string;
  code: string;
  title: string;
  description: string;
  status: string;
  severity: string;
  priority: number;
  assigneeId?: string;
  reporterId?: string;
  plannedFixAt?: string;
  resolvedAt?: string;
  closedAt?: string;
  resolution?: string;
  createdAt?: string;
  updatedAt?: string;
}

interface WorkActivityRow {
  id: string;
  versionId?: string;
  productId?: string;
  projectId?: string;
  actorId?: string;
  actorName: string;
  sourceType: string;
  sourceId: string;
  sourceVersionId?: string;
  action: string;
  title: string;
  metadata: unknown;
  occurredAt: string;
  createdAt: string;
}

interface TaskWorklogRow {
  id: string;
  versionId?: string;
  productId?: string;
  projectId?: string;
  userId?: string;
  sourceType: string;
  sourceId: string;
  sourceVersionId?: string;
  workDate: string;
  hours: number;
  content: string;
  createdAt: string;
  updatedAt?: string;
}

interface OvertimeRecordRow {
  id: string;
  productId?: string;
  projectId?: string;
  versionId?: string;
  userId?: string;
  reason: string;
  startAt: string;
  endAt: string;
  hours: number;
  createdAt: string;
  updatedAt?: string;
}

interface XiaobaoRiskSnapshotRow {
  id: string;
  versionId: string;
  snapshotDate: string;
  riskLevel: string;
  riskScore: number;
  riskSignature: string;
  snapshot: unknown;
  createdAt: string;
}

interface XiaobaoRiskInsightRow {
  id: string;
  versionId: string;
  riskSignature: string;
  status: string;
  insight: unknown;
  createdAt: string;
  updatedAt?: string;
}

interface XiaobaoRiskSummaryRow {
  versionId: string;
  riskLevel: string;
  riskScore: number;
  confidence: number;
  forecastReleaseDate?: string;
  riskSignature: string;
  summary: unknown;
  dirty: boolean;
  recomputedAt?: string;
  updatedAt?: string;
}

interface SourceContext {
  versionId?: string;
  productId?: string;
  projectId?: string;
}

export interface V22MappedRows {
  products: ProductRow[];
  projects: ProjectRow[];
  versions: VersionRow[];
  users: UserRow[];
  requirements: RequirementRow[];
  taskCategories: TaskCategoryRow[];
  versionPlans: VersionPlanRow[];
  devTasks: DevTaskRow[];
  testCases: TestCaseRow[];
  bugs: BugRow[];
  workActivities: WorkActivityRow[];
  taskWorklogs: TaskWorklogRow[];
  overtimeRecords: OvertimeRecordRow[];
  xiaobaoRiskSnapshots: XiaobaoRiskSnapshotRow[];
  xiaobaoRiskInsights: XiaobaoRiskInsightRow[];
  xiaobaoRiskSummaries: XiaobaoRiskSummaryRow[];
  aiLogs: unknown[];
  skipped: SkipReason[];
}

export function mapAppDataToV22Rows(appData: Record<string, unknown>): V22MappedRows {
  const skipped: SkipReason[] = [];
  const products: ProductRow[] = [];
  const projects: ProjectRow[] = [];
  const versions: VersionRow[] = [];
  const users: UserRow[] = [];
  const requirements: RequirementRow[] = [];
  const taskCategories: TaskCategoryRow[] = [];
  const versionPlans: VersionPlanRow[] = [];
  const devTasks: DevTaskRow[] = [];
  const testCases: TestCaseRow[] = [];
  const bugs: BugRow[] = [];
  const workActivities: WorkActivityRow[] = [];
  const taskWorklogs: TaskWorklogRow[] = [];
  const overtimeRecords: OvertimeRecordRow[] = [];
  const xiaobaoRiskSnapshots: XiaobaoRiskSnapshotRow[] = [];
  const xiaobaoRiskInsights: XiaobaoRiskInsightRow[] = [];
  const xiaobaoRiskSummaries: XiaobaoRiskSummaryRow[] = [];

  const productById = new Map<string, ProductRow>();
  const projectById = new Map<string, ProjectRow>();
  const versionById = new Map<string, VersionRow>();
  const requirementById = new Map<string, RequirementRow>();
  const devTaskById = new Map<string, DevTaskRow>();
  const testCaseById = new Map<string, TestCaseRow>();
  const bugById = new Map<string, BugRow>();
  const versionPlanById = new Map<string, VersionPlanRow>();

  const productRecords = asArray(appData['products-overview']);
  for (const [productIndex, product] of productRecords.entries()) {
    const productId = stringField(product, 'id') ?? `product-${productIndex + 1}`;
    const productRow: ProductRow = {
      id: productId,
      name: stringField(product, 'name') ?? productId,
      description: stringField(product, 'description') ?? '',
      createdAt: stringField(product, 'createdAt'),
      updatedAt: stringField(product, 'updatedAt') ?? stringField(product, 'createdAt'),
    };
    products.push(productRow);
    productById.set(productId, productRow);

    const productProjects = asArray(product.projects);
    for (const [projectIndex, project] of productProjects.entries()) {
      const projectId = stringField(project, 'id') ?? `${productId}-project-${projectIndex + 1}`;
      const projectRow: ProjectRow = {
        id: projectId,
        productId,
        name: stringField(project, 'name') ?? projectId,
        description: stringField(project, 'description') ?? '',
        createdAt: stringField(project, 'createdAt') ?? productRow.createdAt,
        updatedAt: stringField(project, 'updatedAt') ?? stringField(project, 'createdAt') ?? productRow.updatedAt,
      };
      projects.push(projectRow);
      projectById.set(projectId, projectRow);
    }

    const productVersions = asArray(product.versions);
    for (const [versionIndex, version] of productVersions.entries()) {
      const versionId = stringField(version, 'id') ?? `${productId}-version-${versionIndex + 1}`;
      const projectId = stringField(version, 'projectId') ?? inferProjectIdForVersion(version, productProjects);
      const versionRow: VersionRow = {
        id: versionId,
        productId,
        projectId,
        name: stringField(version, 'name') ?? versionId,
        description: stringField(version, 'description') ?? '',
        releaseDate: stringField(version, 'releaseDate') ?? stringField(version, 'expectedReleaseDate'),
        createdAt: stringField(version, 'createdAt') ?? productRow.createdAt,
        updatedAt: stringField(version, 'updatedAt') ?? stringField(version, 'createdAt') ?? productRow.updatedAt,
      };
      versions.push(versionRow);
      versionById.set(versionId, versionRow);
    }
  }

  const nextEmail = uniqueEmailFactory();
  for (const [index, member] of readNestedArray(appData.members, 'members').entries()) {
    const id = stringField(member, 'id') ?? `member-${index + 1}`;
    const username = stringField(member, 'username') ?? id;
    const email = normalizeMemberEmail(stringField(member, 'email'), username, id);
    users.push({
      id,
      email: nextEmail(email),
      name: stringField(member, 'name') ?? username,
      avatar: stringField(member, 'avatar'),
      createdAt: stringField(member, 'createdAt'),
      updatedAt: stringField(member, 'updatedAt') ?? stringField(member, 'createdAt'),
    });
  }

  const defaultProductId = products[0]?.id;
  const requirementCode = scopedCodeFactory('REQ');
  for (const [index, requirement] of readNestedArray(appData.requirements, 'requirements').entries()) {
    const id = stringField(requirement, 'id') ?? `requirement-${index + 1}`;
    const productId = stringField(requirement, 'productId') ?? defaultProductId;
    if (!productId) {
      skipped.push({ key: 'requirements', id, reason: 'missing product partition context' });
      continue;
    }
    const versionId = stringField(requirement, 'versionId');
    const version = versionId ? versionById.get(versionId) : undefined;
    const platforms = unknownArray(requirement.platforms)
      .map((platform) => stringFromUnknown(platform))
      .filter((platform): platform is string => Boolean(platform));
    const row: RequirementRow = {
      id,
      productId,
      projectId: stringField(requirement, 'projectId') ?? version?.projectId,
      versionId,
      code: requirementCode(productId, stringField(requirement, 'code'), index),
      title: stringField(requirement, 'title') ?? id,
      description: stringField(requirement, 'description') ?? '',
      status: stringField(requirement, 'status') ?? 'pending_review',
      priority: priorityRank(requirement.priority),
      type: stringField(requirement, 'type') ?? stringField(requirement, 'typeId'),
      sourceType: stringField(requirement, 'sourceType'),
      sourceTarget: stringField(requirement, 'sourceTarget'),
      platform: platforms.join(',') || stringField(requirement, 'platform'),
      creatorId: stringField(requirement, 'creatorId') ?? stringField(requirement, 'creator'),
      createdAt: stringField(requirement, 'createdAt'),
      updatedAt: stringField(requirement, 'updatedAt') ?? stringField(requirement, 'createdAt'),
    };
    requirements.push(row);
    requirementById.set(row.id, row);
  }

  for (const [index, category] of asArray(appData['task-categories']).entries()) {
    const id = stringField(category, 'id') ?? `category-${index + 1}`;
    taskCategories.push({
      id,
      name: stringField(category, 'name') ?? id,
      code: stringField(category, 'code'),
      group: stringField(category, 'group') ?? 'other',
      isSystem: booleanField(category, 'isSystem') ?? false,
      createdAt: stringField(category, 'createdAt'),
      updatedAt: stringField(category, 'updatedAt') ?? stringField(category, 'createdAt'),
    });
  }

  for (const [index, plan] of asArray(appData['version-plans']).entries()) {
    const id = stringField(plan, 'id') ?? `version-plan-${index + 1}`;
    const versionId = stringField(plan, 'versionId');
    const version = versionId ? versionById.get(versionId) : undefined;
    if (!versionId || !version?.productId) {
      skipped.push({ key: 'version-plans', id, reason: 'missing version partition context' });
      continue;
    }
    const row: VersionPlanRow = {
      id,
      versionId,
      productId: version.productId,
      projectId: version.projectId,
      type: stringField(plan, 'type') ?? 'product',
      title: stringField(plan, 'title') ?? id,
      status: stringField(plan, 'status') ?? 'pending',
      ownerId: stringField(plan, 'ownerId') ?? stringField(plan, 'owner'),
      expectedStartAt: stringField(plan, 'expectedStartAt') ?? stringField(plan, 'startTime'),
      expectedEndAt: stringField(plan, 'expectedEndAt') ?? stringField(plan, 'endTime'),
      actualStartAt: stringField(plan, 'actualStartAt'),
      completedAt: stringField(plan, 'completedAt'),
      resultUrl: stringField(plan, 'resultUrl'),
      requirementCoverage: unknownArray(plan.requirementCoverage),
      logs: unknownArray(plan.logs),
      createdAt: stringField(plan, 'createdAt'),
      updatedAt: stringField(plan, 'updatedAt') ?? stringField(plan, 'completedAt') ?? stringField(plan, 'createdAt'),
    };
    versionPlans.push(row);
    versionPlanById.set(row.id, row);
  }

  const devCode = scopedCodeFactory('DEV');
  for (const [index, task] of asArray(appData['dev-tasks']).entries()) {
    const id = stringField(task, 'id') ?? `dev-task-${index + 1}`;
    const context = resolveVersionContext(task, requirementById, versionById);
    if (!context.versionId || !context.productId || !context.projectId) {
      skipped.push({ key: 'dev-tasks', id, reason: 'missing version partition context' });
      continue;
    }
    const requirementId = stringField(task, 'requirementId');
    const requirement = requirementId ? requirementById.get(requirementId) : undefined;
    const row: DevTaskRow = {
      id,
      versionId: context.versionId,
      productId: context.productId,
      projectId: context.projectId,
      requirementId,
      requirementProductId: requirement?.productId,
      categoryId: stringField(task, 'categoryId'),
      code: devCode(context.versionId, stringField(task, 'code') ?? stringField(task, 'taskNo'), index),
      title: stringField(task, 'title') ?? id,
      description: stringField(task, 'description') ?? '',
      status: stringField(task, 'status') ?? 'todo',
      priority: priorityRank(task.priority),
      assigneeId: stringField(task, 'assigneeId'),
      creatorId: stringField(task, 'creatorId') ?? stringField(task, 'createdBy'),
      isBlocked: booleanField(task, 'isBlocked') ?? false,
      blockReason: stringField(task, 'blockReason'),
      expectedStartAt: stringField(task, 'expectedStartAt'),
      expectedEndAt: stringField(task, 'expectedEndAt'),
      startDate: stringField(task, 'startDate') ?? stringField(task, 'actualStartAt'),
      completedAt: stringField(task, 'completedAt') ?? stringField(task, 'actualEndAt'),
      estimateHours: numberField(task, 'estimateHours'),
      aiEstimateHours: numberField(task, 'aiEstimateHours'),
      references: unknownArray(task.references),
      aiDraft: booleanField(task, 'aiDraft') ?? false,
      aiDraftAt: stringField(task, 'aiDraftAt'),
      createdAt: stringField(task, 'createdAt'),
      updatedAt: stringField(task, 'updatedAt') ?? stringField(task, 'createdAt'),
    };
    devTasks.push(row);
    devTaskById.set(row.id, row);
  }

  const testCode = scopedCodeFactory('TC');
  for (const [index, testCase] of asArray(appData['test-cases']).entries()) {
    const id = stringField(testCase, 'id') ?? `test-case-${index + 1}`;
    const context = resolveVersionContext(testCase, requirementById, versionById);
    if (!context.versionId || !context.productId || !context.projectId) {
      skipped.push({ key: 'test-cases', id, reason: 'missing version partition context' });
      continue;
    }
    const requirementId = stringField(testCase, 'requirementId');
    const requirement = requirementId ? requirementById.get(requirementId) : undefined;
    const row: TestCaseRow = {
      id,
      versionId: context.versionId,
      productId: context.productId,
      projectId: context.projectId,
      requirementId,
      requirementProductId: requirement?.productId,
      categoryId: stringField(testCase, 'categoryId'),
      code: testCode(context.versionId, stringField(testCase, 'code') ?? stringField(testCase, 'caseNo'), index),
      title: stringField(testCase, 'title') ?? id,
      description: stringField(testCase, 'description') ?? '',
      status: stringField(testCase, 'status') ?? 'pending',
      roundNo: integerField(testCase, 'roundNo') ?? 1,
      priority: priorityRank(testCase.priority),
      assigneeId: stringField(testCase, 'assigneeId'),
      creatorId: stringField(testCase, 'creatorId') ?? stringField(testCase, 'createdBy'),
      plannedTestAt: stringField(testCase, 'plannedTestAt'),
      plannedEndAt: stringField(testCase, 'plannedEndAt'),
      startedAt: stringField(testCase, 'startedAt'),
      completedAt: stringField(testCase, 'completedAt'),
      estimateHours: numberField(testCase, 'estimateHours'),
      aiEstimateHours: numberField(testCase, 'aiEstimateHours'),
      references: unknownArray(testCase.references),
      aiDraft: booleanField(testCase, 'aiDraft') ?? false,
      aiDraftAt: stringField(testCase, 'aiDraftAt'),
      createdAt: stringField(testCase, 'createdAt'),
      updatedAt: stringField(testCase, 'updatedAt') ?? stringField(testCase, 'createdAt'),
    };
    testCases.push(row);
    testCaseById.set(row.id, row);
  }

  const bugCode = scopedCodeFactory('BUG');
  for (const [index, bug] of asArray(appData.bugs).entries()) {
    const id = stringField(bug, 'id') ?? `bug-${index + 1}`;
    const testCaseId = stringField(bug, 'testCaseId');
    const linkedTestCase = testCaseId ? testCaseById.get(testCaseId) : undefined;
    const context = resolveVersionContext(
      { ...bug, versionId: stringField(bug, 'versionId') ?? linkedTestCase?.versionId },
      requirementById,
      versionById,
    );
    if (!context.versionId || !context.productId || !context.projectId) {
      skipped.push({ key: 'bugs', id, reason: 'missing version partition context' });
      continue;
    }
    const row: BugRow = {
      id,
      versionId: context.versionId,
      productId: context.productId,
      projectId: context.projectId,
      testCaseId,
      testCaseVersionId: linkedTestCase?.versionId,
      code: bugCode(context.versionId, stringField(bug, 'code') ?? stringField(bug, 'bugNo'), index),
      title: stringField(bug, 'title') ?? id,
      description: stringField(bug, 'description') ?? '',
      status: stringField(bug, 'status') ?? 'open',
      severity: stringField(bug, 'severity') ?? 'minor',
      priority: priorityRank(bug.priority),
      assigneeId: stringField(bug, 'assigneeId'),
      reporterId: stringField(bug, 'reporterId') ?? stringField(bug, 'reportedBy'),
      plannedFixAt: stringField(bug, 'plannedFixAt'),
      resolvedAt: stringField(bug, 'resolvedAt'),
      closedAt: stringField(bug, 'closedAt'),
      resolution: stringField(bug, 'resolution'),
      createdAt: stringField(bug, 'createdAt'),
      updatedAt: stringField(bug, 'updatedAt') ?? stringField(bug, 'createdAt'),
    };
    bugs.push(row);
    bugById.set(row.id, row);
  }

  for (const [index, activity] of asArray(appData['work-activities']).entries()) {
    const id = stringField(activity, 'id') ?? `work-activity-${index + 1}`;
    const occurredAt = stringField(activity, 'occurredAt') ?? stringField(activity, 'createdAt');
    if (!occurredAt) {
      skipped.push({ key: 'work-activities', id, reason: 'missing createdAt partition context' });
      continue;
    }
    const sourceType = stringField(activity, 'sourceType') ?? 'manual';
    const sourceId = stringField(activity, 'sourceId') ?? id;
    const sourceContext = resolveSourceContext(sourceType, sourceId, {
      devTaskById,
      testCaseById,
      bugById,
      versionPlanById,
    });
    workActivities.push({
      id,
      versionId: sourceContext.versionId,
      productId: sourceContext.productId,
      projectId: sourceContext.projectId,
      actorId: stringField(activity, 'actorId'),
      actorName: stringField(activity, 'actorName') ?? '',
      sourceType,
      sourceId,
      sourceVersionId: sourceContext.versionId,
      action: stringField(activity, 'action') ?? 'progress_note_added',
      title: stringField(activity, 'title') ?? stringField(activity, 'summary') ?? id,
      metadata: recordField(activity, 'metadata') ?? {},
      occurredAt,
      createdAt: stringField(activity, 'createdAt') ?? occurredAt,
    });
  }

  for (const [index, worklog] of asArray(appData['task-worklogs']).entries()) {
    const id = stringField(worklog, 'id') ?? `task-worklog-${index + 1}`;
    const createdAt = stringField(worklog, 'createdAt');
    const sourceId = stringField(worklog, 'sourceId') ?? stringField(worklog, 'taskId') ?? id;
    const sourceContext = resolveWorklogSource(sourceId, { devTaskById, testCaseById, bugById });
    taskWorklogs.push({
      id,
      versionId: sourceContext.context.versionId,
      productId: sourceContext.context.productId,
      projectId: sourceContext.context.projectId,
      userId: stringField(worklog, 'userId'),
      sourceType: stringField(worklog, 'sourceType') ?? sourceContext.sourceType,
      sourceId,
      sourceVersionId: sourceContext.context.versionId,
      workDate: stringField(worklog, 'workDate') ?? stringField(worklog, 'date') ?? sliceDate(createdAt) ?? '',
      hours: numberField(worklog, 'hours') ?? 0,
      content: stringField(worklog, 'content') ?? stringField(worklog, 'workContent') ?? '',
      createdAt: createdAt ?? stringField(worklog, 'date') ?? '',
      updatedAt: stringField(worklog, 'updatedAt') ?? createdAt,
    });
  }

  for (const [index, overtime] of readNestedArray(appData.overtime, 'records').entries()) {
    const id = stringField(overtime, 'id') ?? `overtime-${index + 1}`;
    const versionId = stringField(overtime, 'versionId');
    const projectId = stringField(overtime, 'projectId');
    const version = versionId ? versionById.get(versionId) : undefined;
    const project = projectId ? projectById.get(projectId) : undefined;
    const startAt = stringField(overtime, 'startAt') ?? stringField(overtime, 'startTime');
    const endAt = stringField(overtime, 'endAt') ?? stringField(overtime, 'endTime');
    const createdAt = stringField(overtime, 'createdAt') ?? startAt;
    if (!startAt || !endAt || !createdAt) {
      skipped.push({ key: 'overtime', id, reason: 'missing createdAt partition context' });
      continue;
    }
    overtimeRecords.push({
      id,
      productId: stringField(overtime, 'productId') ?? version?.productId ?? project?.productId,
      projectId: projectId ?? version?.projectId,
      versionId,
      userId: stringField(overtime, 'userId') ?? stringField(overtime, 'person'),
      reason: stringField(overtime, 'reason') ?? stringField(overtime, 'reasonId') ?? '',
      startAt,
      endAt,
      hours: numberField(overtime, 'hours') ?? numberField(overtime, 'duration') ?? 0,
      createdAt,
      updatedAt: stringField(overtime, 'updatedAt') ?? createdAt,
    });
  }

  const summaryByVersion = new Map<string, XiaobaoRiskSummaryRow>();
  for (const [index, snapshot] of asArray(appData['xiaobao-risk-snapshots']).entries()) {
    const versionId = stringField(snapshot, 'versionId');
    const snapshotDate = stringField(snapshot, 'snapshotDate') ?? stringField(snapshot, 'date');
    const createdAt = stringField(snapshot, 'createdAt') ?? snapshotDate;
    if (!versionId || !snapshotDate || !createdAt) {
      skipped.push({
        key: 'xiaobao-risk-snapshots',
        id: stringField(snapshot, 'id') ?? `xiaobao-risk-snapshot-${index + 1}`,
        reason: 'missing createdAt partition context',
      });
      continue;
    }
    const riskSignature = stringField(snapshot, 'riskSignature') ?? buildRiskSignature(snapshot, versionId, snapshotDate);
    const row: XiaobaoRiskSnapshotRow = {
      id: stringField(snapshot, 'id') ?? `risk-snapshot-${versionId}-${snapshotDate}-${index + 1}`,
      versionId,
      snapshotDate,
      riskLevel: stringField(snapshot, 'riskLevel') ?? 'on_track',
      riskScore: integerField(snapshot, 'riskScore') ?? 0,
      riskSignature,
      snapshot,
      createdAt,
    };
    xiaobaoRiskSnapshots.push(row);
    const summary: XiaobaoRiskSummaryRow = {
      versionId,
      riskLevel: row.riskLevel,
      riskScore: row.riskScore,
      confidence: integerField(snapshot, 'confidence') ?? 0,
      forecastReleaseDate: stringField(snapshot, 'forecastReleaseDate'),
      riskSignature,
      summary: snapshot,
      dirty: false,
      recomputedAt: createdAt,
      updatedAt: createdAt,
    };
    const previous = summaryByVersion.get(versionId);
    if (!previous || compareIso(summary.updatedAt, previous.updatedAt) >= 0) {
      summaryByVersion.set(versionId, summary);
    }
  }
  xiaobaoRiskSummaries.push(...summaryByVersion.values());

  for (const [index, insight] of asArray(appData['xiaobao-risk-insights']).entries()) {
    const versionId = stringField(insight, 'versionId');
    const riskSignature = stringField(insight, 'riskSignature');
    const createdAt = stringField(insight, 'createdAt') ?? stringField(insight, 'generatedAt');
    if (!versionId || !riskSignature || !createdAt) {
      skipped.push({
        key: 'xiaobao-risk-insights',
        id: stringField(insight, 'id') ?? `xiaobao-risk-insight-${index + 1}`,
        reason: 'missing createdAt partition context',
      });
      continue;
    }
    xiaobaoRiskInsights.push({
      id: stringField(insight, 'id') ?? `risk-insight-${versionId}-${index + 1}`,
      versionId,
      riskSignature,
      status: stringField(insight, 'status') ?? 'generated',
      insight: recordField(insight, 'insight') ?? {},
      createdAt,
      updatedAt: stringField(insight, 'updatedAt') ?? createdAt,
    });
  }

  return {
    products,
    projects,
    versions,
    users,
    requirements,
    taskCategories,
    versionPlans,
    devTasks,
    testCases,
    bugs,
    workActivities,
    taskWorklogs,
    overtimeRecords,
    xiaobaoRiskSnapshots,
    xiaobaoRiskInsights,
    xiaobaoRiskSummaries,
    aiLogs: [],
    skipped,
  };
}

function asRecord(value: unknown): AppDataRecord | undefined {
  if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;
  return value as AppDataRecord;
}

function asArray(value: unknown): AppDataRecord[] {
  if (!Array.isArray(value)) return [];
  return value.map(asRecord).filter((item): item is AppDataRecord => Boolean(item));
}

function unknownArray(value: unknown): unknown[] {
  return Array.isArray(value) ? value : [];
}

function readNestedArray(value: unknown, key: string): AppDataRecord[] {
  if (Array.isArray(value)) return asArray(value);
  return asArray(asRecord(value)?.[key]);
}

function stringFromUnknown(value: unknown): string | undefined {
  if (typeof value === 'string') {
    const trimmed = value.trim();
    return trimmed || undefined;
  }
  if (typeof value === 'number' && Number.isFinite(value)) return String(value);
  return undefined;
}

function stringField(record: AppDataRecord, key: string): string | undefined {
  return stringFromUnknown(record[key]);
}

function recordField(record: AppDataRecord, key: string): AppDataRecord | undefined {
  return asRecord(record[key]);
}

function numberField(record: AppDataRecord, key: string): number | undefined {
  const value = record[key];
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  if (typeof value === 'string' && value.trim()) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : undefined;
  }
  return undefined;
}

function integerField(record: AppDataRecord, key: string): number | undefined {
  const value = numberField(record, key);
  return typeof value === 'number' ? Math.floor(value) : undefined;
}

function booleanField(record: AppDataRecord, key: string): boolean | undefined {
  const value = record[key];
  if (typeof value === 'boolean') return value;
  return undefined;
}

function priorityRank(value: unknown): number {
  if (typeof value === 'number' && Number.isFinite(value)) return Math.max(0, Math.floor(value));
  if (typeof value === 'string') {
    const normalized = value.trim().toUpperCase();
    const match = /^P([0-4])$/.exec(normalized);
    if (match) return Number(match[1]);
    const numeric = Number(normalized);
    if (Number.isFinite(numeric)) return Math.max(0, Math.floor(numeric));
  }
  return 0;
}

function scopedCodeFactory(prefix: string) {
  const usedByScope = new Map<string, Set<string>>();
  return (scope: string, candidate: string | undefined, index: number): string => {
    const used = usedByScope.get(scope) ?? new Set<string>();
    usedByScope.set(scope, used);
    const base = candidate?.trim() || `${prefix}-${String(index + 1).padStart(3, '0')}`;
    if (!used.has(base)) {
      used.add(base);
      return base;
    }
    let suffix = 2;
    while (used.has(`${base}-${suffix}`)) suffix++;
    const next = `${base}-${suffix}`;
    used.add(next);
    return next;
  };
}

function uniqueEmailFactory() {
  const used = new Set<string>();
  return (email: string): string => {
    const normalized = email.trim().toLowerCase();
    if (!used.has(normalized)) {
      used.add(normalized);
      return normalized;
    }
    const atIndex = normalized.indexOf('@');
    const name = atIndex >= 0 ? normalized.slice(0, atIndex) : normalized;
    const domain = atIndex >= 0 ? normalized.slice(atIndex + 1) : 'local.ftb';
    let suffix = 2;
    let next = `${name}+${suffix}@${domain}`;
    while (used.has(next)) {
      suffix++;
      next = `${name}+${suffix}@${domain}`;
    }
    used.add(next);
    return next;
  };
}

function normalizeMemberEmail(email: string | undefined, username: string, id: string): string {
  if (email?.includes('@')) return email;
  const base = email || username || id;
  return `${base.trim().toLowerCase()}@local.ftb`;
}

function inferProjectIdForVersion(version: AppDataRecord, projects: AppDataRecord[]): string | undefined {
  const explicitProjectId = stringField(version, 'projectId');
  if (explicitProjectId) return explicitProjectId;
  const versionName = stringField(version, 'name')?.toLowerCase();
  if (!versionName) return undefined;
  const project = projects.find((item) => {
    const projectName = stringField(item, 'name')?.toLowerCase();
    return Boolean(projectName && versionName.startsWith(projectName));
  });
  return project ? stringField(project, 'id') : undefined;
}

function resolveVersionContext(
  row: AppDataRecord,
  requirementById: Map<string, RequirementRow>,
  versionById: Map<string, VersionRow>,
): SourceContext {
  const requirementId = stringField(row, 'requirementId');
  const requirement = requirementId ? requirementById.get(requirementId) : undefined;
  const versionId = stringField(row, 'versionId') ?? requirement?.versionId;
  const version = versionId ? versionById.get(versionId) : undefined;
  return {
    versionId,
    productId: stringField(row, 'productId') ?? requirement?.productId ?? version?.productId,
    projectId: stringField(row, 'projectId') ?? requirement?.projectId ?? version?.projectId,
  };
}

function resolveSourceContext(
  sourceType: string,
  sourceId: string,
  maps: {
    devTaskById: Map<string, DevTaskRow>;
    testCaseById: Map<string, TestCaseRow>;
    bugById: Map<string, BugRow>;
    versionPlanById: Map<string, VersionPlanRow>;
  },
): SourceContext {
  if (sourceType === 'dev_task') return contextFromRow(maps.devTaskById.get(sourceId));
  if (sourceType === 'test_case') return contextFromRow(maps.testCaseById.get(sourceId));
  if (sourceType === 'bug') return contextFromRow(maps.bugById.get(sourceId));
  if (sourceType === 'version_plan') return contextFromRow(maps.versionPlanById.get(sourceId));
  return {};
}

function resolveWorklogSource(
  sourceId: string,
  maps: {
    devTaskById: Map<string, DevTaskRow>;
    testCaseById: Map<string, TestCaseRow>;
    bugById: Map<string, BugRow>;
  },
): { sourceType: string; context: SourceContext } {
  const devTask = maps.devTaskById.get(sourceId);
  if (devTask) return { sourceType: 'dev_task', context: contextFromRow(devTask) };
  const testCase = maps.testCaseById.get(sourceId);
  if (testCase) return { sourceType: 'test_case', context: contextFromRow(testCase) };
  const bug = maps.bugById.get(sourceId);
  if (bug) return { sourceType: 'bug', context: contextFromRow(bug) };
  return { sourceType: 'legacy_task', context: {} };
}

function contextFromRow(row: SourceContext | undefined): SourceContext {
  if (!row) return {};
  return {
    versionId: row.versionId,
    productId: row.productId,
    projectId: row.projectId,
  };
}

function sliceDate(value?: string): string | undefined {
  if (!value) return undefined;
  return /^\d{4}-\d{2}-\d{2}/.test(value) ? value.slice(0, 10) : undefined;
}

function compareIso(a?: string, b?: string): number {
  const aTime = a ? new Date(a).getTime() : 0;
  const bTime = b ? new Date(b).getTime() : 0;
  const safeA = Number.isFinite(aTime) ? aTime : 0;
  const safeB = Number.isFinite(bTime) ? bTime : 0;
  return safeA - safeB;
}

function buildRiskSignature(snapshot: AppDataRecord, versionId: string, date: string): string {
  return [
    versionId,
    date,
    clampScore(integerField(snapshot, 'riskScore') ?? 0),
    stringField(snapshot, 'riskLevel') ?? 'on_track',
    normalizeForecastDate(stringField(snapshot, 'forecastReleaseDate')),
    integerField(snapshot, 'openBugCount') ?? 0,
    integerField(snapshot, 'criticalBugCount') ?? 0,
    integerField(snapshot, 'failedTestCount') ?? 0,
    integerField(snapshot, 'blockedCount') ?? 0,
    integerField(snapshot, 'silentRiskCount') ?? 0,
    clampScore(integerField(snapshot, 'confidence') ?? 0),
  ].join('|');
}

function clampScore(score: number): number {
  return Math.max(0, Math.min(100, Math.round(score)));
}

function normalizeForecastDate(value?: string): string {
  if (!value) return '';
  if (/^\d{4}-\d{2}-\d{2}/.test(value)) return value.slice(0, 10);
  const time = new Date(value).getTime();
  if (!Number.isFinite(time)) return value;
  return new Date(time).toISOString().slice(0, 10);
}
