import {
  normalizeImageRecordForSave,
  normalizeImagesPayloadForSave,
  normalizeTileRecordForSave,
} from "../contentTransforms/graphicsPayload";
import { getSpriteRows } from "../graphics/rowEncoding";
import type { JsonObject, JsonValue } from "../contracts/json";

export type GraphicRole = "tile" | "sprite" | "other";

export function normalizeGraphicRoles(value: unknown): string[] {
  if (!Array.isArray(value)) {
    return [];
  }
  return Array.from(new Set(
    value
      .map((entry) => String(entry || "").trim().toLowerCase())
      .filter((entry) => entry === "tile" || entry === "sprite"),
  ));
}

export function hydrateImageRecordRows(entry: JsonObject | null | undefined): JsonObject | null {
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
    return null;
  }
  return {
    ...entry,
    rows: getSpriteRows(entry),
  };
}

export function getImageRecordFromPayload(
  imagesPayload: JsonObject | null | undefined,
  recordId: string,
): JsonObject | null {
  const normalizedId = String(recordId || "").trim();
  if (!normalizedId) {
    return null;
  }
  const records = Array.isArray(imagesPayload?.images) ? imagesPayload.images : [];
  const matchedEntry = (records.find((entry) => (
    entry
    && typeof entry === "object"
    && !Array.isArray(entry)
    && String((entry as JsonObject).id || "").trim() === normalizedId
  )) as JsonObject | undefined) || null;
  return hydrateImageRecordRows(matchedEntry);
}

export function buildImageRecordFromTileRecord(
  record: JsonObject,
  existingRecord?: JsonObject | null,
  cloneValue: <T>(value: T) => T = structuredClone,
): JsonObject {
  const existing = existingRecord && typeof existingRecord === "object" && !Array.isArray(existingRecord)
    ? existingRecord
    : {};
  const existingRoles = normalizeGraphicRoles(existing.roles);
  return normalizeImageRecordForSave({
    ...cloneValue(existing),
    id: String(record?.id || existing.id || "").trim(),
    name: String(record?.name || existing.name || "").trim(),
    description: String(record?.description || existing.description || "").trim(),
    width: Math.max(1, Number(record?.width) || Number(existing.width) || 16),
    height: Math.max(1, Number(record?.height) || Number(existing.height) || 16),
    pixelScale: Math.max(1, Number(record?.pixelScale) || Number(existing.pixelScale) || 1),
    opacity: Math.max(0, Math.min(1, Number(record?.opacity ?? existing.opacity ?? 1))),
    rows: Array.isArray(record?.rows)
      ? record.rows.map((row) => String(row || ""))
      : getSpriteRows(existing),
    tags: cloneValue(record?.tags) || cloneValue(existing.tags) || [],
    roles: Array.from(new Set([...existingRoles, "tile"])),
    tileSymbol: String(record?.tileSymbol || record?.symbol || existing.tileSymbol || "").trim().charAt(0),
  });
}

export function buildImageRecordFromSpriteRecord(
  record: JsonObject,
  graphicRole: GraphicRole,
  existingRecord?: JsonObject | null,
  cloneValue: <T>(value: T) => T = structuredClone,
): JsonObject {
  const existing = existingRecord && typeof existingRecord === "object" && !Array.isArray(existingRecord)
    ? existingRecord
    : {};
  const existingRoles = normalizeGraphicRoles(existing.roles);
  const wantsSpriteRole = graphicRole !== "other";
  const nextRoles = wantsSpriteRole
    ? Array.from(new Set([...existingRoles, "sprite"]))
    : existingRoles.filter((entry) => entry !== "sprite");
  return normalizeImageRecordForSave({
    ...cloneValue(existing),
    id: String(record?.id || existing.id || "").trim(),
    name: String(record?.name || existing.name || "").trim(),
    description: String(record?.description || existing.description || "").trim(),
    width: Math.max(1, Number(record?.width) || Number(existing.width) || 16),
    height: Math.max(1, Number(record?.height) || Number(existing.height) || 16),
    pixelScale: Math.max(1, Number(record?.pixelScale) || Number(existing.pixelScale) || 1),
    opacity: Math.max(0, Math.min(1, Number(record?.opacity ?? existing.opacity ?? 1))),
    rows: Array.isArray(record?.rows)
      ? record.rows.map((row) => String(row || ""))
      : getSpriteRows(existing),
    tags: cloneValue(record?.tags) || cloneValue(existing.tags) || [],
    roles: nextRoles,
    tileSymbol: String(existing.tileSymbol || record?.tileSymbol || record?.symbol || "").trim().charAt(0),
  });
}

export function normalizeImagesPayloadSnapshot(
  payload: JsonObject | null | undefined,
  cloneValue: <T>(value: T) => T = structuredClone,
): JsonObject {
  const normalized = (normalizeImagesPayloadForSave(cloneValue(payload || { schemaVersion: 1, images: [] }) as JsonValue) as JsonObject) || {
    schemaVersion: 1,
    images: [],
  };
  const records = Array.isArray(normalized.images) ? normalized.images : [];
  return {
    ...normalized,
    images: records.map((entry) => {
      const hydratedEntry = hydrateImageRecordRows(
        entry && typeof entry === "object" && !Array.isArray(entry)
          ? entry as JsonObject
          : null,
      );
      return hydratedEntry || entry;
    }),
  };
}

export function buildTileRecordFromImageRecord(entry: JsonObject, cloneValue: <T>(value: T) => T = structuredClone): JsonObject {
  return normalizeTileRecordForSave({
    id: String(entry.id || "").trim(),
    symbol: String(entry.tileSymbol || entry.symbol || "").trim().charAt(0),
    name: String(entry.name || "").trim(),
    description: String(entry.description || "").trim(),
    width: Number(entry.width) || 16,
    height: Number(entry.height) || 16,
    pixelScale: Number(entry.pixelScale) || 1,
    opacity: Number(entry.opacity ?? 1),
    rows: getSpriteRows(entry),
    tags: cloneValue(entry.tags) || [],
  });
}
