import * as Blockly from 'blockly';
import { pythonGenerator } from 'blockly/python';
import './blocks/esp32_blocks.js';
import './blocks/esp32_generators.js';
import {
  getDeviceId,
  setDeviceId,
  getDevice,
  canFlashInBrowser,
  buildToolbox,
} from './devices/registry.js';
import { connect, disconnect, isConnected, onData, writeString } from './serial/connection.js';
import { executeCode, stopExecution, saveToDevice, writeFileToDevice } from './serial/repl.js';
import { flashFirmware } from './serial/flasher.js';
import { appendToTerminal, clearTerminal } from './ui/terminal.js';
import { initResizablePanels } from './ui/panels.js';
import './style.css';

// ─── Blockly Workspace ───────────────────────────────────

const workspace = Blockly.inject('blockly-div', {
  toolbox: buildToolbox(getDeviceId()),
  theme: Blockly.Themes.Dark,
  grid: { spacing: 25, length: 3, colour: '#333', snap: true },
  zoom: { controls: true, wheel: true, startScale: 0.9, maxScale: 3, minScale: 0.3, scaleSpeed: 1.2 },
  trashcan: true,
  renderer: 'zelos',
});

// ─── Live Code Preview ───────────────────────────────────

const codeOutput = document.getElementById('code-output');

function updateCodePreview() {
  const code = pythonGenerator.workspaceToCode(workspace);
  codeOutput.textContent = code || '# Drag blocks to generate MicroPython code';
}

workspace.addChangeListener((event) => {
  if (event.isUiEvent) return;
  updateCodePreview();
});

updateCodePreview();

// ─── Workspace Persistence (localStorage) ────────────────

const STORAGE_KEY = 'esp32block_workspace';

function saveWorkspace() {
  const state = Blockly.serialization.workspaces.save(workspace);
  localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
}

function loadWorkspace() {
  const json = localStorage.getItem(STORAGE_KEY);
  if (json) {
    try {
      const state = JSON.parse(json);
      Blockly.serialization.workspaces.load(state, workspace);
    } catch (_) {
      /* corrupted state, ignore */
    }
  }
}

workspace.addChangeListener((event) => {
  if (event.isUiEvent) return;
  saveWorkspace();
});

loadWorkspace();

// ─── Resize Handling ─────────────────────────────────────

function onResize() {
  const blocklyArea = document.getElementById('blockly-area');
  const blocklyDiv = document.getElementById('blockly-div');
  blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
  blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
  Blockly.svgResize(workspace);
}

window.addEventListener('resize', onResize);
onResize();
initResizablePanels();

// ─── UI State Helpers ────────────────────────────────────

const deviceSelect = document.getElementById('device-select');
const btnConnect = document.getElementById('btn-connect');
const btnFlash = document.getElementById('btn-flash');
const btnRun = document.getElementById('btn-run');
const btnStop = document.getElementById('btn-stop');
const btnSave = document.getElementById('btn-save');
const btnSaveWorkspace = document.getElementById('btn-save-workspace');
const btnLoadWorkspace = document.getElementById('btn-load-workspace');
const statusEl = document.getElementById('connection-status');
const terminalInput = document.getElementById('terminal-input');

// Sync device dropdown with stored device
deviceSelect.value = getDeviceId();
deviceSelect.addEventListener('change', () => {
  setDeviceId(deviceSelect.value);
  workspace.updateToolbox(buildToolbox(getDeviceId()));
  updateCodePreview();
  // Update Flash button tooltip/label based on device
  btnFlash.title = canFlashInBrowser()
    ? 'Flash MicroPython firmware'
    : 'Download firmware (drag to device)';
});
btnFlash.title = canFlashInBrowser()
  ? 'Flash MicroPython firmware'
  : 'Download firmware (drag to device)';

