/* ═══════════════════════════════════════════════════════════════════
   MechBase PLC — Studio 5000 Minimalist Ladder Canvas
   ═══════════════════════════════════════════════════════════════════ */

// ── Config ─────────────────────────────────────────────────────────
const POLL_INTERVAL = 150;
const CANVAS_PADDING = 30;
const NETWORK_HEIGHT = 150;
const RUNG_HEIGHT = 80;
const RAIL_WIDTH = 6;
const WIRE_H = 2;
const SYMBOL_SIZE = 30; 
const ELEMENT_GAP = 15;

// ── Colors ──────────────────────────────────────────────────────────
const COLORS = {
  RAIL: '#2c3e50',      // Dark Slate (Studio 5000 style)
  WIRE: '#7f8c8d',      // Gray
  WIRE_POWERED: '#27ae60', // Green
  TEXT: '#2c3e50',
  TEXT_DIM: '#95a5a6',
  BACKGROUND: '#ecf0f1' // Light Gray
};

// ── Instruction definitions (Simplified) ───────────
const INST_INFO = {
  'XIC': { cat: 'contact', label: 'XIC' },
  'XIO': { cat: 'contact', label: 'XIO' },
  'OTE': { cat: 'output',  label: 'OTE' }
};

const CONTACT_TYPES = new Set(['XIC','XIO']);
const OUTPUT_TYPES = new Set(['OTE']);

// ── State ──────────────────────────────────────────────────────────
let networks = [];
let plcState = null;
let selectedNet = 0;
let editingElement = null;  // {netNum, rungIdx, branchIdx, elemIdx, side}
let addTarget = null;       // {netNum, rungIdx, branchIdx, side}
let hoveredElement = null;
let selectedElement = null; // {netNum, rungIdx, branchIdx, elemIdx, side}
let canvasHitAreas = [];

const canvas = document.getElementById('ladderCanvas');
const ctx = canvas.getContext('2d');

// ── Init ───────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
  setupDragDrop();
  setupPaletteTypes();
  resizeCanvas();
  window.addEventListener('resize', resizeCanvas);
  startPolling();
  render();
});

function resizeCanvas() {
  const wrapper = document.getElementById('canvasWrapper');
  canvas.width = Math.max(800, wrapper.clientWidth - 20);
  canvas.height = Math.max(400, networks.length * (NETWORK_HEIGHT + 10) + 40);
  render();
}

function setupPaletteTypes() {
  ['editType', 'addType'].forEach(id => {
    const sel = document.getElementById(id);
    if (!sel) return;
    sel.innerHTML = '';
    Object.keys(INST_INFO).forEach(t => {
      const opt = document.createElement('option');
      opt.value = t;
      opt.textContent = t;
      sel.appendChild(opt);
    });
  });
}

// ── Drag and Drop ──────────────────────────────────────────────────
function setupDragDrop() {
  const palette = document.getElementById('palette');
  if (!palette) return;

  palette.addEventListener('dragstart', e => {
    if (e.target.classList.contains('palette-item')) {
      e.dataTransfer.setData('text/plain', e.target.dataset.type);
      e.dataTransfer.effectAllowed = 'copy';
    }
  });

  canvas.addEventListener('dragover', e => {
    e.preventDefault();
    const { x, y } = getCanvasCoords(e);
    const hit = findDropTarget(x, y);
    if (hit) {
      hoveredElement = hit;
      render();
    }
  });

  canvas.addEventListener('dragleave', () => {
    hoveredElement = null;
    render();
  });

  canvas.addEventListener('drop', e => {
    e.preventDefault();
    const type = e.dataTransfer.getData('text/plain');
    if (!type) return;
    const { x, y } = getCanvasCoords(e);
    const hit = findDropTarget(x, y);
    if (hit) {
      openAddDialog(type, hit);
    }
    hoveredElement = null;
    render();
  });
}

