import { applyClothSimulationConfig } from './cloth-simulation.js';
import { applyHeartFieldMaterial } from './heart-field.js';
import { applyLandscapeFieldConfig } from './landscape-field.js';
import { applyStarCrawlConfig } from './star-crawl.js';

const landscapeBindings = {
  land_offset_x: ['offsetX'],
  land_offset_y: ['offsetY'],
  land_offset_z: ['offsetZ'],
  land_rotation_x: ['rotationX'],
  land_rotation_y: ['rotationY'],
  land_rotation_z: ['rotationZ'],
  land_height: ['terrain', 'height'],
  land_frequency: ['terrain', 'frequency'],
  land_ridge: ['terrain', 'ridge'],
  land_warp: ['terrain', 'warp'],
  land_terrace: ['terrain', 'terrace'],
  land_speed: ['animation', 'speed'],
  land_audio: ['animation', 'audioPulse'],
  land_tunnel_twist: ['tunnel', 'twist'],
  land_tunnel_rotate: ['tunnel', 'rotateSpeed'],
  land_metal: ['material', 'metalness'],
  land_rough: ['material', 'roughness'],
  land_env: ['material', 'envMapIntensity'],
  land_emissive_int: ['material', 'emissiveIntensity'],
  land_contour_density: ['material', 'contourDensity'],
  land_contour_strength: ['material', 'contourStrength'],
  land_normal_smoothing: ['material', 'normalSmoothing'],
  land_light_int: ['light', 'intensity'],
  land_ambient_int: ['light', 'ambientIntensity'],
  land_fog_near: ['fog', 'near'],
  land_fog_far: ['fog', 'far'],
};

const heartBindings = {
  heart_offset_x: ['offsetX'],
  heart_offset_y: ['offsetY'],
  heart_offset_z: ['offsetZ'],
  heart_rotation_x: ['rotationX'],
  heart_rotation_y: ['rotationY'],
  heart_rotation_z: ['rotationZ'],
  heart_width: ['width'],
  heart_height: ['height'],
  heart_field_depth: ['fieldDepth'],
  heart_size: ['size'],
  heart_size_var: ['sizeVariation'],
  heart_speed: ['animation', 'speed'],
  heart_drift: ['animation', 'drift'],
  heart_drift_speed: ['animation', 'driftSpeed'],
  heart_tumble: ['animation', 'tumble'],
  heart_audio_pulse: ['animation', 'audioPulse'],
  heart_symbol_opacity: ['symbolOpacity'],
  heart_metalness: ['material', 'metalness'],
  heart_roughness: ['material', 'roughness'],
  heart_clearcoat: ['material', 'clearcoat'],
  heart_coat_rough: ['material', 'clearcoatRoughness'],
  heart_env_int: ['material', 'envMapIntensity'],
  heart_emissive_int: ['material', 'emissiveIntensity'],
};

