import type { FastifyInstance, FastifyRequest } from 'fastify';
import {
  AgentActionLogSchema,
  AgentCheckpointSchema,
  AgentApprovalCheckpointSchema,
  AgentDispatchRequestSchema,
  AgentRunSchema,
  AgentSessionSchema,
  AgentTaskSchema,
  AgentTodoSchema,
  type AgentActionLog,
  type AgentCheckpoint,
  type AgentApprovalCheckpoint,
  type AgentRun,
  type AgentSession,
  type AgentTask,
  type AgentTodo,
} from '@bytelyst/events';
import { BadRequestError, NotFoundError } from '@bytelyst/errors';
import { config } from '../../lib/config.js';
import { getIpcBridge } from '../../lib/ipc-bridge.js';
import { PRODUCT_ID } from '../../lib/product-config.js';
import { getUserId } from '../../lib/request-context.js';

function buildAuth(req: FastifyRequest): Record<string, unknown> {
  const userId = getUserId(req);
  const role = (req.jwtPayload as Record<string, unknown> | undefined)?.role ?? 'user';
  return { userId, role, productId: PRODUCT_ID, isPlatformAuth: !!req.jwtPayload };
}

function asIsoString(value: unknown, fallback: string): string {
  if (typeof value === 'string' && !Number.isNaN(Date.parse(value))) {
    return value;
  }
  if (typeof value === 'number' && Number.isFinite(value)) {
    return new Date(value * 1000).toISOString();
  }
  return fallback;
}

function stringList(value: unknown): string[] {
  return Array.isArray(value)
    ? value.filter((entry): entry is string => typeof entry === 'string')
    : [];
}

function checkpointTaskIdFromId(checkpointId: string): string {
  return checkpointId.startsWith('ckpt_') ? checkpointId.slice(5) : checkpointId;
}

function mapTaskStatus(status: unknown): AgentRun['status'] {
  switch (status) {
    case 'pending':
      return 'queued';
    case 'running':
      return 'running';
    case 'completed':
      return 'completed';
    case 'failed':
      return 'failed';
    case 'cancelled':
      return 'cancelled';
    default:
      return 'paused';
  }
}

function toAgentSession(session: Record<string, unknown>, fallbackNow: string): AgentSession {
  const sessionId =
    typeof session.id === 'string' && session.id.length > 0 ? session.id : `session_${fallbackNow}`;
  const createdAt = asIsoString(
    session.createdAt ?? session.startedAt ?? session.updatedAt,
    fallbackNow
  );
  const updatedAt = asIsoString(session.updatedAt ?? session.lastActiveAt ?? createdAt, createdAt);

  return AgentSessionSchema.parse({
    sessionId,
    productId: PRODUCT_ID,
    userId:
      typeof session.userId === 'string' && session.userId.length > 0
        ? session.userId
        : 'unknown-user',
    status: session.waitingApproval ? 'waiting-approval' : 'active',
    startedAt: createdAt,
    updatedAt,
    resumable: true,
    currentTaskId:
      typeof session.currentTaskId === 'string' && session.currentTaskId.length > 0
        ? session.currentTaskId
        : null,
    memoryRefs: stringList(session.memoryRefs),
    artifactRefs: stringList(session.artifactRefs),
    approvalRefs: stringList(session.approvalRefs),
    dispatchContext: {
      originSurface: 'desktop',
      originProductId: PRODUCT_ID,
      dispatchMode: 'interactive',
      initiatedAt: createdAt,
    },
  });
}

function toAgentRun(task: Record<string, unknown>, fallbackNow: string): AgentRun {
  const runId = typeof task.id === 'string' && task.id.length > 0 ? task.id : `run_${fallbackNow}`;
  const startedAt = asIsoString(task.createdAt ?? task.startedAt, fallbackNow);
  const status = mapTaskStatus(task.status);
  const completedAt =
    status === 'completed' || status === 'failed' || status === 'cancelled'
      ? asIsoString(task.updatedAt ?? task.completedAt, startedAt)
      : null;

  return AgentRunSchema.parse({
    runId,
    sessionId:
      typeof task.sessionId === 'string' && task.sessionId.length > 0 ? task.sessionId : runId,
    productId: PRODUCT_ID,
    status,
    startedAt,
    completedAt,
    checkpointArtifactId:
      typeof task.checkpointArtifactId === 'string' ? task.checkpointArtifactId : null,
    correlationId:
      typeof task.correlationId === 'string'
        ? task.correlationId
        : typeof task.eventId === 'string'
          ? task.eventId
          : typeof task.id === 'string'
            ? task.id
            : null,
  });
}

