// Rôle : fournit l'outil Combos avec palettes d'inputs, rendu visuel et échange texte.
import { useEffect, useMemo, useState } from "react";
import { Icon } from "../../../components/Icon.jsx";
import { useInlineEdit } from "../../../hooks/useInlineEdit.js";
import {
  applyGroupedReorderOperation,
  getGroupedEntries,
  useGroupedReorder
} from "../../../hooks/useGroupedReorder.js";
import { TextExchangeActions } from "./TextExchangeActions.jsx";

const DEVICE_OPTIONS = [
  { value: "playstation", label: "PlayStation" },
  { value: "xbox", label: "Xbox" },
  { value: "switch", label: "Switch" },
  { value: "n64", label: "Nintendo 64" },
  { value: "keyboardMouse", label: "Clavier/Souris" }
];

const SPECIAL_INPUT_GROUP = { title: "Spéciale", column: "actions", inputs: [["button", "fist"], ["button", "foot"], ["button", "grab"], ["button", "protect"], ["button", "shoot"]] };

const DEVICE_GROUPS = {
  playstation: [
    { title: "Directions", kind: "directions", column: "movement", inputs: [["direction", "up-left"], ["direction", "up"], ["direction", "up-right"], ["direction", "left"], null, ["direction", "right"], ["direction", "down-left"], ["direction", "down"], ["direction", "down-right"]] },
    { title: "Actions", column: "actions", inputs: [["button", "triangle"], ["button", "circle"], ["button", "cross"], ["button", "square"], ["button", "l1"], ["button", "r1"], ["button", "l2"], ["button", "r2"]] },
    SPECIAL_INPUT_GROUP,
    { title: "Système", inputs: [["button", "options"], ["button", "share"], ["button", "l3"], ["button", "r3"]] }
  ],
  xbox: [
    { title: "Directions", kind: "directions", column: "movement", inputs: [["direction", "up-left"], ["direction", "up"], ["direction", "up-right"], ["direction", "left"], null, ["direction", "right"], ["direction", "down-left"], ["direction", "down"], ["direction", "down-right"]] },
    { title: "Actions", column: "actions", inputs: [["button", "a"], ["button", "b"], ["button", "x"], ["button", "y"], ["button", "lb"], ["button", "rb"], ["button", "lt"], ["button", "rt"]] },
    SPECIAL_INPUT_GROUP,
    { title: "Système", inputs: [["button", "menu"], ["button", "view"], ["button", "ls"], ["button", "rs"]] }
  ],
  switch: [
    { title: "Directions", kind: "directions", column: "movement", inputs: [["direction", "up-left"], ["direction", "up"], ["direction", "up-right"], ["direction", "left"], null, ["direction", "right"], ["direction", "down-left"], ["direction", "down"], ["direction", "down-right"]] },
    { title: "Actions", column: "actions", inputs: [["button", "a"], ["button", "b"], ["button", "x"], ["button", "y"], ["button", "l"], ["button", "r"], ["button", "zl"], ["button", "zr"]] },
    SPECIAL_INPUT_GROUP,
    { title: "Système", inputs: [["button", "plus"], ["button", "minus"], ["button", "ls"], ["button", "rs"]] }
  ],
  n64: [
    { title: "Stick", kind: "directions", column: "movement", inputs: [["direction", "stick-up-left"], ["direction", "stick-up"], ["direction", "stick-up-right"], ["direction", "stick-left"], null, ["direction", "stick-right"], ["direction", "stick-down-left"], ["direction", "stick-down"], ["direction", "stick-down-right"]] },
    { title: "D-Pad", kind: "directions", column: "movement", inputs: [["direction", "up-left"], ["direction", "up"], ["direction", "up-right"], ["direction", "left"], null, ["direction", "right"], ["direction", "down-left"], ["direction", "down"], ["direction", "down-right"]] },
    { title: "Actions", column: "actions", inputs: [["button", "a"], ["button", "b"], ["button", "z"], ["button", "start"], ["button", "l"], ["button", "r"]] },
    SPECIAL_INPUT_GROUP,
    { title: "C-buttons", kind: "directions", inputs: [["button", "c-up"], ["button", "c-down"], ["button", "c-left"], ["button", "c-right"]] }
  ]
};