const clothBindings = {
  cloth_pos_x: ['position', 'x'],
  cloth_pos_y: ['position', 'y'],
  cloth_pos_z: ['position', 'z'],
  cloth_rot_x: ['rotation', 'x'],
  cloth_rot_y: ['rotation', 'y'],
  cloth_rot_z: ['rotation', 'z'],
  cloth_gravity_x: ['physics', 'gravity', 'x'],
  cloth_gravity_y: ['physics', 'gravity', 'y'],
  cloth_gravity_z: ['physics', 'gravity', 'z'],
  cloth_damping: ['physics', 'damping'],
  cloth_iterations: ['physics', 'iterations'],
  cloth_substeps: ['physics', 'substeps'],
  cloth_stretch: ['physics', 'stretchStiffness'],
  cloth_shear: ['physics', 'shearStiffness'],
  cloth_bend: ['physics', 'bendStiffness'],
  cloth_rest_scale: ['physics', 'restScale'],
  cloth_wind_strength: ['wind', 'strength'],
  cloth_wind_speed: ['wind', 'speed'],
  cloth_turbulence: ['wind', 'turbulence'],
  cloth_wind_frequency: ['wind', 'frequency'],
  cloth_wind_x: ['wind', 'direction', 'x'],
  cloth_wind_y: ['wind', 'direction', 'y'],
  cloth_wind_z: ['wind', 'direction', 'z'],
  cloth_wind_audio: ['wind', 'audioAmount'],
  cloth_anchor_amp: ['animation', 'anchorAmplitude'],
  cloth_anchor_speed: ['animation', 'anchorSpeed'],
  cloth_anchor_frequency: ['animation', 'anchorFrequency'],
  cloth_collision_radius: ['collision', 'radius'],
  cloth_collision_x: ['collision', 'position', 'x'],
  cloth_collision_y: ['collision', 'position', 'y'],
  cloth_collision_z: ['collision', 'position', 'z'],
  cloth_texture_repeat_x: ['material', 'textureRepeatX'],
  cloth_texture_repeat_y: ['material', 'textureRepeatY'],
  cloth_texture_offset_x: ['material', 'textureOffsetX'],
  cloth_texture_offset_y: ['material', 'textureOffsetY'],
  cloth_texture_rotation: ['material', 'textureRotation'],
  cloth_texture_speed: ['material', 'texturePlaybackRate'],
  cloth_texture_time: ['material', 'textureTimeOffset'],
  cloth_stripe_frequency: ['colors', 'stripeFrequency'],
  cloth_stripe_strength: ['colors', 'stripeStrength'],
  cloth_metalness: ['material', 'metalness'],
  cloth_roughness: ['material', 'roughness'],
  cloth_clearcoat: ['material', 'clearcoat'],
  cloth_coat_rough: ['material', 'clearcoatRoughness'],
  cloth_sheen: ['material', 'sheen'],
  cloth_transmission: ['material', 'transmission'],
  cloth_thickness: ['material', 'thickness'],
  cloth_iridescence: ['material', 'iridescence'],
  cloth_env: ['material', 'envMapIntensity'],
  cloth_emissive_int: ['material', 'emissiveIntensity'],
  cloth_opacity: ['material', 'opacity'],
};

const starCrawlBindings = {
  crawl_font_size: ['typography', 'fontSize'],
  crawl_text_width: ['typography', 'textWidth'],
  crawl_line_height: ['typography', 'lineHeight'],
  crawl_letter_spacing: ['typography', 'letterSpacing'],
  crawl_paragraph_gap: ['typography', 'paragraphSpacing'],
  crawl_speed: ['animation', 'speed'],
  crawl_start_offset: ['animation', 'startOffset'],
  crawl_pos_x: ['placement', 'position', 'x'],
  crawl_pos_y: ['placement', 'position', 'y'],
  crawl_pos_z: ['placement', 'position', 'z'],
  crawl_width: ['placement', 'width'],
  crawl_length: ['placement', 'length'],
  crawl_tilt: ['placement', 'tilt'],
  crawl_roll: ['placement', 'rotationZ'],
  crawl_glow_blur: ['appearance', 'glowBlur'],
  crawl_outline_width: ['appearance', 'outlineWidth'],
  crawl_opacity: ['appearance', 'opacity'],
  crawl_horizon_fade: ['appearance', 'horizonFade'],
  crawl_bottom_fade: ['appearance', 'bottomFade'],
  crawl_star_size: ['stars', 'size'],
  crawl_star_opacity: ['stars', 'opacity'],
  crawl_star_twinkle: ['stars', 'twinkle'],
  crawl_star_drift: ['stars', 'drift'],
};

export const STAR_CRAWL_REDRAW_KEYFRAME_PARAMS = Object.freeze([
  'crawl_font_size',
  'crawl_text_width',
  'crawl_line_height',
  'crawl_letter_spacing',
  'crawl_paragraph_gap',
  'crawl_glow_blur',
  'crawl_outline_width',
]);

const starCrawlRedrawParams = new Set(STAR_CRAWL_REDRAW_KEYFRAME_PARAMS);

export const PROCEDURAL_CONFIG_KEYFRAME_BINDINGS = Object.freeze({
  landscapeField: Object.freeze(landscapeBindings),
  heartField: Object.freeze(heartBindings),
  clothSimulation: Object.freeze(clothBindings),
  starCrawl: Object.freeze(starCrawlBindings),
});

