/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck

import {
  buildSpritePreviewDataUrl,
  getSpritePalette,
  normalizeImagePlayback,
} from "../editorCore";
import { normalizeEditorTags } from "./tagUtils";

export const TILE_ART_SIZE = 16;

export const EYEDROPPER_CURSOR = `url("data:image/svg+xml,${encodeURIComponent(
  `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
    <path d="M20.8 4.2c1.6-1.6 4.3-1.6 5.9 0s1.6 4.3 0 5.9l-3.1 3.1-5.9-5.9 3.1-3.1z" fill="#eef6ff" stroke="#08111d" stroke-width="1.5"/>
    <path d="M10.8 14.1l6.9-6.9 6.1 6.1-6.9 6.9-2.7.9-.9 2.7-3 3a2.2 2.2 0 0 1-3.1 0l-1-1a2.2 2.2 0 0 1 0-3.1l3-3 2.7-.9.9-2.7z" fill="#7ee8c6" stroke="#08111d" stroke-width="1.5" stroke-linejoin="round"/>
    <circle cx="8.4" cy="23.6" r="2" fill="#ff5f6d"/>
  </svg>`,
)}") 4 28, crosshair`;

export function cloneValue(value) {
  if (typeof structuredClone === "function") {
    return structuredClone(value);
  }
  return value == null ? value : JSON.parse(JSON.stringify(value));
}

function normalizeRoleList(value) {
  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 normalizeTimelineRows(rows) {
  return Array.from({ length: TILE_ART_SIZE }, (_entry, rowIndex) => {
    const row = Array.isArray(rows) ? String(rows[rowIndex] || "") : "";
    return row.padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
  });
}

export function normalizeWorkingFrames(record) {
  const rawFrames = Array.isArray(record?.frames) ? record.frames.filter((entry) => entry && typeof entry === "object" && !Array.isArray(entry)) : [];
  const normalizedFrames = rawFrames.map((entry, index) => ({
    ...cloneValue(entry),
    id: String(entry.id || `frame_${index}`).trim() || `frame_${index}`,
    enabled: entry.enabled !== false,
    index: Number.isFinite(Number(entry.index)) ? Math.max(0, Math.floor(Number(entry.index))) : index,
    rows: normalizeTimelineRows(entry.rows),
  }));
  if (normalizedFrames.length > 0) {
    return normalizedFrames;
  }
  return [{
    id: "frame_0",
    enabled: true,
    index: 0,
    rows: normalizeTimelineRows(record?.rows),
  }];
}

export function sortWorkingFrames(frames) {
  return frames
    .map((frame, sourceIndex) => ({
      frame,
      sourceIndex,
      sortIndex: Number.isFinite(Number(frame?.index)) ? Number(frame.index) : sourceIndex,
    }))
    .sort((left, right) => (
      left.sortIndex !== right.sortIndex
        ? left.sortIndex - right.sortIndex
        : left.sourceIndex - right.sourceIndex
    ))
    .map((entry) => entry.frame);
}

export function normalizeWorkingGraphicRecord(recordType, record) {
  const source = cloneValue(record) || {};
  const roles = normalizeRoleList(source.roles);
  const nextRoles = recordType === "tile"
    ? Array.from(new Set([...roles, "tile"]))
    : (
      recordType === "sprite"
        ? Array.from(new Set([...roles, "sprite"]))
        : roles.filter((entry) => entry !== "sprite")
    );
  const frames = normalizeWorkingFrames(source).map((frame, index) => ({
    ...frame,
    index,
  }));
  const requestedDefaultFrameId = String(source.defaultFrame || "").trim();
  const defaultFrameId = String(
    frames.find((frame) => String(frame.id || "").trim() === requestedDefaultFrameId)?.id
      || frames[0]?.id
      || "frame_0",
  ).trim() || "frame_0";
  const workingRows = normalizeTimelineRows(
    Array.isArray(source.rows) && source.rows.length > 0
      ? source.rows
      : (frames.find((frame) => String(frame.id || "").trim() === defaultFrameId)?.rows || frames[0]?.rows || [])
  );
  return {
    ...source,
    id: String(source.id || `${recordType === "tile" ? "tile" : "sprite"}_${Date.now()}`).trim(),
    name: typeof source.name === "string" ? source.name : "",
    description: typeof source.description === "string" ? source.description : "",
    width: TILE_ART_SIZE,
    height: TILE_ART_SIZE,
    pixelScale: Math.max(1, Number(source.pixelScale) || 2),
    opacity: Number.isFinite(Number(source.opacity)) ? Math.max(0, Math.min(1, Number(source.opacity))) : 1,
    tags: normalizeEditorTags(source.tags),
    roles: nextRoles,
    tileSymbol: nextRoles.includes("tile")
      ? (String(source.tileSymbol ?? source.symbol ?? source.id ?? "T").trim().charAt(0) || "T")
      : "",
    defaultFrame: defaultFrameId,
    speed: Number.isFinite(Number(source.speed)) && Number(source.speed) >= 0 ? Number(source.speed) : 0,
    playback: normalizeImagePlayback(source.playback),
    frames,
    rows: workingRows,
  };
}

export function normalizeOpacityValue(value, fallback = 1) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) {
    return fallback;
  }
  return Math.max(0, Math.min(1, parsed));
}

