import type {
  AgentTicketState,
  AgentWorkState,
  BackgroundCompletion,
  ConversationPreviewStatus,
  ConversationTurnWorkPreview,
  ConversationWorkSummary,
  InboxItem,
  ProjectWorkState,
  TicketWorkSource,
  TicketWorkStatus,
  WorkBusyState,
} from "@/domain";

type RecordLike = Record<string, unknown>;

function isRecord(value: unknown): value is RecordLike {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}

function stringValue(value: unknown, fallback = ""): string {
  return typeof value === "string" ? value : fallback;
}

function numberValue(value: unknown, fallback = 0): number {
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}

function nullableNumber(value: unknown): number | null {
  return typeof value === "number" && Number.isFinite(value) ? value : null;
}

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

function optionalString(value: unknown): string | undefined {
  return typeof value === "string" ? value : undefined;
}

function nullableString(value: unknown): string | null {
  return typeof value === "string" ? value : null;
}

function normalizeSource(value: unknown): TicketWorkSource {
  if (!isRecord(value)) return { kind: "human" };
  if (value.kind === "agent") {
    return { kind: "agent", agentId: stringValue(value.agentId, "unknown") };
  }
  return { kind: "human" };
}

function normalizeTicketStatus(value: unknown): TicketWorkStatus {
  return value === "inProgress" ? "inProgress" : "queued";
}

function normalizeBusy(value: unknown): WorkBusyState {
  if (isRecord(value) && value.state === "busy") {
    return {
      state: "busy",
      ticket: stringValue(value.ticket, "unknown"),
      sinceMs: numberValue(value.sinceMs, 0),
    };
  }
  return { state: "idle" };
}

function normalizeTicket(value: unknown, index: number): AgentTicketState {
  const ticket = isRecord(value) ? value : {};
  const taskPreview = stringValue(ticket.taskPreview);
  return {
    ticketId: stringValue(ticket.ticketId, `legacy-ticket-${index}`),
    conversationId: stringValue(ticket.conversationId),
    position: numberValue(ticket.position, index),
    status: normalizeTicketStatus(ticket.status),
    source: normalizeSource(ticket.source),
    requesterLabel: stringValue(ticket.requesterLabel),
    taskPreview,
    taskLen: numberValue(ticket.taskLen, taskPreview.length),
  };
}

function normalizeBackgroundStatus(
  value: unknown,
): BackgroundCompletion["status"] {
  const raw = typeof value === "string" ? value.toLowerCase() : "";
  if (raw === "completed" || raw === "success" || raw === "succeeded") return "completed";
  if (raw === "failed" || raw === "failure") return "failed";
  if (raw === "cancelled" || raw === "canceled" || raw === "expired") return "cancelled";
  if (raw === "running" || raw === "started") return "running";
  if (raw === "delivered") return "delivered";
  // Backend `queued`/`waiting` collapse to the cancellable "pending" bucket.
  if (raw === "queued" || raw === "waiting") return "pending";
  return "pending";
}

function normalizeBackgroundTask(
  value: unknown,
  index: number,
  fallbackAgentId: string,
): BackgroundCompletion {
  const task = isRecord(value) ? value : {};
  return {
    taskId: stringValue(task.taskId, `legacy-task-${index}`),
    ownerAgentId: stringValue(task.ownerAgentId, fallbackAgentId),
    projectId: stringValue(task.projectId),
    kind: stringValue(task.kind, "background"),
    status: normalizeBackgroundStatus(task.status ?? task.state),
    exitCode: nullableNumber(task.exitCode),
    summary: nullableString(task.summary),
    stdoutTail: nullableString(task.stdoutTail),
    stderrTail: nullableString(task.stderrTail),
    updatedAtMs: numberValue(task.updatedAtMs),
  };
}

function normalizeInboxItem(
  value: unknown,
  index: number,
  fallbackAgentId: string,
): InboxItem {
  const item = isRecord(value) ? value : {};
  return {
    id: stringValue(item.id, `legacy-inbox-${index}`),
    agentId: stringValue(item.agentId ?? item.agent_id, fallbackAgentId),
    source: stringValue(item.source, "unknown"),
    kind: stringValue(item.kind, "message"),
    body: stringValue(item.body),
    createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms, 0),
    ...(optionalString(item.correlationId ?? item.correlation_id)
      ? { correlationId: optionalString(item.correlationId ?? item.correlation_id) }
      : {}),
    priority: numberValue(item.priority, 0),
  };
}

