<?php

declare(strict_types=1);

namespace App\Framework\QrCode\Structure;

use App\Framework\QrCode\ValueObjects\ErrorCorrectionLevel;
use App\Framework\QrCode\ValueObjects\Module;
use App\Framework\QrCode\ValueObjects\ModulePosition;
use App\Framework\QrCode\ValueObjects\QrCodeMatrix;

/**
 * QR Code Format Information
 *
 * Encodes error correction level and mask pattern into 15-bit format string
 * Uses BCH(15,5) error correction
 */
final readonly class FormatInformation
{
    // Format information with error correction for each EC level and mask pattern
    // Pre-calculated to avoid BCH encoding at runtime
    private const FORMAT_INFO_TABLE = [
        'L' => [
            0 => 0b111011111000100,  // Level L, Mask 0
            1 => 0b111001011110011,
            2 => 0b111110110101010,
            3 => 0b111100010011101,
            4 => 0b110011000101111,
            5 => 0b110001100011000,
            6 => 0b110110001000001,
            7 => 0b110100101110110,
        ],
        'M' => [
            0 => 0b101010000010010,  // Level M, Mask 0
            1 => 0b101000100100101,
            2 => 0b101111001111100,
            3 => 0b101101101001011,
            4 => 0b100010111111001,
            5 => 0b100000011001110,
            6 => 0b100111110010111,
            7 => 0b100101010100000,
        ],
        'Q' => [
            0 => 0b011010101011111,  // Level Q, Mask 0
            1 => 0b011000001101000,
            2 => 0b011111100110001,
            3 => 0b011101000000110,
            4 => 0b010010010110100,
            5 => 0b010000110000011,
            6 => 0b010111011011010,
            7 => 0b010101111101101,
        ],
        'H' => [
            0 => 0b001011010001001,  // Level H, Mask 0
            1 => 0b001001110111110,
            2 => 0b001110011100111,
            3 => 0b001100111010000,
            4 => 0b000011101100010,
            5 => 0b000001001010101,
            6 => 0b000110100001100,
            7 => 0b000100000111011,
        ],
    ];

    /**
     * Apply format information to matrix
     */
    public static function apply(
        QrCodeMatrix $matrix,
        ErrorCorrectionLevel $level,
        int $maskPattern
    ): QrCodeMatrix {
        $formatBits = self::getFormatBits($level, $maskPattern);

        // Apply format information in two locations
        $matrix = self::applyFormatBitsHorizontal($matrix, $formatBits);
        $matrix = self::applyFormatBitsVertical($matrix, $formatBits);

        return $matrix;
    }

    /**
     * Get 15-bit format information
     */
    private static function getFormatBits(ErrorCorrectionLevel $level, int $maskPattern): int
    {
        $levelValue = $level->value;

        if (!isset(self::FORMAT_INFO_TABLE[$levelValue][$maskPattern])) {
            throw new \InvalidArgumentException(
                "Invalid EC level ({$levelValue}) or mask pattern ({$maskPattern})"
            );
        }

        return self::FORMAT_INFO_TABLE[$levelValue][$maskPattern];
    }

    /**
     * Apply format bits horizontally (top-left to top-right)
     *
     * ISO/IEC 18004:2015 Section 7.9
     * Bits are placed sequentially (0-14), but the COLUMN ORDER creates
     * the appearance of bit swaps when read left-to-right
     */
    private static function applyFormatBitsHorizontal(QrCodeMatrix $matrix, int $formatBits): QrCodeMatrix
    {
        $size = $matrix->getSize();

        // Horizontal placement columns (size-dependent, non-sequential due to timing separator)
        // First 8 bits: columns 0-5, 7-8 (skip timing column 6)
        // Last 7 bits: columns from right edge backwards (size-1 down to size-7)
        $columns = [
            0, 1, 2, 3, 4, 5, 7, 8,  // First 8 bits (left side)
            $size - 1, $size - 2, $size - 3, $size - 4, $size - 5, $size - 6, $size - 7  // Last 7 bits (right side)
        ];

        // Place bits SEQUENTIALLY - the column order creates the swap effect
        for ($i = 0; $i < 15; $i++) {
            $bit = ($formatBits >> (14 - $i)) & 1;
            $module = $bit === 1 ? Module::dark() : Module::light();
            $matrix = $matrix->setModuleAt(8, $columns[$i], $module);
        }

        return $matrix;
    }

    /**
     * Apply format bits vertically (top-left to bottom-left)
     */
    private static function applyFormatBitsVertical(QrCodeMatrix $matrix, int $formatBits): QrCodeMatrix
    {
        $size = $matrix->getSize();

        // Format bits are placed MSB-first

        // Bits 0-6: Column 8, rows from bottom (MSB first)
        for ($i = 0; $i < 7; $i++) {
            $bit = ($formatBits >> (14 - $i)) & 1;
            $module = $bit === 1 ? Module::dark() : Module::light();
            $matrix = $matrix->setModuleAt($size - 1 - $i, 8, $module);
        }

        // Bits 7-14: Column 8, rows 8 down to 0 (skip row 6 - timing)
        // Rows 8, 7, 5, 4, 3, 2, 1, 0 (skip 6)
        $rows = [8, 7, 5, 4, 3, 2, 1, 0];
        for ($i = 0; $i < 8; $i++) {
            $bit = ($formatBits >> (14 - (7 + $i))) & 1;
            $module = $bit === 1 ? Module::dark() : Module::light();
            $matrix = $matrix->setModuleAt($rows[$i], 8, $module);
        }

        return $matrix;
    }
}