function setConnectedUI(connected) {
  btnConnect.textContent = connected ? '⏏ Disconnect' : '▶ Connect';
  btnRun.disabled = !connected;
  btnStop.disabled = !connected;
  btnSave.disabled = !connected;
  btnSaveWorkspace.disabled = !connected;
  btnLoadWorkspace.disabled = !connected;
  terminalInput.disabled = !connected;
  statusEl.textContent = connected ? 'Connected' : 'Disconnected';
  statusEl.className = connected ? 'status-connected' : 'status-disconnected';
}

// ─── Serial Event Listeners ──────────────────────────────

// Workspace loading state
let workspaceCaptureState = null;

onData((text) => {
  if (!workspaceCaptureState) {
    appendToTerminal(text);
    return;
  }

  const { startMarker, endMarker } = workspaceCaptureState;
  workspaceCaptureState.raw += text;
  const raw = workspaceCaptureState.raw;

  const startIdx = raw.indexOf(startMarker);

  if (startIdx === -1) {
    // No start marker yet — flush text that can't be part of the marker
    const keep = startMarker.length - 1;
    if (raw.length > keep) {
      appendToTerminal(raw.substring(0, raw.length - keep));
      workspaceCaptureState.raw = raw.substring(raw.length - keep);
    }
    return;
  }

  const contentStart = startIdx + startMarker.length;
  const endIdx = raw.indexOf(endMarker, contentStart);

  if (endIdx === -1) {
    // Have start but no end yet — show text before start marker once
    if (!workspaceCaptureState.flushedPre && startIdx > 0) {
      appendToTerminal(raw.substring(0, startIdx));
      workspaceCaptureState.flushedPre = true;
    }
    return;
  }

  // Both markers found — extract content and load
  const jsonContent = raw.substring(contentStart, endIdx).trim();
  const beforeStart = startIdx > 0 && !workspaceCaptureState.flushedPre
    ? raw.substring(0, startIdx) : '';
  const afterEnd = raw.substring(endIdx + endMarker.length);

  workspaceCaptureState = null;

  if (beforeStart) appendToTerminal(beforeStart);

  try {
    const state = JSON.parse(jsonContent);
    Blockly.serialization.workspaces.load(state, workspace);
    appendToTerminal('Workspace loaded successfully!\n');
  } catch (parseErr) {
    appendToTerminal(`\nParse error: ${parseErr.message}\n`);
  }

  if (afterEnd.trim()) appendToTerminal(afterEnd);
});

// ─── Toolbar Buttons ─────────────────────────────────────

btnConnect.addEventListener('click', async () => {
  try {
    if (isConnected()) {
      await disconnect();
      setConnectedUI(false);
      appendToTerminal('\n--- Disconnected ---\n');
    } else {
      await connect();
      setConnectedUI(true);
      appendToTerminal('--- Connected ---\n');
    }
  } catch (err) {
    appendToTerminal(`\nConnection error: ${err.message}\n`);
  }
});

const flashOverlay = document.getElementById('flash-overlay');
const flashLog = document.getElementById('flash-log');
const flashFill = document.getElementById('flash-progress-fill');
const flashPctText = document.getElementById('flash-progress-text');
const flashCloseBtn = document.getElementById('flash-close');

function showFlashOverlay() {
  flashLog.textContent = '';
  flashFill.style.width = '0%';
  flashPctText.textContent = '0%';
  flashCloseBtn.classList.add('hidden');
  flashOverlay.classList.remove('hidden');
}

function appendFlashLog(msg) {
  flashLog.textContent += msg;
  flashLog.scrollTop = flashLog.scrollHeight;
}

function setFlashProgress(pct) {
  flashFill.style.width = pct + '%';
  flashPctText.textContent = pct + '%';
}

flashCloseBtn.addEventListener('click', () => {
  flashOverlay.classList.add('hidden');
});