function getCanvasCoords(e) {
  const rect = canvas.getBoundingClientRect();
  return {
    x: e.clientX - rect.left + canvas.parentElement.scrollLeft,
    y: e.clientY - rect.top + canvas.parentElement.scrollTop
  };
}

function findDropTarget(mx, my) {
  for (const area of canvasHitAreas) {
    if (mx >= area.x && mx <= area.x + area.w && my >= area.y && my <= area.y + area.h) {
      return area.target;
    }
  }
  // Fallback: nearest rung
  for (const area of canvasHitAreas) {
    if (area.type === 'rung' && my >= area.y && my <= area.y + area.h) {
      return { netNum: area.target.netNum, rungIdx: area.target.rungIdx, side: 'inline', branchIdx: -1, elemIdx: -1 };
    }
  }
  return null;
}

// ── Canvas Rendering ───────────────────────────────────────────────
function render() {
  if (networks.length === 0) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#888';
    ctx.font = '14px "Segoe UI", sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText('No networks — click "+ Network" or "Templates" to get started.', canvas.width / 2, 80);
    return;
  }

  canvas.height = networks.length * (NETWORK_HEIGHT + 10) + 40;
  canvasHitAreas = [];

  // Background
  ctx.fillStyle = COLORS.BACKGROUND;
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  const railX = 50;
  const railEndX = canvas.width - 50;

  networks.forEach((net, ni) => {
    const netY = ni * (NETWORK_HEIGHT + 10) + 20;
    renderNetwork(net, ni, railX, railEndX, netY);
  });

  // Hover highlight
  if (hoveredElement) {
    canvasHitAreas.forEach(area => {
      if (area.target && JSON.stringify(area.target) === JSON.stringify(hoveredElement)) {
        ctx.strokeStyle = '#4a90d9';
        ctx.lineWidth = 1;
        ctx.setLineDash([4, 4]);
        ctx.strokeRect(area.x - 2, area.y - 2, area.w + 4, area.h + 4);
        ctx.setLineDash([]);
      }
    });
  }

  syncTree();
}

function renderNetwork(net, ni, railX, railEndX, netY) {
  // Network background
  ctx.fillStyle = '#ffffff';
  ctx.strokeStyle = '#e0e0e0';
  ctx.lineWidth = 1;
  const pad = 8;
  ctx.fillRect(railX - pad, netY - 20, railEndX - railX + pad * 2, NETWORK_HEIGHT + 30);
  ctx.strokeRect(railX - pad, netY - 20, railEndX - railX + pad * 2, NETWORK_HEIGHT + 30);

  // Header
  ctx.fillStyle = '#f8f8f8';
  ctx.fillRect(railX - pad, netY - 20, railEndX - railX + pad * 2, 20);
  ctx.fillStyle = '#1a6dd4';
  ctx.font = 'bold 10px sans-serif';
  ctx.textAlign = 'left';
  ctx.fillText(`Network ${ni}`, railX, netY - 7);

  canvasHitAreas.push({
    type: 'network',
    x: railX - pad, y: netY - 20,
    w: railEndX - railX + pad * 2, h: NETWORK_HEIGHT + 30,
    target: { netNum: ni }
  });

  (net.rungs || []).forEach((rung, ri) => {
    const rungY = netY + ri * RUNG_HEIGHT;
    renderRung(rung, ri, rungY, ni, railX, railEndX);
  });
}