const KEYBOARD_GROUPS = {
  azerty: [
    { title: "ZQSD", kind: "directions", column: "movement", inputs: [null, ["key", "z"], null, ["key", "q"], ["key", "s"], ["key", "d"], null, null, null] },
    { title: "Flèches", kind: "directions", column: "movement", inputs: [null, ["direction", "up"], null, ["direction", "left"], ["direction", "down"], ["direction", "right"], null, null, null] },
    { title: "Touches", column: "actions", inputs: [["key", "ctrl"], ["key", "alt"], ["key", "shift"], ["key", "space"], ["key", "enter"], ["key", "tab"], ["key", "e"], ["key", "a"], ["key", "r"], ["key", "f"]] },
    SPECIAL_INPUT_GROUP,
    { title: "Souris", inputs: [["mouse", "left"], ["mouse", "right"], ["mouse", "middle"], ["mouse", "wheel-up"], ["mouse", "wheel-down"]] }
  ],
  qwerty: [
    { title: "WASD", kind: "directions", column: "movement", inputs: [null, ["key", "w"], null, ["key", "a"], ["key", "s"], ["key", "d"], null, null, null] },
    { title: "Flèches", kind: "directions", column: "movement", inputs: [null, ["direction", "up"], null, ["direction", "left"], ["direction", "down"], ["direction", "right"], null, null, null] },
    { title: "Touches", column: "actions", inputs: [["key", "ctrl"], ["key", "alt"], ["key", "shift"], ["key", "space"], ["key", "enter"], ["key", "tab"], ["key", "e"], ["key", "q"], ["key", "r"], ["key", "f"]] },
    SPECIAL_INPUT_GROUP,
    { title: "Souris", inputs: [["mouse", "left"], ["mouse", "right"], ["mouse", "middle"], ["mouse", "wheel-up"], ["mouse", "wheel-down"]] }
  ]
};

const INPUT_LABELS = {
  triangle: "△",
  circle: "○",
  cross: "×",
  square: "□",
  options: "Options",
  share: "Share",
  up: "↑",
  down: "↓",
  left: "←",
  right: "→",
  "up-left": "↖",
  "up-right": "↗",
  "down-left": "↙",
  "down-right": "↘",
  "stick-up": "↑",
  "stick-down": "↓",
  "stick-left": "←",
  "stick-right": "→",
  "stick-up-left": "↖",
  "stick-up-right": "↗",
  "stick-down-left": "↙",
  "stick-down-right": "↘",
  plus: "+",
  minus: "-",
  "c-up": "C↑",
  "c-down": "C↓",
  "c-left": "C←",
  "c-right": "C→",
  fist: "Poing",
  foot: "Pied",
  grab: "Grab",
  protect: "Protect",
  shoot: "Shoot",
  space: "Espace"
};

const INPUT_ICONS = {
  fist: "fist",
  foot: "foot",
  grab: "grab",
  protect: "shield",
  shoot: "gun"
};

const MOUSE_LABELS = {
  left: "Clic G",
  right: "Clic D",
  middle: "Molette",
  "wheel-up": "Molette ↑",
  "wheel-down": "Molette ↓"
};

function getInputLabel(input) {
  const value = String(input?.value || "");
  if (input?.kind === "key" && value.length === 1) return value.toUpperCase();
  if (input?.kind === "mouse") return MOUSE_LABELS[value] || value;
  return INPUT_LABELS[value] || value.toUpperCase();
}

function inputKey(input) {
  return `${input.kind}:${input.value}:${input.hold ? "hold" : ""}:${input.holdMs || 0}`;
}

function createInput(kind, value, holdMs = 0) {
  const input = { kind, value };
  if (holdMs > 0) input.holdMs = holdMs;
  return input;
}

function getHoldSeconds(input) {
  return Math.round((Number(input?.holdMs) || 0) / 1000);
}

function formatHoldLabel(input) {
  const seconds = getHoldSeconds(input);
  if (!seconds) return input?.hold ? "hold" : "";
  return `${seconds}s`;
}

function parseHoldSeconds(value) {
  const seconds = Number.parseInt(String(value || "").replace(/[^\d]/g, ""), 10);
  if (!Number.isInteger(seconds) || seconds <= 0) return 0;
  return Math.min(99, seconds);
}

function isDirectionInput(input) {
  return input?.kind === "direction";
}

function createCombo(context, id, name, category, inputs, fallbackName, device) {
  const combo = {
    id: id || context.uid("combo"),
    name: name.trim() || fallbackName,
    device,
    inputs: inputs.map((step) => step.map((input) => ({ ...input }))).filter((step) => step.length)
  };
  if (category.trim()) combo.category = category.trim();
  return combo;
}

function normalizeCustomKey(value) {
  return String(value || "")
    .trim()
    .replace(/\s+/g, " ")
    .slice(0, 16);
}

function getPaletteGroups(device, keyboardLayout) {
  return device === "keyboardMouse" ? KEYBOARD_GROUPS[keyboardLayout] : DEVICE_GROUPS[device] || DEVICE_GROUPS.playstation;
}

function getPaletteColumns(groups) {
  return [
    groups.filter((group) => group.column === "movement"),
    groups.filter((group) => group.column !== "movement")
  ];
}

function getComboCategory(combo) {
  return String(combo?.category || "").trim();
}

function setComboItemCategory(combo, category) {
  const nextCombo = { ...combo };
  if (category) nextCombo.category = category;
  else delete nextCombo.category;
  return nextCombo;
}


