import React, { useMemo, useState } from 'react';
import { bytesToHex } from '@noble/hashes/utils.js';

import { Badge, Card, Stack } from 'react-bootstrap';

import type { MeshCorePacketRecord } from './MeshCoreContext';
import {
  payloadDisplayByValue,
  routeDisplayByValue,
} from './MeshCoreData';

const HeaderFact: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (
  <div className="meshcore-fact-row">
    <span className="meshcore-fact-label">{label}</span>
    <span className="meshcore-fact-value">{value}</span>
  </div>
);

const toBitString = (value: number): string => value.toString(2).padStart(8, '0');

const byteHex = (value: number | undefined): string => {
  if (typeof value !== 'number') {
    return '0x??';
  }
  return `0x${value.toString(16).padStart(2, '0')}`;
};

const bitSlice = (value: number, msb: number, lsb: number): number => {
  const width = msb - lsb + 1;
  const mask = (1 << width) - 1;
  return (value >> lsb) & mask;
};

const toAscii = (value: number): string => {
  if (value >= 32 && value <= 126) {
    return String.fromCharCode(value);
  }
  return '.';
};

const asRecord = (value: unknown): Record<string, unknown> | null => {
  if (value && typeof value === 'object') {
    return value as Record<string, unknown>;
  }
  return null;
};

interface BitFieldSpec {
  msb: number;
  lsb: number;
  shortLabel: string;
  label: string;
  value: number;
}

const buildBitPointerLine = (msb: number, lsb: number): string => {
  const width = 15;
  const start = (7 - msb) * 2;
  const end = (7 - lsb) * 2;
  const chars = Array.from({ length: width }, () => ' ');

  if (start === end) {
    chars[start] = '↑';
    return chars.join('');
  }

  chars[start] = '└';
  for (let i = start + 1; i < end; i += 1) {
    chars[i] = '─';
  }
  chars[end] = '┘';
  return chars.join('');
};

const renderBitPointerArt = (
  value: number,
  fields: BitFieldSpec[],
  mode: 'compact' | 'verbose'
): string => {
  const header = 'bits: 7 6 5 4 3 2 1 0';
  const bitRow = `val : ${toBitString(value).split('').join(' ')}`;
  const pointers = fields.map((field) => {
    const name = mode === 'compact' ? field.shortLabel : field.label;
    return `     ${buildBitPointerLine(field.msb, field.lsb)} ${name} = ${field.value}`;
  });

  if (mode === 'compact') {
    const legend = `key : ${fields.map((field) => field.shortLabel).join(', ')}`;
    return [header, bitRow, ...pointers, legend].join('\n');
  }

  return [header, bitRow, ...pointers].join('\n');
};

interface ByteDissectionRow {
  index: number;
  byte: number;
  zone: 'header' | 'path' | 'payload';
}

const buildByteDissection = (packet: MeshCorePacketRecord): {
  rows: ByteDissectionRow[];
  pathHashSize: number;
  pathHashCount: number;
  pathBytesAvailable: number;
  payloadOffset: number;
} => {
  const pathField = packet.raw.length > 1 ? packet.raw[1] : 0;
  const pathHashSize = bitSlice(pathField, 7, 6) + 1;
  const pathHashCount = bitSlice(pathField, 5, 0);
  const pathBytesExpected = pathHashCount === 0 || pathHashSize === 4 ? 0 : pathHashCount * pathHashSize;
  const pathBytesAvailable = Math.min(pathBytesExpected, Math.max(packet.raw.length - 2, 0));
  const payloadOffset = 2 + pathBytesAvailable;

  const rows: ByteDissectionRow[] = Array.from(packet.raw).map((byte, index) => {
    if (index <= 1) {
      return {
        index,
        byte,
        zone: 'header',
      };
    }

    if (index < payloadOffset) {
      return {
        index,
        byte,
        zone: 'path',
      };
    }

    return {
      index,
      byte,
      zone: 'payload',
    };
  });

  return {
    rows,
    pathHashSize,
    pathHashCount,
    pathBytesAvailable,
    payloadOffset,
  };
};

