class SuggesterPhraseQuery extends Query {

    private enum SuggestPosition {
        START, MIDDLE, END;

        private static SuggestPosition from(final int pos, final int size) {
            if (pos == 0) {
                return SuggestPosition.START;
            } else if (pos == size - 1) {
                return SuggestPosition.END;
            } else {
                return SuggestPosition.MIDDLE;
            }
        }
    }

    private final CustomPhraseQuery phraseQuery;

    private final SuggesterQuery suggesterQuery;

    /**
     * @param field term field
     * @param identifier unique String which identifies the token for which the suggestions should be made
     * @param tokens all the tokens of the phrase query
     * @param slop word Levenshtein's distance
     */
    public SuggesterPhraseQuery(
            final String field,
            final String identifier,
            final List<String> tokens,
            final int slop
    ) {
        String prefix = null;
        int pos = -1;
        int i = 0;

        List<Integer> positions = new ArrayList<>();
        List<Term> terms = new ArrayList<>();

        for (String token : tokens) {
            if (token.contains(identifier)) {
                prefix = token.replace(identifier, "");
                pos = i;
            } else {
                positions.add(i);
                terms.add(new Term(field, token));
            }
            i++;
        }

        if (pos < 0 || pos > tokens.size()) {
            throw new IllegalStateException();
        }

        SuggestPosition p = SuggestPosition.from(pos, tokens.size());

        if (p == SuggestPosition.START) {
            phraseQuery = new CustomPhraseQuery(slop, field, tokens.stream().filter(t -> !t.contains(identifier)).toArray(String[]::new));
            phraseQuery.setOffset(-1);
        } else if (p == SuggestPosition.MIDDLE) {
            phraseQuery = new CustomPhraseQuery(slop, terms.toArray(new Term[0]), positions.stream().mapToInt(in -> in).toArray());
            phraseQuery.setOffset(pos);
        } else {
            phraseQuery = new CustomPhraseQuery(slop, field, tokens.stream().filter(t -> !t.contains(identifier)).toArray(String[]::new));
            phraseQuery.setOffset(tokens.size() - 1);
        }

        suggesterQuery = new SuggesterPrefixQuery(new Term(field, prefix));
    }

    public SuggesterQuery getSuggesterQuery() {
         return suggesterQuery;
    }

    public CustomPhraseQuery getPhraseQuery() {
        return phraseQuery;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        SuggesterPhraseQuery that = (SuggesterPhraseQuery) o;
        return Objects.equals(phraseQuery, that.phraseQuery) &&
                Objects.equals(suggesterQuery, that.suggesterQuery);
    }

    @Override
    public int hashCode() {
        return Objects.hash(phraseQuery, suggesterQuery);
    }

    @Override
    public String toString(final String field) {<FILL_FUNCTION_BODY>}

    @Override
    public Query rewrite(IndexSearcher indexSearcher) {
        return phraseQuery.rewrite(indexSearcher);
    }

    @Override
    public void visit(QueryVisitor visitor) {
        visitor.visitLeaf(this);
    }

}

        return "SuggesterPhraseQuery{" +
                "phraseQuery=" + phraseQuery +
                ", suggesterQuery=" + suggesterQuery +
                '}';
    
<no_super_class>