import type { BackendEvent, BackendService } from "./api";

export type ConnectionState = "connecting" | "online" | "offline";
export type ServiceStatus = "running" | "stopped" | "faulted";
export type EventTone = "success" | "warning" | "danger" | "neutral";

export type Service = {
  id: string;
  revision: string;
  artifact: string;
  world: string;
  status: ServiceStatus;
  updatedAt: string;
  memoryMb: number;
  fuel: string;
  deadlineMs: number;
  mailbox: number;
  maxInputKb: number;
  capabilities: BackendService["capabilities"];
  calls: number;
  errors: number;
  latencyMs: number | null;
  active: boolean;
};

export type RuntimeEvent = {
  id: number;
  time: string;
  title: string;
  detail: string;
  tone: EventTone;
};

export type FormattedInvocationOutput = {
  text: string;
  format: "UTF-8" | "HEX" | "U64 LE";
  automatic: boolean;
};

export const STATUS_META: Record<ServiceStatus, { label: string }> = {
  running: { label: "运行中" },
  stopped: { label: "已停止" },
  faulted: { label: "故障" },
};

export function serviceKey(service: Pick<Service, "id" | "revision">): string {
  return `${service.id}@${service.revision}`;
}

export function toService(service: BackendService, activeRevision?: string): Service {
  return {
    id: service.id,
    revision: service.revision,
    artifact: service.artifact.split(/[\\/]/).pop() ?? service.artifact,
    world: service.world,
    status: service.status,
    updatedAt: new Date(service.updated_at_ms).toLocaleString("zh-CN", { hour12: false }),
    memoryMb: Math.round(service.limits.memory_bytes / 1024 / 1024),
    fuel: compactNumber(service.limits.fuel_per_call),
    deadlineMs: service.limits.deadline_ms,
    mailbox: service.limits.mailbox_capacity,
    maxInputKb: Math.round(service.limits.max_input_bytes / 1024),
    capabilities: service.capabilities,
    calls: service.calls,
    errors: service.errors,
    latencyMs: service.last_latency_ms,
    active: service.revision === activeRevision,
  };
}

export function toEvent(event: BackendEvent): RuntimeEvent {
  const identity =
    event.service_id && event.revision ? `${event.service_id}@${event.revision}` : "Wasmeld";
  const meta: Record<BackendEvent["kind"], { label: string; tone: EventTone }> = {
    registered: { label: "已注册", tone: "success" },
    unregistered: { label: "已注销", tone: "neutral" },
    deployed: { label: "部署切换", tone: "success" },
    started: { label: "已启动", tone: "success" },
    stopped: { label: "已停止", tone: "neutral" },
    invoked: { label: "调用完成", tone: "success" },
    failed: { label: "操作失败", tone: "danger" },
  };
  return {
    id: event.id,
    time: new Date(event.timestamp_ms).toLocaleTimeString("zh-CN", { hour12: false }),
    title: `${identity} ${meta[event.kind].label}`,
    detail: event.message,
    tone: meta[event.kind].tone,
  };
}

export function compactNumber(value: number): string {
  if (value >= 1_000_000) return `${value / 1_000_000}M`;
  if (value >= 1_000) return `${value / 1_000}K`;
  return value.toString();
}

export function formatDuration(milliseconds: number): string {
  const totalSeconds = Math.floor(milliseconds / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m ${seconds}s`;
}

export function parseInvocationInput(value: string, format: "utf8" | "hex"): Uint8Array {
  if (format === "utf8") return new TextEncoder().encode(value);
  const compact = value.replace(/\s+/g, "");
  if (compact.length % 2 !== 0 || /[^0-9a-f]/i.test(compact)) {
    throw new Error("HEX 输入必须由成对的十六进制字符组成");
  }
  return Uint8Array.from(compact.match(/.{2}/g)?.map((byte) => Number.parseInt(byte, 16)) ?? []);
}

export function invocationInputSize(value: string, format: "utf8" | "hex"): number {
  try {
    return parseInvocationInput(value, format).byteLength;
  } catch {
    return 0;
  }
}

export function formatInvocationOutput(
  service: Service,
  output: Uint8Array,
  format: "utf8" | "hex",
): FormattedInvocationOutput {
  if (service.id === "counter" && output.byteLength === 8) {
    return {
      text: new DataView(output.buffer, output.byteOffset, output.byteLength)
        .getBigUint64(0, true)
        .toString(),
      format: "U64 LE",
      automatic: false,
    };
  }
  if (format === "hex") {
    return { text: formatHex(output) || "(empty)", format: "HEX", automatic: false };
  }
  const text = decodeReadableUtf8(output);
  if (text !== null) {
    return { text: text || "(empty)", format: "UTF-8", automatic: false };
  }
  return { text: formatHex(output), format: "HEX", automatic: true };
}

function formatHex(bytes: Uint8Array): string {
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(" ");
}

function decodeReadableUtf8(bytes: Uint8Array): string | null {
  let text: string;
  try {
    text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
  } catch {
    return null;
  }
  for (const character of text) {
    const codePoint = character.codePointAt(0) ?? 0;
    const allowedWhitespace = codePoint === 0x09 || codePoint === 0x0a || codePoint === 0x0d;
    if ((codePoint < 0x20 && !allowedWhitespace) || codePoint === 0x7f) return null;
  }
  return text;
}