const WireDissector: React.FC<{ packet: MeshCorePacketRecord }> = ({ packet }) => {
  const [bitArtMode, setBitArtMode] = useState<'compact' | 'verbose'>('compact');

  const { rows, pathHashSize, pathHashCount, pathBytesAvailable, payloadOffset } = useMemo(
    () => buildByteDissection(packet),
    [packet]
  );

  const headerByte = packet.raw[0] ?? 0;
  const pathByte = packet.raw[1] ?? 0;
  const pathBytes = packet.raw.slice(2, payloadOffset);
  const payloadBytes = packet.raw.slice(payloadOffset);

  const hexdumpRows = useMemo(() => {
    const chunks: ByteDissectionRow[][] = [];
    for (let i = 0; i < rows.length; i += 16) {
      chunks.push(rows.slice(i, i + 16));
    }
    return chunks.map((chunk, rowIndex) => ({
      offset: rowIndex * 16,
      cells: [...chunk, ...Array<ByteDissectionRow | null>(Math.max(16 - chunk.length, 0)).fill(null)],
    }));
  }, [rows]);

  return (
    <Card body className="data-table-card">
      <Stack direction="horizontal" className="justify-content-between align-items-center mb-2">
        <h6 className="mb-0">Packet Bytes (Wire View)</h6>
        <button
          type="button"
          className="meshcore-bitart-toggle"
          onClick={() => setBitArtMode((prev) => (prev === 'compact' ? 'verbose' : 'compact'))}
          title="Toggle compact/verbose bit pointer annotations"
        >
          Bit Art: {bitArtMode === 'compact' ? 'Compact' : 'Verbose'}
        </button>
      </Stack>
      <div className="meshcore-wire-subtitle">Protocol tree + hex dump, similar to a packet analyzer output</div>

      <div className="meshcore-ws-layout mt-3">
        <div className="meshcore-ws-panel">
          <div className="meshcore-ws-panel-title">Packet Details</div>
          <ul className="meshcore-ws-tree">
            <li>
              <span className="meshcore-ws-node">Frame 1: {packet.raw.length} bytes on wire ({packet.raw.length * 8} bits)</span>
            </li>
            <li>
              <span className="meshcore-ws-node">MeshCore Header</span>
              <ul>
                <li>Byte[0] = <code>{byteHex(headerByte)}</code> = <code>{toBitString(headerByte)}</code></li>
                <li>b7..b6: Version = <strong>{bitSlice(headerByte, 7, 6)}</strong></li>
                <li>b5..b2: Payload Type = <strong>{bitSlice(headerByte, 5, 2)}</strong> ({payloadDisplayByValue[packet.payloadType] ?? 'Unknown'})</li>
                <li>b1..b0: Route Type = <strong>{bitSlice(headerByte, 1, 0)}</strong> ({routeDisplayByValue[packet.routeType] ?? 'Unknown'})</li>
                <li>
                  <pre className="meshcore-bit-art">
                    {renderBitPointerArt(headerByte, [
                      {
                        msb: 7,
                        lsb: 6,
                        shortLabel: 'ver',
                        label: 'version (b7..b6)',
                        value: bitSlice(headerByte, 7, 6),
                      },
                      {
                        msb: 5,
                        lsb: 2,
                        shortLabel: 'ptype',
                        label: 'payload_type (b5..b2)',
                        value: bitSlice(headerByte, 5, 2),
                      },
                      {
                        msb: 1,
                        lsb: 0,
                        shortLabel: 'route',
                        label: 'route_type (b1..b0)',
                        value: bitSlice(headerByte, 1, 0),
                      },
                    ], bitArtMode)}
                  </pre>
                </li>
              </ul>
            </li>
            <li>
              <span className="meshcore-ws-node">Path Descriptor</span>
              <ul>
                <li>Byte[1] = <code>{byteHex(pathByte)}</code> = <code>{toBitString(pathByte)}</code></li>
                <li>b7..b6: Hash size = ({bitSlice(pathByte, 7, 6)} + 1) = <strong>{pathHashSize}</strong></li>
                <li>b5..b0: Hash count = <strong>{pathHashCount}</strong></li>
                <li>Path bytes in frame = <strong>{pathBytesAvailable}</strong></li>
                <li>Path data = <code>{pathBytes.length > 0 ? bytesToHex(pathBytes) : 'none'}</code></li>
                <li>
                  <pre className="meshcore-bit-art">
                    {renderBitPointerArt(pathByte, [
                      {
                        msb: 7,
                        lsb: 6,
                        shortLabel: 'hsel',
                        label: 'hash_size_selector (b7..b6)',
                        value: bitSlice(pathByte, 7, 6),
                      },
                      {
                        msb: 5,
                        lsb: 0,
                        shortLabel: 'hcnt',
                        label: 'hash_count (b5..b0)',
                        value: bitSlice(pathByte, 5, 0),
                      },
                    ], bitArtMode)}
                  </pre>
                </li>
              </ul>
            </li>
            <li>
              <span className="meshcore-ws-node">Payload</span>
              <ul>
                <li>Payload offset = <code>{payloadOffset}</code></li>
                <li>Payload length = <strong>{payloadBytes.length}</strong> bytes</li>
                <li>Payload bytes = <code>{payloadBytes.length > 0 ? bytesToHex(payloadBytes) : 'none'}</code></li>
              </ul>
            </li>
          </ul>
        </div>

        <div className="meshcore-ws-panel">
          <div className="meshcore-ws-panel-title">Hex Dump</div>
          <div className="meshcore-ws-legend mb-2">
            <span><i className="meshcore-ws-chip meshcore-ws-zone-header" />Header</span>
            <span><i className="meshcore-ws-chip meshcore-ws-zone-path" />Path</span>
            <span><i className="meshcore-ws-chip meshcore-ws-zone-payload" />Payload</span>
          </div>
          <div className="meshcore-ws-hexdump">
            {hexdumpRows.map((row) => (
              <div key={row.offset} className="meshcore-ws-hexdump-row">
                <span className="meshcore-ws-offset">{row.offset.toString(16).padStart(4, '0')}</span>
                <span className="meshcore-ws-bytes">
                  {row.cells.map((cell, index) => (
                    <span key={`${row.offset}-${index}`} className={`meshcore-ws-byte ${cell ? `meshcore-ws-zone-${cell.zone}` : 'meshcore-ws-byte-empty'}`}>
                      {cell ? cell.byte.toString(16).padStart(2, '0') : '  '}
                    </span>
                  ))}
                </span>
                <span className="meshcore-ws-ascii">
                  {row.cells.map((cell, index) => (
                    <span key={`${row.offset}-ascii-${index}`} className={`meshcore-ws-char ${cell ? `meshcore-ws-zone-${cell.zone}` : 'meshcore-ws-byte-empty'}`}>
                      {cell ? toAscii(cell.byte) : ' '}
                    </span>
                  ))}
                </span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </Card>
  );
};

const PayloadDetails: React.FC<{ packet: MeshCorePacketRecord }> = ({ packet }) => {
  const payload = packet.decodedPayload;
  const payloadObj = asRecord(payload);

  if (!payloadObj) {
    return (
      <Card body className="data-table-card">
        <h6 className="mb-2">Payload</h6>
        <div>Unable to decode payload; showing raw bytes only.</div>
        <code>{bytesToHex(packet.raw)}</code>
      </Card>
    );
  }

  if (typeof payloadObj.flags === 'number' && payloadObj.data instanceof Uint8Array) {
    return (
      <Card body className="data-table-card">
        <h6 className="mb-2">CONTROL Payload</h6>
        <HeaderFact label="Flags" value={`0x${payloadObj.flags.toString(16)}`} />
        <HeaderFact label="Data Length" value={payloadObj.data.length} />
        <HeaderFact label="Data" value={<code>{bytesToHex(payloadObj.data)}</code>} />
      </Card>
    );
  }

  if (
    typeof payloadObj.channelHash === 'string'
    && payloadObj.cipherText instanceof Uint8Array
    && payloadObj.cipherMAC instanceof Uint8Array
  ) {
    return (
      <Card body className="data-table-card">
        <h6 className="mb-2">GROUP Payload</h6>
        <HeaderFact label="Channel Hash" value={payloadObj.channelHash} />
        <HeaderFact label="Cipher Text Length" value={payloadObj.cipherText.length} />
        <HeaderFact label="Cipher MAC" value={<code>{bytesToHex(payloadObj.cipherMAC)}</code>} />
      </Card>
    );
  }

  if (
    typeof payloadObj.dstHash === 'string'
    && typeof payloadObj.srcHash === 'string'
    && payloadObj.cipherText instanceof Uint8Array
    && payloadObj.cipherMAC instanceof Uint8Array
  ) {
    return (
      <Card body className="data-table-card">
        <h6 className="mb-2">Encrypted Payload</h6>
        <HeaderFact label="Destination" value={payloadObj.dstHash} />
        <HeaderFact label="Source" value={payloadObj.srcHash} />
        <HeaderFact label="Cipher Text Length" value={payloadObj.cipherText.length} />
        <HeaderFact label="Cipher MAC" value={<code>{bytesToHex(payloadObj.cipherMAC)}</code>} />
      </Card>
    );
  }

  if (payloadObj.data instanceof Uint8Array) {
    return (
      <Card body className="data-table-card">
        <h6 className="mb-2">Raw Payload</h6>
        <HeaderFact label="Data Length" value={payloadObj.data.length} />
        <HeaderFact label="Data" value={<code>{bytesToHex(payloadObj.data)}</code>} />
      </Card>
    );
  }

  return (
    <Card body className="data-table-card">
      <h6 className="mb-2">Payload</h6>
      <code>{JSON.stringify(payloadObj)}</code>
    </Card>
  );
};

interface MeshCorePacketDetailsPaneProps {
  packet: MeshCorePacketRecord | null;
  streamReady: boolean;
}

const MeshCorePacketDetailsPane: React.FC<MeshCorePacketDetailsPaneProps> = ({ packet, streamReady }) => {
  if (!packet) {
    return (
      <Card body className="data-table-card h-100">
        <h6>Select a packet</h6>
        <div>Click any hash in the table to inspect MeshCore header and payload details.</div>
        <div className="mt-2 text-secondary">Stream prepared: {streamReady ? 'yes' : 'no'}</div>
      </Card>
    );
  }

  return (
    <Stack gap={2} className="h-100 meshcore-detail-stack">
      <Card body className="data-table-card">
        <Stack direction="horizontal" gap={2} className="mb-2">
          <h6 className="mb-0">Packet Header</h6>
          <Badge bg="primary">{payloadDisplayByValue[packet.payloadType] ?? packet.payloadType}</Badge>
        </Stack>
        <HeaderFact label="Time" value={packet.timestamp.toISOString()} />
        <HeaderFact label="Hash" value={<code>{packet.hash}</code>} />
        {packet.radioName && <HeaderFact label="Radio" value={packet.radioName} />}
        <HeaderFact label="Version" value={packet.version} />
        <HeaderFact label="Payload Type" value={`${payloadDisplayByValue[packet.payloadType] ?? 'Unknown'} (${packet.payloadType})`} />
        <HeaderFact label="Route Type" value={routeDisplayByValue[packet.routeType] ?? packet.routeType} />
        <HeaderFact label="Raw Length" value={`${packet.raw.length} bytes`} />
        <HeaderFact label="Path" value={<code>{bytesToHex(packet.path)}</code>} />
        <HeaderFact label="Raw Packet" value={<code>{bytesToHex(packet.raw)}</code>} />
      </Card>
      <WireDissector packet={packet} />
      <PayloadDetails packet={packet} />
      <Card body className="data-table-card">
        <h6 className="mb-2">Stream Preparation</h6>
        <div>MeshCore stream service is initialized and ready for topic subscriptions.</div>
        <div className="text-secondary">Ready: {streamReady ? 'yes' : 'no'}</div>
      </Card>
    </Stack>
  );
};

export default MeshCorePacketDetailsPane;