function normalizeAgent(value: unknown, index: number): AgentWorkState {
  const agent = isRecord(value) ? value : {};
  const liveKind: "pty" | "structured" =
    isRecord(agent.live) && agent.live.kind === "structured" ? "structured" : "pty";
  const live = isRecord(agent.live)
    ? {
        nodeId: stringValue(agent.live.nodeId),
        sessionId: stringValue(agent.live.sessionId),
        kind: liveKind,
      }
    : undefined;

  const agentId = stringValue(agent.agentId, `legacy-agent-${index}`);
  const inbox = arrayValue(agent.inbox).map((item, itemIndex) =>
    normalizeInboxItem(item, itemIndex, agentId),
  );
  const backgroundTasks = arrayValue(
    agent.backgroundTasks ?? agent.backgroundCompletions,
  ).map((task, taskIndex) =>
    normalizeBackgroundTask(task, taskIndex, agentId),
  );

  return {
    agentId,
    name: stringValue(agent.name, "Unnamed agent"),
    profileId: stringValue(agent.profileId, "unknown"),
    ...(live ? { live } : {}),
    busy: normalizeBusy(agent.busy),
    tickets: arrayValue(agent.tickets).map(normalizeTicket),
    inboxDepth: numberValue(agent.inboxDepth, inbox.length),
    inbox,
    backgroundTasks,
  };
}

function normalizeSummaryStatus(value: unknown): ConversationPreviewStatus {
  if (
    value === "ready" ||
    value === "partial" ||
    value === "unavailable" ||
    value === "missing"
  ) {
    return value;
  }
  return "missing";
}

function normalizeTurn(value: unknown, index: number): ConversationTurnWorkPreview {
  const turn = isRecord(value) ? value : {};
  const textPreview = stringValue(turn.textPreview);
  const role =
    turn.role === "response" || turn.role === "toolActivity"
      ? turn.role
      : "prompt";
  return {
    role,
    source: normalizeSource(turn.source),
    atMs: numberValue(turn.atMs, index),
    textPreview,
    textLen: numberValue(turn.textLen, textPreview.length),
  };
}

function normalizeConversation(
  value: unknown,
  index: number,
): ConversationWorkSummary {
  const summary = isRecord(value) ? value : {};
  return {
    conversationId: stringValue(summary.conversationId, `legacy-conversation-${index}`),
    status: normalizeSummaryStatus(summary.status),
    objectivePreview: nullableString(summary.objectivePreview),
    summaryPreview: nullableString(summary.summaryPreview),
    summaryLen: numberValue(summary.summaryLen, 0),
    upTo: nullableString(summary.upTo),
    recentTurns: arrayValue(summary.recentTurns).map(normalizeTurn),
  };
}

export function normalizeProjectWorkState(value: unknown): ProjectWorkState {
  const state = isRecord(value) ? value : {};
  const agents = arrayValue(state.agents).map(normalizeAgent);
  // Background tasks are always emitted nested under their owning agent
  // (`agent.backgroundTasks`) and the backend DTO carries no `ownerAgentId`, so a
  // top-level merge keyed on `task.ownerAgentId` is unreachable for this contract
  // and has been removed (ticket #5). The per-agent `ownerAgentId = agentId`
  // fallback in `normalizeBackgroundTask` is what nested tasks rely on.
  const topLevelInbox = arrayValue(state.inboxItems).map((item, index) =>
    normalizeInboxItem(item, index, ""),
  );
  for (const item of topLevelInbox) {
    const owner = agents.find((agent) => agent.agentId === item.agentId);
    if (!owner) continue;
    owner.inbox ??= [];
    owner.inboxDepth ??= 0;
    if (!owner.inbox.some((entry) => entry.id === item.id)) {
      owner.inbox.push(item);
      owner.inboxDepth = Math.max(owner.inboxDepth, owner.inbox.length);
    }
  }
  return {
    agents,
    conversations: arrayValue(state.conversations).map(normalizeConversation),
  };
}
