export const FIELD_HELP = Object.freeze({
  __recipe__:
    "Uses the selected state’s authored blend of fields, preserving its intended visual character.",
  fbm: "Builds soft, organic variation by layering several scales of smooth gradient noise.",
  ridged:
    "Folds layered noise into sharp ridges and filaments for a more etched texture.",
  "domain-warp":
    "Distorts layered noise with two moving noise fields, creating fluid folds and swirls.",
  curl: "Turns the curl magnitude of animated noise into turbulent, smoke-like density.",
  flow: "Carries animated ribbons along a curl-noise direction for a flowing texture.",
  worley:
    "Creates bright cellular islands around moving feature points, separated by darker gaps.",
  voronoi:
    "Smoothly morphs between seeded cellular layouts while keeping their moving division lines crisp.",
  plasma:
    "Combines layered sine waves with noise-driven phase shifts for an energetic plasma pattern.",
  interference:
    "Overlaps circular waves from moving emitters to create traveling beats and bands.",
  vortex:
    "Spins noisy spiral arms around the center for a rotating whirlpool-like field.",
  metaballs:
    "Blends several seeded moving blobs so nearby forms merge into one liquid mass.",
  caustics:
    "Produces sharp cellular highlights that resemble refracted light moving across water.",
  strata:
    "Stacks noise-warped sediment-like bands with a directional slope and sharp edges.",
  radar:
    "Combines a rotating beam, concentric rings, and seeded target blips.",
  constellation:
    "Connects twinkling cellular stars with faint Voronoi filaments.",
  liquid:
    "Layers domain-warped waves and highlights into a gently moving water surface.",
  electric:
    "Generates repeating branching lightning channels with pulses and traveling sparks.",
  ripple: "Sends noise-distorted concentric rings outward from the center.",
  kaleidoscope:
    "Folds rotating polar noise into mirrored, mandala-like symmetry.",
});

export const TRANSITION_HELP = Object.freeze({
  "field-morph":
    "Morphs cells on a soft schedule combining seeded randomness, radius, and vertical position.",
  "seeded-dissolve":
    "Changes pixels in a stable, seed-determined random order.",
  "radial-cascade":
    "Moves the new state outward from the origin with slight seeded variation.",
  "angular-sweep":
    "Rotates the new state around the origin with a subtle outward offset.",
  scanline:
    "Reveals the new state from top to bottom in a lightly rippled scan.",
  "contour-trace":
    "Starts on a circular mid-radius contour, then spreads inward and outward.",
  "path-draw":
    "Draws the new state diagonally from the upper left toward the lower right.",
  "axis-flip":
    "Staggers alternating columns while advancing top to bottom, producing a shutter-like flip.",
  "cluster-dissolve":
    "Changes coarse pixel clusters in a seeded random order for a chunkier dissolve.",
  "neighbor-ignite":
    "Spreads the new state in a connected-looking wave from the origin.",
  "glitch-bands":
    "Switches horizontal bands in a scrambled order for a brief glitch effect.",
  instant: "Applies the new state immediately with no spatial interpolation.",
});

export const PIXEL_SHAPE_HELP = Object.freeze({
  disc: "Draws each cell as a filled round dot.",
  square: "Draws each cell as a filled square for a crisp grid texture.",
  diamond: "Draws each cell as a compact square rotated 45 degrees.",
  capsule: "Draws each cell as a rounded horizontal pill.",
  line: "Draws each cell as a thin bar aligned tangentially around the center.",
  ring: "Draws each cell as a hollow circular outline.",
  cross: "Draws each cell as a compact plus sign.",
  "square-cross":
    "Uses filled squares for dim sampled signal cells and crosses for bright cells.",
  "square-cross-ring":
    "Uses filled squares, crosses, then rings as sampled signal lightness rises.",
});

export const PIXEL_SWITCH_HELP = Object.freeze({
  "ordered-dither":
    "Uses a fixed Bayer pattern for stable, evenly distributed pixel activation.",
  "temporal-blue-noise":
    "Blends an ordered grid with time-varying noise for lively, fine-grained switching.",
  "threshold-hysteresis":
    "Uses a uniform midpoint threshold with hysteresis for calm, stable switching.",
  "sdf-wavefront":
    "Adds a moving radial wavefront to ordered thresholds so activation travels in rings.",
  "contour-trace":
    "Modulates thresholds along repeating field-value contours so similar intensity bands switch together.",
  "curl-advect":
    "Offsets ordered thresholds with a traveling wave so activation appears to drift diagonally.",
  "neighbor-propagation":
    "Lowers a cell’s threshold when adjacent cells are on, encouraging activation to spread locally.",
  "radial-cascade":
    "Biases ordered activation from the center outward.",
  "path-draw":
    "Biases ordered activation along a top-left-to-bottom-right path.",
  "axis-flip":
    "Staggers alternating columns and compresses cells vertically as they turn on, creating a flip.",
  "seeded-dissolve":
    "Gives each cell a stable, seed-determined random threshold for a repeatable dissolve texture.",
  "field-morph":
    "Blends ordered and seed-random thresholds for both structure and organic variation.",
});

