import * as React from 'react';
import styles from './style.module.css';

type BinaryDataCanvasDerivedElement = HTMLDivElement;

export interface BinaryDataCanvasProps extends Omit<React.HTMLProps<BinaryDataCanvasDerivedElement>, 'children' | 'headers'> {
  arrayBuffer?: ArrayBuffer;
  lineClassName?: string;
  byteClassName?: string | ((byte: number, index: number) => string)
  headers?: boolean;
}

const BYTES_PER_LINE = 16 as const;

export const BinaryDataCanvas = React.forwardRef<BinaryDataCanvasDerivedElement, BinaryDataCanvasProps>(({
  arrayBuffer,
  lineClassName,
  byteClassName,
  headers = false,
  className,
  style,
  ...etcProps
}, forwardedRef) => {
  const bytesGrouped = arrayBuffer ? (Array.from(new Uint8Array(arrayBuffer)).slice(0, BYTES_PER_LINE * 20) as number[])
    .reduce(
      (byteArray: number[][], byte: number, i) => {
        if (i % BYTES_PER_LINE === 0) {
          return [
            ...byteArray,
            [byte],
          ]
        }

        const lastLine = byteArray.at(-1) as number[]

        return [
          ...(byteArray.slice(0, -1)),
          [...lastLine, byte],
        ]
      },
      [] as number[][],
    ) : [] as number[][];

  return (
    <div
      {...etcProps}
      className={`${styles['react-binary-data-canvas']} ${className ?? ''}`.trim()}
      style={style}
      ref={forwardedRef}
    >
      {headers && (
        <pre
          className={styles['line-numbers']}
        >
          <code>
            {'\u00a0'}
          </code>
          {bytesGrouped.map((_, i) => (
            <React.Fragment key={i}>
              {'\n'}
              <code
                className={styles['code']}
              >
                0x{(BYTES_PER_LINE * i).toString(16).padStart(2, '0')}
              </code>
              {' '}
            </React.Fragment>
          ))}
        </pre>
      )}
      <div className="highlight">
        <pre>
          <code>
            {headers && new Array(BYTES_PER_LINE).fill(0).map((_, i) => (
              <span
                key={i}
              >
                {i > 0 && ' '}
                {i.toString(16).padStart(2, '+')}
              </span>
            ))}
            {
              bytesGrouped.map((line: number[], l) => (
                <React.Fragment key={l}>
                  {'\n'}
                  <span
                    className={lineClassName}
                  >
                    {line.map((byte, b) => (
                      <React.Fragment key={b}>
                        {b > 0 && ' '}
                        <span
                          className={typeof byteClassName === 'function' ? byteClassName(byte, l * BYTES_PER_LINE + b) : byteClassName}
                        >
                          {byte.toString(16).padStart(2, '0')}
                        </span>
                      </React.Fragment>
                    ))}
                  </span>
                </React.Fragment>
              ))
            }
          </code>
        </pre>
      </div>
    </div>
  )
});

BinaryDataCanvas.displayName = 'BinaryDataCanvas';