function mapTaskProjectionStatus(status: unknown): AgentTask['status'] {
  switch (status) {
    case 'running':
      return 'running';
    case 'completed':
      return 'completed';
    case 'failed':
      return 'failed';
    case 'cancelled':
      return 'cancelled';
    case 'pending':
    default:
      return 'queued';
  }
}

function toAgentTask(task: Record<string, unknown>, fallbackNow: string): AgentTask {
  const taskId =
    typeof task.id === 'string' && task.id.length > 0 ? task.id : `task_${fallbackNow}`;
  const createdAt = asIsoString(task.createdAt ?? task.startedAt, fallbackNow);
  const updatedAt = asIsoString(task.updatedAt ?? task.completedAt ?? createdAt, createdAt);
  const goal =
    typeof task.goal === 'string' && task.goal.trim().length > 0 ? task.goal.trim() : 'Cowork task';

  return AgentTaskSchema.parse({
    taskId,
    sessionId:
      typeof task.sessionId === 'string' && task.sessionId.length > 0 ? task.sessionId : taskId,
    title: goal.length > 120 ? `${goal.slice(0, 117)}...` : goal,
    intent: goal,
    status: mapTaskProjectionStatus(task.status),
    priority: typeof task.priority === 'string' ? task.priority : null,
    createdAt,
    updatedAt,
  });
}

function mapTodoStatus(status: unknown): AgentTodo['status'] {
  switch (status) {
    case 'running':
      return 'in-progress';
    case 'completed':
      return 'done';
    case 'cancelled':
      return 'dropped';
    case 'failed':
      return 'open';
    case 'pending':
    default:
      return 'open';
  }
}

function toAgentTodo(
  task: Record<string, unknown> | null,
  checkpoint: Record<string, unknown>,
  fallbackNow: string
): AgentTodo {
  const taskId =
    typeof checkpoint.task_id === 'string' && checkpoint.task_id.length > 0
      ? checkpoint.task_id
      : typeof task?.id === 'string' && task.id.length > 0
        ? task.id
        : fallbackNow;
  const todoId = `todo_${taskId}`;
  const createdAt = asIsoString(
    task?.createdAt ?? checkpoint.created_at ?? checkpoint.last_updated,
    fallbackNow
  );
  const updatedAt = asIsoString(task?.updatedAt ?? checkpoint.last_updated ?? createdAt, createdAt);
  const text =
    typeof checkpoint.goal === 'string' && checkpoint.goal.trim().length > 0
      ? checkpoint.goal.trim()
      : typeof task?.goal === 'string' && task.goal.trim().length > 0
        ? task.goal.trim()
        : 'Cowork task';
  const status =
    checkpoint.completed === true
      ? 'done'
      : checkpoint.cancelled === true
        ? 'dropped'
        : typeof checkpoint.error === 'string' && checkpoint.error.length > 0
          ? 'dropped'
          : mapTodoStatus(task?.status);

  return AgentTodoSchema.parse({
    todoId,
    sessionId:
      typeof task?.sessionId === 'string' && task.sessionId.length > 0 ? task.sessionId : taskId,
    text,
    status,
    createdAt,
    updatedAt,
  });
}

function mapCheckpointStatus(
  task: Record<string, unknown> | null,
  checkpoint: Record<string, unknown>
): AgentCheckpoint['statusAtCapture'] {
  if (checkpoint.completed === true) return 'completed';
  if (checkpoint.cancelled === true) return 'cancelled';
  if (typeof checkpoint.error === 'string' && checkpoint.error.length > 0) return 'failed';

  switch (task?.status) {
    case 'running':
      return 'running';
    case 'completed':
      return 'completed';
    case 'failed':
      return 'failed';
    case 'cancelled':
      return 'cancelled';
    case 'pending':
      return 'queued';
    default:
      return 'paused';
  }
}

