/**
 * QR Code component for displaying scannable QR codes in the terminal.
 *
 * Uses the lower-half-block character (▄) exclusively for rendering. The top
 * half of each cell is controlled via backgroundColor and the bottom half via
 * the foreground color. This avoids the sub-pixel seams that occur when mixing
 * different Unicode block characters (█, ▀, ▄, space) across adjacent rows.
 */

import React, { useMemo } from 'react';
import { Box, Text } from 'ink';
import QRCodeLib from 'qrcode';
import { DialogWrapper } from './Dialog.js';
import { colors } from '../theme.js';

/** Color used for light (background) QR modules. */
const LIGHT = 'white';

/** Color used for dark (data) QR modules. Must match the dialog/terminal bg. */
const DARK = colors.bg as string;

/** Default quiet zone size in modules (QR spec recommends 4, 2 is usually sufficient). */
const QUIET_ZONE = 2;

/**
 * A run of consecutive characters in a rendered QR row that share the
 * same foreground/background color pair.
 */
interface ColorSpan {
  /** The repeated ▄ characters for this span. */
  chars: string;
  /** Foreground color (controls the bottom half of each cell). */
  fg: string;
  /** Background color (controls the top half of each cell). */
  bg: string;
}

/**
 * Props for the QRCode component.
 */
interface QRCodeProps {
  /** The data to encode in the QR code. */
  value: string;
  /** Whether to wrap the QR code in a DialogWrapper. */
  dialog?: boolean;
  /** Dialog title (only used when dialog is true). Defaults to "QR Code". */
  dialogTitle?: string;
  /** Whether to display the raw encoded value as copyable text above the QR code. */
  showValue?: boolean;
  /** Optional subtitle to display below the QR code. */
  subtitle?: React.ReactNode;
}

/**
 * Generates the QR code module matrix with a quiet zone border.
 *
 * @param value - The string to encode.
 * @param quietZone - Number of light-module rows/columns to add around the QR data.
 * @returns A 2D array where `true` means dark module and `false` means light module.
 */
function generateMatrix(value: string, quietZone: number = QUIET_ZONE): boolean[][] {
  const qr = QRCodeLib.create(value, { errorCorrectionLevel: 'M' });
  const { size, data } = qr.modules;
  const totalSize = size + quietZone * 2;

  const matrix: boolean[][] = [];

  for (let row = 0; row < totalSize; row++) {
    const matrixRow: boolean[] = [];

    for (let col = 0; col < totalSize; col++) {
      const qrRow = row - quietZone;
      const qrCol = col - quietZone;
      const insideData = qrRow >= 0 && qrRow < size && qrCol >= 0 && qrCol < size;

      // Quiet zone modules are always light (false).
      matrixRow.push(insideData ? data[qrRow * size + qrCol] === 1 : false);
    }

    matrix.push(matrixRow);
  }

  return matrix;
}

/**
 * Converts a pair of module rows into an array of {@link ColorSpan}s.
 *
 * Every cell uses the `▄` (lower half block) character. The foreground color
 * paints the bottom half and the backgroundColor paints the top half, giving
 * us artifact-free rendering with a single glyph.
 *
 * Consecutive cells that share the same color pair are merged into one span
 * to keep the element count low.
 *
 * @param matrix - The full module matrix.
 * @param row - The index of the top row in the pair (the bottom row is row + 1).
 * @returns An array of color spans for this terminal line.
 */
function buildRowSpans(matrix: boolean[][], row: number): ColorSpan[] {
  const width = matrix[0]?.length ?? 0;
  const spans: ColorSpan[] = [];

  for (let col = 0; col < width; col++) {
    const topDark = matrix[row]?.[col] ?? false;
    const bottomDark = matrix[row + 1]?.[col] ?? false;

    // ▄ lower-half block: foreground = bottom color, backgroundColor = top color
    const fg = bottomDark ? DARK : LIGHT;
    const bg = topDark ? DARK : LIGHT;

    const last = spans[spans.length - 1];
    if (last && last.fg === fg && last.bg === bg) {
      last.chars += '▄';
    } else {
      spans.push({ chars: '▄', fg, bg });
    }
  }

  return spans;
}

/**
 * Renders the full module matrix into an array of span-arrays, one per
 * terminal row (each covering two QR module rows).
 *
 * @param matrix - The 2D dark/light module matrix from {@link generateMatrix}.
 */
function renderMatrix(matrix: boolean[][]): ColorSpan[][] {
  const rows: ColorSpan[][] = [];
  const height = matrix.length;

  for (let row = 0; row < height; row += 2) {
    rows.push(buildRowSpans(matrix, row));
  }

  return rows;
}

/**
 * Displays a scannable QR code in the terminal.
 *
 * Supports optional dialog wrapping via the `dialog` prop and an optional
 * copyable text display of the encoded value via `showValue`.
 *
 * @example
 * ```tsx
 * // Minimal usage
 * <QRCode value="bitcoincash:qr..." />
 *
 * // Inside a dialog with the raw value shown
 * <QRCode value="bitcoincash:qr..." dialog dialogTitle="Receive Address" showValue />
 * ```
 */
export function QRCode({
  value,
  dialog = false,
  dialogTitle = 'QR Code',
  showValue = false,
  subtitle = null,
}: QRCodeProps): React.ReactElement {
  const { rows, moduleCount } = useMemo(() => {
    const matrix = generateMatrix(value);
    return {
      rows: renderMatrix(matrix),
      moduleCount: matrix[0]?.length ?? 0,
    };
  }, [value]);

  const qrContent = (
    <Box flexDirection="column" alignItems="center">
      {showValue && (
        <Box marginBottom={1} width={moduleCount}>
          <Text color={colors.textMuted} wrap="wrap">{value}</Text>
        </Box>
      )}
      <Box flexDirection="column">
        {rows.map((spans, i) => (
          <Text key={i}>
            {spans.map((span, j) => (
              <Text key={j} color={span.fg} backgroundColor={span.bg}>
                {span.chars}
              </Text>
            ))}
          </Text>
        ))}
      </Box>
    </Box>
  );

  if (dialog) {
    const dialogWidth = Math.max(moduleCount + 8, 40);
    return (
      <DialogWrapper title={dialogTitle} borderColor={colors.primary} width={dialogWidth}>
        {qrContent}
        {subtitle}
      </DialogWrapper>
    );
  }

  return qrContent;
}