export function formatOpacityValue(value) {
  const normalized = normalizeOpacityValue(value, 1);
  return normalized.toFixed(2).replace(/\.?0+$/, "");
}

export function cloneRows(rows) {
  return Array.isArray(rows)
    ? rows.map((row) => String(row || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE))
    : Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE));
}

export function buildRowsPreviewRecord(rows) {
  return {
    width: TILE_ART_SIZE,
    height: TILE_ART_SIZE,
    rows: cloneRows(rows),
  };
}

export function formatPlaybackLabel(value) {
  const normalized = normalizeImagePlayback(value);
  if (normalized === "rewind") {
    return "Rewind";
  }
  if (normalized === "stop") {
    return "Stop";
  }
  return "Normal";
}

export function getWorkingCellSymbol(record, x, y) {
  const rows = Array.isArray(record?.rows) ? record.rows : [];
  const row = String(rows[y] || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
  return String(row.charAt(x) || ".").charAt(0) || ".";
}

export function paintWorkingRowsCell(rows, x, y, symbol) {
  const nextRows = cloneRows(rows);
  const nextSymbol = String(symbol || ".").charAt(0) || ".";
  const targetRow = String(nextRows[y] || "").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
  nextRows[y] = `${targetRow.slice(0, x)}${nextSymbol}${targetRow.slice(x + 1)}`;
  return nextRows;
}

export function getRowsMatrix(rows) {
  return cloneRows(rows).map((row) => Array.from(row));
}

export function buildRowsFromMatrix(matrix) {
  return Array.from({ length: TILE_ART_SIZE }, (_entry, rowIndex) => {
    const sourceRow = Array.isArray(matrix?.[rowIndex]) ? matrix[rowIndex] : [];
    return sourceRow.join("").padEnd(TILE_ART_SIZE, ".").slice(0, TILE_ART_SIZE);
  });
}

export function getAlternatePaintSymbol(record, preferredSymbol) {
  const normalizedPreferred = String(preferredSymbol || "").charAt(0) || ".";
  const palette = getSpritePalette(record || undefined);
  const nextSymbol = Object.keys(palette)
    .map((symbol) => String(symbol || "").charAt(0))
    .find((symbol) => symbol && symbol !== normalizedPreferred && symbol !== ".");
  return nextSymbol || ".";
}

export function shiftRows(rows, offsetX, offsetY) {
  const nextRows = Array.from({ length: TILE_ART_SIZE }, () => ".".repeat(TILE_ART_SIZE).split(""));
  const sourceRows = cloneRows(rows);
  for (let y = 0; y < TILE_ART_SIZE; y += 1) {
    const row = sourceRows[y] || ".".repeat(TILE_ART_SIZE);
    for (let x = 0; x < TILE_ART_SIZE; x += 1) {
      const nextX = x + offsetX;
      const nextY = y + offsetY;
      if (nextX < 0 || nextX >= TILE_ART_SIZE || nextY < 0 || nextY >= TILE_ART_SIZE) {
        continue;
      }
      nextRows[nextY][nextX] = String(row.charAt(x) || ".").charAt(0) || ".";
    }
  }
  return buildRowsFromMatrix(nextRows);
}

export function flipRowsHorizontally(rows) {
  return cloneRows(rows).map((row) => row.split("").reverse().join(""));
}

export function flipRowsVertically(rows) {
  return cloneRows(rows).slice().reverse();
}

export function rotateRowsClockwise(rows) {
  const matrix = getRowsMatrix(rows);
  const nextMatrix = Array.from({ length: TILE_ART_SIZE }, () => Array.from({ length: TILE_ART_SIZE }, () => "."));
  for (let y = 0; y < TILE_ART_SIZE; y += 1) {
    for (let x = 0; x < TILE_ART_SIZE; x += 1) {
      nextMatrix[x][TILE_ART_SIZE - 1 - y] = String(matrix[y]?.[x] || ".").charAt(0) || ".";
    }
  }
  return buildRowsFromMatrix(nextMatrix);
}

export function rotateRowsCounterClockwise(rows) {
  const matrix = getRowsMatrix(rows);
  const nextMatrix = Array.from({ length: TILE_ART_SIZE }, () => Array.from({ length: TILE_ART_SIZE }, () => "."));
  for (let y = 0; y < TILE_ART_SIZE; y += 1) {
    for (let x = 0; x < TILE_ART_SIZE; x += 1) {
      nextMatrix[TILE_ART_SIZE - 1 - x][y] = String(matrix[y]?.[x] || ".").charAt(0) || ".";
    }
  }
  return buildRowsFromMatrix(nextMatrix);
}

export function buildShapeFillMask(shapeKind, startX, startY, endX, endY) {
  const minX = Math.max(0, Math.min(startX, endX));
  const maxX = Math.min(TILE_ART_SIZE - 1, Math.max(startX, endX));
  const minY = Math.max(0, Math.min(startY, endY));
  const maxY = Math.min(TILE_ART_SIZE - 1, Math.max(startY, endY));
  const fillMask = new Set();
  const shape = shapeKind === "circle" || shapeKind === "triangle" ? shapeKind : "rectangle";
  const width = Math.max(1, (maxX - minX) + 1);
  const height = Math.max(1, (maxY - minY) + 1);
  const centerX = minX + (width / 2);
  const centerY = minY + (height / 2);
  const denomX = Math.max(0.5, width / 2);
  const denomY = Math.max(0.5, height / 2);
  const triangleAx = minX + (width - 1) / 2;
  const triangleAy = minY;
  const triangleBx = minX;
  const triangleBy = maxY;
  const triangleCx = maxX;
  const triangleCy = maxY;
  const triangleDenominator = ((triangleBy - triangleCy) * (triangleAx - triangleCx)) + ((triangleCx - triangleBx) * (triangleAy - triangleCy));
  for (let y = minY; y <= maxY; y += 1) {
    for (let x = minX; x <= maxX; x += 1) {
      let include;
      const sampleX = x + 0.5;
      const sampleY = y + 0.5;
      if (shape === "rectangle") {
        include = true;
      } else if (shape === "circle") {
        const normX = (sampleX - centerX) / denomX;
        const normY = (sampleY - centerY) / denomY;
        include = (normX * normX) + (normY * normY) <= 1;
      } else if (triangleDenominator !== 0) {
        const a = (((triangleBy - triangleCy) * (sampleX - triangleCx)) + ((triangleCx - triangleBx) * (sampleY - triangleCy))) / triangleDenominator;
        const b = (((triangleCy - triangleAy) * (sampleX - triangleCx)) + ((triangleAx - triangleCx) * (sampleY - triangleCy))) / triangleDenominator;
        const c = 1 - a - b;
        include = a >= 0 && b >= 0 && c >= 0;
      } else {
        include = x === Math.round(triangleAx) && y >= minY && y <= maxY;
      }
      if (include === true) {
        fillMask.add(`${x}:${y}`);
      }
    }
  }
  return fillMask;
}

export function buildOutlineMask(fillMask) {
  const outlineMask = new Set();
  fillMask.forEach((key) => {
    const [xText, yText] = String(key || "").split(":");
    const x = Number(xText);
    const y = Number(yText);
    const neighbors = [
      `${x - 1}:${y}`,
      `${x + 1}:${y}`,
      `${x}:${y - 1}`,
      `${x}:${y + 1}`,
    ];
    if (neighbors.some((neighbor) => !fillMask.has(neighbor))) {
      outlineMask.add(key);
    }
  });
  return outlineMask;
}

export function applyMaskToRows(baseRows, mask, symbol) {
  const matrix = getRowsMatrix(baseRows);
  mask.forEach((key) => {
    const [xText, yText] = String(key || "").split(":");
    const x = Number(xText);
    const y = Number(yText);
    if (x < 0 || x >= TILE_ART_SIZE || y < 0 || y >= TILE_ART_SIZE) {
      return;
    }
    matrix[y][x] = String(symbol || ".").charAt(0) || ".";
  });
  return buildRowsFromMatrix(matrix);
}

export function getLineRows(baseRows, startX, startY, endX, endY, symbol) {
  const normalizedSymbol = String(symbol || ".").charAt(0) || ".";
  const matrix = getRowsMatrix(baseRows);
  let x0 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(startX) || 0));
  let y0 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(startY) || 0));
  const x1 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(endX) || 0));
  const y1 = Math.max(0, Math.min(TILE_ART_SIZE - 1, Number(endY) || 0));
  const deltaX = Math.abs(x1 - x0);
  const deltaY = Math.abs(y1 - y0);
  const stepX = x0 < x1 ? 1 : -1;
  const stepY = y0 < y1 ? 1 : -1;
  let error = deltaX - deltaY;
  while (true) {
    matrix[y0][x0] = normalizedSymbol;
    if (x0 === x1 && y0 === y1) {
      break;
    }
    const nextError = error * 2;
    if (nextError > -deltaY) {
      error -= deltaY;
      x0 += stepX;
    }
    if (nextError < deltaX) {
      error += deltaX;
      y0 += stepY;
    }
  }
  return buildRowsFromMatrix(matrix);
}