function renderRung(rung, ri, rungY, netNum, railX, railEndX) {
  const baseY = rungY + 40;
  const powered = plcState && plcState.running;
  const railColor = powered ? '#2ecc40' : COLORS.RAIL;

  // Rung ID
  ctx.fillStyle = COLORS.TEXT_DIM;
  ctx.font = '9px monospace';
  ctx.textAlign = 'right';
  ctx.fillText(`R${ri}`, railX - 10, baseY + 4);

  canvasHitAreas.push({
    type: 'rung',
    x: railX, y: rungY,
    w: railEndX - railX, h: RUNG_HEIGHT,
    target: { netNum, rungIdx: ri, side: 'inline', branchIdx: -1, elemIdx: -1 }
  });

  // Rails
  ctx.strokeStyle = railColor;
  ctx.lineWidth = RAIL_WIDTH;
  ctx.beginPath();
  ctx.moveTo(railX, baseY - 30); ctx.lineTo(railX, baseY + 30);
  ctx.moveTo(railEndX, baseY - 30); ctx.lineTo(railEndX, baseY + 30);
  ctx.stroke();

  // Horizontal line
  ctx.strokeStyle = railColor;
  ctx.lineWidth = WIRE_H;
  ctx.beginPath();
  ctx.moveTo(railX, baseY);
  ctx.lineTo(railEndX, baseY);
  ctx.stroke();

  const inlineElems = (rung.inline && rung.inline.elements) || [];
  const outputs = rung.outputs || [];

  // 1. Render Inline elements starting from the left rail
  let inlineX = railX + 20;
  inlineElems.forEach((elem, ei) => {
    const info = INST_INFO[elem.type];
    drawInstruction(ctx, elem, info, inlineX, baseY, 'inline', netNum, ri, -1, ei, powered);
    inlineX += SYMBOL_SIZE + ELEMENT_GAP;
  });

  // 2. Render Output elements starting from the right rail (Industry Standard)
  outputs.forEach((elem, oi) => {
    const info = INST_INFO[elem.type];
    const x = (railEndX - 20) - (outputs.length - 1 - oi) * (SYMBOL_SIZE + ELEMENT_GAP) - (SYMBOL_SIZE / 2);
    drawInstruction(ctx, elem, info, x, baseY, 'output', netNum, ri, -1, oi, powered);
  });
}

