"""
    $TYPEDEF

A generalization of the circulant bivariate bicycle code introduced in [bravyi2024high](@cite),
extending the original three-term polynomial representation up to `l` terms (for `x`-powers) and
`m` terms (for `y`-powers).

## Bivariate Bicycle Codes

Bivariate bicycle codes are a class of group algebra codes defined for the finite
abelian group ``G = \\mathbb{Z}/\\ell\\mathbb{Z} \\times \\mathbb{Z}/m\\mathbb{Z}``. The
group algebra ``\\mathbb{F}_2[G]`` admits an identification with the quotient ring
``R = \\mathbb{F}_2[x,y]/(x^\\ell - 1, y^m - 1)``, where the standard basis consists of monomials
``{x^iy^j}`` for ``0 \\leq i < \\ell`` and ``0 \\leq j < m`` [eberhardt2024logical](@cite).
Within this framework, every pair of elements ``c,d \\in R`` determines a BB code ``(C(c,d)``
through the group algebra construction.

### Circulant Matrix Representation

BB codes admit a matrix representation via the [injective](https://en.wikipedia.org/wiki/Injective_function)
[ring homomorphism](https://en.wikipedia.org/wiki/Ring_homomorphism) [eberhardt2024logical](@cite):

```math
\\begin{aligned}
\\varphi: R = \\mathbb{F}_2[x,y]/(x^\\ell - 1, y^m - 1) \\rightarrow \\mathbb{F}_2^{\\ell m \\times \\ell m}
\\end{aligned}
```

where ``\\varphi(x) = S_\\ell \\otimes I_m`` and ``\\varphi(y) = I_\\ell \\otimes S_m`` provides
matrix representations of the algebraic generators. Through this mapping, any code-defining polynomials
``c,d \\in R`` correspond to parity-check matrices ``A = \\varphi(c)`` and ``B = \\varphi(d)`` composed
of tensor products of circulant blocks.

### Three-term Polynomials

The original construction in [bravyi2024high](@cite) constrained the polynomials to specific forms:

```math
\\begin{aligned}
a(x, y) = x^a + y^b + y^c, \\quad b(x, y) = y^d + x^e + x^f
\\end{aligned}
```

Thus, ``A`` and ``B`` can be expressed as ``A = A_1 + A_2 + A_3`` and ``B = B_1 + B_2 + B_3``. Consequently,
each stabilizer generator has weight at most 6 for any valid three-term polynomials [postema2025existencecharacterisationbivariatebicycle](@cite).

### Generalized Polynomials

We extend the original three-term construction of [bravyi2024high](@cite) to arbitrary finite sums
of commuting monomials in ``x`` and ``y``:

```math
\\begin{aligned}
A = \\sum_{i \\in I} x^{p_i} + \\sum_{j \\in J} y^{q_j}, \\quad B = \\sum_{k \\in K} x^{r_k} + \\sum_{l \\in L} y^{s_l}
\\end{aligned}
```

where index sets ``I, J, K, L`` are finite and non-empty, with powers bounded by:

```math
\\begin{aligned}
0 \\leq p_i, r_k \\leq \\ell-1, \\quad 0 \\leq q_j, s_l \\leq m-1
\\end{aligned}
```

## Commuting Shift Matrices

The matrices ``x = S_\\ell \\otimes I_m`` and ``y = I_\\ell \\otimes S_m`` form a
pair of commuting operators, where ``S_\\ell`` and ``S_m`` are cyclic shift matrices
of sizes ``\\ell \\times \\ell`` and ``m \\times m`` respectively, and ``I_\\ell``,
``I_m`` denote identity matrices of corresponding dimensions. Their commutativity
``xy = yx`` follows from the *mixed-product property* of Kronecker products [wang2024coprime](@cite):

```math
\\begin{aligned}
(A \\otimes B)(C \\otimes D) = (AC) \\otimes (BD)
\\end{aligned}
```

which yields:

```math
\\begin{aligned}
(S_\\ell \\otimes I_m)(I_\\ell \\otimes S_m) = (I_\\ell \\otimes S_m)(S_\\ell \\otimes I_m) = S_\\ell \\otimes S_m
\\end{aligned}
```

The set of monomials ``\\{x^i y^j \\mid 0 \\leq i < \\ell, 0 \\leq j < m}`` forms a basis for the matrix
algebra generated by ``x`` and ``y``, establishing a [bijection](https://en.wikipedia.org/wiki/Bijection)
between these monomials and the resulting ``(\\ell m) \\times (\\ell m)`` matrices. This correspondence
allows us to work equivalently with either the polynomial or matrix formulation [wang2024coprime](@cite).

## Parity Checks

The commuting polynomial matrices ``A = a(x, y)`` and ``B = b(x, y)`` define the code’s parity checks:

```math
\\begin{aligned}
H_x = [A \\mid B], \\quad H_z = [B^\\top \\mid A^\\top]
\\end{aligned}
```

The addition and multiplication on binary matrices are performed modulo 2. Both ``H_x``
and ``H_z`` are ``(n/2)×n`` matrices.

## Circulant Bivariate Bicycle Code

The BB code is represented by matrices `A` and `B`, defined as: ``A = A_1 + A_2 + A_3``
and ``B = B_1 + B_2 + B_3``.

!!! note
    Both A and B are matrices in which each row and column contains exactly three non-zero
    entries when using a 3-term polynomial representation [bravyi2024high](@cite).

The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/qcga).

### Example

Here is `[[756, 16, ≤ 34]]` circulant bivariate bicycle code from Table 1 of [bravyi2024high](@cite)
with polynomials ``A = x^3 + y^10 + y^17`` and ``B = y^5 + x^3 + x^19``.

```jldoctest
julia> using QuantumClifford; using QuantumClifford.ECC; # hide

julia> l, m = 21, 18;

julia> A = [(:x,3), (:y,10), (:y,17)];

julia> B = [(:y,5), (:x,3), (:x,19)];

julia> c = BivariateBicycleViaCirculantMat(l, m, A, B);

julia> code_n(c), code_k(c)
(756, 16)
```

## Generalized Circulant Bivariate Bicycle Code

Here is an example of `[[128, 14, 12]]` generalized circulant bivariate bicycle code
that uses 4-term polynomials ``c = x^2 + y + y^3 + y^4`` and ``d = y^2 + x + x^3 + x^4``
with group orders ``l, m = 8, 8`` from [eberhardt2024logical](@cite).

```jldoctest
julia> using QuantumClifford; using QuantumClifford.ECC; # hide

julia> l, m = 8, 8;

julia> A = [(:x,2), (:y,1), (:y,3), (:y,4)];

julia> B = [(:y,2), (:x,1), (:x,3), (:x,4)];

julia> c = BivariateBicycleViaCirculantMat(l, m, A, B);

julia> code_n(c), code_k(c)
(128, 14)
```

Here is `[[18, 4, 4]]` generalized circulant bivariate bicycle code
that uses 3-term polynomials ``a = 1 + x + y`` and ``b = 1 + x^2 + y^2``
with group orders ``l, m = 3, 3`` from Table III of [wang2024coprime](@cite).

!!! note
    We can also employ 3-term polynomials of a different form than those proposed in [bravyi2024high](@cite).

```jldoctest
julia> using QuantumClifford; using QuantumClifford.ECC;

julia> l, m = 3, 3;

julia> A = [(:x, 0), (:x, 1), (:y, 1)];

julia> B = [(:y, 0), (:x, 2), (:y, 2)];

julia> c = BivariateBicycleViaCirculantMat(l, m, A, B);

julia> code_n(c), code_k(c)
(18, 4)
```

!!! note
    The Bivariate Bicycle code ``\\mathrm{QC}(A,B)`` is a specific instance of the
    *Lifted Product* construction, where the underlying group is the direct product
    ``\\mathbb{Z}_\\ell \\times \\mathbb{Z}_m`` (with ``\\mathbb{Z}_j`` denoting the
    cyclic group of order ``j``).

Here is `[[36, 4, 6]]` generalized circulant bivariate bicycle code
that uses 3-term polynomials ``a = x + y^2 + y^3`` and ``b = 1 + y + x^2``
with group orders ``l, m = 3, 6`` from Table III of [wang2024coprime](@cite).

```jldoctest
julia> using QuantumClifford; using QuantumClifford.ECC;

julia> import Hecke: group_algebra, GF, abelian_group, gens;

julia> l, m = 3, 6;

julia> GA = group_algebra(GF(2), abelian_group([l, m]));

julia> x, y = gens(GA);

julia> A = x + y^2 + y^3;

julia> B = 1 + y + x^2;

julia> c = two_block_group_algebra_code(A,B);

julia> code_n(c), code_k(c)
(36, 4)
```

### Fields
    $TYPEDFIELDS
"""
struct BivariateBicycleViaCirculantMat <: AbstractCSSCode
    """Dimension of cyclic shift matrix `Sₗ` where `x = Sₗ ⊗ Iₘ`"""
    l::Int
    """ Dimension of cyclic shift matrix `Sₘ` where `y = Iₗ ⊗ Sₘ`"""
    m::Int
    """Terms in matrix A, where each tuple is (:x or :y, power)"""
    A::Vector{Tuple{Symbol,Int}}
    """Terms in matrix B, where each tuple is (:x or :y, power)"""
    B::Vector{Tuple{Symbol,Int}}

    function BivariateBicycleViaCirculantMat(l, m, A, B)
        (l >= 0 && m >= 0) || throw(ArgumentError("l and m must be non-negative"))
        (length(A) >= 1 && length(B) >= 1) || throw(ArgumentError("A and B must each have at least one entry"))
        for (mat, terms) in [(:A, A), (:B, B)]
            for (var, pow) in terms
                var ∈ [:x, :y] || throw(ArgumentError("Matrix $mat contains invalid variable $var (must be :x or :y)"))
                pow >= 0 || throw(ArgumentError("Matrix $mat contains negative power $pow"))
                max_pow = var == :x ? l : m
                pow <= max_pow || throw(ArgumentError("Power $pow in matrix $mat exceeds maximum $max_pow for $var"))
            end
        end
        new(l, m, A, B)
    end
