import {useUnit} from "effector-react";
import {useEffect, useRef} from "react";

import {SymbolInput} from "../symbol-input/index.jsx";
import * as styles from './word-input.module.css';
import {$userGuess} from "@/features/word-input/model/store.js";
import {revealSymbol, setWord} from "@/features/word-input/model/events.js";

export const WordInput = ({word}) => {
  const userGuess = useUnit($userGuess);
  const reveal = useUnit(revealSymbol);

  const inputRefs = useRef([]);

  const focusNextUnrevealed = (currentIdx, step) => {
    for(let i = currentIdx + step; i < userGuess.length && i > 0; i += step) {
      if(userGuess[i] && !userGuess[i].isRevealed && inputRefs.current[i]) {
        inputRefs.current[i].focus();
        return
      }
    }

    requestAnimationFrame(() => {
      inputRefs.current[currentIdx].focus();
    })
  }

  const focusNext = (currentIdx) => {
    focusNextUnrevealed(currentIdx, 1);
  }

  const focusPrev = (currentIdx) => {
    focusNextUnrevealed(currentIdx, -1);
  }

  useEffect(() => {
    setWord(word);
  }, [word]);

  useEffect(() => {
    inputRefs.current[0]?.focus();
    userGuess.forEach(item => item.idx % 2 === 0 && reveal(item.idx));
  }, [userGuess.length]); import React, {forwardRef, useState} from 'react';

import * as styles from './symbol-input.module.css';
import {useUnit} from "effector-react/compat";
import {setSymbol} from "@/features/word-input/model/events.js";
import {TEXT_REGEX} from "@/shared/constants/index.js";

const CSS_CLASS_OPENED = styles['symbol-opened']

export const SymbolInput = forwardRef((
  {idx, focusNext, focusPrev, value, isRevealed = false},
   ref
) => {
  const onSetSymbol = useUnit(setSymbol);
  const symbolClasses = `${styles.symbol} ${isRevealed ? CSS_CLASS_OPENED : ''}`;

  const handleInput = (key) => {
    const formattedKey = key.toLowerCase();

    if(TEXT_REGEX.test(formattedKey) && formattedKey.length === 1) {
      onSetSymbol({value: formattedKey, idx});
      focusNext(idx);
    }
  };

  const handleKeyDown = (e) => {
    switch (e.key) {
      case 'ArrowLeft': focusPrev(idx); break;
      case 'ArrowRight': focusNext(idx); break;
      case 'Backspace':
        e.preventDefault();
        setSymbol({value: '', idx});
        focusPrev(idx);
        break;
      default: handleInput(e.key); break;
    }
  };

  return (
    <input
      ref={ref}
      type={'text'}
      className={symbolClasses}
      disabled={isRevealed}
      maxLength={1}
      value={value}
      onKeyDown={handleKeyDown}
    >
    </input>
  );
}) import React, {forwardRef, useState} from 'react';

import * as styles from './symbol-input.module.css';
import {useUnit} from "effector-react/compat";
import {setSymbol} from "@/features/word-input/model/events.js";
import {TEXT_REGEX} from "@/shared/constants/index.js";

const CSS_CLASS_OPENED = styles['symbol-opened']

export const SymbolInput = forwardRef((
  {idx, focusNext, focusPrev, value, isRevealed = false},
   ref
) => {
  const onSetSymbol = useUnit(setSymbol);
  const symbolClasses = `${styles.symbol} ${isRevealed ? CSS_CLASS_OPENED : ''}`;

  const handleInput = (key) => {
    const formattedKey = key.toLowerCase();

    if(TEXT_REGEX.test(formattedKey) && formattedKey.length === 1) {
      onSetSymbol({value: formattedKey, idx});
      focusNext(idx);
    }
  };

  const handleKeyDown = (e) => {
    switch (e.key) {
      case 'ArrowLeft': focusPrev(idx); break;
      case 'ArrowRight': focusNext(idx); break;
      case 'Backspace':
        e.preventDefault();
        setSymbol({value: '', idx});
        focusPrev(idx);
        break;
      default: handleInput(e.key); break;
    }
  };

  return (
    <input
      ref={ref}
      type={'text'}
      className={symbolClasses}
      disabled={isRevealed}
      maxLength={1}
      value={value}
      onKeyDown={handleKeyDown}
    >
    </input>
  );
}) 
  return (
    <div className={styles.wordDisplay}>
      { userGuess.map(({idx, value, isRevealed}) =>
        <SymbolInput
          key={`${value}-${idx}`}
          ref={el => inputRefs.current[idx] = el}
          idx={idx}
          value={value}
          isRevealed={isRevealed}
          focusNext={focusNext}
          focusPrev={focusPrev}
        />
      )}
    </div>
  );
}; import {createStore} from "effector";

import {$currentWord} from "@/features/game/model/store.js";
import {initializeUserGuess, revealRandomSymbol, revealSymbol, setSymbol} from "@/features/word-input/model/events.js";

export const $inputWord = createStore('');
export const $userGuess = createStore([]);

export const $userGuessString = $userGuess.map(guesses =>
  guesses.map(({ value }) => value).join('')
);

$userGuess.on(initializeUserGuess, (_, length) =>
  Array.from({length}).map((_, idx) => ({
      idx,
      value: '',
      isRevealed: false
    }))
);

$userGuess.on(setSymbol, (state, { value, idx }) =>
  state.map((item) => item.idx === idx ? { ...item, value } : item)
);

$userGuess.on(revealSymbol, (state, idx ) => {
  const currentWord = $currentWord.getState();

  return state.map(item =>
    item.idx === idx
      ? {...item, isRevealed: true, value: currentWord[item.idx] }
      : item
  );
});

$userGuess.on(revealRandomSymbol, (state) => {
  const unrevealedSymbols = state.filter(item => !item.isRevealed);

  if (!unrevealedSymbols.length) return;

  const currentWord = $currentWord.getState();
  const randomIdx = Math.floor(Math.random() * unrevealedSymbols.length);

  return state.map(item =>
      item.idx === unrevealedSymbols[randomIdx].idx
        ? {...item, isRevealed: true, value: currentWord[item.idx] }
        : item
    );
}); что тут стои вынести в отдельные entites