const uniform = (path, scale = 1) => ({ path, scale });

export const PROCEDURAL_UNIFORM_KEYFRAME_BINDINGS = Object.freeze({
  dsphere_disp_strength: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uDispStrength'])],
  dsphere_disp_scale: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uDispScale'])],
  dsphere_disp_speed: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uDispSpeed'])],
  dsphere_spike_count: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uSpikeCount'])],
  dsphere_spike_sharp: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uSpikeSharpness'])],
  dsphere_crack_width: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uCrackWidth'])],
  dsphere_crack_depth: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uCrackDepth'])],
  dsphere_pulse_speed: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uPulseSpeed'])],
  dsphere_pulse_strength: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uPulseStrength'])],
  dsphere_fractal_power: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uFractalPower'])],
  dsphere_fractal_iter: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uFractalIterations'])],
  dsphere_emissive_int: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uEmissiveIntensity'])],
  dsphere_metalness: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uMetalness'])],
  dsphere_roughness: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uRoughness'])],
  dsphere_envmap_int: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uEnvMapIntensity'])],
  dsphere_fresnel: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uFresnelPower'])],
  dsphere_audio_disp: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uAudioDisplace'])],
  dsphere_audio_emis: [uniform(['displacedSphereMesh', 'material', 'uniforms', 'uAudioEmissive'])],

  tree_growth: [
    uniform(['fractalTreeMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uGrowth']),
    uniform(['fractalTreeMesh', 'userData', 'leafMesh', 'material', 'uniforms', 'uGrowth']),
  ],
  tree_wind_strength: [
    uniform(['fractalTreeMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uWindStrength']),
    uniform(['fractalTreeMesh', 'userData', 'leafMesh', 'material', 'uniforms', 'uWindStrength'], 1.5),
  ],
  tree_wind_speed: [
    uniform(['fractalTreeMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uWindSpeed']),
    uniform(['fractalTreeMesh', 'userData', 'leafMesh', 'material', 'uniforms', 'uWindSpeed']),
  ],
  tree_sway: [uniform(['fractalTreeMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uSwayAmount'])],
  tree_emissive_int: [
    uniform(['fractalTreeMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uEmissiveIntensity']),
    uniform(['fractalTreeMesh', 'userData', 'leafMesh', 'material', 'uniforms', 'uEmissiveIntensity']),
  ],
  tree_metalness: [uniform(['fractalTreeMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uMetalness'])],
  tree_roughness: [uniform(['fractalTreeMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uRoughness'])],
  tree_envmap: [uniform(['fractalTreeMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uEnvMapIntensity'])],

  sierp_explosion: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uExplosion'])],
  sierp_rot_speed: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uRotationSpeed'])],
  sierp_pulse_speed: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uPulseSpeed'])],
  sierp_pulse_amt: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uPulseAmount'])],
  sierp_color_cycle: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uColorCycleSpeed'])],
  sierp_emissive_int: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uEmissiveIntensity'])],
  sierp_metalness: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uMetalness'])],
  sierp_roughness: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uRoughness'])],
  sierp_envmap: [uniform(['sierpinskiMesh', 'userData', 'tetraMesh', 'material', 'uniforms', 'uEnvMapIntensity'])],

  menger_assembly: [uniform(['mengerSpongeMesh', 'userData', 'cubeMesh', 'material', 'uniforms', 'uAssembly'])],
  menger_rot_speed: [uniform(['mengerSpongeMesh', 'userData', 'cubeMesh', 'material', 'uniforms', 'uRotationSpeed'])],
  menger_breath_amt: [uniform(['mengerSpongeMesh', 'userData', 'cubeMesh', 'material', 'uniforms', 'uBreathAmount'])],
  menger_breath_speed: [uniform(['mengerSpongeMesh', 'userData', 'cubeMesh', 'material', 'uniforms', 'uBreathSpeed'])],
  menger_emissive_int: [uniform(['mengerSpongeMesh', 'userData', 'cubeMesh', 'material', 'uniforms', 'uEmissiveIntensity'])],
  menger_metalness: [uniform(['mengerSpongeMesh', 'userData', 'cubeMesh', 'material', 'uniforms', 'uMetalness'])],
  menger_roughness: [uniform(['mengerSpongeMesh', 'userData', 'cubeMesh', 'material', 'uniforms', 'uRoughness'])],
  menger_envmap: [uniform(['mengerSpongeMesh', 'userData', 'cubeMesh', 'material', 'uniforms', 'uEnvMapIntensity'])],

  coral_growth: [uniform(['coralGrowthMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uGrowth'])],
  coral_sway_str: [uniform(['coralGrowthMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uSwayStrength'])],
  coral_sway_speed: [uniform(['coralGrowthMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uSwaySpeed'])],
  coral_wave_speed: [uniform(['coralGrowthMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uWaveSpeed'])],
  coral_pulse_speed: [uniform(['coralGrowthMesh', 'userData', 'polypMesh', 'material', 'uniforms', 'uPulseSpeed'])],
  coral_emissive_int: [
    uniform(['coralGrowthMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uEmissiveIntensity']),
    uniform(['coralGrowthMesh', 'userData', 'polypMesh', 'material', 'uniforms', 'uEmissiveIntensity']),
  ],
  coral_metalness: [uniform(['coralGrowthMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uMetalness'])],
  coral_roughness: [uniform(['coralGrowthMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uRoughness'])],
  coral_envmap: [uniform(['coralGrowthMesh', 'userData', 'branchMesh', 'material', 'uniforms', 'uEnvMapIntensity'])],
});