export function buildShapeOptionIconMarkup(shapeKind, variant, tone = "draw") {
  const normalizedShape = shapeKind === "circle" || shapeKind === "triangle" ? shapeKind : "rectangle";
  const normalizedVariant = variant === "outline" || variant === "two-tone" ? variant : "fill";
  const normalizedTone = tone === "erase" ? "erase" : "draw";
  return ""
    + `<span class="tile-art-menu-shape-icon is-${normalizedShape} is-${normalizedVariant} is-${normalizedTone}" aria-hidden="true">`
    + "<span class=\"tile-art-menu-shape-outline\"></span>"
    + "<span class=\"tile-art-menu-shape-fill\"></span>"
    + "</span>";
}

export function buildLineOptionIconMarkup(tone = "draw") {
  const normalizedTone = tone === "erase" ? "erase" : "draw";
  return ""
    + `<span class="tile-art-menu-line-icon is-${normalizedTone}" aria-hidden="true">`
    + "<span class=\"tile-art-menu-line-stroke\"></span>"
    + "</span>";
}

export function buildCurrentShapeToolIconMarkup(state) {
  if (state?.activeTool === "line" || String(state?.activeShapeMenuId || "").trim() === "line") {
    return buildLineOptionIconMarkup("draw");
  }
  return buildShapeOptionIconMarkup(
    state?.activeShapeKind || "rectangle",
    state?.activeShapeVariant || "outline",
    "draw",
  );
}