btnFlash.addEventListener('click', async () => {
  if (isConnected()) {
    await disconnect();
    setConnectedUI(false);
  }

  const device = getDevice();
  const fw = device.firmware;

  if (canFlashInBrowser()) {
    showFlashOverlay();
    try {
      await flashFirmware(
        (msg) => appendFlashLog(msg),
        (pct) => setFlashProgress(pct),
      );
      setFlashProgress(100);
      appendFlashLog('\nFlash complete! You can now Connect to use the device.\n');
    } catch (err) {
      appendFlashLog(`\nFlash error: ${err.message}\n`);
    } finally {
      flashCloseBtn.classList.remove('hidden');
    }
  } else {
    // Download firmware: open URL and show instructions
    window.open(fw.url, '_blank');
    appendToTerminal(`\n--- Firmware: ${fw.label} ---\n`);
    appendToTerminal(`Download opened in new tab: ${fw.url}\n`);
    if (fw.instructions) {
      appendToTerminal(`${fw.instructions}\n`);
    }
    appendToTerminal('After flashing, connect here to run code.\n');
  }
});

btnRun.addEventListener('click', async () => {
  const code = pythonGenerator.workspaceToCode(workspace);
  if (!code.trim()) {
    appendToTerminal('\nNo code to run. Add some blocks!\n');
    return;
  }
  appendToTerminal('\n>>> Running...\n');
  try {
    await executeCode(code);
  } catch (err) {
    appendToTerminal(`\nRun error: ${err.message}\n`);
  }
});

btnStop.addEventListener('click', async () => {
  try {
    await stopExecution();
    appendToTerminal('\n--- Stopped ---\n');
  } catch (err) {
    appendToTerminal(`\nStop error: ${err.message}\n`);
  }
});

btnSave.addEventListener('click', async () => {
  const code = pythonGenerator.workspaceToCode(workspace);
  if (!code.trim()) {
    appendToTerminal('\nNo code to save.\n');
    return;
  }
  appendToTerminal('\nSaving to device as main.py...\n');
  try {
    await saveToDevice(code);
  } catch (err) {
    appendToTerminal(`\nSave error: ${err.message}\n`);
  }
});

// ─── Workspace Save/Load ───────────────────────────────────

async function saveWorkspaceToDevice() {
  try {
    const state = Blockly.serialization.workspaces.save(workspace);
    const json = JSON.stringify(state);

    appendToTerminal('\nSaving workspace to device...\n');
    await writeFileToDevice(json, 'workspace.json');
    appendToTerminal('Workspace saved to workspace.json\n');
  } catch (err) {
    appendToTerminal(`\nSave workspace error: ${err.message}\n`);
  }
}

async function loadWorkspaceFromDevice() {
  try {
    appendToTerminal('\nLoading workspace from device...\n');
    
    // Use unique markers to identify workspace content
    const timestamp = Date.now();
    const startMarker = `__WS_START_${timestamp}__`;
    const endMarker = `__WS_END_${timestamp}__`;
    
    workspaceCaptureState = {
      startMarker,
      endMarker,
      raw: '',
      flushedPre: false,
    };
    
    const script = [
      `try:`,
      `  f = open('workspace.json', 'r')`,
      `  data = f.read()`,
      `  f.close()`,
      `  print('${startMarker}')`,
      `  print(data, end='')`,
      `  print('${endMarker}')`,
      `except Exception as e:`,
      `  print('Error reading workspace.json: ' + str(e))`,
    ].join('\n');
    
    await executeCode(script);
    
    // Clean up capture state after timeout
    setTimeout(() => {
      if (workspaceCaptureState) {
        appendToTerminal('\nTimeout waiting for workspace data\n');
        workspaceCaptureState = null;
      }
    }, 10000);
    
  } catch (err) {
    appendToTerminal(`\nLoad workspace error: ${err.message}\n`);
    workspaceCaptureState = null;
  }
}

btnSaveWorkspace.addEventListener('click', async () => {
  await saveWorkspaceToDevice();
});

btnLoadWorkspace.addEventListener('click', async () => {
  await loadWorkspaceFromDevice();
});

terminalInput.addEventListener('keydown', async (e) => {
  if (e.key === 'Enter') {
    const text = terminalInput.value;
    terminalInput.value = '';
    await writeString(text + '\r\n');
  }
});