function setNested(target, path, value) {
  let cursor = target;
  for (let index = 0; index < path.length - 1; index++) {
    cursor = cursor[path[index]] ||= {};
  }
  cursor[path[path.length - 1]] = value;
}

function getNested(target, path) {
  let cursor = target;
  for (const key of path) {
    cursor = cursor?.[key];
    if (cursor == null) return null;
  }
  return cursor;
}

function applyConfigBinding(THREE, sceneObj, paramId, value) {
  const systems = [
    {
      configKey: 'landscapeField',
      meshKey: 'landscapeMesh',
      bindings: landscapeBindings,
      apply: (config, mesh) => applyLandscapeFieldConfig(config, mesh),
    },
    {
      configKey: 'heartField',
      meshKey: 'heartFieldMesh',
      bindings: heartBindings,
      apply: (config, mesh) => applyHeartFieldMaterial(config, mesh),
    },
    {
      configKey: 'clothSimulation',
      meshKey: 'clothSimulationMesh',
      bindings: clothBindings,
      apply: (config, mesh) => applyClothSimulationConfig(THREE, config, mesh),
    },
    {
      configKey: 'starCrawl',
      meshKey: 'starCrawlMesh',
      bindings: starCrawlBindings,
      apply: (config, mesh, currentParamId) => applyStarCrawlConfig(
        THREE,
        config,
        mesh,
        { redrawText: starCrawlRedrawParams.has(currentParamId) },
      ),
    },
  ];

  for (const system of systems) {
    const path = system.bindings[paramId];
    const config = sceneObj?.data?.[system.configKey];
    const mesh = sceneObj?.[system.meshKey];
    if (!path || !config || !mesh) continue;
    setNested(config, path, value);
    if (mesh.userData) mesh.userData.config = config;
    system.apply(config, mesh, paramId);
    return true;
  }
  return false;
}

function applyUniformBinding(sceneObj, paramId, value) {
  const targets = PROCEDURAL_UNIFORM_KEYFRAME_BINDINGS[paramId];
  if (!targets) return false;
  let applied = false;
  for (const target of targets) {
    const uniformValue = getNested(sceneObj, target.path);
    if (!uniformValue || !Object.hasOwn(uniformValue, 'value')) continue;
    uniformValue.value = value * target.scale;
    applied = true;
  }
  return applied;
}

export function applyProceduralSceneKeyframeValue(THREE, sceneObj, paramId, value) {
  return applyConfigBinding(THREE, sceneObj, paramId, value)
    || applyUniformBinding(sceneObj, paramId, value);
}