function ComboInputToken({ input, device, palette = false, onClick, onDragStart }) {
  const label = getInputLabel(input);
  const iconName = INPUT_ICONS[input.value];
  const holdLabel = !palette ? formatHoldLabel(input) : "";
  const classNames = [
    "combo-input-token",
    `combo-input-${input.kind}`,
    iconName ? "combo-input-icon" : "",
    holdLabel ? "has-hold" : "",
    input.kind === "key" && label.length === 1 ? "combo-input-short" : "",
    `combo-device-${device}`,
    `combo-value-${String(input.value || "").replace(/[^a-z0-9]+/gi, "-").toLowerCase()}`
  ].filter(Boolean).join(" ");

  if (palette) {
    return (
      <button className={classNames} type="button" draggable onClick={onClick} onDragStart={onDragStart} title={label} aria-label={label}>
        <span>{iconName ? <Icon name={iconName} /> : label}</span>
      </button>
    );
  }

  return (
    <span className={classNames} draggable={Boolean(onDragStart)} onClick={onClick} onDragStart={onDragStart} title={holdLabel ? `${label} · ${holdLabel}` : label}>
      <span>{iconName ? <Icon name={iconName} /> : label}</span>
      {holdLabel && <em className="combo-input-hold">{holdLabel}</em>}
    </span>
  );
}

function ComboStep({ step, stepIndex, device, active = false, selectedInputIndex = -1, onClick, onSelectInput, onDropInput, onDragInput }) {
  const className = [
    "combo-step-inputs",
    step.length > 1 ? "is-simultaneous" : "",
    step.length <= 1 ? "is-single" : "",
    active ? "is-active" : ""
  ].filter(Boolean).join(" ");
  const content = step.length ? step.map((input, inputIndex) => (
    <span className={`combo-step-input ${selectedInputIndex === inputIndex ? "is-selected" : ""}`} key={`${inputKey(input)}-${inputIndex}`}>
      {inputIndex > 0 && <span className="combo-plus" aria-hidden="true">+</span>}
      <ComboInputToken
        input={input}
        device={device}
        onClick={onSelectInput ? (event) => {
          event.stopPropagation();
          onSelectInput(stepIndex, inputIndex);
        } : null}
        onDragStart={onDragInput ? (event) => onDragInput(event, stepIndex, inputIndex, input) : null}
      />
    </span>
  )) : <span className="combo-empty-step">Étape vide</span>;

  if (onClick) {
    return (
      <button
        type="button"
        className={className}
        onClick={onClick}
        onDragOver={(event) => {
          if (!onDropInput) return;
          event.preventDefault();
        }}
        onDrop={(event) => {
          if (!onDropInput) return;
          event.preventDefault();
          onDropInput(event);
        }}
      >
        {content}
      </button>
    );
  }

  return <span className={className}>{content}</span>;
}

function ComboSequence({ inputs, device, emptyLabel, activeStepIndex = -1, selectedInput, onSelectStep, onSelectInput, onDropInput, onDropNewStep, onDragInput }) {
  const visibleInputs = inputs.map((step, index) => ({ step, index })).filter(({ step }) => step.length || onSelectStep);
  if (!visibleInputs.length) return <span className="combo-empty-sequence">{emptyLabel}</span>;
  return (
    <div
      className="combo-sequence"
      onDragOver={(event) => {
        if (!onDropNewStep) return;
        event.preventDefault();
      }}
      onDrop={(event) => {
        if (!onDropNewStep || event.target.closest(".combo-step-inputs")) return;
        event.preventDefault();
        onDropNewStep(event);
      }}
    >
      {visibleInputs.map(({ step, index: stepIndex }, visibleIndex) => {
        const nextStep = visibleInputs[visibleIndex + 1]?.step || [];
        const hideSeparator = step.every(isDirectionInput) && nextStep.every(isDirectionInput);
        return (
        <span className="combo-step" key={`${stepIndex}-${step.map(inputKey).join("+")}`}>
          <ComboStep
            step={step}
            stepIndex={stepIndex}
            device={device}
            active={activeStepIndex === stepIndex}
            selectedInputIndex={selectedInput?.stepIndex === stepIndex ? selectedInput.inputIndex : -1}
            onClick={onSelectStep ? () => onSelectStep(stepIndex) : null}
            onSelectInput={onSelectInput}
            onDropInput={onDropInput ? (event) => onDropInput(event, stepIndex) : null}
            onDragInput={onDragInput}
          />
          {visibleIndex < visibleInputs.length - 1 && !hideSeparator && <span className="combo-step-separator" aria-hidden="true">›</span>}
        </span>
        );
      })}
    </div>
  );
}

function InlineTextInput({ value, onCommit, className, ariaLabel, placeholder = "" }) {
  const inlineEdit = useInlineEdit({
    value,
    onCommit,
    blurOnEscape: true
  });

  return (
    <input
      {...inlineEdit.getInputProps({
        className,
        placeholder,
        "aria-label": ariaLabel
      })}
    />
  );
}

