// JavaScript version of funcPLL (ported from your Python code)
function funcPLL(fc, Lf, kv, Io, R2, R3, C1, C2, C3, N, np_val, fmin_val, fmax_val) {
  const GPI = Math.PI;
  const alf = Math.pow(fmax_val / fmin_val, 1.0 / np_val);
  const dataRows = [];    // Each row: [f, log10(f), YoDB, HHmar, YcDB, YeDB, pnoiv, HHang, HHx, HHy]
  const pNoiData = [];    // For phase noise integration: [f, pnoiv, YeDB]
  const plotPoints = [];  // For plotting closed-loop gain vs. frequency

  // --- Compute PLL data over frequency range ---
  let f_val = fmin_val;
  while (f_val < fmax_val) {
    const w = 2.0 * GPI * f_val;
    const Nx = kv * Io;
    const Ny = kv * w * C2 * Io * R2;
    const Dx = (Math.pow(w, 4) * C1 * C2 * C3 * N * R2 * R3) +
               (Math.pow(w, 2) * (-(C3 + C2 + C1)) * N);
    const Dy = Math.pow(w, 3) *
               (((-C2 - C1) * C3 * N * R3) + ((-C2 * C3 - C1 * C2) * N * R2));
    const denom = (Dy * Dy + Dx * Dx);
    const HH2 = (denom !== 0) ? ((Ny * Ny + Nx * Nx) / denom) : 0.0;
    const HHx = (denom !== 0) ? ((Dy * Ny + Dx * Nx) / denom) : 0.0;
    const HHy = (denom !== 0) ? ((Dx * Ny - Dy * Nx) / denom) : 0.0;
    const HHang = (180.0 / GPI) * Math.atan2(HHy, HHx);
    const HHmar = 180.0 + HHang;
    const denom2 = (Ny * Ny + 2 * Dy * Ny + Nx * Nx + 2 * Dx * Nx + Dy * Dy + Dx * Dx);
    const HC2 = (denom2 !== 0) ? ((Ny * Ny + Nx * Nx) / denom2) : 0.0;
    const HE2 = (denom2 !== 0) ? ((Dy * Dy + Dx * Dx) / denom2) : 0.0;
    const YoDB = (HH2 > 0) ? 10.0 * Math.log10(HH2) : -Infinity;
    const YcDB = (HC2 > 0) ? 10.0 * Math.log10(HC2) : -Infinity;
    const YeDB = (HE2 > 0) ? 10.0 * Math.log10(HE2) : -Infinity;
    const logf = Math.log10(f_val);
    // pnoiv is computed as in your Python code:
    const pnoiv = Lf + 30.0 * Math.log10(1.0e6 / f_val);

    // Build a row with 10 columns (matching your Python file):
    // [f_val, log10(f_val), YoDB, HHmar, YcDB, YeDB, pnoiv, HHang, HHx, HHy]
    const row = [f_val, logf, YoDB, HHmar, YcDB, YeDB, pnoiv, HHang, HHx, HHy];
    dataRows.push(row);
    plotPoints.push([f_val, YcDB]);  // For the closed-loop gain plot
    pNoiData.push([f_val, pnoiv, YeDB]);
    
    f_val = f_val * alf;
  }

  // --- Helper functions (ported from your Python version) ---
  function gregFindXval(ytarg, x0, y0, x1, y1, dxmin = 0.0) {
    const absDiff = Math.abs(x1 - x0);
    if (absDiff < dxmin) {
      return (x0 + x1) / 2.0;
    } else {
      const ms = (y1 - y0) / (x1 - x0);
      const bs = y1 - ms * x1;
      return (ytarg - bs) / ms;
    }
  }

  function gregFindYval(xtarg, x0, y0, x1, y1, dxmin = 0.0) {
    const absDiff = Math.abs(x1 - x0);
    if (absDiff < dxmin) {
      return (y0 + y1) / 2.0;
    } else {
      const ms = (y1 - y0) / (x1 - x0);
      return ms * xtarg + (y1 - ms * x1);
    }
  }

  // data: array of rows, ncol: expected number of columns,
  // xcol, ycol: indices to use; ytarg: target y-value for interpolation.
  function gregExtractCol(data, ncol, xcol, ycol, ytarg) {
    let cnt = 0;
    let x0, y0, x1, y1;
    let xo, yo, xs, ys;
    let found = false;
    for (let i = 0; i < data.length; i++) {
      const tokens = data[i];
      if (tokens.length < ncol) continue;
      if (cnt === 0) {
        xo = tokens[xcol];
        yo = tokens[ycol];
      } else {
        xo = xs;
        yo = ys;
      }
      xs = tokens[xcol];
      ys = tokens[ycol];
      cnt++;
      if ((yo <= ytarg && ys > ytarg) || (yo >= ytarg && ys < ytarg)) {
        x0 = xo; y0 = yo; x1 = xs; y1 = ys;
        found = true;
        break;
      }
    }
    if (!found) return null;
    return gregFindXval(ytarg, x0, y0, x1, y1);
  }

  // Interpolate to get the y-value corresponding to xtarg.
  function gregEvaluateCol(data, ncol, xcol, ycol, xtarg) {
    let cnt = 0;
    let x0, y0, x1, y1;
    let xo, yo, xs, ys;
    let found = false;
    for (let i = 0; i < data.length; i++) {
      const tokens = data[i];
      if (tokens.length < ncol) continue;
      if (cnt === 0) {
        xo = tokens[xcol];
        yo = tokens[ycol];
      } else {
        xo = xs;
        yo = ys;
      }
      xs = tokens[xcol];
      ys = tokens[ycol];
      cnt++;
      if ((xo <= xtarg && xs > xtarg) || (xo >= xtarg && xs < xtarg)) {
        x0 = xo; y0 = yo; x1 = xs; y1 = ys;
        found = true;
        break;
      }
    }
    if (!found) return null;
    return gregFindYval(xtarg, x0, y0, x1, y1);
  }

  // Finds the peak (maximum) in the ycol using quadratic interpolation.
  function gregExtractMax(data, ncol, xcol, ycol, ydelmin = 0.0) {
    let cnt = 0;
    let xdc, ydc;
    let xl, yl, xo, yo, xs, ys;
    let candidate = null; // To store the best (last) candidate triple of points
    let ymax = -Infinity;
  
    for (let i = 0; i < data.length; i++) {
      const tokens = data[i];
      if (tokens.length < ncol) continue;
  
      if (cnt === 0) {
        xdc = tokens[xcol];
        ydc = tokens[ycol];
        xl = xdc; yl = ydc;
        xo = xdc; yo = ydc;
        ymax = ydc;
      } else {
        xl = xo; yl = yo;
        xo = xs; yo = ys;
      }
      xs = tokens[xcol];
      ys = tokens[ycol];
      cnt++;
  
      // Update candidate only if we have at least 3 points and we see a new high
      if (cnt > 2 && yo >= ymax) {
        candidate = { x0: xl, y0: yl, x1: xo, y1: yo, x2: xs, y2: ys };
        ymax = yo;
      }
    }
    // If a candidate was found and the peak is sufficiently above the DC level:
    if (candidate !== null && ((ymax - ydc) > ydelmin)) {
      const { x0, y0, x1, y1, x2, y2 } = candidate;
      const denom = ((2 * x1 - 2 * x0) * y2 +
                     (2 * x0 - 2 * x2) * y1 +
                     (2 * x2 - 2 * x1) * y0);
      let xp;
      if (denom === 0) {
        xp = 0.0;
      } else {
        xp = (((x1 * x1 - x0 * x0) * y2 +
               (x0 * x0 - x2 * x2) * y1 +
               (x2 * x2 - x1 * x1) * y0) / denom);
      }
      const denom1 = (x2 - x0) * (x2 - x1);
      const denom2 = (x1 - x0) * (x1 - x2);
      const denom3 = (x0 - x1) * (x0 - x2);
      const part1 = (denom1 !== 0) ? ((xp - x0) * (xp - x1) * y2 / denom1) : 0.0;
      const part2 = (denom2 !== 0) ? ((xp - x0) * (xp - x2) * y1 / denom2) : 0.0;
      const part3 = (denom3 !== 0) ? ((xp - x1) * (xp - x2) * y0 / denom3) : 0.0;
      const yp = part1 + part2 + part3;
      return [xp, yp];
    } else {
      return [xdc, 0.0];
    }
  }

  // Integrate the phase noise data.
  function integPNoise(idata, fmin, fmax, fc) {
    let sumPhi2 = 0.0;
    let cnt = 0;
    let item0 = null;
    for (const item of idata) {
      if (cnt > 0) {
        const fv1 = item0[0], yv1 = item0[1], gv1 = item0[2];
        const fv2 = item[0], yv2 = item[1], gv2 = item[2];
        const xv1 = Math.log10(fv1);
        const xv2 = Math.log10(fv2);
        // Adjust the phase values as in the Python code:
        const yy1 = yv1 + gv1;
        const yy2 = yv2 + gv2;
        const av = (xv2 - xv1 !== 0) ? (yy2 - yy1) / (xv2 - xv1) : 0.0;
        const bv = yy1 - av * xv1;
        const kav = 1.0 + av / 10.0;
        const kbv = bv / 10.0;
        const abskav = Math.abs(kav);
        let phi2;
        if (abskav < 1.0e-4) {
          phi2 = 2.0 * Math.pow(10.0, kbv) * Math.log(10.0) * (xv2 - xv1);
        } else {
          phi2 = (2.0 * Math.pow(10.0, kbv) / kav) *
                 (Math.pow(10.0, kav * xv2) - Math.pow(10.0, kav * xv1));
        }
        if (fv1 >= fmin && fv2 <= fmax) {
          sumPhi2 += phi2;
        }
      }
      item0 = item;
      cnt++;
    }
    const phi = Math.sqrt(sumPhi2);
    const tps = (phi / (2.0 * GPI) / fc) * 1.0e12;
    return tps;
  }

  // --- Compute additional PLL parameters from the data ---
  const Num_COL   = 10;
  const FrqDB_COL = 1;  // log10(f)
  const YoDB_COL  = 2;  // Open-loop gain in dB
  const Pmar_COL  = 3;  // "Phase margin" column (HHmar)
  const YcDB_COL  = 4;  // Closed-loop gain in dB

  const fvalDB  = gregExtractCol(dataRows, Num_COL, FrqDB_COL, YoDB_COL, 0.0);
  const fvalDBc = gregExtractCol(dataRows, Num_COL, FrqDB_COL, YcDB_COL, -3.0);
  const pmarVal = gregEvaluateCol(dataRows, Num_COL, FrqDB_COL, Pmar_COL, fvalDB);
  const xyPeak  = gregExtractMax(dataRows, Num_COL, FrqDB_COL, YcDB_COL, 0.00001);
  let frqPK = 0.0, magPK = 0.0;
  if (xyPeak !== null && xyPeak.length >= 2) {
    frqPK = Math.pow(10.0, xyPeak[0]);
    magPK = xyPeak[1];
  }
  const foLoop = (fvalDB !== null) ? Math.pow(10.0, fvalDB) : 0.0;
  const fcLoop = (fvalDBc !== null) ? Math.pow(10.0, fvalDBc) : 0.0;
  const jitv = integPNoise(pNoiData, 10e3, 10e6, fc);

  // Return an object containing both the computed arrays and key PLL parameters.
  return {
    dataRows: dataRows,
    plotPoints: plotPoints,
    openLoopBW: foLoop,
    closedLoopBW: fcLoop,
    phaseMargin: pmarVal,
    peaking: magPK,
    peakingFrequency: frqPK,
    rmsJitter: jitv,
    pNoiData: pNoiData
  };
}

