import { createHash } from "node:crypto";
import type { JsonValue, LiveOutput, PromptHistoryItem, QueueTask } from "./types";

const steerIdPattern = /^[A-Za-z0-9._:-]{8,128}$/u;

export type SteerDeliveryState = "accepted" | "not_accepted" | "accepted_response_timeout" | "unknown";

export interface SteerTraceMatch {
  seq: number;
  at: string;
  method: "turn/steer";
  steerId: string;
  promptChars: number;
  promptHash: string;
  promptOmitted: true;
  source: "promptHistory" | "output";
}

export interface SteerTraceConfirmation {
  taskId: string;
  steerId: string;
  found: boolean;
  accepted: boolean;
  deliveryState: SteerDeliveryState;
  trace: SteerTraceMatch | null;
  matches: SteerTraceMatch[];
  duplicateSuppressionKey: string;
}

export interface SteerDuplicateDecision {
  duplicate: boolean;
  conflict: boolean;
  confirmation: SteerTraceConfirmation;
  existingPromptHash: string | null;
  requestedPromptHash: string;
}

export function normalizeSteerId(value: unknown): string | null {
  if (typeof value !== "string") return null;
  const text = value.trim();
  if (!steerIdPattern.test(text)) return null;
  return text;
}

export function steerPromptHash(prompt: string): string {
  return createHash("sha256").update(prompt, "utf8").digest("hex");
}

export function createSteerId(taskId: string, prompt: string): string {
  const hash = createHash("sha256")
    .update("unidesk-code-queue-steer:v1", "utf8")
    .update("\0", "utf8")
    .update(taskId, "utf8")
    .update("\0", "utf8")
    .update(prompt, "utf8")
    .digest("hex")
    .slice(0, 24);
  return `steer_${hash}`;
}

export function steerTraceText(steerId: string, prompt: string): string {
  return `\n[steer id=${steerId}] ${prompt}\n`;
}

export function normalizeSteerPromptText(text: string): string {
  return text.replace(/^\s*\[steer(?:\s+[^\]]*)?\]\s*/u, "").trimEnd();
}

function promptHistorySteerId(item: PromptHistoryItem): string | null {
  const record = item as PromptHistoryItem & { steerId?: unknown };
  return normalizeSteerId(record.steerId);
}

function promptHistoryMatch(item: PromptHistoryItem, steerId: string): SteerTraceMatch | null {
  if (promptHistorySteerId(item) !== steerId) return null;
  const prompt = normalizeSteerPromptText(String(item.text || ""));
  return {
    seq: item.seq,
    at: item.at,
    method: "turn/steer",
    steerId,
    promptChars: prompt.length,
    promptHash: steerPromptHash(prompt),
    promptOmitted: true,
    source: "promptHistory",
  };
}

function outputMatch(item: LiveOutput, steerId: string): SteerTraceMatch | null {
  if (item.channel !== "user" || item.method !== "turn/steer" || normalizeSteerId(item.itemId) !== steerId) return null;
  const prompt = normalizeSteerPromptText(item.text);
  return {
    seq: item.seq,
    at: item.at,
    method: "turn/steer",
    steerId,
    promptChars: prompt.length,
    promptHash: steerPromptHash(prompt),
    promptOmitted: true,
    source: "output",
  };
}

export function findSteerTraceConfirmation(task: QueueTask, steerId: string, output: LiveOutput[] = task.output): SteerTraceConfirmation {
  const matches = [
    ...(Array.isArray(task.promptHistory) ? task.promptHistory : []).map((item) => promptHistoryMatch(item, steerId)),
    ...output.map((item) => outputMatch(item, steerId)),
  ]
    .filter((item): item is SteerTraceMatch => item !== null)
    .sort((left, right) => left.seq - right.seq);
  const bySeq = new Map<number, SteerTraceMatch>();
  for (const match of matches) {
    const existing = bySeq.get(match.seq);
    if (existing === undefined || existing.source === "promptHistory") bySeq.set(match.seq, match);
  }
  const uniqueMatches = Array.from(bySeq.values()).sort((left, right) => left.seq - right.seq);
  const trace = uniqueMatches[0] ?? null;
  return {
    taskId: task.id,
    steerId,
    found: trace !== null,
    accepted: trace !== null,
    deliveryState: trace !== null ? "accepted" : "unknown",
    trace,
    matches: uniqueMatches,
    duplicateSuppressionKey: steerId,
  };
}

export function steerDuplicateDecision(task: QueueTask, steerId: string, prompt: string, output: LiveOutput[] = task.output): SteerDuplicateDecision {
  const confirmation = findSteerTraceConfirmation(task, steerId, output);
  const requestedPromptHash = steerPromptHash(prompt);
  const existingPromptHash = confirmation.trace?.promptHash ?? null;
  return {
    duplicate: confirmation.found && existingPromptHash === requestedPromptHash,
    conflict: confirmation.found && existingPromptHash !== requestedPromptHash,
    confirmation,
    existingPromptHash,
    requestedPromptHash,
  };
}

export function steerTraceConfirmationJson(confirmation: SteerTraceConfirmation): JsonValue {
  return {
    taskId: confirmation.taskId,
    steerId: confirmation.steerId,
    found: confirmation.found,
    accepted: confirmation.accepted,
    deliveryState: confirmation.deliveryState,
    trace: confirmation.trace as unknown as JsonValue,
    matchCount: confirmation.matches.length,
    matches: confirmation.matches.slice(0, 5) as unknown as JsonValue,
    matchesTruncated: confirmation.matches.length > 5,
    duplicateSuppressionKey: confirmation.duplicateSuppressionKey,
    promptOmitted: true,
  };
}
