import { isModelDocumentPath } from 'ejclaw-runners-shared';

import { buildArbiterContextPrompt } from './arbiter-context.js';
import { TASK_USER_CONTEXT_START_SKEW_MS } from './message-runtime-task-context.js';
import { formatMessages } from './router.js';
import type {
  NewMessage,
  OutboundAttachment,
  PairedTask,
  PairedTurnOutput,
} from './types.js';

const CARRIED_FORWARD_OWNER_FINAL_MARKER =
  '[Carried forward context from the previous task: latest owner final]';

const CARRIED_FORWARD_OWNER_FINAL_GUIDANCE = `System note:
If you see a message beginning with "${CARRIED_FORWARD_OWNER_FINAL_MARKER}", treat it as background only. Do not repeat, continue, or answer that carried-forward final directly. Respond only to the latest human request and the current task.`;

const ARBITER_TURN_OUTPUT_CONTEXT_LIMIT = 6;

function isImageAttachment(attachment: OutboundAttachment): boolean {
  if (attachment.mime?.toLowerCase().startsWith('image/')) return true;
  return /\.(?:png|jpe?g|gif|webp|bmp)$/i.test(attachment.path);
}

function isDocumentAttachment(attachment: OutboundAttachment): boolean {
  const mime = attachment.mime?.toLowerCase();
  if (mime === 'application/pdf' || mime?.startsWith('text/')) return true;
  return isModelDocumentPath(attachment.path);
}

function attachmentLabel(attachment: OutboundAttachment): string {
  return attachment.name?.trim() || attachment.path.split('/').at(-1) || 'file';
}

function nonImageAttachmentReason(attachment: OutboundAttachment): string {
  const mime = attachment.mime?.trim();
  if (mime) {
    return `${mime} is not loaded as structured model input`;
  }
  return 'non-image attachment is not loaded as structured model input';
}

function formatTurnOutputAttachmentContext(
  attachments: OutboundAttachment[] | undefined,
): string {
  if (!attachments?.length) return '';
  const lines = attachments.map((attachment) => {
    const label = attachmentLabel(attachment);
    if (isImageAttachment(attachment)) {
      return `[Image: ${label} → ${attachment.path}]`;
    }
    if (isDocumentAttachment(attachment)) {
      return `[File: ${label} → ${attachment.path}]`;
    }
    return `[Attachment unavailable: ${label} → ${attachment.path} — ${nonImageAttachmentReason(
      attachment,
    )}]`;
  });
  return `\n\nAttached evidence from this turn:\n${lines.join('\n')}`;
}

function formatTurnOutputMessageContent(turnOutput: PairedTurnOutput): string {
  return `${turnOutput.output_text}${formatTurnOutputAttachmentContext(
    turnOutput.attachments,
  )}`;
}

function turnOutputsToMessages(
  outputs: PairedTurnOutput[],
  chatJid: string,
): NewMessage[] {
  return outputs.map((turnOutput) => ({
    id: `turn-${turnOutput.task_id}-${turnOutput.turn_number}`,
    chat_jid: chatJid,
    sender: turnOutput.role,
    sender_name: turnOutput.role,
    content: formatTurnOutputMessageContent(turnOutput),
    timestamp: turnOutput.created_at,
    is_bot_message: true as const,
    is_from_me: false as const,
  }));
}

function omittedTurnOutputsMessage(args: {
  chatJid: string;
  omittedCount: number;
  firstVisibleOutput?: PairedTurnOutput;
}): NewMessage {
  return {
    id: `turn-output-omitted-${args.firstVisibleOutput?.task_id ?? 'unknown'}-${args.omittedCount}`,
    chat_jid: args.chatJid,
    sender: 'system',
    sender_name: 'system',
    content: `[Earlier paired turn outputs omitted: ${args.omittedCount} older outputs not shown]`,
    timestamp:
      args.firstVisibleOutput?.created_at ?? '1970-01-01T00:00:00.000Z',
    is_bot_message: true as const,
    is_from_me: false as const,
  };
}

function latestTurnOutputMessagesWithOmissionMarker(
  outputs: PairedTurnOutput[],
  chatJid: string,
  limit: number,
): NewMessage[] {
  const latestOutputs = latestTurnOutputs(outputs, limit);
  const messages = turnOutputsToMessages(latestOutputs, chatJid);
  const omittedCount = outputs.length - latestOutputs.length;
  if (omittedCount <= 0) {
    return messages;
  }
  return [
    omittedTurnOutputsMessage({
      chatJid,
      omittedCount,
      firstVisibleOutput: latestOutputs[0],
    }),
    ...messages,
  ];
}

function mergeHumanAndTurnOutputMessages(
  chatJid: string,
  humanMessages: NewMessage[],
  turnOutputs: PairedTurnOutput[],
): NewMessage[] {
  return [
    ...humanMessages,
    ...turnOutputsToMessages(turnOutputs, chatJid),
  ].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
}

function latestTurnOutputs(
  outputs: PairedTurnOutput[],
  limit: number,
): PairedTurnOutput[] {
  if (outputs.length <= limit) {
    return outputs;
  }
  return outputs.slice(-limit);
}