export function CombosModule({ toolboxId, moduleId, context, editing }) {
  const data = context.normalizeCombosData(context.getModuleData(toolboxId, moduleId, { combos: [] }));
  const textContent = context.moduleText?.combos || {};
  const [comboCategory, setComboCategory] = useState("");
  const [comboName, setComboName] = useState("");
  const [draftInputs, setDraftInputs] = useState([[]]);
  const [activeStepIndex, setActiveStepIndex] = useState(0);
  const [selectedInput, setSelectedInput] = useState(null);
  const [simultaneousMode, setSimultaneousMode] = useState(false);
  const [keyboardLayout, setKeyboardLayout] = useState("azerty");
  const [customKey, setCustomKey] = useState("");
  const [editingComboId, setEditingComboId] = useState("");
  const paletteGroups = useMemo(() => getPaletteGroups(data.device, keyboardLayout), [data.device, keyboardLayout]);
  const paletteColumns = useMemo(() => getPaletteColumns(paletteGroups), [paletteGroups]);
  const cleanDraftInputs = draftInputs.map((step) => step.filter(Boolean)).filter((step) => step.length);
  const selectedDraftInput = Number.isInteger(selectedInput?.stepIndex) && Number.isInteger(selectedInput?.inputIndex)
    ? draftInputs[selectedInput.stepIndex]?.[selectedInput.inputIndex] || null
    : null;
  const entries = getGroupedEntries(data.combos, {
    groupOrder: data.categoryOrder,
    getItemGroup: getComboCategory,
    groupIdKey: "category",
    groupItemKey: "combos"
  });
  const {
    itemReorder,
    groupReorder,
    getItemProps,
    getGroupProps,
    getGroupBoundaryProps,
    isItemDragging,
    isItemDropTarget,
    isGroupDragging,
    isGroupDropTarget,
    isBoundaryDropTarget,
    getDropPlacement,
    shouldShowGroupBoundaries
  } = useGroupedReorder({
    namespace: `combos-${moduleId}`,
    items: data.combos,
    getItemId: (combo) => combo.id,
    getItemGroup: getComboCategory,
    reorderFeatures: {
      item: {
        groupChange: true,
        boundaryDrop: true,
        ungroupedDrop: true
      },
      group: {
        reorder: true,
        boundaryDrop: true,
        ungroupedDrop: true,
        itemDrop: true
      }
    },
    onItemMove: applyComboReorder,
    onGroupMove: applyComboReorder,
    hierarchy: { enabled: false, stickyParents: false }
  });

  function save(nextData) {
    context.setModuleData(toolboxId, moduleId, nextData, "combos");
  }

  function updateDevice(device) {
    const stampComboDevice = (combo) => combo.device ? combo : { ...combo, device: data.device };
    save({
      ...data,
      device,
      combos: data.combos.map(stampComboDevice)
    });
  }

  function applyComboReorder(operation) {
    save(applyGroupedReorderOperation(data, {
      operation,
      itemsKey: "combos",
      groupOrderKey: "categoryOrder",
      collapsedGroupsKey: "collapsedCategories",
      getItemGroup: getComboCategory,
      setItemGroup: setComboItemCategory
    }));
  }

  function addInputToStep(kind, value, stepIndex) {
    const input = createInput(kind, value);
    setDraftInputs((steps) => {
      const nextSteps = steps.length ? steps.map((step) => [...step]) : [[]];
      const targetIndex = Math.min(Math.max(0, stepIndex), nextSteps.length - 1);
      nextSteps[targetIndex] = [...nextSteps[targetIndex], input];
      setSelectedInput({ stepIndex: targetIndex, inputIndex: nextSteps[targetIndex].length - 1 });
      return nextSteps;
    });
  }

  function addDraftInput(kind, value) {
    const input = createInput(kind, value);
    if (simultaneousMode) {
      addInputToStep(kind, value, activeStepIndex);
      return;
    }

    setDraftInputs((steps) => {
      const filledSteps = steps.filter((step) => step.length);
      const nextSteps = [...filledSteps, [input]];
      setActiveStepIndex(nextSteps.length - 1);
      setSelectedInput({ stepIndex: nextSteps.length - 1, inputIndex: 0 });
      return nextSteps;
    });
  }

  function dragPaletteInput(event, kind, value) {
    event.dataTransfer.effectAllowed = "copy";
    event.dataTransfer.setData("application/x-sokkog-combo-input", JSON.stringify(createInput(kind, value)));
  }

  function dragDraftInput(event, stepIndex, inputIndex, input) {
    event.dataTransfer.effectAllowed = "move";
    event.dataTransfer.setData("application/x-sokkog-combo-input", JSON.stringify({
      ...input,
      sourceStepIndex: stepIndex,
      sourceInputIndex: inputIndex
    }));
  }

  function dropInputOnStep(event, stepIndex) {
    const rawInput = event.dataTransfer.getData("application/x-sokkog-combo-input");
    if (!rawInput) return;
    try {
      const input = JSON.parse(rawInput);
      if (!input?.kind || !input?.value) return;
      if (Number.isInteger(input.sourceStepIndex) && Number.isInteger(input.sourceInputIndex)) {
        moveDraftInput(input.sourceStepIndex, input.sourceInputIndex, stepIndex);
      } else {
        addInputToStep(input.kind, input.value, stepIndex);
      }
      setActiveStepIndex(stepIndex);
    } catch {
      // Ignore invalid drag payloads from outside the app.
    }
  }

  function dropInputAsNewStep(event) {
    const rawInput = event.dataTransfer.getData("application/x-sokkog-combo-input");
    if (!rawInput) return;
    try {
      const input = JSON.parse(rawInput);
      if (!input?.kind || !input?.value) return;
      if (Number.isInteger(input.sourceStepIndex) && Number.isInteger(input.sourceInputIndex)) {
        moveDraftInputToNewStep(input.sourceStepIndex, input.sourceInputIndex);
      } else {
        addDraftInput(input.kind, input.value);
      }
    } catch {
      // Ignore invalid drag payloads from outside the app.
    }
  }

  function moveDraftInput(sourceStepIndex, sourceInputIndex, targetStepIndex) {
    setDraftInputs((steps) => {
      if (sourceStepIndex === targetStepIndex) return steps;
      const nextSteps = steps.map((step) => [...step]);
      const [input] = nextSteps[sourceStepIndex]?.splice(sourceInputIndex, 1) || [];
      if (!input || !nextSteps[targetStepIndex]) return steps;
      nextSteps[targetStepIndex].push(input);
      setSelectedInput({ stepIndex: targetStepIndex, inputIndex: nextSteps[targetStepIndex].length - 1 });
      return nextSteps.filter((step, index) => step.length || index === targetStepIndex);
    });
  }

  function moveDraftInputToNewStep(sourceStepIndex, sourceInputIndex) {
    setDraftInputs((steps) => {
      const nextSteps = steps.map((step) => [...step]);
      const [input] = nextSteps[sourceStepIndex]?.splice(sourceInputIndex, 1) || [];
      if (!input) return steps;
      const safeSteps = nextSteps.filter((step) => step.length);
      setActiveStepIndex(safeSteps.length);
      setSelectedInput({ stepIndex: safeSteps.length, inputIndex: 0 });
      return [...safeSteps, [input]];
    });
  }

  function removeActiveStep() {
    setDraftInputs((steps) => {
      const nextSteps = steps.filter((_, index) => index !== activeStepIndex);
      const safeSteps = nextSteps.length ? nextSteps : [[]];
      setActiveStepIndex(Math.min(activeStepIndex, safeSteps.length - 1));
      setSelectedInput(null);
      return safeSteps;
    });
  }

  function removeLastInput() {
    setDraftInputs((steps) => {
      const nextSteps = steps.length ? steps.map((step) => [...step]) : [[]];
      let targetIndex = -1;
      for (let index = nextSteps.length - 1; index >= 0; index -= 1) {
        if (nextSteps[index].length) {
          targetIndex = index;
          break;
        }
      }
      if (targetIndex < 0) return [[]];
      nextSteps[targetIndex].pop();
      const safeSteps = nextSteps.filter((step) => step.length);
      setActiveStepIndex(Math.max(0, safeSteps.length - 1));
      setSelectedInput(null);
      return safeSteps.length ? safeSteps : [[]];
    });
  }

  function updateSelectedInputHold(value) {
    if (!selectedDraftInput) return;
    const holdMs = Math.round(parseHoldSeconds(value) * 1000);
    setDraftInputs((steps) => steps.map((step, stepIndex) => step.map((input, inputIndex) => {
      if (stepIndex !== selectedInput.stepIndex || inputIndex !== selectedInput.inputIndex) return input;
      const nextInput = { ...input };
      if (holdMs > 0) {
        nextInput.hold = true;
        nextInput.holdMs = holdMs;
      }
      else delete nextInput.holdMs;
      return nextInput;
    })));
  }

  function toggleSelectedInputHold() {
    if (!selectedDraftInput) return;
    setDraftInputs((steps) => steps.map((step, stepIndex) => step.map((input, inputIndex) => {
      if (stepIndex !== selectedInput.stepIndex || inputIndex !== selectedInput.inputIndex) return input;
      const nextInput = { ...input };
      if (nextInput.hold || nextInput.holdMs) {
        delete nextInput.hold;
        delete nextInput.holdMs;
      } else {
        nextInput.hold = true;
      }
      return nextInput;
    })));
  }

  function selectDraftStep(stepIndex) {
    setActiveStepIndex(stepIndex);
    setSelectedInput(null);
  }

  function addCustomKey(event) {
    event.preventDefault();
    const value = normalizeCustomKey(customKey);
    if (!value || data.device !== "keyboardMouse") return;
    addDraftInput("key", value.toLowerCase());
    setCustomKey("");
  }

  function saveCombo(event) {
    event.preventDefault();
    if (!comboName.trim() && !cleanDraftInputs.length) return;
    const combo = createCombo(context, editingComboId, comboName, comboCategory, cleanDraftInputs, textContent.defaultComboName || "Combo", data.device);
    const nextCombos = data.combos.filter((item) => item.id !== editingComboId);
    const nextCategoryOrder = combo.category && !data.categoryOrder.includes(combo.category)
      ? [...data.categoryOrder, combo.category]
      : data.categoryOrder;
    save({ ...data, combos: [...nextCombos, combo], categoryOrder: nextCategoryOrder });
    cancelComboEdit();
  }

  function startComboEdit(combo) {
    setComboCategory(getComboCategory(combo));
    setEditingComboId(combo.id);
    setComboName(combo.name);
    setDraftInputs(combo.inputs.length ? combo.inputs.map((step) => step.map((input) => ({ ...input }))) : [[]]);
    setActiveStepIndex(0);
    setSelectedInput(null);
  }

  function cancelComboEdit() {
    setEditingComboId("");
    setComboName("");
    setComboCategory("");
    setDraftInputs([[]]);
    setActiveStepIndex(0);
    setSelectedInput(null);
    setSimultaneousMode(false);
  }

  function updateCombo(comboId, updater) {
    save({ ...data, combos: data.combos.map((combo) => combo.id === comboId ? updater(combo) : combo).filter((combo) => combo.name || combo.inputs.length) });
  }

  function deleteCombo(comboId) {
    updateCombo(comboId, () => ({ name: "", inputs: [] }));
    if (editingComboId === comboId) cancelComboEdit();
  }

  function toggleCategoryCollapsed(category) {
    const collapsedCategories = data.collapsedCategories.includes(category)
      ? data.collapsedCategories.filter((item) => item !== category)
      : [...data.collapsedCategories, category];
    save({ ...data, collapsedCategories });
  }

  function renderCombo(combo) {
    const category = getComboCategory(combo);
    const comboClassName = [
      "combo-card",
      "is-editing",
      isItemDragging(combo.id) ? "is-dragging" : "",
      isItemDropTarget(combo.id) ? "is-drop-target" : "",
      getDropPlacement("item", combo.id) === "after" ? "drop-after" : ""
    ].filter(Boolean).join(" ");
    return (
      <article className={comboClassName} key={combo.id} {...getItemProps({ itemId: combo.id, groupId: category })}>
        <button
          className="task-planner-drag-handle combo-drag-handle"
          type="button"
          onPointerDown={(event) => itemReorder.startDrag(event, combo.id)}
          aria-label={`${textContent.reorderComboTitle || "Déplacer le combo"} ${combo.name}`}
          title={textContent.reorderComboTitle || "Déplacer le combo"}
        >
          <Icon name="drag" />
        </button>
        <div className="combo-card-main">
          <InlineTextInput
            className="tool-split-entry-label combo-title-input"
            value={combo.name}
            onCommit={(name) => updateCombo(combo.id, (current) => ({ ...current, name: name || current.name }))}
            ariaLabel={textContent.comboNameLabel || "Nom"}
          />
          <ComboSequence inputs={combo.inputs} device={combo.device || data.device} emptyLabel={textContent.emptySequence || "Combo sans touches"} />
        </div>
        <div className="combo-card-actions">
          <button type="button" className="checklist-delete-button" onClick={() => startComboEdit(combo)} aria-label={`${textContent.editComboTitle || "Modifier le combo"} ${combo.name}`} title={textContent.editComboTitle || "Modifier le combo"}>
            <Icon name="edit" />
          </button>
          <button type="button" className="checklist-delete-button danger" onClick={() => deleteCombo(combo.id)} aria-label={`${textContent.deleteComboTitle || "Supprimer le combo"} ${combo.name}`} title={textContent.deleteComboTitle || "Supprimer le combo"}>
            <Icon name="trash" />
          </button>
        </div>
      </article>
    );
  }

  function renderCategory(group, index, groupCount) {
    const category = group.category;
    const isCollapsed = data.collapsedCategories.includes(category);
    const isLastCategory = index === groupCount - 1;
    const categoryClassName = [
      "combos-category checklist-section is-grouped",
      isCollapsed ? "is-collapsed" : "",
      isGroupDragging(category) ? "is-dragging" : "",
      isGroupDropTarget(category) ? "is-drop-target" : "",
      getDropPlacement("group", category) === "after" ? "drop-after" : ""
    ].filter(Boolean).join(" ");
    const showBoundaryDropZones = shouldShowGroupBoundaries();
    const beforeDropZone = showBoundaryDropZones ? (
      <div
        className={`combos-category-boundary-drop-zone ${isBoundaryDropTarget({ groupId: category, placement: "before" }) ? "is-drop-target" : ""}`}
        key={`${category}:before-drop`}
        {...getGroupBoundaryProps({ groupId: category, placement: "before" })}
      />
    ) : null;
    const afterDropZone = showBoundaryDropZones ? (
      <div
        className={`combos-category-boundary-drop-zone ${isBoundaryDropTarget({ groupId: category, placement: "after" }) ? "is-drop-target" : ""}`}
        key={`${category}:after-drop`}
        {...getGroupBoundaryProps({ groupId: category, placement: "after" })}
      />
    ) : null;
    return [
      beforeDropZone,
      <section className={categoryClassName} key={category} {...getGroupProps({ groupId: category })}>
        <div className="checklist-section-header combos-category-header">
          <div className="combos-category-title">
            <button
              className="task-planner-category-drag-handle"
              type="button"
              onPointerDown={(event) => groupReorder.startDrag(event, { groupId: category })}
              aria-label={`${textContent.reorderCategoryTitle || "Déplacer la catégorie"} ${category}`}
              title={textContent.reorderCategoryTitle || "Déplacer la catégorie"}
            >
              <Icon name="drag" />
            </button>
            <h3>{category}</h3>
          </div>
          <div className="checklist-section-actions">
            <span>{group.combos.length}</span>
            <button
              className="checklist-section-collapse-button"
              type="button"
              onClick={() => toggleCategoryCollapsed(category)}
              aria-expanded={!isCollapsed}
              aria-label={isCollapsed ? textContent.showCategoryTitle || "Afficher la catégorie" : textContent.hideCategoryTitle || "Réduire la catégorie"}
              title={isCollapsed ? textContent.showCategoryTitle || "Afficher la catégorie" : textContent.hideCategoryTitle || "Réduire la catégorie"}
            >
              <Icon name={isCollapsed ? "chevron-down" : "chevron-up"} />
            </button>
          </div>
        </div>
        {!isCollapsed && (
          <div className="combos-list">
            {group.combos.map((combo) => renderCombo(combo))}
          </div>
        )}
      </section>,
      showBoundaryDropZones && isLastCategory ? afterDropZone : null
    ];
  }

  const categoryCount = entries.filter((entry) => entry.type === "group").length;
  let renderedCategoryIndex = -1;

  function renderEntry(entry) {
    if (entry.type !== "group") return renderCombo(entry.item);
    renderedCategoryIndex += 1;
    return renderCategory(entry.group, renderedCategoryIndex, categoryCount);
  }

  return (
    <div className="combos-module">
      {(editing || editingComboId) && (
        <div className="module-add-panel combos-editor">
          {editing && !editingComboId && (
            <TextExchangeActions type="combos" data={data} context={context} textContent={textContent} onImport={(nextData) => save(nextData)} />
          )}
          <div className="combos-device-row">
            <label>
              <span>{textContent.deviceLabel || "Périphérique"}</span>
              <select value={data.device} onChange={(event) => updateDevice(event.target.value)}>
                {DEVICE_OPTIONS.map((device) => (
                  <option key={device.value} value={device.value}>{device.label}</option>
                ))}
              </select>
            </label>
            {data.device === "keyboardMouse" && (
              <div className="combos-keyboard-layout-switch" role="group" aria-label={textContent.keyboardLayoutLabel || "Disposition clavier"}>
                {[
                  { value: "azerty", label: "AZERTY" },
                  { value: "qwerty", label: "QWERTY" }
                ].map((layout) => (
                  <button
                    key={layout.value}
                    type="button"
                    className={keyboardLayout === layout.value ? "active" : ""}
                    onClick={() => setKeyboardLayout(layout.value)}
                    aria-pressed={keyboardLayout === layout.value}
                  >
                    {layout.label}
                  </button>
                ))}
              </div>
            )}
          </div>

          <form className="combos-add-form" onSubmit={saveCombo}>
            <div className="combos-form-grid">
              <label>
                <span>{textContent.targetCategoryLabel || "Catégorie"}</span>
                <input value={comboCategory} onChange={(event) => setComboCategory(event.target.value)} placeholder={textContent.noCategoryLabel || "Sans catégorie"} />
              </label>
              <label>
                <span>{textContent.comboNameLabel || "Nom"}</span>
                <input value={comboName} onChange={(event) => setComboName(event.target.value)} placeholder={textContent.comboNamePlaceholder || "Nom du combo"} />
              </label>
            </div>
            <div className="combos-palette" aria-label={textContent.paletteLabel || "Palette de touches"}>
              {paletteColumns.map((columnGroups, columnIndex) => (
                <div className="combos-palette-column" key={columnIndex}>
                  {columnGroups.map((group) => (
                    <div className={`combos-palette-group ${group.kind === "directions" ? "is-direction-grid" : ""}`} key={group.title}>
                      <span>{group.title}</span>
                      <div>
                        {group.inputs.map((input, inputIndex) => {
                          if (!input) return <span className="combo-palette-empty-cell" key={`empty-${inputIndex}`} aria-hidden="true" />;
                          const [kind, value] = input;
                          return (
                            <ComboInputToken
                              key={`${kind}-${value}`}
                              input={createInput(kind, value)}
                              device={data.device}
                              palette
                              onClick={() => addDraftInput(kind, value)}
                              onDragStart={(event) => dragPaletteInput(event, kind, value)}
                            />
                          );
                        })}
                      </div>
                    </div>
                  ))}
                </div>
              ))}
            </div>

            {data.device === "keyboardMouse" && (
              <div className="inline-form combos-custom-key-form">
                <input value={customKey} onChange={(event) => setCustomKey(event.target.value)} placeholder={textContent.customKeyPlaceholder || "Touche personnalisée"} />
                <button type="button" onClick={addCustomKey}>{textContent.addCustomKeyButton || "Ajouter touche"}</button>
              </div>
            )}

            <div className="combo-draft-panel">
              <ComboSequence
                inputs={draftInputs}
                device={data.device}
                emptyLabel={textContent.emptyDraft || "Aucune touche sélectionnée."}
                activeStepIndex={activeStepIndex}
                selectedInput={selectedInput}
                onSelectStep={selectDraftStep}
                onSelectInput={(stepIndex, inputIndex) => {
                  setActiveStepIndex(stepIndex);
                  setSelectedInput({ stepIndex, inputIndex });
                }}
                onDropInput={dropInputOnStep}
                onDropNewStep={dropInputAsNewStep}
                onDragInput={dragDraftInput}
              />
              <div className="combo-draft-actions">
                <label className="combo-hold-field">
                  <span className="combo-hold-controls">
                    <button
                      type="button"
                      className={selectedDraftInput?.hold || selectedDraftInput?.holdMs ? "active" : ""}
                      onClick={toggleSelectedInputHold}
                      disabled={!selectedDraftInput}
                      aria-pressed={Boolean(selectedDraftInput?.hold || selectedDraftInput?.holdMs)}
                    >
                      {textContent.holdToggleLabel || "Hold"}
                    </button>
                    <input
                      type="number"
                      min="0"
                      max="99"
                      step="1"
                      inputMode="numeric"
                      value={selectedDraftInput ? getHoldSeconds(selectedDraftInput) || "" : ""}
                      onChange={(event) => updateSelectedInputHold(event.target.value)}
                      disabled={!selectedDraftInput}
                      placeholder="0"
                      aria-label={textContent.holdLabel || "Maintien en secondes"}
                    />
                  </span>
                </label>
                <button
                  type="button"
                  className={`combos-simultaneous-toggle ${simultaneousMode ? "active" : ""}`}
                  onClick={() => setSimultaneousMode((enabled) => !enabled)}
                  aria-pressed={simultaneousMode}
                  title={textContent.simultaneousModeTitle || "Ajout simultané"}
                  aria-label={textContent.simultaneousModeTitle || "Ajout simultané"}
                >
                  <span className={!simultaneousMode ? "active" : ""}>{textContent.successiveModeLabel || "A › B"}</span>
                  <span className={simultaneousMode ? "active" : ""}>{textContent.simultaneousModeLabel || "A + B"}</span>
                </button>
                <button type="button" className="notepad-toolbar-button" onClick={removeLastInput} disabled={!cleanDraftInputs.length} title={textContent.removeLastInputTitle || "Retirer la dernière touche"} aria-label={textContent.removeLastInputTitle || "Retirer la dernière touche"}>
                  <Icon name="back" />
                </button>
                <button type="button" className="notepad-toolbar-button danger" onClick={removeActiveStep} disabled={draftInputs.length <= 1} title={textContent.removeStepTitle || "Supprimer l'étape"} aria-label={textContent.removeStepTitle || "Supprimer l'étape"}>
                  <Icon name="trash" />
                </button>
                <div className="combo-save-actions">
                  {editingComboId && (
                    <button type="button" className="combo-cancel-button" onClick={cancelComboEdit}>
                      {textContent.cancelEditButton || "Annuler"}
                    </button>
                  )}
                  <button className="primary" disabled={!comboName.trim() && !cleanDraftInputs.length}>
                    {editingComboId ? textContent.saveComboButton || "Enregistrer" : textContent.addComboButton || "Ajouter le combo"}
                  </button>
                </div>
              </div>
            </div>
          </form>
        </div>
      )}

      {!data.combos.length && (
        <p className="empty-state">{textContent.emptyCombos || "Aucun combo enregistré."}</p>
      )}

      <div className="combos-category-list">
        {entries.map(renderEntry)}
      </div>
    </div>
  );
}
