// @wuys/map · core/state.js
//
// Single store per map instance — no globals. Holds:
//   - data: topology, countries, flags, labels (loaded async)
//   - nav: lon, lat, zoom (current + animation target)
//   - view: which projection + mode (flat/globe)
//   - theme: visual styling + flag visibility
//   - layers: enabled set + z-order + per-layer config
//   - editor: label-editor state machine
//   - interactions: transient input state (drag, hover)
//   - perf: dev HUD counters
//
// Mutations route through commands (see core/nav.js). Subscribers via core/events.js.

export const NAV_ZOOM_MIN = 1;
export const NAV_ZOOM_MAX = 100;         // lossless geoBoundaries data holds detail to here
export const NAV_LAT_MAX  = 89.9;

export function createState(opts = {}) {
  return {
    /** Static data; loaded async via data/loader.js */
    data: {
      topology: null,
      countries: new Map(),     // iso → { bbox, centroid, area, geom }
      flags: new Map(),         // iso → data URI (or null when not yet loaded)
      labels: new Map(),        // iso → canonical label entry
      spotlights: [],
      contestedRegions: [],
      isoCodes: { numToA2: new Map(), a2ToNum: new Map() },
      ready: false,
    },

    /** Navigation state — current (animated) and target. */
    nav: {
      lon: opts.lon ?? 0, lat: opts.lat ?? 0, zoom: opts.zoom ?? 1,
      tLon: opts.lon ?? 0, tLat: opts.lat ?? 0, tZoom: opts.zoom ?? 1,
    },

    /** View mode + active projection. */
    view: {
      mode: opts.view ?? "flat",        // "flat" | "globe"
      projection: opts.projection ?? "equalEarth",
    },

    /** Theme + display flags. Three INDEPENDENT toggles — none clobbers the
     *  others. Theme controls visual styling; flags/labels control feature
     *  visibility. Each persists independently in the host's storage. */
    theme: {
      id: opts.theme ?? "vector",
      flagsVisible:  opts.flagsVisible  ?? true,
      labelsVisible: opts.labelsVisible ?? false,
      customColors: null,
    },

    /** Layers — set of enabled IDs + their config + render order. */
    layers: {
      enabled: new Set(opts.layers ?? ["countries", "labels", "spotlights", "contested"]),
      order:   new Map(),               // id → number (smaller = drawn first / lower z)
      config:  new Map(),               // id → arbitrary layer-specific config
    },

    /** Label editor state machine. */
    editor: {
      mode: "off",                      // "off" | "labels"
      selectedIso: null,
      drag: null,
    },

    /** Transient interaction state. */
    interactions: {
      isPointerDown: false,
      isAnimating: false,
      hoveredIso: null,
    },

    /** Performance counters (for dev HUD). */
    perf: {
      fps: 0,
      frameTime: 0,
      drawCalls: 0,
      lastFrameAt: 0,
    },
  };
}

/** Clamp the nav target to legal range. */
export function clampTarget(state) {
  const n = state.nav;
  n.tZoom = Math.max(NAV_ZOOM_MIN, Math.min(NAV_ZOOM_MAX, n.tZoom));
  n.tLat  = Math.max(-NAV_LAT_MAX, Math.min(NAV_LAT_MAX, n.tLat));
  while (n.tLon >  180) n.tLon -= 360;
  while (n.tLon < -180) n.tLon += 360;
}

/** Wrap current longitude into [-180, 180]. */
export function normalizeLon(state) {
  const n = state.nav;
  while (n.lon >  180) n.lon -= 360;
  while (n.lon < -180) n.lon += 360;
}
