struct Transvection <: GSymbol
    id::Symbol
    i::UInt16
    j::UInt16
    inv::Bool

    function Transvection(id::Symbol, i::Integer, j::Integer, inv = false)
        @assert id in (:ϱ, :λ)
        return new(id, i, j, inv)
    end
end

ϱ(i, j) = Transvection(:ϱ, i, j)
λ(i, j) = Transvection(:λ, i, j)

function Base.show(io::IO, t::Transvection)
    id = if t.id === :ϱ
        'ϱ'
    else # if t.id === :λ
        'λ'
    end
    print(io, id, subscriptify(t.i), '.', subscriptify(t.j))
    t.inv && print(io, "^-1")
end

Base.inv(t::Transvection) = Transvection(t.id, t.i, t.j, !t.inv)

Base.:(==)(t::Transvection, s::Transvection) =
    t.id === s.id && t.i == s.i && t.j == s.j && t.inv == s.inv

Base.hash(t::Transvection, h::UInt) = hash(hash(t.id, hash(t.i)), hash(t.j, hash(t.inv, h)))

Base.@propagate_inbounds @inline function evaluate!(
    v::NTuple{T,N},
    t::Transvection,
    tmp = one(first(v)),
) where {T,N}
    i, j = t.i, t.j
    @assert 1 ≤ i ≤ length(v) && 1 ≤ j ≤ length(v)

    A = alphabet(parent(first(v)))

    @inbounds begin
        if t.id === :ϱ
            if !t.inv
                append!(word(v[i]), word(v[j]))
            else
                # append!(word(v[i]), inv(A, word(v[j])))
                for l in Iterators.reverse(word(v[j]))
                    push!(word(v[i]), inv(A, l))
                end
            end
        else # if t.id === :λ
            if !t.inv
                # prepend!(word(v[i]), word(v[j]))
                for l in Iterators.reverse(word(v[j]))
                    pushfirst!(word(v[i]), l)
                end
            else
                # prepend!(word(v[i]), inv(A, word(v[j])))
                for l in word(v[j])
                    pushfirst!(word(v[i]), inv(A, l))
                end
            end
        end

        _setnormalform!(v[i], false)
        _setvalidhash!(v[i], false)
    end
    normalform!(tmp, v[i])
    copyto!(v[i], tmp)

    return v
end

struct PermRightAut <: GSymbol
    perm::Vector{UInt8}

    function PermRightAut(p::AbstractVector{<:Integer})
        @assert sort(p) == 1:length(p)
        return new(p)
    end
end

function Base.show(io::IO, p::PermRightAut)
    print(io, 'σ')
    join(io, (subscriptify(Int(i)) for i in p.perm))
end

Base.inv(p::PermRightAut) = PermRightAut(invperm(p.perm))

Base.:(==)(p::PermRightAut, q::PermRightAut) = p.perm == q.perm
Base.hash(p::PermRightAut, h::UInt) = hash(p.perm, hash(PermRightAut, h))

evaluate!(v::NTuple{T,N}, p::PermRightAut, tmp = nothing) where {T,N} = v[p.perm]
