import { useState, useEffect, useCallback, Fragment } from "react";
import { LuPlus, LuTrash, LuX, LuPenLine, LuLoader, LuInfo, LuCopy, LuArrowUp, LuArrowDown, LuMoveRight, LuCornerDownRight } from "react-icons/lu";

import { KeySequence, useMacrosStore } from "@/hooks/stores";
import { SettingsPageHeader } from "@/components/SettingsPageheader";
import { Button } from "@/components/Button";
import Checkbox from "@/components/Checkbox";
import { keys, modifiers, keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications";
import { SettingsItem } from "@/routes/devices.$id.settings";
import { InputFieldWithLabel, FieldError } from "@/components/InputField";
import Fieldset from "@/components/Fieldset";
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
import EmptyCard from "@/components/EmptyCard";
import { Combobox } from "@/components/Combobox";
import { CardHeader } from "@/components/CardHeader";
import Card from "@/components/Card";
import { SortableList } from "@/components/SortableList";

const DEFAULT_DELAY = 50;

interface MacroStep {
  keys: string[];
  modifiers: string[];
  delay: number;
}

interface KeyOption {
  value: string;
  label: string;
}

interface KeyOptionData {
  value: string | null;
  keys?: string[];
  label?: string;
}

const generateId = () => {
  return `macro-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
};

// Filter out modifier keys since they're handled in the modifiers section
const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta'];

const keyOptions = Object.keys(keys)
  .filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix)))
  .map(key => ({
    value: key,
    label: keyDisplayMap[key] || key,
  }));

const modifierOptions = Object.keys(modifiers).map(modifier => ({
  value: modifier,
  label: modifier.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1 $2"),
}));

const groupedModifiers = {
  Control: modifierOptions.filter(mod => mod.value.startsWith('Control')),
  Shift: modifierOptions.filter(mod => mod.value.startsWith('Shift')),
  Alt: modifierOptions.filter(mod => mod.value.startsWith('Alt')),
  Meta: modifierOptions.filter(mod => mod.value.startsWith('Meta')),
};


const PRESET_DELAYS = [
  { value: "50", label: "50ms" },
  { value: "100", label: "100ms" },
  { value: "200", label: "200ms" },
  { value: "300", label: "300ms" },
  { value: "500", label: "500ms" },
  { value: "750", label: "750ms" },
  { value: "1000", label: "1000ms" },
  { value: "1500", label: "1500ms" },
  { value: "2000", label: "2000ms" },
];

const MAX_STEPS_PER_MACRO = 10;
const MAX_TOTAL_MACROS = 25;
const MAX_KEYS_PER_STEP = 10;

const ensureArray = <T,>(arr: T[] | null | undefined): T[] => {
  return Array.isArray(arr) ? arr : [];
};

// Helper function to normalize sort orders, ensuring they start at 1 and have no gaps
const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => {
  return macros.map((macro, index) => ({
    ...macro,
    sortOrder: index + 1,
  }));
};

interface MacroStepCardProps {
  step: MacroStep;
  stepIndex: number;
  onDelete?: () => void;
  onMoveUp?: () => void;
  onMoveDown?: () => void;
  isDesktop: boolean;
  onKeySelect: (option: KeyOptionData) => void;
  onKeyQueryChange: (query: string) => void;
  keyQuery: string;
  getFilteredKeys: () => KeyOption[];
  onModifierChange: (modifiers: string[]) => void;
  onDelayChange: (delay: number) => void;
  isLastStep: boolean;
}

function MacroStepCard({
  step,
  stepIndex,
  onDelete,
  onMoveUp,
  onMoveDown,
  onKeySelect,
  onKeyQueryChange,
  keyQuery,
  getFilteredKeys,
  onModifierChange,
  onDelayChange,
  isLastStep
}: MacroStepCardProps) {
  return (
    <div className="macro-step-card rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 p-4 shadow-sm">
      <div className="mb-2 flex items-center justify-between">
        <div className="flex items-center gap-1.5">
          <div className="flex items-center gap-1">
            <Button
              size="XS"
              theme="light"
              onClick={onMoveUp}
              disabled={stepIndex === 0}
              LeadingIcon={LuArrowUp}
            />
            <Button
              size="XS"
              theme="light"
              onClick={onMoveDown}
              disabled={isLastStep}
              LeadingIcon={LuArrowDown}
            />
          </div>
          <span className="macro-step-number flex h-5 w-5 items-center justify-center rounded-full bg-blue-100 text-xs font-semibold text-blue-700 dark:bg-blue-900/40 dark:text-blue-200">
            {stepIndex + 1}
          </span>
        </div>
        
        <div className="flex items-center space-x-2">
          {onDelete && (
            <Button
              size="XS"
              theme="danger"
              text="Delete"
              onClick={onDelete}
              LeadingIcon={LuTrash}
              />
          )}
        </div>
      </div>
      
      <div className="space-y-4 mt-2">
        <div className="w-full flex flex-col gap-2">
          <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
            Modifiers:
          </label>
          <div className="macro-modifiers-container inline-flex flex-wrap gap-3">
            {Object.entries(groupedModifiers).map(([group, mods]) => (
              <div key={group} className="relative min-w-[120px] rounded-md border border-slate-200 dark:border-slate-700 p-2">
                <span className="absolute -top-2.5 left-2 px-1 text-xs font-medium bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400">
                  {group}
                </span>
                <div className="flex flex-wrap gap-1">
                  {mods.map(option => (
                    <label 
                      key={option.value} 
                      className={`flex items-center px-2 py-1 rounded border cursor-pointer text-xs font-medium transition-colors ${
                        ensureArray(step.modifiers).includes(option.value) 
                          ? 'bg-blue-100 border-blue-300 text-blue-700 dark:bg-blue-900/40 dark:border-blue-600 dark:text-blue-200' 
                          : 'bg-slate-100 border-slate-200 text-slate-600 hover:bg-slate-200 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700'
                      }`}
                    >
                      <Checkbox
                        className="sr-only"
                        size="SM"
                        checked={ensureArray(step.modifiers).includes(option.value)}
                        onChange={e => {
                          const modifiersArray = ensureArray(step.modifiers);
                          const newModifiers = e.target.checked
                            ? [...modifiersArray, option.value]
                            : modifiersArray.filter(m => m !== option.value);
                          onModifierChange(newModifiers);
                        }}
                      />
                      {option.label.split(' ')[1] || option.label}
                    </label>
                  ))}
                </div>
              </div>
            ))}
          </div>
        </div>
        
        <div className="w-full flex flex-col gap-1">
          <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
            Keys:
          </label>
          
          <div className="macro-key-group flex flex-wrap gap-1 pb-2">
            {ensureArray(step.keys).map((key, keyIndex) => (
              <span
                key={keyIndex}
                className="inline-flex items-center rounded-md bg-blue-100 px-1 text-xs font-medium text-blue-800 dark:bg-blue-900/40 dark:text-blue-200"
              >
                <span className="px-1">
                  {keyDisplayMap[key] || key}
                </span>
                <Button
                  size="XS"
                  className=""
                  theme="blank"
                  onClick={() => {
                    const newKeys = ensureArray(step.keys).filter((_, i) => i !== keyIndex);
                    onKeySelect({ value: null, keys: newKeys });
                  }}
                  LeadingIcon={LuX}
                />
              </span>
            ))}
          </div>
          <div className="relative w-full">
            <Combobox<KeyOption>
              onChange={(value: KeyOption) => onKeySelect(value)}
              displayValue={() => keyQuery}
              onInputChange={onKeyQueryChange}
              options={getFilteredKeys}
              disabledMessage="Max keys reached"
              size="SM"
              disabled={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP}
              placeholder={ensureArray(step.keys).length >= MAX_KEYS_PER_STEP ? "Max keys reached" : "Search for key..."}
              emptyMessage="No matching keys found"
            />
          </div>
        </div>
        
        <div className="w-full flex flex-col gap-1">
          <div className="flex items-center gap-1">
            <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
              Step Duration:
            </label>
            <div className="group relative">
              <LuInfo className="h-4 w-4 text-slate-400 hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-400" />
              <div className="absolute left-1/2 top-full z-10 mt-1 hidden w-64 -translate-x-1/2 rounded-md bg-slate-800 px-3 py-2 text-xs text-white shadow-lg group-hover:block dark:bg-slate-700">
                <p>The time to wait after pressing the keys in this step before moving to the next step. This helps ensure reliable key presses when automating keyboard input.</p>
              </div>
            </div>
          </div>
          <div className="flex items-center gap-3">
            <SelectMenuBasic
              size="SM"
              fullWidth
              value={step.delay.toString()}
              onChange={(e) => onDelayChange(parseInt(e.target.value, 10))}
              options={PRESET_DELAYS}
            />
          </div>
        </div>
      </div>
    </div>
  );
}

// Helper to update step keys used by both new and edit flows
const updateStepKeys = (
  steps: MacroStep[],
  stepIndex: number,
  keyOption: { value: string | null; keys?: string[] },
  showTemporaryError: (msg: string) => void
) => {
  const newSteps = [...steps];
  
  // Check if the step at stepIndex exists
  if (!newSteps[stepIndex]) {
    console.error(`Step at index ${stepIndex} does not exist`);
    return steps; // Return original steps to avoid mutation
  }
  
  if (keyOption.keys) {
    newSteps[stepIndex].keys = keyOption.keys;
  } else if (keyOption.value) {
    // Initialize keys array if it doesn't exist
    if (!newSteps[stepIndex].keys) {
      newSteps[stepIndex].keys = [];
    }
    const keysArray = ensureArray(newSteps[stepIndex].keys);
    if (keysArray.length >= MAX_KEYS_PER_STEP) {
      showTemporaryError(`Maximum of ${MAX_KEYS_PER_STEP} keys per step allowed`);
      return newSteps;
    }
    newSteps[stepIndex].keys = [...keysArray, keyOption.value];
  }
  return newSteps;
};

interface StepError {
  keys?: string;
  modifiers?: string;
  delay?: string;
}

interface ValidationErrors {
  name?: string;
  description?: string;
  steps?: Record<number, StepError>;
}

export default function SettingsMacrosRoute() {
  const { macros, loading, initialized, loadMacros, saveMacros, setSendFn } = useMacrosStore();
  const [editingMacro, setEditingMacro] = useState<KeySequence | null>(null);
  const [newMacro, setNewMacro] = useState<Partial<KeySequence>>({
    name: "",
    description: "",
    steps: [{ keys: [], modifiers: [], delay: DEFAULT_DELAY }],
  });
  const [macroToDelete, setMacroToDelete] = useState<string | null>(null);
  
  const [keyQueries, setKeyQueries] = useState<Record<number, string>>({});
  const [editKeyQueries, setEditKeyQueries] = useState<Record<number, string>>({});
  
  const [errorMessage, setErrorMessage] = useState<string | null>(null);
  const [isDesktop, setIsDesktop] = useState(window.innerWidth >= 768);
  
  const [send] = useJsonRpc();

  const isMaxMacrosReached = macros.length >= MAX_TOTAL_MACROS;
  const isMaxStepsReachedForNewMacro = (newMacro.steps?.length || 0) >= MAX_STEPS_PER_MACRO;
  
  const showTemporaryError = useCallback((message: string) => {
    setErrorMessage(message);
    setTimeout(() => setErrorMessage(null), 3000);
  }, []);
  
  // Helper for both new and edit key select
  const handleKeySelectUpdate = (stepIndex: number, option: KeyOptionData, isEditing = false) => {
    if (isEditing && editingMacro) {
      const updatedSteps = updateStepKeys(editingMacro.steps, stepIndex, option, showTemporaryError);
      setEditingMacro({ ...editingMacro, steps: updatedSteps });
    } else {
      const updatedSteps = updateStepKeys(newMacro.steps || [], stepIndex, option, showTemporaryError);
      setNewMacro({ ...newMacro, steps: updatedSteps });
    }
  };
  
  const handleKeySelect = (stepIndex: number, option: KeyOptionData) => {
    handleKeySelectUpdate(stepIndex, option, false);
  };
  
  const handleEditKeySelect = (stepIndex: number, option: KeyOptionData) => {
    handleKeySelectUpdate(stepIndex, option, true);
  };
  
  const handleKeyQueryChange = (stepIndex: number, query: string) => {
    setKeyQueries(prev => ({ ...prev, [stepIndex]: query }));
  };
  
  const handleEditKeyQueryChange = (stepIndex: number, query: string) => {
    setEditKeyQueries(prev => ({ ...prev, [stepIndex]: query }));
  };
  
  const getFilteredKeys = (stepIndex: number, isEditing = false) => {
    const query = isEditing 
      ? (editKeyQueries[stepIndex] || '')
      : (keyQueries[stepIndex] || '');
    
    const currentStep = isEditing 
      ? editingMacro?.steps[stepIndex] 
      : newMacro.steps?.[stepIndex];
    
    const selectedKeys = ensureArray(currentStep?.keys);
    
    const availableKeys = keyOptions.filter(option => !selectedKeys.includes(option.value));
    
    if (query === '') {
      return availableKeys;
    } else {
      return availableKeys.filter(option => option.label.toLowerCase().includes(query.toLowerCase()));
    }
  };
  
  useEffect(() => {
    setSendFn(send);
    if (!initialized) {
      loadMacros();
    }
  }, [initialized, loadMacros, setSendFn, send]);
  
  const [errors, setErrors] = useState<ValidationErrors>({});
  
  const clearErrors = useCallback(() => {
    setErrors({});
  }, []);
  
  const validateMacro = (macro: Partial<KeySequence>): ValidationErrors => {
    const errors: ValidationErrors = {};

    // Name validation
    if (!macro.name?.trim()) {
      errors.name = "Name is required";
    } else if (macro.name.trim().length > 50) {
      errors.name = "Name must be less than 50 characters";
    }

    // Description validation (optional)
    if (macro.description && macro.description.trim().length > 200) {
      errors.description = "Description must be less than 200 characters";
    }

    // Steps validation
    if (!macro.steps?.length) {
      errors.steps = { 0: { keys: "At least one step is required" } };
      return errors;
    }

    // Check if at least one step has keys or modifiers
    const hasKeyOrModifier = macro.steps.some(step => 
      (step.keys?.length || 0) > 0 || (step.modifiers?.length || 0) > 0
    );

    if (!hasKeyOrModifier) {
      errors.steps = { 0: { keys: "At least one step must have keys or modifiers" } };
      return errors;
    }

    const stepErrors: Record<number, StepError> = {};
    
    macro.steps.forEach((step, index) => {
      const stepError: StepError = {};

      // Keys validation (only if keys are present)
      if (step.keys?.length && step.keys.length > MAX_KEYS_PER_STEP) {
        stepError.keys = `Maximum ${MAX_KEYS_PER_STEP} keys allowed`;
      }

      // Delay validation
      if (typeof step.delay !== 'number' || step.delay < 0) {
        stepError.delay = "Invalid delay value";
      }

      if (Object.keys(stepError).length > 0) {
        stepErrors[index] = stepError;
      }
    });

    if (Object.keys(stepErrors).length > 0) {
      errors.steps = stepErrors;
    }

    return errors;
  };

  const resetNewMacro = () => {
    setNewMacro({
      name: "",
      description: "",
      steps: [{ keys: [], modifiers: [], delay: DEFAULT_DELAY }],
    });
    setKeyQueries({});
    setErrors({});
  };

  const [isSaving, setIsSaving] = useState(false);
  const [isUpdating, setIsUpdating] = useState(false);
  const [isDeleting, setIsDeleting] = useState(false);

  const handleAddMacro = useCallback(async () => {
    if (isMaxMacrosReached) {
      showTemporaryError(`Maximum of ${MAX_TOTAL_MACROS} macros allowed`);
      return;
    }

    const validationErrors = validateMacro(newMacro);
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      return;
    }

    setIsSaving(true);
    try {
      const macro: KeySequence = {
        id: generateId(),
        name: newMacro.name!.trim(),
        description: newMacro.description?.trim() || "",
        steps: newMacro.steps || [],
        sortOrder: macros.length + 1,
      };

      await saveMacros(normalizeSortOrders([...macros, macro]));
      resetNewMacro();
      setShowAddMacro(false);
      notifications.success(`Macro "${macro.name}" created successfully`);
    } catch (error) {
      if (error instanceof Error) {
        notifications.error(`Failed to create macro: ${error.message}`);
        showTemporaryError(error.message);
      } else {
        notifications.error("Failed to create macro");
        showTemporaryError("Failed to save macro");
      }
    } finally {
      setIsSaving(false);
    }
  }, [isMaxMacrosReached, newMacro, macros, saveMacros, showTemporaryError]);

  const handleEditMacro = (macro: KeySequence) => {
    setEditingMacro({
      ...macro,
      description: macro.description || "",
      steps: macro.steps.map(step => ({
        ...step,
        keys: ensureArray(step.keys),
        modifiers: ensureArray(step.modifiers),
        delay: typeof step.delay === 'number' ? step.delay : DEFAULT_DELAY
      }))
    });
    clearErrors();
    setEditKeyQueries({});
  };

  const handleDeleteMacro = async (id: string) => {
    const macroToBeDeleted = macros.find(m => m.id === id);
    if (!macroToBeDeleted) return;

    setIsDeleting(true);
    try {
      const updatedMacros = normalizeSortOrders(macros.filter(macro => macro.id !== id));
      await saveMacros(updatedMacros);
      if (editingMacro?.id === id) {
        setEditingMacro(null);
      }
      setMacroToDelete(null);
      notifications.success(`Macro "${macroToBeDeleted.name}" deleted successfully`);
    } catch (error) {
      if (error instanceof Error) {
        notifications.error(`Failed to delete macro: ${error.message}`);
        showTemporaryError(error.message);
      } else {
        notifications.error("Failed to delete macro");
        showTemporaryError("Failed to delete macro");
      }
    } finally {
      setIsDeleting(false);
    }
  };

  const handleDuplicateMacro = async (macro: KeySequence) => {
    if (isMaxMacrosReached) {
      showTemporaryError(`Maximum of ${MAX_TOTAL_MACROS} macros allowed`);
      return;
    }

    const newMacroCopy: KeySequence = {
      ...JSON.parse(JSON.stringify(macro)),
      id: generateId(),
      name: `${macro.name} (copy)`,
      sortOrder: macros.length + 1,
    };

    newMacroCopy.steps = newMacroCopy.steps.map(step => ({
      ...step,
      keys: ensureArray(step.keys),
      modifiers: ensureArray(step.modifiers),
      delay: step.delay || DEFAULT_DELAY
    }));

    try {
      await saveMacros(normalizeSortOrders([...macros, newMacroCopy]));
      notifications.success(`Macro "${newMacroCopy.name}" duplicated successfully`);
    } catch (error) {
      if (error instanceof Error) {
        notifications.error(`Failed to duplicate macro: ${error.message}`);
        showTemporaryError(error.message);
      } else {
        notifications.error("Failed to duplicate macro");
        showTemporaryError("Failed to duplicate macro");
      }
    }
  };

  const handleStepMove = (stepIndex: number, direction: 'up' | 'down', steps: MacroStep[]) => {
    const newSteps = [...steps];
    const newIndex = direction === 'up' ? stepIndex - 1 : stepIndex + 1;
    [newSteps[stepIndex], newSteps[newIndex]] = [newSteps[newIndex], newSteps[stepIndex]];
    return newSteps;
  };

  useEffect(() => {
    const handleResize = () => {
      setIsDesktop(window.innerWidth >= 768);
    };
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  const [showClearConfirm, setShowClearConfirm] = useState(false);
  const [showAddMacro, setShowAddMacro] = useState(false);

  const handleUpdateMacro = useCallback(async () => {
    if (!editingMacro) return;

    const validationErrors = validateMacro(editingMacro);
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      return;
    }

    setIsUpdating(true);
    try {
      const newMacros = macros.map(m => 
        m.id === editingMacro.id ? {
          ...editingMacro,
          name: editingMacro.name.trim(),
          description: editingMacro.description?.trim() || "",
        } : m
      );

      await saveMacros(normalizeSortOrders(newMacros));
      setEditingMacro(null);
      clearErrors();
      notifications.success(`Macro "${editingMacro.name}" updated successfully`);
    } catch (error) {
      if (error instanceof Error) {
        notifications.error(`Failed to update macro: ${error.message}`);
        showTemporaryError(error.message);
      } else {
        notifications.error("Failed to update macro");
        showTemporaryError("Failed to update macro");
      }
    } finally {
      setIsUpdating(false);
    }
  }, [editingMacro, macros, saveMacros, showTemporaryError, clearErrors]);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && editingMacro) {
        setEditingMacro(null);
        setErrors({});
      }
      if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
        if (editingMacro) {
          handleUpdateMacro();
        } else if (!isMaxMacrosReached) {
          handleAddMacro();
        }
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [editingMacro, isMaxMacrosReached, handleAddMacro, handleUpdateMacro]);

  const handleModifierChange = (stepIndex: number, modifiers: string[]) => {
    if (editingMacro) {
      const newSteps = [...editingMacro.steps];
      newSteps[stepIndex].modifiers = modifiers;
      setEditingMacro({ ...editingMacro, steps: newSteps });
    } else {
      const newSteps = [...(newMacro.steps || [])];
      newSteps[stepIndex].modifiers = modifiers;
      setNewMacro({ ...newMacro, steps: newSteps });
    }
  };

  const handleDelayChange = (stepIndex: number, delay: number) => {
    if (editingMacro) {
      const newSteps = [...editingMacro.steps];
      newSteps[stepIndex].delay = delay;
      setEditingMacro({ ...editingMacro, steps: newSteps });
    } else {
      const newSteps = [...(newMacro.steps || [])];
      newSteps[stepIndex].delay = delay;
      setNewMacro({ ...newMacro, steps: newSteps });
    }
  };

  const ErrorMessage = ({ error }: { error?: string }) => {
    if (!error) return null;
    return (
      <FieldError error={error} />
    );
  };
  return (
    <div className="space-y-4">
          <SettingsPageHeader
            title="Keyboard Macros"
            description="Create and manage keyboard macros for quick actions"
        />
        {macros.length > 0 && (
          <div className="flex items-center justify-between mb-4">
            <SettingsItem 
              title="Macros"
              description={`${macros.length}/${MAX_TOTAL_MACROS}`}
            >
              <div className="flex items-center gap-2">
                {!showAddMacro && (
                  <Button
                    size="SM"
                    theme="primary"
                    text={isMaxMacrosReached ? `Max Reached` : "Add New Macro"}
                    onClick={() => setShowAddMacro(true)}
                    disabled={isMaxMacrosReached}
                  />
                )}
              </div>
            </SettingsItem>
          </div>
        )}

      {errorMessage && (<FieldError error={errorMessage} />)}

      {loading && (
        <div className="flex items-center justify-center p-8">
          <LuLoader className="h-6 w-6 animate-spin text-blue-500" />
        </div>
      )}
      <div className={`space-y-4 ${loading ? 'hidden' : ''}`}>
        {showAddMacro && (
          <Card className="p-3">
            <CardHeader
              headline="Add New Macro"
            />
            <Fieldset className="mt-4">
              <InputFieldWithLabel
                type="text"
                label="Macro Name"
                placeholder="Macro Name"
                value={newMacro.name}
                error={errors.name}
                onChange={e => {
                  setNewMacro(prev => ({ ...prev, name: e.target.value }));
                  if (errors.name) {
                    const newErrors = { ...errors };
                    delete newErrors.name;
                    setErrors(newErrors);
                  }
                }}
              />
            </Fieldset>
            
            <div className="mt-4">
              <div className="macro-section-header">
                <label className="macro-section-title">
                  Steps:
                </label>
                <span className="macro-section-subtitle">
                  {newMacro.steps?.length || 0}/{MAX_STEPS_PER_MACRO} steps
                </span>
              </div>
              {errors.steps && errors.steps[0]?.keys && (
                <div className="mt-2">
                  <ErrorMessage error={errors.steps[0].keys} />
                </div>
              )}
              <div className="mt-2 text-xs text-slate-500 dark:text-slate-400">
                You can add up to {MAX_STEPS_PER_MACRO} steps per macro
              </div>
              <Fieldset>
                <div className="mt-2 space-y-4">
                  {(newMacro.steps || []).map((step, stepIndex) => (
                    <MacroStepCard
                      key={stepIndex}
                      step={step}
                      stepIndex={stepIndex}
                      onDelete={newMacro.steps && newMacro.steps.length > 1 ? () => {
                        const newSteps = [...(newMacro.steps || [])];
                        newSteps.splice(stepIndex, 1);
                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
                      } : undefined}
                      onMoveUp={() => {
                        const newSteps = handleStepMove(stepIndex, 'up', newMacro.steps || []);
                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
                      }}
                      onMoveDown={() => {
                        const newSteps = handleStepMove(stepIndex, 'down', newMacro.steps || []);
                        setNewMacro(prev => ({ ...prev, steps: newSteps }));
                      }}
                      isDesktop={isDesktop}
                      onKeySelect={(option) => handleKeySelect(stepIndex, option)}
                      onKeyQueryChange={(query) => handleKeyQueryChange(stepIndex, query)}
                      keyQuery={keyQueries[stepIndex] || ''}
                      getFilteredKeys={() => getFilteredKeys(stepIndex)}
                      onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
                      onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
                      isLastStep={stepIndex === (newMacro.steps?.length || 0) - 1}
                    />
                  ))}
                </div>
              </Fieldset>

              <div className="mt-4 border-t border-slate-200 pt-4 dark:border-slate-700">
                <Button
                  size="MD"
                  theme="light"
                  fullWidth
                  LeadingIcon={LuPlus}
                  text={`Add Step ${isMaxStepsReachedForNewMacro ? `(${MAX_STEPS_PER_MACRO} max)` : ''}`}
                  onClick={() => {
                    if (isMaxStepsReachedForNewMacro) {
                      showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
                      return;
                    }
                    
                    setNewMacro(prev => ({
                      ...prev,
                      steps: [
                        ...(prev.steps || []), 
                        { keys: [], modifiers: [], delay: DEFAULT_DELAY }
                      ],
                    }));
                    clearErrors();
                  }}
                  disabled={isMaxStepsReachedForNewMacro}
                />
              </div>

              <div className="mt-6 flex items-center justify-between border-t border-slate-200 pt-4 dark:border-slate-700">
                {showClearConfirm ? (
                  <div className="flex items-center gap-2">
                    <span className="text-sm text-slate-600 dark:text-slate-400">
                      Cancel changes?
                    </span>
                    <Button
                      size="SM"
                      theme="danger"
                      text="Yes"
                      onClick={() => {
                        resetNewMacro();
                        setShowAddMacro(false);
                        setShowClearConfirm(false);
                      }}
                    />
                    <Button
                      size="SM"
                      theme="light"
                      text="No"
                      onClick={() => setShowClearConfirm(false)}
                    />
                  </div>
                ) : (
                  <div className="flex gap-x-2">
                    <Button
                      size="SM"
                      theme="primary"
                      text={isSaving ? "Saving..." : "Save Macro"}
                      onClick={handleAddMacro}
                      disabled={isSaving}
                    />
                    <Button
                      size="SM"
                      theme="light"
                      text="Cancel"
                      onClick={() => {
                        if (newMacro.name || newMacro.description || newMacro.steps?.some(s => s.keys?.length || s.modifiers?.length)) {
                          setShowClearConfirm(true);
                        } else {
                          resetNewMacro();
                          setShowAddMacro(false);
                        }
                      }}
                    />
                  </div>
                )}
              </div>
            </div>
          </Card>
        )}
        {macros.length === 0 && !showAddMacro && (
          <EmptyCard
            headline="No macros created yet"
            BtnElm={
              <Button
                size="SM"
                theme="primary"
                text="Add New Macro"
                onClick={() => setShowAddMacro(true)}
                disabled={isMaxMacrosReached}
              />
            }
          />
        )}
        {macros.length > 0 && (
          <SortableList<KeySequence>
            keyFn={(macro) => macro.id}
            items={macros}
            itemClassName="rounded-md border border-slate-200 p-2 dark:border-slate-700 bg-white dark:bg-slate-800"
            onSort={async (newMacros) => {
              const updatedMacros = normalizeSortOrders(newMacros);
              try {
                await saveMacros(updatedMacros);
                notifications.success("Macro order updated successfully");
              } catch (error) {
                if (error instanceof Error) {
                  notifications.error(`Failed to reorder macros: ${error.message}`);
                  showTemporaryError(error.message);
                } else {
                  notifications.error("Failed to reorder macros");
                  showTemporaryError("Failed to save reordered macros");
                }
              }
            }}
            disabled={!!editingMacro}
            variant="list"
            size="XS"
            handlePosition="left"
          >
            {(macro) => (
              editingMacro && editingMacro.id === macro.id ? (
                <Card className="border-blue-300 bg-blue-50 p-3 dark:border-blue-700 dark:bg-blue-900/20">
                  <CardHeader
                    headline="Edit Macro"
                  />
                  <Fieldset className="mt-4">
                    <InputFieldWithLabel
                      type="text"
                      label="Macro Name"
                      placeholder="Macro Name"
                      value={editingMacro.name}
                      error={errors.name}
                      onChange={e => {
                        setEditingMacro({ ...editingMacro, name: e.target.value });
                        if (errors.name) {
                          const newErrors = { ...errors };
                          delete newErrors.name;
                          setErrors(newErrors);
                        }
                      }}
                    />
                  </Fieldset>
                  
                  <div className="mt-4">
                    <div className="flex items-center justify-between">
                      <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
                        Steps:
                      </label>
                      <span className="text-sm text-slate-500 dark:text-slate-400">
                        {editingMacro.steps.length}/{MAX_STEPS_PER_MACRO} steps
                      </span>
                    </div>
                    {errors.steps && errors.steps[0]?.keys && (
                      <div className="mt-2">
                        <ErrorMessage error={errors.steps[0].keys} />
                      </div>
                    )}
                    <div className="mt-2 text-xs text-slate-500 dark:text-slate-400">
                      You can add up to {MAX_STEPS_PER_MACRO} steps per macro
                    </div>
                    <Fieldset>
                      <div className="mt-2 space-y-4">
                        {editingMacro.steps.map((step, stepIndex) => (
                          <MacroStepCard
                            key={stepIndex}
                            step={step}
                            stepIndex={stepIndex}
                            onDelete={editingMacro.steps.length > 1 ? () => {
                              const newSteps = [...editingMacro.steps];
                              newSteps.splice(stepIndex, 1);
                              setEditingMacro({ ...editingMacro, steps: newSteps });
                            } : undefined}
                            onMoveUp={() => {
                              const newSteps = handleStepMove(stepIndex, 'up', editingMacro.steps);
                              setEditingMacro({ ...editingMacro, steps: newSteps });
                            }}
                            onMoveDown={() => {
                              const newSteps = handleStepMove(stepIndex, 'down', editingMacro.steps);
                              setEditingMacro({ ...editingMacro, steps: newSteps });
                            }}
                            isDesktop={isDesktop}
                            onKeySelect={(option) => handleEditKeySelect(stepIndex, option)}
                            onKeyQueryChange={(query) => handleEditKeyQueryChange(stepIndex, query)}
                            keyQuery={editKeyQueries[stepIndex] || ''}
                            getFilteredKeys={() => getFilteredKeys(stepIndex, true)}
                            onModifierChange={(modifiers) => handleModifierChange(stepIndex, modifiers)}
                            onDelayChange={(delay) => handleDelayChange(stepIndex, delay)}
                            isLastStep={stepIndex === editingMacro.steps.length - 1}
                          />
                        ))}
                      </div>
                    </Fieldset>
                    
                    <div className="mt-4 border-t border-slate-200 pt-4 dark:border-slate-700">
                      <Button
                        size="MD"
                        theme="light"
                        fullWidth
                        LeadingIcon={LuPlus}
                        text={`Add Step ${editingMacro.steps.length >= MAX_STEPS_PER_MACRO ? `(${MAX_STEPS_PER_MACRO} max)` : ''}`}
                        onClick={() => {
                          if (editingMacro.steps.length >= MAX_STEPS_PER_MACRO) {
                            showTemporaryError(`You can only add a maximum of ${MAX_STEPS_PER_MACRO} steps per macro.`);
                            return;
                          }
                          
                          setEditingMacro({
                            ...editingMacro,
                            steps: [
                              ...editingMacro.steps, 
                              { keys: [], modifiers: [], delay: DEFAULT_DELAY }
                            ],
                          });
                          clearErrors();
                        }}
                        disabled={editingMacro.steps.length >= MAX_STEPS_PER_MACRO}
                      />
                    </div>

                    <div className="mt-4 flex items-center justify-between border-t border-slate-200 pt-4 dark:border-slate-700">
                      <div className="flex gap-x-2">
                        <Button
                          size="SM"
                          theme="primary"
                          text={isUpdating ? "Saving..." : "Save Changes"}
                          onClick={handleUpdateMacro}
                          disabled={isUpdating}
                        />
                        <Button
                          size="SM"
                          theme="light"
                          text="Cancel"
                          onClick={() => {
                            setEditingMacro(null);
                            setErrors({});
                          }}
                        />
                      </div>
                    </div>
                  </div>
                </Card>
              ) : (
                <div className="flex items-center justify-between">
                  <div className="flex-1 min-w-0 flex flex-col justify-center">
                    <h3 className="truncate text-sm font-semibold text-black dark:text-white">
                      {macro.name}
                    </h3>
                    <p className="mt-1 ml-2 text-xs text-slate-500 dark:text-slate-400 overflow-hidden">
                      <span className="flex flex-col items-start gap-1">
                        {macro.steps.map((step, stepIndex) => {
                          const StepIcon = stepIndex === 0 ? LuMoveRight : LuCornerDownRight;

                          return (
                            <span key={stepIndex} className="inline-flex items-center">
                              <StepIcon className="mr-1 text-slate-400 dark:text-slate-500 h-3 w-3 flex-shrink-0" />
                              <span className="bg-slate-50 dark:bg-slate-800 px-2 py-0.5 rounded-md border border-slate-200/50 dark:border-slate-700/50">
                                {(ensureArray(step.modifiers).length > 0 || ensureArray(step.keys).length > 0) ? (
                                  <>
                                    {ensureArray(step.modifiers).map((modifier, idx) => (
                                      <Fragment key={`mod-${idx}`}>
                                        <span className="font-medium text-slate-600 dark:text-slate-200">
                                          {modifierDisplayMap[modifier] || modifier}
                                        </span>
                                        {idx < ensureArray(step.modifiers).length - 1 && (
                                          <span className="text-slate-400 dark:text-slate-600"> + </span>
                                        )}
                                      </Fragment>
                                    ))}
                                    
                                    {ensureArray(step.modifiers).length > 0 && ensureArray(step.keys).length > 0 && (
                                      <span className="text-slate-400 dark:text-slate-600"> + </span>
                                    )}
                                    
                                    {ensureArray(step.keys).map((key, idx) => (
                                      <Fragment key={`key-${idx}`}>
                                        <span className="font-medium text-blue-600 dark:text-blue-200">
                                          {keyDisplayMap[key] || key}
                                        </span>
                                        {idx < ensureArray(step.keys).length - 1 && (
                                          <span className="text-slate-400 dark:text-slate-600"> + </span>
                                        )}
                                      </Fragment>
                                    ))}
                                  </>
                                ) : (
                                  <span className="font-medium text-slate-500 dark:text-slate-400">Delay only</span>
                                )}
                                <span className="ml-1 text-slate-400 dark:text-slate-500">({step.delay}ms)</span>
                              </span>
                            </span>
                          );
                        })}
                      </span>
                    </p>
                  </div>
                  
                  <div className="flex items-center gap-1 ml-4">
                    {macroToDelete === macro.id ? (
                      <div className="flex items-center gap-2">
                        <span className="text-sm text-slate-600 dark:text-slate-400">
                          Delete macro?
                        </span>
                        <div className="flex items-center gap-x-2">
                          <Button
                            size="XS"
                            theme="danger"
                            text="Yes"
                            onClick={() => {
                              handleDeleteMacro(macro.id);
                            }}
                            disabled={isDeleting}
                          />
                          <Button
                            size="XS"
                            theme="light"
                            text="No"
                            onClick={() => setMacroToDelete(null)}
                          />
                        </div>
                      </div>
                    ) : (
                      <>
                        <Button
                          size="XS"
                          theme="light"
                          LeadingIcon={LuPenLine}
                          onClick={() => handleEditMacro(macro)}
                        />
                        <Button
                          size="XS"
                          theme="light"
                          LeadingIcon={LuCopy}
                          onClick={() => handleDuplicateMacro(macro)}
                        />
                        <Button
                          size="XS"
                          theme="light"
                          LeadingIcon={LuTrash}
                          onClick={() => setMacroToDelete(macro.id)}
                          className="text-red-500 dark:text-red-400"
                        />
                      </>
                    )}
                  </div>
                </div>
              )
            )}
          </SortableList>
        )}
      </div>
    </div>
  );
}