function toAgentCheckpoint(
  task: Record<string, unknown> | null,
  checkpoint: Record<string, unknown>,
  fallbackNow: string
): AgentCheckpoint {
  const taskId =
    typeof checkpoint.task_id === 'string' && checkpoint.task_id.length > 0
      ? checkpoint.task_id
      : typeof task?.id === 'string' && task.id.length > 0
        ? task.id
        : `checkpoint_${fallbackNow}`;
  const createdAt = asIsoString(checkpoint.created_at ?? checkpoint.last_updated, fallbackNow);
  const lastActionAt = asIsoString(checkpoint.last_updated, createdAt);
  const goal =
    typeof checkpoint.goal === 'string' && checkpoint.goal.trim().length > 0
      ? checkpoint.goal.trim()
      : typeof task?.goal === 'string' && task.goal.trim().length > 0
        ? task.goal.trim()
        : 'Cowork task';
  const completedToolCalls =
    typeof checkpoint.completed_tool_calls === 'number' ? checkpoint.completed_tool_calls : 0;
  const summary = [
    `${completedToolCalls} tool calls recorded`,
    checkpoint.completed === true
      ? 'task completed'
      : checkpoint.cancelled === true
        ? 'task cancelled'
        : typeof checkpoint.error === 'string' && checkpoint.error.length > 0
          ? `task failed: ${checkpoint.error}`
          : 'checkpoint available for resume',
  ].join('; ');

  const artifactRefs = stringList(checkpoint.artifact_refs);
  const checkpointArtifactId =
    typeof checkpoint.checkpoint_artifact_id === 'string' &&
    checkpoint.checkpoint_artifact_id.length > 0
      ? checkpoint.checkpoint_artifact_id
      : (artifactRefs[0] ?? null);

  return AgentCheckpointSchema.parse({
    checkpointId: `ckpt_${taskId}`,
    sessionId:
      typeof task?.sessionId === 'string' && task.sessionId.length > 0 ? task.sessionId : taskId,
    runId: typeof task?.id === 'string' && task.id.length > 0 ? task.id : taskId,
    productId: PRODUCT_ID,
    userId:
      typeof checkpoint.user_id === 'string' && checkpoint.user_id.length > 0
        ? checkpoint.user_id
        : 'unknown-user',
    createdAt,
    statusAtCapture: mapCheckpointStatus(task, checkpoint),
    currentTaskId: taskId,
    checkpointArtifactId,
    todoIds: [`todo_${taskId}`],
    artifactRefs,
    memoryRefs: stringList(checkpoint.memory_refs),
    approvalRefs: stringList(checkpoint.approval_refs),
    dispatchContext: {
      originSurface: 'desktop',
      originProductId: PRODUCT_ID,
      dispatchMode: 'interactive',
      initiatedAt: createdAt,
    },
    resumeToken: taskId,
    stateSummary: {
      title: goal.length > 120 ? `${goal.slice(0, 117)}...` : goal,
      summary,
      lastActionAt,
    },
  });
}

function toCheckpointDetail(
  task: Record<string, unknown> | null,
  checkpoint: Record<string, unknown>,
  fallbackNow: string
) {
  const projected = toAgentCheckpoint(task, checkpoint, fallbackNow);
  return {
    checkpoint: projected,
    detail: {
      goal:
        typeof checkpoint.goal === 'string' && checkpoint.goal.trim().length > 0
          ? checkpoint.goal.trim()
          : projected.stateSummary.title,
      folder:
        typeof checkpoint.folder === 'string' && checkpoint.folder.length > 0
          ? checkpoint.folder
          : null,
      model:
        typeof checkpoint.model === 'string' && checkpoint.model.length > 0
          ? checkpoint.model
          : null,
      createdAt: asIsoString(checkpoint.created_at, projected.createdAt),
      updatedAt: asIsoString(
        checkpoint.last_updated,
        projected.stateSummary.lastActionAt ?? projected.createdAt
      ),
      completedToolCalls:
        typeof checkpoint.completed_tool_calls === 'number' ? checkpoint.completed_tool_calls : 0,
      completed: checkpoint.completed === true,
      cancelled: checkpoint.cancelled === true,
      error:
        typeof checkpoint.error === 'string' && checkpoint.error.length > 0
          ? checkpoint.error
          : null,
      eventId:
        typeof checkpoint.event_id === 'string' && checkpoint.event_id.length > 0
          ? checkpoint.event_id
          : null,
      correlationId:
        typeof checkpoint.correlation_id === 'string' && checkpoint.correlation_id.length > 0
          ? checkpoint.correlation_id
          : null,
      source: {
        observationKind: 'persisted-checkpoint',
        projectionKind: 'agent-runtime-summary',
      },
      observedRefs: {
        checkpointArtifactId: projected.checkpointArtifactId ?? null,
        artifactRefs: projected.artifactRefs,
        memoryRefs: projected.memoryRefs,
        approvalRefs: projected.approvalRefs,
      },
    },
  };
}