function drawInstruction(ctx, elem, info, x, y, side, netNum, rungIdx, branchIdx, elemIdx, powered) {
  const isHovered = hoveredElement &&
    hoveredElement.netNum === netNum &&
    hoveredElement.rungIdx === rungIdx &&
    hoveredElement.branchIdx === branchIdx &&
    hoveredElement.elemIdx === elemIdx &&
    hoveredElement.side === side;

  const isSelected = selectedElement &&
    selectedElement.netNum === netNum &&
    selectedElement.rungIdx === rungIdx &&
    selectedElement.branchIdx === branchIdx &&
    selectedElement.elemIdx === elemIdx &&
    selectedElement.side === side;

  // Hit area for the element
  canvasHitAreas.push({
    type: 'element',
    x: x - SYMBOL_SIZE/2, y: y - SYMBOL_SIZE/2,
    w: SYMBOL_SIZE, h: SYMBOL_SIZE,
    target: { netNum, rungIdx, branchIdx, elemIdx, side }
  });

  // 1. Draw Instruction Plate (The \"Box\")
  const boxW = 42;
  const boxH = 26;
  ctx.fillStyle = '#ffffff';
  ctx.shadowBlur = 3;
  ctx.shadowColor = 'rgba(0,0,0,0.15)';
  ctx.fillRect(x - boxW/2, y - boxH/2, boxW, boxH);
  ctx.shadowBlur = 0;

  // 2. Draw Border / Selection Highlight
  ctx.lineWidth = 1;
  if (isHovered) {
    ctx.strokeStyle = '#4a90d9';
    ctx.lineWidth = 2;
  } else if (isSelected) {
  // 1. Draw Instruction Plate (The \"Box\")
  const boxW = 42;
  const boxH = 26;
  ctx.fillStyle = '#ffffff';
  ctx.shadowBlur = 3;
  ctx.shadowColor = 'rgba(0,0,0,0.15)';
  ctx.fillRect(x - boxW/2, y - boxH/2, boxW, boxH);
  ctx.shadowBlur = 0;

  // 2. Draw Border / Selection Highlight
  ctx.lineWidth = 1;
  if (isHovered) {
    ctx.strokeStyle = '#4a90d9';
    ctx.lineWidth = 2;
  } else if (isSelected) {
    ctx.strokeStyle = '#2ecc40'; // Green for selected
    ctx.lineWidth = 2;
  } else {
    ctx.strokeStyle = '#bdc3c7'; // Light gray border
    ctx.lineWidth = 1;
  }
  ctx.strokeRect(x - boxW/2, y - boxH/2, boxW, boxH);

  // 3. Draw Symbol
  ctx.lineWidth = 2;
  ctx.strokeStyle = isSelected ? '#2ecc40' : (isHovered ? '#4a90d9' : COLORS.RAIL);

  if (elem.type === 'XIC' || elem.type === 'XIO') {
    // Contact: | |
    ctx.beginPath();
    ctx.moveTo(x - 6, y - 10); ctx.lineTo(x - 6, y + 10);
    ctx.moveTo(x + 6, y - 10); ctx.lineTo(x + 6, y + 10);
    ctx.stroke();

    if (elem.type === 'XIO') {
      // Diagonal for NC contact
      ctx.beginPath();
      ctx.moveTo(x - 6, y + 10); ctx.lineTo(x + 6, y - 10);
      ctx.stroke();
    }
  } else if (elem.type === 'OTE') {
    // Coil: ( )
    ctx.beginPath();
    ctx.arc(x - 6, y, 6, Math.PI/2, -Math.PI/2);
    ctx.stroke();
    ctx.beginPath();
    ctx.arc(x + 6, y, 6, -Math.PI/2, Math.PI/2);
    ctx.stroke();
  }

  // 4. Draw Tag Text (Above the box)
  ctx.fillStyle = COLORS.TEXT;
  ctx.font = 'bold 10px sans-serif';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'bottom';
  ctx.fillText(elem.tag || '?', x, y - (boxH / 2) - 4);

}

// ── Interaction ────────────────────────────────────────────────────
canvas.addEventListener('click', e => {
  const { x, y } = getCanvasCoords(e);
  for (const area of canvasHitAreas) {
    if (area.type === 'element' && x >= area.x && x <= area.x + area.w && y >= area.y && y <= area.y + area.h) {
      selectedElement = area.target;
      render();
      return;
    }
  }
  for (const area of canvasHitAreas) {
    if (area.type === 'rung' && y >= area.y && y <= area.y + area.h) {
      openAddDialog('XIC', area.target);
      return;
    }
  }
});

canvas.addEventListener('dblclick', e => {
  const { x, y } = getCanvasCoords(e);
  for (const area of canvasHitAreas) {
    if (area.type === 'element' && x >= area.x && x <= area.x + area.w && y >= area.y && y <= area.y + area.h) {
      openEditDialog(area.target);
      return;
    }
  }
});

function openEditDialog(target) {
  const net = networks[target.netNum];
  const rung = net.rungs[target.rungIdx];
  let elem;
  if (target.side === 'output') elem = rung.outputs[target.elemIdx];
  else if (target.side === 'parallel') elem = rung.parallel[target.branchIdx]?.elements[target.elemIdx];
  else elem = rung.inline.elements[target.elemIdx];

  if (!elem) return;
  editingElement = { ...target };

  const sel = document.getElementById('editType');
  sel.innerHTML = '';
  Object.keys(INST_INFO).forEach(t => {
    const opt = document.createElement('option');
    opt.value = t; opt.textContent = t;
    if (t === elem.type) opt.selected = true;
    sel.appendChild(opt);
  });

  document.getElementById('editTag').value = elem.tag || '';
  document.getElementById('editDialog').classList.add('active');
}

function closeEditDialog() {
  document.getElementById('editDialog').classList.remove('active');
  editingElement = null;
}

function saveEdit() {
  if (!editingElement) return;
  const { netNum, rungIdx, branchIdx, elemIdx, side } = editingElement;
  const net = networks[netNum];
  const rung = net.rungs[rungIdx];
  let elem;
  if (side === 'output') elem = rung.outputs[elemIdx];
  else if (side === 'parallel') elem = rung.parallel[branchIdx]?.elements[elemIdx];
  else elem = rung.inline.elements[elemIdx];

  elem.type = document.getElementById('editType').value;
  elem.tag = document.getElementById('editTag').value;
  closeEditDialog();
  render();
}

function deleteSelectedElement() {
  if (!editingElement) return;
  const { netNum, rungIdx, branchIdx, elemIdx, side } = editingElement;
  const net = networks[netNum];
  const rung = net.rungs[rungIdx];
  if (side === 'output') rung.outputs.splice(elemIdx, 1);
  else if (side === 'parallel') rung.parallel[branchIdx]?.elements.splice(elemIdx, 1);
  else rung.inline.elements.splice(elemIdx, 1);
  closeEditDialog();
  render();
}

function openAddDialog(defaultType, target) {
  addTarget = { ...target };
  const sel = document.getElementById('addType');
  sel.innerHTML = '';
  Object.keys(INST_INFO).forEach(t => {
    const opt = document.createElement('option');
    opt.value = t; opt.textContent = t;
    if (t === defaultType) opt.selected = true;
    sel.appendChild(opt);
  });
  document.getElementById('addTag').value = 'Tag';
  document.getElementById('addDialog').classList.add('active');
}

function closeAddDialog() {
  document.getElementById('addDialog').classList.remove('active');
  addTarget = null;
}

function confirmAdd() {
  if (!addTarget) return;
  const type = document.getElementById('addType').value;
  const tag = document.getElementById('addTag').value;
  const net = networks[addTarget.netNum];
  const rung = net.rungs[addTarget.rungIdx];
  const elem = { type, tag };

  if (type === 'OTE') rung.outputs.push(elem);
  else rung.inline.elements.push(elem);

  closeAddDialog();
  render();
}

// ── Network / Rung Management ──────────────────────────────────────
function addNetwork() {
  networks.push({
    number: networks.length,
    rungs: [{ number: 0, inline: { elements: [] }, outputs: [] }],
  });
  render();
}

function addRungToSelected() {
  const net = networks[selectedNet];
  net.rungs.push({ number: net.rungs.length, inline: { elements: [] }, outputs: [] });
  render();
}

function scrollToNetwork(ni) {
  const wrapper = document.getElementById('canvasWrapper');
  const y = ni * (NETWORK_HEIGHT + 10) + 20 - wrapper.scrollTop;
  wrapper.scrollTo({ top: y, behavior: 'smooth' });
  selectedNet = ni;
}

function syncTree() {
  const netsDiv = document.getElementById('treeNets');
  if (!netsDiv) return;
  const existing = netsDiv.querySelectorAll('.tree-net');
  if (existing.length === networks.length) return;
  netsDiv.innerHTML = '';
  networks.forEach((net, i) => {
    const div = document.createElement('div');
    div.className = 'tree-net' + (i === selectedNet ? ' active' : '');
    div.textContent = `Network ${i}`;
    div.addEventListener('click', () => scrollToNetwork(i));
    netsDiv.appendChild(div);
  });
}

function clearProgram() {
  if (confirm('Clear all?')) { networks = []; render(); }
}

// ── I/O Monitor ────────────────────────────────────────────────────
async function startPolling() {
  while (true) {
    try {
      const resp = await fetch('/api/state');
      if (resp.ok) {
        plcState = await resp.json();
        updateStatus();
        renderIO('ioInputs', plcState.inputs || {}, 'I');
        renderIO('ioOutputs', plcState.outputs || {}, 'Q');
        renderIO('ioMemory', plcState.memory || {}, 'M');
      }
    } catch (e) {}
    await new Promise(r => setTimeout(r, POLL_INTERVAL));
  }
}

function updateStatus() {
  const dot = document.getElementById('statusDot');
  const text = document.getElementById('statusText');
  const btn = document.getElementById('btnRun');
  if (!plcState) return;
  if (plcState.running) {
    dot.classList.add('running');
    text.textContent = 'RUN';
    btn.textContent = '■ STOP';
    btn.classList.add('running');
  } else {
    dot.classList.remove('running');
    text.textContent = 'STOP';
    btn.textContent = '▶ RUN';
    btn.classList.remove('running');
  }
}

function renderIO(containerId, ioMap, prefix) {
  const container = document.getElementById(containerId);
  if (!container) return;
  const keys = Object.keys(ioMap).filter(k => k.startsWith(prefix)).sort();
  container.innerHTML = '';
  keys.forEach(tag => {
    const div = document.createElement('div');
    div.className = 'io-item';
    const val = ioMap[tag];
    div.innerHTML = `<span class="tag">${tag}</span><span class="state ${val ? 'on' : 'off'}">${val ? 'ON' : 'OFF'}</span>`;
    container.appendChild(div);
  });
}

// ── L5X Import Logic ─────────────────────────────────────────────

/**
 * Parses an L5X <Text> string into a structured format for the ladder editor.
 */
function parseL5XText(text) {
    const result = {
        inline: { elements: [] },
        outputs: [],
        parallel: []
    };

    const parseSegment = (segment) => {
        const elements = [];
        const outputs = [];
        const pattern = /([A-Z]+)\s*\(([^)]+)\)/g;
        let match;

        while ((match = pattern.exec(segment)) !== null) {
            const type = match[1];
            const label = match[2].trim();
            const obj = { type, tag: label };

            if (['OTE', 'OTL', 'OTU', 'RES'].includes(type)) {
                outputs.push(obj);
            } else {
                elements.push(obj);
            }
        }
        return { elements, outputs };
    };

    const branchRegex = /\[(.*?)\]/g;
    const branches = [];
    let branchMatch;
    let textWithoutBranches = text;

    while ((branchMatch = branchRegex.exec(text)) !== null) {
        branches.push(branchMatch[1]);
    }
    textWithoutBranches = text.replace(branchRegex, '');

    const main = parseSegment(textWithoutBranches);
    result.inline.elements = main.elements;
    result.outputs = main.outputs;

    for (const branchContent of branches) {
        const segments = branchContent.split(',');
        for (const seg of segments) {
            const bParsed = parseSegment(seg);
            if (bParsed.elements.length > 0 || bParsed.outputs.length > 0) {
                result.parallel.push({
                    inline: { elements: bParsed.elements },
                    outputs: bParsed.outputs
                });
            }
        }
    }
    return result;
}

