import { postJson } from "../api/controlApi";
import { LayerCard } from "./LayerCard";

function moveItem(array, fromIndex, toIndex) {
  if (fromIndex < 0 || fromIndex >= array.length || toIndex < 0 || toIndex >= array.length) {
    return array;
  }

  const copy = [...array];
  const [item] = copy.splice(fromIndex, 1);
  copy.splice(toIndex, 0, item);
  return copy;
}

export function LayerStack({
  dragLayerId,
  dropTargetLayerId,
  expandedLayerIds,
  layers,
  pendingShaderId,
  setAppState,
  setDragLayerId,
  setDropTargetLayerId,
  setExpandedLayerIds,
  setPendingShaderId,
  shaders,
}) {
  const expandedSet = new Set(expandedLayerIds);

  function updateLayerParameterOptimistically(layerId, parameterId, value) {
    return postJson("/api/layers/update-parameter", {
      layerId,
      parameterId,
      value,
    });
  }

  function toggleExpanded(layerId) {
    setExpandedLayerIds((current) =>
      current.includes(layerId) ? current.filter((id) => id !== layerId) : [...current, layerId],
    );
  }

  function removeLayer(layerId) {
    setExpandedLayerIds((current) => current.filter((id) => id !== layerId));
    postJson("/api/layers/remove", { layerId });
  }

  function handleDrop(event, targetLayerId, targetIndex) {
    const sourceLayerId = event.dataTransfer.getData("text/plain") || dragLayerId;
    if (!sourceLayerId || sourceLayerId === targetLayerId) {
      setDragLayerId(null);
      setDropTargetLayerId(null);
      return;
    }

    setAppState((current) => {
      if (!current?.layers) {
        return current;
      }

      const sourceIndex = current.layers.findIndex((layer) => layer.id === sourceLayerId);
      const destinationIndex = current.layers.findIndex((layer) => layer.id === targetLayerId);
      if (sourceIndex < 0 || destinationIndex < 0 || sourceIndex === destinationIndex) {
        return current;
      }

      return {
        ...current,
        layers: moveItem(current.layers, sourceIndex, destinationIndex),
      };
    });

    postJson("/api/layers/reorder", {
      layerId: sourceLayerId,
      targetIndex,
    });
    setDragLayerId(null);
    setDropTargetLayerId(null);
  }

  return (
    <section className="panel">
      <div className="panel__header">
        <h2>Layers</h2>
        <p className="muted">Drag layers to reorder them. Each layer processes the output of the one above it.</p>
      </div>

      <div className="layer-stack">
        {layers.map((layer, index) => (
          <LayerCard
            key={layer.id}
            layer={layer}
            index={index}
            shaders={shaders}
            expanded={expandedSet.has(layer.id)}
            isDragging={dragLayerId === layer.id}
            isDropTarget={dropTargetLayerId === layer.id}
            onToggleExpanded={toggleExpanded}
            onDragStart={setDragLayerId}
            onDragEnd={() => {
              setDragLayerId(null);
              setDropTargetLayerId(null);
            }}
            onDragOver={setDropTargetLayerId}
            onDrop={handleDrop}
            onRemove={removeLayer}
            onLayerParameterChange={updateLayerParameterOptimistically}
          />
        ))}

        <div className="layer-card layer-card--add">
          <div className="layer-card__header">
            <div className="layer-card__meta">
              <span className="layer-card__index">+</span>
              <div className="layer-card__title layer-card__title--static">Add Layer</div>
            </div>
            <div className="layer-card__actions">
              <button
                type="button"
                disabled={!pendingShaderId}
                onClick={() => {
                  if (pendingShaderId) {
                    postJson("/api/layers/add", { shaderId: pendingShaderId });
                  }
                }}
              >
                Add
              </button>
            </div>
          </div>
          <div className="layer-card__body">
            <div className="layer-card__field">
              <label htmlFor="add-layer-select">Shader</label>
              <select
                id="add-layer-select"
                value={pendingShaderId}
                onChange={(event) => setPendingShaderId(event.target.value)}
              >
                {shaders.map((shader) => (
                  <option key={shader.id} value={shader.id}>
                    {shader.name}
                  </option>
                ))}
              </select>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}