function drawPlot(plotPoints, pllResults) {
  // Helper function to convert numbers to superscript text.
  function toSuperscript(num) {
    const superscriptDigits = { "0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹", "-": "⁻" };
    return String(num).split('').map(ch => superscriptDigits[ch] || ch).join('');
  }

  // Compute SVG dimensions (80% of browser width, 0.6 aspect ratio).
  const svgWidth = window.innerWidth * 0.8;
  const svgHeight = svgWidth * 0.6;

  // Define margins to allow room for titles and labels.
  const margin = { top: 60, right: 20, bottom: 100, left: 70 };

  // Define the plot rectangle inside the SVG.
  const rectX = margin.left;
  const rectY = margin.top;
  const rectWidth = svgWidth - margin.left - margin.right;
  const rectHeight = svgHeight - margin.top - margin.bottom;

  // Select the plot area and create/clear the SVG element.
  let plotArea = document.getElementById('plotArea');
  let svg = document.getElementById('plotSVG');
  if (!svg) {
    svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("id", "plotSVG");
    plotArea.appendChild(svg);
  }
  svg.setAttribute("width", svgWidth);
  svg.setAttribute("height", svgHeight);
  // Center the SVG horizontally.
  svg.style.display = "block";
  svg.style.margin = "0 auto";
  while (svg.firstChild) {
    svg.removeChild(svg.firstChild);
  }

  // Fixed y-axis range.
  const yMin = -40;
  const yMax = 5;

  // Determine x-axis range from the data.
  const freqs = plotPoints.map(p => p[0]);
  const minFreq_exact = Math.min(...freqs);
  const maxFreq_exact = Math.max(...freqs);
  const minFreq = Math.pow(10, Math.floor(Math.log10(minFreq_exact * 1.000001)));
  const maxFreq = Math.pow(10, Math.ceil( Math.log10(maxFreq_exact * 0.999999)));

  // Define scaling functions.
  const logMin = Math.log10(minFreq);
  const logMax = Math.log10(maxFreq);
  const xScale = f => rectX + ((Math.log10(f) - logMin) / (logMax - logMin)) * rectWidth;
  const yScale = v => rectY + ((yMax - v) / (yMax - yMin)) * rectHeight;

  // --- Draw Background & Data ---
  // Background rectangle.
  const borderRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  borderRect.setAttribute("x", rectX);
  borderRect.setAttribute("y", rectY);
  borderRect.setAttribute("width", rectWidth);
  borderRect.setAttribute("height", rectHeight);
  borderRect.setAttribute("fill", "#f0f0f0");
  borderRect.setAttribute("stroke", "black");
  borderRect.setAttribute("stroke-width", "3");
  svg.appendChild(borderRect);

  // Blue data polyline.
  const pointsStr = plotPoints.map(p => `${xScale(p[0])},${yScale(p[1])}`).join(" ");
  const polyline = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
  polyline.setAttribute("points", pointsStr);
  polyline.setAttribute("stroke", "blue");
  polyline.setAttribute("stroke-width", "2");
  polyline.setAttribute("fill", "none");
  svg.appendChild(polyline);

  // --- Draw Axes, Ticks, and Titles ---
  const majorTickLength = 10;
  const minorTickLength = 5;
  const tickColor = "black";
  const labelFontSize = "20"; // 20px, bold

  const axisGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");

  // X-axis ticks and labels.
  for (let decade = Math.floor(logMin); decade <= Math.ceil(logMax); decade++) {
    const decadeValue = Math.pow(10, decade);
    const decadeValueL = decadeValue * (1.0 - 1.0e-6);
    const decadeValueH = decadeValue * (1.0 + 1.0e-6);
    console.log("GREG : ", decade, " decadeValue=", decadeValue, " logMin =", logMin, " logMax=", logMax);
    if (decadeValueH < minFreq || decadeValueL > maxFreq) continue;
    const xPos = xScale(decadeValue);

    // Ticks on top and bottom.
    const bottomTick = document.createElementNS("http://www.w3.org/2000/svg", "line");
    bottomTick.setAttribute("x1", xPos);
    bottomTick.setAttribute("y1", rectY + rectHeight);
    bottomTick.setAttribute("x2", xPos);
    bottomTick.setAttribute("y2", rectY + rectHeight - majorTickLength);
    bottomTick.setAttribute("stroke", tickColor);
    axisGroup.appendChild(bottomTick);

    const topTick = document.createElementNS("http://www.w3.org/2000/svg", "line");
    topTick.setAttribute("x1", xPos);
    topTick.setAttribute("y1", rectY);
    topTick.setAttribute("x2", xPos);
    topTick.setAttribute("y2", rectY + majorTickLength);
    topTick.setAttribute("stroke", tickColor);
    axisGroup.appendChild(topTick);

    // X-axis tick label.
    const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
    label.setAttribute("x", xPos);
    label.setAttribute("y", rectY + rectHeight + majorTickLength + 30);
    label.setAttribute("text-anchor", "middle");
    label.setAttribute("font-size", labelFontSize);
    label.setAttribute("font-weight", "bold");
    label.setAttribute("fill", "black");
    if (decadeValue >= 1e6) {
      label.textContent = "1x10" + toSuperscript(decade);
    } else {
      label.textContent = decadeValue;
    }
    axisGroup.appendChild(label);

    // Minor ticks.
    for (let i = 2; i <= 9; i++) {
      const minorValue = i * decadeValue;
      if (minorValue < minFreq || minorValue > maxFreq) continue;
      const xMinor = xScale(minorValue);
      const bottomMinor = document.createElementNS("http://www.w3.org/2000/svg", "line");
      bottomMinor.setAttribute("x1", xMinor);
      bottomMinor.setAttribute("y1", rectY + rectHeight);
      bottomMinor.setAttribute("x2", xMinor);
      bottomMinor.setAttribute("y2", rectY + rectHeight - minorTickLength);
      bottomMinor.setAttribute("stroke", tickColor);
      axisGroup.appendChild(bottomMinor);

      const topMinor = document.createElementNS("http://www.w3.org/2000/svg", "line");
      topMinor.setAttribute("x1", xMinor);
      topMinor.setAttribute("y1", rectY);
      topMinor.setAttribute("x2", xMinor);
      topMinor.setAttribute("y2", rectY + minorTickLength);
      topMinor.setAttribute("stroke", tickColor);
      axisGroup.appendChild(topMinor);
    }
  }

  // Y-axis ticks and labels.
  for (let yVal = yMin; yVal <= yMax; yVal += 5) {
    const yPos = yScale(yVal);
    const leftTick = document.createElementNS("http://www.w3.org/2000/svg", "line");
    leftTick.setAttribute("x1", rectX);
    leftTick.setAttribute("y1", yPos);
    leftTick.setAttribute("x2", rectX + majorTickLength);
    leftTick.setAttribute("y2", yPos);
    leftTick.setAttribute("stroke", tickColor);
    axisGroup.appendChild(leftTick);

    const rightTick = document.createElementNS("http://www.w3.org/2000/svg", "line");
    rightTick.setAttribute("x1", rectX + rectWidth);
    rightTick.setAttribute("y1", yPos);
    rightTick.setAttribute("x2", rectX + rectWidth - majorTickLength);
    rightTick.setAttribute("y2", yPos);
    rightTick.setAttribute("stroke", tickColor);
    axisGroup.appendChild(rightTick);

    const yLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
    yLabel.setAttribute("x", rectX - 10);
    yLabel.setAttribute("y", yPos + 7);
    yLabel.setAttribute("text-anchor", "end");
    yLabel.setAttribute("font-size", labelFontSize);
    yLabel.setAttribute("font-weight", "bold");
    yLabel.setAttribute("fill", "black");
    yLabel.textContent = yVal;
    axisGroup.appendChild(yLabel);
  }

  // X-axis title.
  const xAxisTitle = document.createElementNS("http://www.w3.org/2000/svg", "text");
  xAxisTitle.setAttribute("x", rectX + rectWidth / 2);
  xAxisTitle.setAttribute("y", rectY + rectHeight + majorTickLength + 60);
  xAxisTitle.setAttribute("text-anchor", "middle");
  xAxisTitle.setAttribute("font-size", labelFontSize);
  xAxisTitle.setAttribute("font-weight", "bold");
  xAxisTitle.setAttribute("fill", "black");
  xAxisTitle.textContent = "Frequency";
  axisGroup.appendChild(xAxisTitle);

  // Y-axis title.
  const yAxisTitle = document.createElementNS("http://www.w3.org/2000/svg", "text");
  yAxisTitle.setAttribute("x", rectX - margin.left / 2 - 20);
  yAxisTitle.setAttribute("y", rectY + rectHeight / 2);
  yAxisTitle.setAttribute("text-anchor", "middle");
  yAxisTitle.setAttribute("font-size", labelFontSize);
  yAxisTitle.setAttribute("font-weight", "bold");
  yAxisTitle.setAttribute("fill", "black");
  yAxisTitle.textContent = "H(s)";
  yAxisTitle.setAttribute("transform", `rotate(-90, ${rectX - margin.left / 2 - 20}, ${rectY + rectHeight / 2})`);
  axisGroup.appendChild(yAxisTitle);

  // Main title.
  const mainTitle = document.createElementNS("http://www.w3.org/2000/svg", "text");
  mainTitle.setAttribute("x", rectX + rectWidth / 2);
  mainTitle.setAttribute("y", rectY - 20);
  mainTitle.setAttribute("text-anchor", "middle");
  mainTitle.setAttribute("font-size", labelFontSize);
  mainTitle.setAttribute("font-weight", "bold");
  mainTitle.setAttribute("fill", "black");
  mainTitle.textContent = "calcPLL";
  axisGroup.appendChild(mainTitle);

  svg.appendChild(axisGroup);

  // --- Print Calculated Values Inside the Plot ---
  // Only print if pllResults is provided.
  if (pllResults) {
    const calcGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
    // Set text properties and ensure text is drawn from the top.

    // calcGroup.setAttribute("stroke", "red");
    // calcGroup.setAttribute("stroke-width", "1")

    calcGroup.setAttribute("font-size", labelFontSize);
    calcGroup.setAttribute("font-weight", "bold");
    calcGroup.setAttribute("fill", "black");
    calcGroup.setAttribute("dominant-baseline", "hanging");

    // Position the calculated values starting at 10% in from the left and 20% down from the top of the plot rectangle.
    const xCalc = rectX + 0.05 * rectWidth;
    let   yCalc = rectY + 0.20 * rectHeight;
    
    // Get parameters from the DOM.
    const IoVal = document.getElementById('Io').value;
    const RVal = document.getElementById('R2').value;
    const kvVal = document.getElementById('kv').value;
    const NVal = document.getElementById('N').value;
    const C1Val = document.getElementById('C1').value;
    const C2Val = document.getElementById('C2').value;
    const fcVal = document.getElementById('fc').value;
    const LfVal = document.getElementById('Lf').value;
    
    const calcLines = [
      "Io = " + IoVal,
      "R = " + RVal,
      "kv = " + kvVal,
      "N = " + NVal,
      "C1 = " + C1Val,
      "C2 = " + C2Val,
      "",
      "Open   \u00A0 Loop Bandwidth = " + (pllResults.openLoopBW / 1e6).toFixed(3) + " MHz",
      "Closed Loop Bandwidth = " + (pllResults.closedLoopBW / 1e6).toFixed(3) + " MHz",
      "Phase Margin = " + pllResults.phaseMargin.toFixed(1) + " deg.",
      "Peaking = " + pllResults.peaking.toFixed(3) + " dB @ " + (pllResults.peakingFrequency / 1e6).toFixed(3) + " MHz",
      "",
      "fc = " + fcVal,
      "L(f) = " + LfVal + " @ 1.0 MHz",
      "RMS Jitter (10k - 10M) = " + pllResults.rmsJitter.toFixed(3) + " ps"
    ];
    
    for (let i = 0; i < calcLines.length; i++) {
      const textElem = document.createElementNS("http://www.w3.org/2000/svg", "text");
      textElem.setAttribute("x", xCalc);
      textElem.setAttribute("y", yCalc);
      textElem.setAttribute("text-anchor", "start");
      textElem.textContent = calcLines[i];
      calcGroup.appendChild(textElem);
      yCalc += 25; // Space each line.
    }
    svg.appendChild(calcGroup);
  } else {
    console.log("pllResults undefined");
  }
}