function loadL5X(xmlString) {
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(xmlString, "text/xml");
    const rungs = xmlDoc.getElementsByTagName("Rung");

    if (rungs.length === 0) {
        alert("No <Rung> elements found in L5X.");
        return;
    }

    const newNetworks = [];
    for (let i = 0; i < rungs.length; i++) {
        const rung = rungs[i];
        const textElement = rung.getElementsByTagName("Text")[0];
        const commentElement = rung.getElementsByTagName("Comment")[0];
        
        if (textElement) {
            const text = textElement.textContent;
            const comment = commentElement ? commentElement.textContent : "";
            const parsedRung = parseL5XText(text);
            
            newNetworks.push({
                id: `rung_${i}`,
                comment: comment,
                inline: parsedRung.inline,
                outputs: parsedRung.outputs,
                parallel: parsedRung.parallel
            });
        }
    }

    networks = newNetworks;
    render();
    console.log(`Successfully loaded ${newNetworks.length} rungs from L5X.`);
}

function showL5xImport() {
    document.getElementById('l5xImportDialog').classList.add('active');
    document.getElementById('l5xInfo').textContent = "Note: To test, paste your L5X XML content into the console and call loadL5X(xmlString).";
}

function closeL5xImport() {
    document.getElementById('l5xImportDialog').classList.remove('active');
}

function confirmL5xImport() {
    const content = prompt("Paste the content of your .L5X file here:");
    if (content) {
        loadL5X(content);
        closeL5xImport();
    }
}