function mapApprovalStatus(action: unknown): AgentApprovalCheckpoint['status'] {
  switch (action) {
    case 'approval_granted':
      return 'approved';
    case 'approval_denied':
      return 'denied';
    default:
      return 'requested';
  }
}

function mapResolverSurface(surface: unknown): 'mobile' | 'web' | 'desktop' | null {
  switch (surface) {
    case 'mobile':
    case 'web':
    case 'desktop':
      return surface;
    default:
      return null;
  }
}

function getCanonicalAuditEventId(
  record: Record<string, unknown>,
  details: Record<string, unknown>
): string | null {
  if (typeof details.eventId === 'string' && details.eventId.length > 0) {
    return details.eventId;
  }
  if (typeof record.id === 'string' && record.id.length > 0) {
    return record.id;
  }
  return null;
}

function toApprovalCheckpoint(
  record: Record<string, unknown>,
  fallbackNow: string
): AgentApprovalCheckpoint {
  const details =
    record.details && typeof record.details === 'object'
      ? (record.details as Record<string, unknown>)
      : {};
  const status = mapApprovalStatus(record.action);
  const requestedAt = asIsoString(record.timestamp ?? record.createdAt, fallbackNow);
  const canonicalEventId = getCanonicalAuditEventId(record, details);

  return AgentApprovalCheckpointSchema.parse({
    approvalId: canonicalEventId ?? `approval_${fallbackNow}`,
    sessionId:
      typeof details.sessionId === 'string' && details.sessionId.length > 0
        ? details.sessionId
        : typeof details.taskId === 'string' && details.taskId.length > 0
          ? details.taskId
          : 'unknown-session',
    runId:
      typeof details.runId === 'string' && details.runId.length > 0
        ? details.runId
        : typeof details.taskId === 'string' && details.taskId.length > 0
          ? details.taskId
          : 'unknown-run',
    actionLabel:
      typeof details.inputSummary === 'string' && details.inputSummary.length > 0
        ? details.inputSummary
        : typeof details.tool === 'string' && details.tool.length > 0
          ? `Approve ${details.tool}`
          : 'Approval required',
    riskLevel: status === 'denied' ? 'high' : 'medium',
    status,
    requestedAt,
    resolvedAt: status === 'requested' ? null : requestedAt,
    resolverSurface: mapResolverSurface(details.resolverSurface),
  });
}

function mapActorType(record: Record<string, unknown>): AgentActionLog['actorType'] {
  if (typeof record.action === 'string' && record.action.startsWith('approval_')) {
    return 'user';
  }

  const category = typeof record.category === 'string' ? record.category : '';
  if (category === 'system' || category === 'platform') {
    return 'system';
  }

  return 'agent';
}

function toActionLog(record: Record<string, unknown>, fallbackNow: string): AgentActionLog {
  const details =
    record.details && typeof record.details === 'object'
      ? (record.details as Record<string, unknown>)
      : {};
  const canonicalEventId = getCanonicalAuditEventId(record, details);
  const sessionId =
    typeof details.sessionId === 'string' && details.sessionId.length > 0
      ? details.sessionId
      : typeof details.taskId === 'string' && details.taskId.length > 0
        ? details.taskId
        : 'unknown-session';
  const runId =
    typeof details.runId === 'string' && details.runId.length > 0
      ? details.runId
      : typeof details.taskId === 'string' && details.taskId.length > 0
        ? details.taskId
        : 'unknown-run';

  return AgentActionLogSchema.parse({
    actionLogId: canonicalEventId ?? `action_${fallbackNow}`,
    sessionId,
    runId,
    eventName:
      typeof record.action === 'string' && record.action.length > 0
        ? record.action
        : 'unknown_action',
    occurredAt: asIsoString(record.timestamp ?? record.createdAt, fallbackNow),
    actorType: mapActorType(record),
    correlationId:
      typeof details.correlationId === 'string'
        ? details.correlationId
        : typeof details.taskId === 'string'
          ? details.taskId
          : null,
    payload: details,
  });
}

