// File: host/src/MainWindow_Actions.cpp
#include "MainWindow.h"
#include <cmath>
#include <complex>
#include <fftw3.h>
#include <vector>

void MainWindow::checkDeviceId() {
  if (serial->isOpen())
    serial->write("v\n");
}

void MainWindow::runCalibration() {
  if (serial->isOpen()) {
    int hpVal = comboTiaRange->currentData().toInt();
    serial->write(QString("r %1\n").arg(hpVal).toUtf8());
    serial->write("c\n");
  }
}

void MainWindow::onLPFChanged(int index) {
  if (serial->isOpen()) {
    int val = comboLPF->itemData(index).toInt();
    serial->write(QString("f %1\n").arg(val).toUtf8());
  }
}

// Removed: onShortRe0Se0Toggled

void MainWindow::initiateSequence(const QString &cmd) {
  if (!serial->isOpen())
    return;

  pendingCommand = cmd;
  currentSequence = SEQ_WAIT_BOOT;

  logWidget->append(">> Sequence: Resetting device (z)...");
  serial->write("z\n"); // Watchdog reboot to force clean state
}

void MainWindow::startSweep() {
  if (!serial->isOpen())
    return;

  if (isSweeping) {
    // Stop Sweep
    serial->write("x\n");
    return;
  }

  if (isMeasuringAmp)
    toggleAmperometry();
  if (lsvState != LSV_IDLE)
    stopLSV();
  if (isMeasuringImp)
    toggleMeasurement();

  rawGraph->clear();
  nyquistGraph->clear();

  sweepFreqs.clear();
  sweepReals.clear();
  sweepImags.clear();

  double start = spinSweepStart->value();
  double stop = spinSweepStop->value();
  int ppd = spinSweepPPD->value();
  double bias = spinImpBias->value(); // Use the Imp Bias control

  // Calculate Total Points based on Decades + 1 for Artifact Removal
  double decades = std::log10(stop / start);
  if (decades < 0)
    decades = std::abs(decades); // Handle swap

  // Calculate intended total points (inclusive)
  int totalPoints = (int)(decades * ppd) + 1;
  if (totalPoints < 2)
    totalPoints = 2;

  expectedSweepPoints = totalPoints; // Store what we want to keep

  // Send request for N+1 points to Firmware (Last point is artifact)
  QString cmd = QString("s %1 %2 %3 %4")
                    .arg(start)
                    .arg(stop)
                    .arg(totalPoints + 1)
                    .arg(bias);

  isSweeping = true;
  sweepBtn->setText("Stop Sweep");
  setButtonBlinking(sweepBtn, true);

  initiateSequence(cmd);
}

void MainWindow::toggleMeasurement() {
  if (!serial->isOpen())
    return;
  if (isMeasuringAmp)
    toggleAmperometry();
  if (lsvState != LSV_IDLE)
    stopLSV();
  if (isSweeping)
    startSweep(); // Stop sweep

  if (isMeasuringImp) {
    serial->write("x\n");
    measureBtn->setText("Measure");
    setButtonBlinking(nullptr, false);
    isMeasuringImp = false;
  } else {
    double freq = spinFreq->value();
    double bias = spinImpBias->value();
    QString cmd = QString("m %1 %2").arg(freq).arg(bias);

    measureBtn->setText("Stop");
    setButtonBlinking(measureBtn, true);
    isMeasuringImp = true;

    initiateSequence(cmd);
  }
}

void MainWindow::toggleAmperometry() {
  if (!serial->isOpen())
    return;
  if (isMeasuringImp)
    toggleMeasurement();
  if (lsvState != LSV_IDLE)
    stopLSV();
  if (isSweeping)
    startSweep();

  if (isMeasuringAmp) {
    serial->write("x\n");
    ampBtn->setText("Start Amp");
    setButtonBlinking(nullptr, false);
    isMeasuringAmp = false;
  } else {
    double bias = spinAmpBias->value();
    QString cmd = QString("a %1").arg(bias);

    ampBtn->setText("Stop Amp");
    setButtonBlinking(ampBtn, true);
    isMeasuringAmp = true;
    ampGraph->clear();

    initiateSequence(cmd);
  }
}

