import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { useStore } from '@nanostores/react';
import {
  fileMetadata,
  isCompiling,
  hasModifications,
  totalHexChunks,
  compileImage,
  resetToOriginal,
  byteToChar,
  originalFileData,
  modifiedFileData,
  hexChunkSize,
  pushToUndoStack,
  undo,
  canUndo,
} from '../stores/imageStore';
import ActionButtons from './ActionButtons';

export default function BinaryEditor() {
  const metadata = useStore(fileMetadata);
  const compiling = useStore(isCompiling);
  const modified = useStore(hasModifications);
  const totalChunks = useStore(totalHexChunks);
  const originalData = useStore(originalFileData);
  const modifiedData = useStore(modifiedFileData);
  const chunkSize = useStore(hexChunkSize);
  const canUndoState = useStore(canUndo);

  const [hexInput, setHexInput] = useState('');
  const [editingByte, setEditingByte] = useState<{ row: number; col: number; value: string; globalOffset: number } | null>(null);
  const [editingAscii, setEditingAscii] = useState<{ row: number; col: number; value: string; globalOffset: number } | null>(null);
  const [scrollTop, setScrollTop] = useState(0);
  const editorRef = useRef<HTMLDivElement>(null);
  const scrollContainerRef = useRef<HTMLDivElement>(null);

  // Virtual scrolling configuration
  const ROW_HEIGHT = 20; // Height of each hex row in pixels
  const BYTES_PER_ROW = 16;
  const BUFFER_SIZE = 10; // Extra rows to render above/below viewport for smooth scrolling

  // Calculate virtual scrolling parameters
  const virtualScrollData = useMemo(() => {
    if (!metadata || !originalData) return null;
    
    const totalRows = Math.ceil(metadata.fileSize / BYTES_PER_ROW);
    const containerHeight = 400; // Fixed height for hex container
    const visibleRows = Math.ceil(containerHeight / ROW_HEIGHT);
    
    const startRow = Math.floor(scrollTop / ROW_HEIGHT);
    const endRow = Math.min(startRow + visibleRows + BUFFER_SIZE * 2, totalRows);
    const actualStartRow = Math.max(0, startRow - BUFFER_SIZE);
    
    const startByte = actualStartRow * BYTES_PER_ROW;
    const endByte = Math.min(endRow * BYTES_PER_ROW, metadata.fileSize);
    
    return {
      totalRows,
      visibleRows,
      startRow: actualStartRow,
      endRow,
      startByte,
      endByte,
      totalHeight: Math.max(totalRows * ROW_HEIGHT, containerHeight),
      offsetY: actualStartRow * ROW_HEIGHT,
    };
  }, [metadata, originalData, scrollTop]);

  // Get data for virtual scrolling viewport
  const viewportData = useMemo(() => {
    if (!virtualScrollData || !originalData) return null;
    
    const dataToUse = modifiedData || originalData;
    const viewportBytes = dataToUse.slice(virtualScrollData.startByte, virtualScrollData.endByte);
    
    return viewportBytes;
  }, [virtualScrollData, originalData, modifiedData]);

  // Update hex input when viewport data changes
  useEffect(() => {
    if (viewportData) {
      const hexString = Array.from(viewportData)
        .map(byte => byte.toString(16).padStart(2, '0'))
        .join('');
      setHexInput(hexString);
    }
  }, [viewportData]);

  // Handle scroll events
  const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
    setScrollTop(e.currentTarget.scrollTop);
  }, []);

  // Random button functionality
  const handleRandomize = useCallback((count: number = 1) => {
    if (!originalData || !virtualScrollData) return;
    
    // Push current state to undo stack
    const currentData = modifiedData || new Uint8Array(originalData);
    pushToUndoStack(currentData);
    
    const newData = new Uint8Array(currentData);
    
    // Get visible range
    const visibleStart = virtualScrollData.startByte;
    const visibleEnd = virtualScrollData.endByte;
    
    // Make multiple random changes
    for (let i = 0; i < count; i++) {
      const randomIndex = Math.floor(Math.random() * (visibleEnd - visibleStart)) + visibleStart;
      const randomValue = Math.floor(Math.random() * 256);
      newData[randomIndex] = randomValue;
    }
    
    modifiedFileData.set(newData);
    
    // Auto-update image after random change
    setTimeout(() => compileImage(), 100);
  }, [originalData, modifiedData, virtualScrollData]);

  // Undo functionality
  const handleUndo = useCallback(() => {
    undo();
    setTimeout(() => compileImage(), 100);
  }, []);

  // Global random button functionality (affects entire file)
  const handleGlobalRandomize = useCallback((count: number = 1) => {
    if (!originalData || !metadata) return;
    
    // Push current state to undo stack
    const currentData = modifiedData || new Uint8Array(originalData);
    pushToUndoStack(currentData);
    
    const newData = new Uint8Array(currentData);
    
    // Make random changes across the entire file
    for (let i = 0; i < count; i++) {
      const randomIndex = Math.floor(Math.random() * metadata.fileSize);
      const randomValue = Math.floor(Math.random() * 256);
      newData[randomIndex] = randomValue;
    }
    
    modifiedFileData.set(newData);
    
    // Auto-update image after random change
    setTimeout(() => compileImage(), 100);
  }, [originalData, modifiedData, metadata]);


  const handleHexInputChange = (
    event: React.ChangeEvent<HTMLTextAreaElement>
  ) => {
    const value = event.target.value.replace(/[^0-9a-fA-F]/gi, '');
    setHexInput(value);
  };

  // Auto-updates happen immediately now

  const handleByteClick = (row: number, col: number, currentValue: number, globalOffset: number) => {
    setEditingByte({
      row,
      col,
      value: currentValue.toString(16).padStart(2, '0').toUpperCase(),
      globalOffset
    });
  };

  const handleByteEdit = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!editingByte) return;
    const value = e.target.value.replace(/[^0-9a-fA-F]/gi, '').slice(0, 2);
    setEditingByte({ ...editingByte, value });
  };

  const handleByteSubmit = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && editingByte) {
      applyByteEdit();
    } else if (e.key === 'Escape') {
      setEditingByte(null);
    }
  };

  const applyByteEdit = () => {
    if (!editingByte || !originalData) return;
    
    const newValue = parseInt(editingByte.value, 16);
    if (isNaN(newValue)) {
      setEditingByte(null);
      return;
    }

    // Push current state to undo stack
    const currentData = modifiedData || new Uint8Array(originalData);
    pushToUndoStack(currentData);
    
    // Update the full modified data directly
    const newData = new Uint8Array(currentData);
    newData[editingByte.globalOffset] = newValue;
    
    // Update store with new modified data
    modifiedFileData.set(newData);
    
    setEditingByte(null);
    // Auto-update image after byte edit
    setTimeout(() => compileImage(), 100);
  };

  const handleAsciiClick = (row: number, col: number, currentValue: number, globalOffset: number) => {
    const char = currentValue >= 32 && currentValue <= 126 ? String.fromCharCode(currentValue) : '.';
    setEditingAscii({
      row,
      col,
      value: char,
      globalOffset
    });
  };

  const handleAsciiEdit = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!editingAscii) return;
    const value = e.target.value.slice(0, 1); // Only allow single character
    setEditingAscii({ ...editingAscii, value });
  };

  const handleAsciiSubmit = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && editingAscii) {
      applyAsciiEdit();
    } else if (e.key === 'Escape') {
      setEditingAscii(null);
    }
  };

  const applyAsciiEdit = () => {
    if (!editingAscii || !originalData) return;
    
    const char = editingAscii.value;
    if (char.length !== 1) {
      setEditingAscii(null);
      return;
    }

    const newValue = char.charCodeAt(0);
    
    // Push current state to undo stack
    const currentData = modifiedData || new Uint8Array(originalData);
    pushToUndoStack(currentData);
    
    // Update the full modified data directly
    const newData = new Uint8Array(currentData);
    newData[editingAscii.globalOffset] = newValue;
    
    // Update store with new modified data
    modifiedFileData.set(newData);
    
    setEditingAscii(null);
    // Auto-update image after ASCII edit
    setTimeout(() => compileImage(), 100);
  };

  const renderFallbackHexView = (data: Uint8Array) => {
    const rows = [];
    const bytesPerRow = BYTES_PER_ROW;

    for (let i = 0; i < data.length; i += bytesPerRow) {
      const rowData = data.slice(i, i + bytesPerRow);
      const address = i;
      
      // Address column
      const addressStr = address.toString(16).toUpperCase().padStart(8, '0');

      // Hex column
      const hexBytes = Array.from(rowData).map((byte, colIndex) => {
        const globalOffset = address + colIndex;
        
        return (
          <span
            key={colIndex}
            className="hex-byte"
            onClick={() => handleByteClick(Math.floor(i / bytesPerRow), colIndex, byte, globalOffset)}
            title={`Click to edit byte at offset ${globalOffset.toString(16).toUpperCase()}`}
          >
            {byte.toString(16).padStart(2, '0').toUpperCase()}
          </span>
        );
      });

      // Pad remaining columns if row is incomplete
      while (hexBytes.length < bytesPerRow) {
        hexBytes.push(
          <span key={hexBytes.length} className="hex-byte hex-byte-empty">
            {'  '}
          </span>
        );
      }

      // ASCII column
      const asciiChars = Array.from(rowData).map((byte, colIndex) => {
        const globalOffset = address + colIndex;
        const char = byteToChar(byte);
        
        return (
          <span
            key={colIndex}
            className="ascii-char"
            onClick={() => handleAsciiClick(Math.floor(i / bytesPerRow), colIndex, byte, globalOffset)}
            title={`Click to edit ASCII char at offset ${globalOffset.toString(16).toUpperCase()} (byte value: ${byte})`}
          >
            {char}
          </span>
        );
      });

      // Pad remaining columns if row is incomplete
      while (asciiChars.length < bytesPerRow) {
        asciiChars.push(
          <span key={asciiChars.length} className="ascii-char ascii-char-empty">
            {' '}
          </span>
        );
      }

      rows.push(
        <div key={address} className="hex-row" style={{ height: ROW_HEIGHT }}>
          <span className="hex-address">{addressStr}</span>
          <div className="hex-data">
            {hexBytes.map((byte, idx) => (
              <React.Fragment key={idx}>
                {byte}
                {idx < bytesPerRow - 1 && <span className="hex-space"> </span>}
              </React.Fragment>
            ))}
          </div>
          <div className="hex-ascii">
            {asciiChars}
          </div>
        </div>
      );
    }

    return rows;
  };

  const renderVirtualizedHexView = () => {
    if (!virtualScrollData || !viewportData) {
      // Fallback: render first chunk if virtual scrolling data not available
      if (!originalData) return null;
      
      const fallbackData = originalData.slice(0, Math.min(512, originalData.length));
      return renderFallbackHexView(fallbackData);
    }

    const rows = [];
    const bytesPerRow = BYTES_PER_ROW;

    for (let i = 0; i < viewportData.length; i += bytesPerRow) {
      const rowData = viewportData.slice(i, i + bytesPerRow);
      const globalRowIndex = virtualScrollData.startRow + Math.floor(i / bytesPerRow);
      const address = globalRowIndex * bytesPerRow;
      
      // Address column
      const addressStr = address.toString(16).toUpperCase().padStart(8, '0');

      // Hex column with editable bytes
      const hexBytes = Array.from(rowData).map((byte, colIndex) => {
        const globalOffset = address + colIndex;
        const isEditing = editingByte?.globalOffset === globalOffset;
        
        if (isEditing) {
          return (
            <input
              key={colIndex}
              type="text"
              value={editingByte.value}
              onChange={handleByteEdit}
              onKeyDown={handleByteSubmit}
              onBlur={applyByteEdit}
              className="hex-byte-input"
              autoFocus
              maxLength={2}
            />
          );
        }
        
        return (
          <span
            key={colIndex}
            className="hex-byte"
            onClick={() => handleByteClick(globalRowIndex, colIndex, byte, globalOffset)}
            title={`Click to edit byte at offset ${globalOffset.toString(16).toUpperCase()}`}
          >
            {byte.toString(16).padStart(2, '0').toUpperCase()}
          </span>
        );
      });

      // Pad remaining columns if row is incomplete
      while (hexBytes.length < bytesPerRow) {
        hexBytes.push(
          <span key={hexBytes.length} className="hex-byte hex-byte-empty">
            {'  '}
          </span>
        );
      }

      // ASCII column with editable characters
      const asciiChars = Array.from(rowData).map((byte, colIndex) => {
        const globalOffset = address + colIndex;
        const isEditingAsciiChar = editingAscii?.globalOffset === globalOffset;
        
        if (isEditingAsciiChar) {
          return (
            <input
              key={colIndex}
              type="text"
              value={editingAscii.value}
              onChange={handleAsciiEdit}
              onKeyDown={handleAsciiSubmit}
              onBlur={applyAsciiEdit}
              className="ascii-char-input"
              autoFocus
              maxLength={1}
            />
          );
        }
        
        const char = byteToChar(byte);
        return (
          <span
            key={colIndex}
            className="ascii-char"
            onClick={() => handleAsciiClick(globalRowIndex, colIndex, byte, globalOffset)}
            title={`Click to edit ASCII char at offset ${globalOffset.toString(16).toUpperCase()} (byte value: ${byte})`}
          >
            {char}
          </span>
        );
      });

      // Pad remaining columns if row is incomplete
      while (asciiChars.length < bytesPerRow) {
        asciiChars.push(
          <span key={asciiChars.length} className="ascii-char ascii-char-empty">
            {' '}
          </span>
        );
      }

      rows.push(
        <div key={address} className="hex-row" style={{ height: ROW_HEIGHT }}>
          <span className="hex-address">{addressStr}</span>
          <div className="hex-data">
            {hexBytes.map((byte, idx) => (
              <React.Fragment key={idx}>
                {byte}
                {idx < bytesPerRow - 1 && <span className="hex-space"> </span>}
              </React.Fragment>
            ))}
          </div>
          <div className="hex-ascii">
            {asciiChars}
          </div>
        </div>
      );
    }

    return rows;
  };



  if (!metadata) {
    return (
      <div className="hex-editor">
        <div className="hex-editor-header">
          <span className="hex-editor-title">
            Hex Editor - No file loaded
          </span>
        </div>
        <div className="hex-editor-content">
          <div style={{ padding: '20px', textAlign: 'center', color: '#888' }}>
            Upload an image to start editing
          </div>
        </div>
      </div>
    );
  }

  if (!originalData) {
    return (
      <div className="hex-editor">
        <div className="hex-editor-header">
          <span className="hex-editor-title">
            Hex Editor - {metadata.fileName} ({metadata.fileSize} bytes)
          </span>
        </div>
        <div className="hex-editor-content">
          <div style={{ padding: '20px', textAlign: 'center' }}>
            Loading file data...
          </div>
        </div>
      </div>
    );
  }

  return (
    <div
      ref={editorRef}
      className="hex-editor"
    >
      <div className="hex-editor-header">
        <span className="hex-editor-title">
          Hex Editor - {metadata.fileName} ({metadata.fileSize} bytes)
        </span>
      </div>

      <div className="hex-editor-content">
        <div className="hex-controls">
          <div className="chunk-info">
            <small>
              File: {metadata.fileSize} bytes • Scroll position: {Math.floor(scrollTop / ROW_HEIGHT * BYTES_PER_ROW).toString(16).toUpperCase()}
              {modified && ' • Modified'}
            </small>
          </div>

          <ActionButtons
            handleRandomize={handleRandomize}
            handleGlobalRandomize={handleGlobalRandomize}
            virtualScrollData={virtualScrollData}
          />
        </div>

        <div className="hex-view-container">
          <div className="hex-view">
            <div className="hex-header">
              <span className="hex-address-header">Address</span>
              <span className="hex-data-header">Hex Data</span>
              <span className="hex-ascii-header">ASCII</span>
            </div>
            <div 
              ref={scrollContainerRef}
              className="hex-content-scroll"
              style={{ 
                height: '400px',
                overflow: 'auto',
                position: 'relative'
              }}
              onScroll={handleScroll}
            >
              <div 
                className="hex-content-virtual"
                style={{ 
                  height: virtualScrollData?.totalHeight || 0,
                  position: 'relative'
                }}
              >
                <div 
                  className="hex-content-viewport"
                  style={{ 
                    transform: `translateY(${virtualScrollData?.offsetY || 0}px)`,
                    position: 'absolute',
                    top: 0,
                    left: 0,
                    right: 0
                  }}
                >
                  {renderVirtualizedHexView()}
                </div>
              </div>
            </div>
          </div>
        </div>

        <div className="hex-input-section">
          <label>Raw Hex Input (Current View):</label>
          <textarea
            className="hex-input"
            value={hexInput}
            onChange={handleHexInputChange}
            placeholder="Enter hex values..."
            rows={8}
            style={{ minHeight: '120px', maxHeight: '200px' }}
          />
        </div>
      </div>

    </div>
  );
}