export const SELECT_CONTROL_HELP = Object.freeze({
  fieldSelect: Object.freeze({
    summary: "Chooses the procedural texture sampled inside the glyph.",
    options: FIELD_HELP,
  }),
  transitionSelect: Object.freeze({
    summary:
      "Controls the spatial order used when moving between semantic states.",
    options: TRANSITION_HELP,
  }),
  pixelShapeSelect: Object.freeze({
    summary: "Changes the silhouette used to draw each active cell.",
    options: PIXEL_SHAPE_HELP,
  }),
  pixelSwitchSelect: Object.freeze({
    summary:
      "Controls how continuous field intensity becomes persistent on/off pixels.",
    options: PIXEL_SWITCH_HELP,
  }),
  orbBackgroundModeSelect: Object.freeze({
    summary: "Chooses how the optional orb background disk is constructed.",
    options: Object.freeze({
      solid:
        "Fills one smooth circle behind the orb while preserving the grid above it.",
      pixelated:
        "Builds the disk from full grid-aligned cells so its edge matches the selected resolution.",
    }),
  }),
});

export const VALUE_CONTROL_HELP = Object.freeze({
  resolutionRange:
    "Sets the number of cells per side. Higher values add detail but increase rendering work roughly with the square.",
  speedRange:
    "Multiplies the procedural animation clock, making textures and sprite motion run slower or faster.",
  densityRange:
    "Biases activation so higher values keep more pixels on and lower values make the signal sparser.",
  seedInput:
    "A stable text key that determines noise and per-cell randomness. Reuse it with the same settings to reproduce the signal.",
  randomizeButton:
    "Creates a new seed and a new deterministic variation of the current signal.",
  backgroundColorInput:
    "Sets the canvas fill behind the grid across non-error states.",
  offColorInput:
    "Sets the faint color of grid cells that are currently off across non-error states.",
  inkColorInput:
    "Sets the base color of active pixels across non-error states.",
  accentColorInput:
    "Sets the color active pixels move toward as sampled luminance rises.",
  glowEnabledInput:
    "Turns the halo around active pixels on or off across non-error states.",
  orbBoundaryInput:
    "Defined draws the authored rim. Gestalt removes the continuous outline and lets clustered edge pixels suggest the circle through proximity, closure, and continuing arcs.",
  orbBackgroundEnabledInput:
    "Adds or removes the optional orb background layer in supported presence orbs. It is separate from the canvas background.",
  orbBackgroundColorInput:
    "Sets the optional color behind supported orb pixels. The color carries across states but stays visually inert for non-orb recipes.",
  glowColorInput:
    "Sets the halo around active pixels. Glow renders in high quality at resolutions up to 48×48.",
  resetPaletteButton:
    "Clears custom color overrides and returns non-error states to the current theme’s defaults.",
});

const clamp = (value, minimum, maximum) =>
  Math.min(Math.max(value, minimum), maximum);

export function placeControlTooltip(
  anchor,
  tooltip,
  viewport,
  options = {},
) {
  const gap = Number(options.gap) || 8;
  const gutter = Number(options.gutter) || 12;
  const maximumLeft = Math.max(
    gutter,
    Number(viewport.width) - Number(tooltip.width) - gutter,
  );
  const centeredLeft =
    Number(anchor.left) +
    Number(anchor.width) / 2 -
    Number(tooltip.width) / 2;
  const left = clamp(centeredLeft, gutter, maximumLeft);
  const below = Number(anchor.bottom) + gap;
  const above = Number(anchor.top) - Number(tooltip.height) - gap;
  const fitsBelow =
    below + Number(tooltip.height) <= Number(viewport.height) - gutter;
  const fitsAbove = above >= gutter;
  const maximumTop = Math.max(
    gutter,
    Number(viewport.height) - Number(tooltip.height) - gutter,
  );
  const placement = !fitsBelow && fitsAbove ? "top" : "bottom";
  const preferredTop = placement === "top" ? above : below;

  return {
    left: Math.round(left),
    top: Math.round(clamp(preferredTop, gutter, maximumTop)),
    placement,
  };
}