void MainWindow::startLSVBlank() {
  if (!serial->isOpen())
    return;

  // Toggle Logic: If running, stop it.
  if (lsvState == LSV_RUNNING_BLANK) {
    stopLSV();
    return;
  }

  if (isMeasuringImp)
    toggleMeasurement();
  if (isMeasuringAmp)
    toggleAmperometry();
  if (lsvState != LSV_IDLE)
    stopLSV(); // Stop sample if running
  if (isSweeping)
    startSweep();

  double start = spinLsvStart->value();
  double stop = spinLsvStop->value();
  int steps = spinLsvSteps->value();
  int duration = spinLsvDuration->value();

  QString cmd =
      QString("l %1 %2 %3 %4").arg(start).arg(stop).arg(steps).arg(duration);

  lsvBlankBtn->setText("Stop Blank");
  setButtonBlinking(lsvBlankBtn, true);
  lsvState = LSV_RUNNING_BLANK;

  // Only clear if we are actually starting a new run
  lsvGraph->clearLSV(GraphWidget::LSV_BLANK);
  lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
  lsvBlankData.clear();

  initiateSequence(cmd);
}

void MainWindow::startLSVSample() {
  if (!serial->isOpen())
    return;

  // Toggle Logic
  if (lsvState == LSV_RUNNING_SAMPLE) {
    stopLSV();
    return;
  }

  if (isMeasuringImp)
    toggleMeasurement();
  if (isMeasuringAmp)
    toggleAmperometry();
  if (lsvState != LSV_IDLE)
    stopLSV(); // Stop blank if running
  if (isSweeping)
    startSweep();

  double start = spinLsvStart->value();
  double stop = spinLsvStop->value();
  int steps = spinLsvSteps->value();
  int duration = spinLsvDuration->value();

  QString cmd =
      QString("l %1 %2 %3 %4").arg(start).arg(stop).arg(steps).arg(duration);

  lsvSampleBtn->setText("Stop Sample");
  setButtonBlinking(lsvSampleBtn, true);
  lsvState = LSV_RUNNING_SAMPLE;

  // Only clear if we are actually starting a new run
  lsvGraph->clearLSV(GraphWidget::LSV_SAMPLE);
  lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
  lsvSampleData.clear();

  initiateSequence(cmd);
}

void MainWindow::stopLSV() {
  serial->write("x\n");
  lsvBlankBtn->setText("Run Blank");
  lsvSampleBtn->setText("Run Sample");
  setButtonBlinking(nullptr, false);

  // If we just finished a sample run, calculate the difference
  if (lsvState == LSV_RUNNING_SAMPLE) {
    calculateLSVDiff();
  }

  lsvState = LSV_IDLE;
}

void MainWindow::calculateLSVDiff() {
  if (lsvBlankData.isEmpty() || lsvSampleData.isEmpty())
    return;

  lsvGraph->clearLSV(GraphWidget::LSV_DIFF);

  // Simple index-based subtraction (assumes same parameters used)
  int count = std::min(lsvBlankData.size(), lsvSampleData.size());

  for (int i = 0; i < count; i++) {
    double v = lsvSampleData[i].voltage; // Use sample voltage
    double diffCurrent = lsvSampleData[i].current - lsvBlankData[i].current;
    lsvGraph->addLSVData(v, diffCurrent, GraphWidget::LSV_DIFF);
  }

  logWidget->append(">> Calculated Difference Curve.");
}

void MainWindow::computeHilbert() {
  int n = sweepReals.size();
  if (n == 0)
    return;

  // Pad to next power of 2 for efficiency and consistency with previous logic
  int n_fft = 1;
  while (n_fft < n)
    n_fft *= 2;

  // Allocate FFTW arrays
  fftw_complex *in = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * n_fft);
  fftw_complex *out = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * n_fft);

  // Create Plans (ESTIMATE is fast for one-off sizes)
  fftw_plan p_fwd =
      fftw_plan_dft_1d(n_fft, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
  fftw_plan p_bwd =
      fftw_plan_dft_1d(n_fft, out, in, FFTW_BACKWARD, FFTW_ESTIMATE);

  // Prepare Input: Copy data and zero-pad
  for (int i = 0; i < n; i++) {
    in[i][0] = sweepReals[i]; // Real part
    in[i][1] = 0.0;           // Imag part
  }
  for (int i = n; i < n_fft; i++) {
    in[i][0] = 0.0;
    in[i][1] = 0.0;
  }

  // Forward FFT
  fftw_execute(p_fwd);

  // Apply Hilbert Mask in Frequency Domain (Analytic Signal)
  // H[0] (DC) and H[N/2] (Nyquist) are left alone.
  // Positive Frequencies (1 to N/2 - 1) multiplied by 2.
  // Negative Frequencies (N/2 + 1 to N - 1) zeroed out.

  int half_n = n_fft / 2;

  // Multiply positive frequencies by 2
  for (int i = 1; i < half_n; i++) {
    out[i][0] *= 2.0;
    out[i][1] *= 2.0;
  }

  // Zero out negative frequencies
  for (int i = half_n + 1; i < n_fft; i++) {
    out[i][0] = 0.0;
    out[i][1] = 0.0;
  }

  // Inverse FFT
  fftw_execute(p_bwd);

  // Extract Imaginary part of Analytic Signal (Hilbert Transform)
  // Note: FFTW IFFT is unnormalized, divide by N
  QVector<double> hilbertImag;
  hilbertImag.reserve(n);

  for (int i = 0; i < n; i++) {
    double val = in[i][1] / n_fft; // Imaginary part normalized
    hilbertImag.append(val);
  }

  rawGraph->addHilbertData(sweepFreqs, hilbertImag);

  // Cleanup
  fftw_destroy_plan(p_fwd);
  fftw_destroy_plan(p_bwd);
  fftw_free(in);
  fftw_free(out);
}