async function fetchApprovalRecords(req: FastifyRequest): Promise<Record<string, unknown>[]> {
  const params = new URLSearchParams({
    days: '30',
    limit: '100',
  });

  const actions = ['approval_granted', 'approval_denied'];
  const records: Record<string, unknown>[] = [];

  for (const action of actions) {
    params.set('action', action);
    const res = await fetch(`${config.PLATFORM_SERVICE_URL}/audit?${params.toString()}`, {
      headers: {
        'x-product-id': PRODUCT_ID,
        'x-request-id': req.id,
      },
    });

    if (!res.ok) {
      throw new BadRequestError(`Platform returned ${res.status} while fetching approval records`);
    }

    const body = (await res.json()) as { records?: Record<string, unknown>[] };
    records.push(...(Array.isArray(body.records) ? body.records : []));
  }

  return records;
}

async function fetchTasks(req: FastifyRequest): Promise<Record<string, unknown>[]> {
  const bridge = getIpcBridge();
  if (!bridge?.isRunning) return [];

  const response = await bridge.call('list_tasks', { auth: buildAuth(req) });
  const result =
    response.result && typeof response.result === 'object'
      ? (response.result as Record<string, unknown>)
      : {};
  return Array.isArray(result.tasks) ? (result.tasks as Record<string, unknown>[]) : [];
}

async function fetchCheckpoints(req: FastifyRequest): Promise<Record<string, unknown>[]> {
  const bridge = getIpcBridge();
  if (!bridge?.isRunning) return [];

  const response = await bridge.call('list_checkpoints', { auth: buildAuth(req) });
  const result =
    response.result && typeof response.result === 'object'
      ? (response.result as Record<string, unknown>)
      : {};
  return Array.isArray(result.checkpoints) ? (result.checkpoints as Record<string, unknown>[]) : [];
}

async function fetchAuditRecords(req: FastifyRequest): Promise<Record<string, unknown>[]> {
  const params = new URLSearchParams({
    days: '30',
    limit: '100',
  });

  const res = await fetch(`${config.PLATFORM_SERVICE_URL}/audit?${params.toString()}`, {
    headers: {
      'x-product-id': PRODUCT_ID,
      'x-request-id': req.id,
    },
  });

  if (!res.ok) {
    throw new BadRequestError(`Platform returned ${res.status} while fetching action logs`);
  }

  const body = (await res.json()) as { records?: Record<string, unknown>[] };
  return Array.isArray(body.records) ? body.records : [];
}