end

function parity_matrix_xz(c::BivariateBicycleViaCirculantMat)
    Iₗ = Matrix{Bool}(I, c.l, c.l)
    Iₘ = Matrix{Bool}(I, c.m, c.m)
    xₚ = Dict(i => kron(circshift(Iₗ, (0,i)), Iₘ) for i in 0:c.l)
    yₚ = Dict(i => kron(Iₗ, circshift(Iₘ, (0,i))) for i in 0:c.m)
    A = zeros(Bool, c.l*c.m, c.l*c.m)
    for (var, pow) in c.A
        mat = var == :x ? xₚ[pow] : yₚ[pow]
        A .+= mat
    end
    A = mod.(A, 2)
    B = zeros(Bool, c.l*c.m, c.l*c.m)
    for (var, pow) in c.B
        mat = var == :x ? xₚ[pow] : yₚ[pow]
        B .+= mat
    end
    B = mod.(B, 2)
    Hx = hcat(A, B)
    Hz = hcat(B', A')
    return Hx, Hz
end

code_n(c::BivariateBicycleViaCirculantMat) = 2*c.l*c.m

parity_matrix_x(c::BivariateBicycleViaCirculantMat) = parity_matrix_xz(c)[1]

parity_matrix_z(c::BivariateBicycleViaCirculantMat) = parity_matrix_xz(c)[2]