function currentTaskHumanMessages(
  messages: NewMessage[],
  taskCreatedAt: string | null | undefined,
): NewMessage[] {
  if (!taskCreatedAt) return [];
  const taskStartMs = Date.parse(taskCreatedAt);
  if (!Number.isFinite(taskStartMs)) return [];
  return messages.filter((message) => {
    if (message.is_bot_message) return false;
    const messageMs = Date.parse(message.timestamp);
    return (
      Number.isFinite(messageMs) &&
      messageMs >= taskStartMs - TASK_USER_CONTEXT_START_SKEW_MS
    );
  });
}

function hasCarriedForwardOwnerFinal(outputs: PairedTurnOutput[]): boolean {
  return outputs.some((output) =>
    output.output_text.startsWith(CARRIED_FORWARD_OWNER_FINAL_MARKER),
  );
}

function prependCarriedForwardGuidance(
  prompt: string,
  turnOutputs: PairedTurnOutput[],
): string {
  if (!hasCarriedForwardOwnerFinal(turnOutputs)) {
    return prompt;
  }
  return `${CARRIED_FORWARD_OWNER_FINAL_GUIDANCE}\n\n${prompt}`;
}

export function buildPairedTurnPrompt(args: {
  taskId: string;
  chatJid: string;
  timezone: string;
  missedMessages: NewMessage[];
  labeledFallbackMessages: NewMessage[];
  turnOutputs: PairedTurnOutput[];
}): string {
  if (args.turnOutputs.length === 0) {
    return formatMessages(args.labeledFallbackMessages, args.timezone);
  }

  const humanMessages = args.missedMessages.filter(
    (message) => !message.is_bot_message,
  );
  return prependCarriedForwardGuidance(
    formatMessages(
      mergeHumanAndTurnOutputMessages(
        args.chatJid,
        humanMessages,
        args.turnOutputs,
      ),
      args.timezone,
    ),
    args.turnOutputs,
  );
}

function turnOutputsOnlyPrompt(
  chatJid: string,
  timezone: string,
  turnOutputs: PairedTurnOutput[],
  taskHumanMessages: NewMessage[] = [],
): string {
  return prependCarriedForwardGuidance(
    formatMessages(
      [...taskHumanMessages, ...turnOutputsToMessages(turnOutputs, chatJid)],
      timezone,
    ),
    turnOutputs,
  );
}

export function buildReviewerPendingPrompt(args: {
  chatJid: string;
  timezone: string;
  turnOutputs: PairedTurnOutput[];
  recentHumanMessages: NewMessage[];
  lastHumanMessage: string | null | undefined;
  taskCreatedAt?: string | null;
}): string {
  if (args.turnOutputs.length > 0) {
    return turnOutputsOnlyPrompt(
      args.chatJid,
      args.timezone,
      args.turnOutputs,
      currentTaskHumanMessages(args.recentHumanMessages, args.taskCreatedAt),
    );
  }

  if (!args.lastHumanMessage) {
    return 'Review the latest owner changes in the workspace.';
  }

  return `User request:\n---\n${args.lastHumanMessage}\n---\n\nReview the latest owner changes in the workspace.`;
}

export function buildOwnerPendingPrompt(args: {
  chatJid: string;
  timezone: string;
  turnOutputs: PairedTurnOutput[];
  recentHumanMessages: NewMessage[];
  lastHumanMessage: string | null | undefined;
  taskCreatedAt?: string | null;
}): string {
  if (args.turnOutputs.length > 0) {
    return turnOutputsOnlyPrompt(
      args.chatJid,
      args.timezone,
      args.turnOutputs,
      currentTaskHumanMessages(args.recentHumanMessages, args.taskCreatedAt),
    );
  }

  if (!args.lastHumanMessage) {
    return 'Continue the owner turn using the latest reviewer or arbiter feedback.';
  }

  return `User request:\n---\n${args.lastHumanMessage}\n---\n\nContinue the owner turn using the latest reviewer or arbiter feedback.`;
}

export function buildArbiterPromptForTask(args: {
  task: PairedTask;
  chatJid: string;
  timezone: string;
  turnOutputs: PairedTurnOutput[];
  recentMessages: NewMessage[];
  labeledRecentMessages: NewMessage[];
}): string {
  const taskHumanMessages = currentTaskHumanMessages(
    args.recentMessages,
    args.task.created_at,
  );
  const messages =
    args.turnOutputs.length > 0
      ? [
          ...taskHumanMessages,
          ...latestTurnOutputMessagesWithOmissionMarker(
            args.turnOutputs,
            args.chatJid,
            ARBITER_TURN_OUTPUT_CONTEXT_LIMIT,
          ),
        ]
      : args.labeledRecentMessages;

  return buildArbiterContextPrompt({
    chatJid: args.chatJid,
    taskId: args.task.id,
    roundTripCount: args.task.round_trip_count,
    timezone: args.timezone,
    messages,
  });
}

export function buildFinalizePendingPrompt(_args: {
  turnOutputs: PairedTurnOutput[];
}): string {
  return `The reviewer approved the current task scope (TASK_DONE / legacy DONE). Finalize and report the result.
If you intend to close this paired turn now, your first line must be TASK_DONE.
Do not use STEP_DONE only because a broader roadmap still has remaining work; close the approved slice and continue the next slice in a new owner turn.
Use STEP_DONE only when this same approved scope still needs additional owner changes and another review pass.
If your first line is DONE_WITH_CONCERNS, the system will reopen review instead of finishing.`;
}