export async function agentRuntimeRoutes(app: FastifyInstance) {
  const bridge = getIpcBridge();

  app.get('/api/agent-runtime/sessions', async req => {
    if (!bridge.isRunning) {
      return { sessions: [], count: 0 };
    }

    const resp = await bridge.call('list_sessions', { auth: buildAuth(req) });
    if (resp.error) throw new BadRequestError(resp.error.message);

    const raw =
      (resp.result as { sessions?: Record<string, unknown>[] } | undefined)?.sessions ?? [];
    const now = new Date().toISOString();
    const sessions = raw.map(session => toAgentSession(session, now));

    return {
      sessions,
      count: sessions.length,
    };
  });

  app.get('/api/agent-runtime/runs', async req => {
    if (!bridge.isRunning) {
      return { runs: [], count: 0 };
    }

    const resp = await bridge.call('list_tasks', { auth: buildAuth(req) });
    if (resp.error) throw new BadRequestError(resp.error.message);

    const raw = (resp.result as { tasks?: Record<string, unknown>[] } | undefined)?.tasks ?? [];
    const now = new Date().toISOString();
    const runs = raw.map(task => toAgentRun(task, now));

    return {
      runs,
      count: runs.length,
    };
  });

  app.get('/api/agent-runtime/tasks', async req => {
    if (!bridge.isRunning) {
      return { tasks: [], count: 0 };
    }

    const resp = await bridge.call('list_tasks', { auth: buildAuth(req) });
    if (resp.error) throw new BadRequestError(resp.error.message);

    const raw = (resp.result as { tasks?: Record<string, unknown>[] } | undefined)?.tasks ?? [];
    const now = new Date().toISOString();
    const tasks = raw.map(task => toAgentTask(task, now));

    return {
      tasks,
      count: tasks.length,
    };
  });

  app.get('/api/agent-runtime/todos', async req => {
    if (!bridge.isRunning) {
      return { todos: [], count: 0 };
    }

    const [rawTasks, checkpoints] = await Promise.all([fetchTasks(req), fetchCheckpoints(req)]);
    const taskMap = new Map(
      rawTasks
        .filter(task => typeof task.id === 'string' && task.id.length > 0)
        .map(task => [task.id as string, task])
    );
    const now = new Date().toISOString();
    const todos =
      checkpoints.length > 0
        ? checkpoints.map(checkpoint =>
            toAgentTodo(
              typeof checkpoint.task_id === 'string'
                ? (taskMap.get(checkpoint.task_id) ?? null)
                : null,
              checkpoint,
              now
            )
          )
        : rawTasks.map(task => toAgentTodo(task, { task_id: task.id, goal: task.goal }, now));

    return {
      todos,
      count: todos.length,
    };
  });

  app.get('/api/agent-runtime/checkpoints', async req => {
    if (!bridge.isRunning) {
      return { checkpoints: [], count: 0 };
    }

    const [rawTasks, checkpoints] = await Promise.all([fetchTasks(req), fetchCheckpoints(req)]);
    const taskMap = new Map(
      rawTasks
        .filter(task => typeof task.id === 'string' && task.id.length > 0)
        .map(task => [task.id as string, task])
    );
    const now = new Date().toISOString();
    const projected = checkpoints.map(checkpoint =>
      toAgentCheckpoint(
        typeof checkpoint.task_id === 'string' ? (taskMap.get(checkpoint.task_id) ?? null) : null,
        checkpoint,
        now
      )
    );

    return {
      checkpoints: projected,
      count: projected.length,
    };
  });

  app.get('/api/agent-runtime/checkpoints/:checkpointId', async req => {
    if (!bridge.isRunning) {
      throw new NotFoundError('Checkpoint not found');
    }

    const checkpointId = checkpointTaskIdFromId(
      (req.params as { checkpointId: string }).checkpointId ?? ''
    );
    if (!checkpointId) {
      throw new BadRequestError('checkpointId is required');
    }

    const [rawTasks, checkpoints] = await Promise.all([fetchTasks(req), fetchCheckpoints(req)]);
    const rawCheckpoint = checkpoints.find(
      checkpoint => typeof checkpoint.task_id === 'string' && checkpoint.task_id === checkpointId
    );
    if (!rawCheckpoint) {
      throw new NotFoundError('Checkpoint not found');
    }

    const task =
      rawTasks.find(rawTask => typeof rawTask.id === 'string' && rawTask.id === checkpointId) ??
      null;
    const now = new Date().toISOString();

    return toCheckpointDetail(task, rawCheckpoint, now);
  });

  app.get('/api/agent-runtime/approvals', async req => {
    const records = await fetchApprovalRecords(req);
    const now = new Date().toISOString();
    const approvals = records.map(record => toApprovalCheckpoint(record, now));

    return {
      approvals,
      count: approvals.length,
    };
  });

  app.get('/api/agent-runtime/actions', async req => {
    const records = await fetchAuditRecords(req);
    const now = new Date().toISOString();
    const actions = records.map(record => toActionLog(record, now));

    return {
      actions,
      count: actions.length,
    };
  });

  app.post('/api/agent-runtime/dispatch/validate', async req => {
    const parsed = AgentDispatchRequestSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    if (parsed.data.targetProductId !== PRODUCT_ID) {
      throw new BadRequestError(
        `Dispatch target product '${parsed.data.targetProductId}' must match '${PRODUCT_ID}'`
      );
    }

    if (parsed.data.targetExecutor !== 'cowork' && parsed.data.targetExecutor !== 'generic-agent') {
      throw new BadRequestError('Cowork runtime only accepts cowork or generic-agent dispatch');
    }

    return {
      valid: true,
      dispatchRequest: parsed.data,
    };
  });
}