export function buildCurrentEraseToolIconMarkup(state) {
  return buildShapeOptionIconMarkup(
    state?.activeEraseKind || "rectangle",
    "fill",
    "erase",
  );
}

export function buildTransformCategoryIconMarkup(kind) {
  const normalizedKind = kind === "flip" ? "flip" : "rotate";
  return ""
    + `<span class="tile-art-menu-transform-icon is-${normalizedKind}" aria-hidden="true">`
    + "<span class=\"tile-art-menu-transform-part part-a\"></span>"
    + "<span class=\"tile-art-menu-transform-part part-b\"></span>"
    + "</span>";
}

export function buildTransformOptionIconMarkup(kind) {
  const normalizedKind = [
    "rotate-cw",
    "rotate-ccw",
    "flip-h",
    "flip-v",
  ].includes(String(kind || "").trim()) ? String(kind || "").trim() : "rotate-cw";
  return ""
    + `<span class="tile-art-menu-transform-icon is-${normalizedKind}" aria-hidden="true">`
    + "<span class=\"tile-art-menu-transform-part part-a\"></span>"
    + "<span class=\"tile-art-menu-transform-part part-b\"></span>"
    + "</span>";
}

export function buildFramePreviewDataUrl(rows, scale = 10) {
  return buildSpritePreviewDataUrl(buildRowsPreviewRecord(rows), scale);
}
