import { MAX_DIG, MAX_STACK } from "../game/terrain.js";

export const STORAGE_KEY = "mixpark-data-v2";

/**
 * Read the current browser-only save.
 * @param {Storage} storage
 */
export function loadLocalState(storage) {
  try {
    const data = JSON.parse(storage.getItem(STORAGE_KEY) || "null");
    if (data && typeof data.maps === "object" && data.maps !== null) {
      return data;
    }
  } catch {
    // Ignore corrupt or unavailable browser storage.
  }
  return null;
}

/**
 * Validate one map's saved state. Returns null when there is nothing usable
 * (so the caller can fall back to the map's defaults); otherwise every entry
 * is well-formed: known placement types on integer cells with a rotation of
 * 0..3 (exact duplicates dropped), integer tree ids, and land edits on
 * canonical "x,z" keys clamped to what the terrain can show. When two keys
 * name the same cell ("01,1" and "1,1"), the later one wins.
 * @param {unknown} saved
 * @param {Set<string>} knownTypes placement types this build can render
 * @returns {{placements: {t:string,x:number,z:number,r:number}[], removedTrees: number[], terra: Record<string, number>} | null}
 */
export function sanitizeMapState(saved, knownTypes) {
  const s = /** @type {any} */ (saved);
  if (!s || typeof s !== "object" || !Array.isArray(s.placements)) return null;
  /** @type {{t:string,x:number,z:number,r:number}[]} */
  const placements = [];
  const seen = new Set();
  for (const p of s.placements) {
    if (!p || typeof p !== "object" || !knownTypes.has(p.t)) continue;
    if (!Number.isSafeInteger(p.x) || !Number.isSafeInteger(p.z)) continue;
    const r = Number.isInteger(p.r) ? ((p.r % 4) + 4) % 4 : 0;
    const key = `${p.t}:${p.x}:${p.z}:${r}`;
    if (seen.has(key)) continue;
    seen.add(key);
    placements.push({ t: p.t, x: p.x, z: p.z, r });
  }
  /** @type {number[]} */
  const removedTrees = [];
  if (Array.isArray(s.removedTrees)) {
    for (const id of new Set(s.removedTrees)) {
      if (Number.isInteger(id) && id >= 0) removedTrees.push(id);
    }
  }
  /** @type {Record<string, number>} */
  const terra = {};
  if (s.terra && typeof s.terra === "object" && !Array.isArray(s.terra)) {
    for (const [key, n] of Object.entries(s.terra)) {
      const m = /^(-?\d+),(-?\d+)$/.exec(key);
      if (!m || !Number.isInteger(n)) continue;
      const x = Number(m[1]);
      const z = Number(m[2]);
      if (!Number.isSafeInteger(x) || !Number.isSafeInteger(z)) continue;
      const layers = Math.max(-MAX_STACK, Math.min(MAX_DIG, n));
      const canonical = `${x},${z}`;
      if (layers === 0) delete terra[canonical];
      else terra[canonical] = layers;
    }
  }
  return { placements, removedTrees, terra };
}

/**
 * Save only gameplay data in this browser.
 * @param {Storage} storage
 * @param {{kid: string, maps: Record<string, unknown>}} data
 */
export function saveLocalState(storage, data) {
  try {
    storage.setItem(
      STORAGE_KEY,
      JSON.stringify({
        kid: data.kid,
        maps: data.maps,
      }),
    );
    return true;
  } catch {
    return false;
  }
}