void MainWindow::performCircleFit() {
  int n = sweepReals.size();
  if (n < 5)
    return;

  // 1. Centering (Crucial for stability)
  double meanX = 0, meanY = 0;
  for (int i = 0; i < n; i++) {
    meanX += sweepReals[i];
    meanY += -sweepImags[i];
  }
  meanX /= n;
  meanY /= n;

  // 2. Kasa Fit (Algebraic) on Centered Data
  // Minimizes sum((x^2 + y^2) - (2Ax + 2By + C))^2

  double sum_x2 = 0, sum_y2 = 0, sum_xy = 0;
  double sum_z = 0, sum_zx = 0, sum_zy = 0;

  for (int i = 0; i < n; i++) {
    double xi = sweepReals[i] - meanX;
    double yi = -sweepImags[i] - meanY;
    double zi = xi * xi + yi * yi;

    sum_x2 += xi * xi;
    sum_y2 += yi * yi;
    sum_xy += xi * yi;
    sum_z += zi;
    sum_zx += zi * xi;
    sum_zy += zi * yi;
  }

  // Solve 3x3 Linear System (Normal Equations) for Centered Kasa
  // [ 4*sum_x2  4*sum_xy  0 ] [ A ]   [ 2*sum_zx ]
  // [ 4*sum_xy  4*sum_y2  0 ] [ B ] = [ 2*sum_zy ]
  // [ 0         0         n ] [ C ]   [ sum_z    ]

  double C = sum_z / n;

  // Solve 2x2 for A, B
  double D = 16 * (sum_x2 * sum_y2 - sum_xy * sum_xy);

  if (std::abs(D) < 1e-9)
    return; // Collinear or insufficient data

  double A = (2 * sum_zx * 4 * sum_y2 - 2 * sum_zy * 4 * sum_xy) / D;
  double B = (4 * sum_x2 * 2 * sum_zy - 4 * sum_xy * 2 * sum_zx) / D;

  double xc = A + meanX;
  double yc = B + meanY;
  double r_sq = A * A + B * B + C;

  if (r_sq <= 0)
    return;
  double r = std::sqrt(r_sq);

  // Calculate Intercepts with Real Axis (y=0)
  // (x - xc)^2 + (0 - yc)^2 = r^2
  // (x - xc)^2 = r^2 - yc^2

  double term = r * r - yc * yc;
  if (term < 0)
    return; // Circle doesn't intersect real axis

  double x1 = xc - std::sqrt(term);
  double x2 = xc + std::sqrt(term);

  double Rs = std::min(x1, x2);
  if (Rs < 0)
    Rs = std::max(x1, x2); // If one is negative, take the positive one

  if (Rs > 0) {
    lblResultRs->setText(QString(" Rs: %1 Ω").arg(Rs, 0, 'f', 2));

    double cond = (cellConstant / Rs) * 1000000.0;
    lblResultCond->setText(QString(" Cond: %1 µS/cm").arg(cond, 0, 'f', 2));

    nyquistGraph->setExtrapolatedPoint(Rs, 0);
  }
}

void MainWindow::calibrateCellConstant() {
  QString txt = lblResultRs->text();
  if (txt.contains("--")) {
    QMessageBox::warning(this, "Calibration Error",
                         "No valid Rs measurement found. Run a sweep first.");
    return;
  }

  QString numStr = txt.section(':', 1).section(QChar(0x03A9), 0, 0).trimmed();
  double measuredRs = numStr.toDouble();

  if (measuredRs <= 0)
    return;

  double stdCond = spinCondStd->value();

  cellConstant = (stdCond * 1e-6) * measuredRs;

  saveSettings();

  QMessageBox::information(this, "Calibration Success",
                           QString("Cell Constant (K) calibrated to: %1 cm⁻¹")
                               .arg(cellConstant, 0, 'f', 4));

  performCircleFit();
}