## -*- texinfo -*-
## @deftypefn {Function File} dsp_generate_report (@var{h})
## Generate a self-contained HTML report of the current filter design
## with specifications, stability analysis, plots, and coefficients.
## @end deftypefn

function dsp_generate_report(h)
    if ~isfield(h, "last_b") || isempty(h.last_b)
        errordlg("Please design a filter first.", "Report Error");
        return;
    end

    [fname, fpath] = uiputfile({"*.html", "HTML Report (*.html)"}, "Save Report");
    if isequal(fname, 0); return; end
    filepath = fullfile(fpath, fname);

    b = h.last_b;
    a = h.last_a;
    Fs       = str2double(get(h.edit_Fs,    "string"));
    Fc       = str2double(get(h.edit_Fc,    "string"));
    Fc2      = str2double(get(h.edit_Fc2,   "string"));
    N        = str2double(get(h.edit_Order, "string"));
    type_items = get(h.popup_Type, "String");
    topo_items = get(h.popup_Topo, "String");
    arch_names = {"IIR", "FIR"};
    type_name  = type_items{get(h.popup_Type, "value")};
    topo_name  = topo_items{get(h.popup_Topo, "value")};
    arch_name  = arch_names{get(h.popup_Arch, "value")};

    stab = dsp_check_stability(b, a);
    [H, w] = freqz(b, a, 1024);
    wn     = w / pi;   % normalized frequency (×π rad/sample)

    mag_db     = 20 * log10(abs(H));
    max_db     = max(mag_db);
    dc_gain_db = 20 * log10(abs(H(1)));
    thresh     = max_db - 3;
    cross      = find(diff(sign(mag_db - thresh)));
    if ~isempty(cross)
        f3db = sprintf("%.4f &times;&pi; rad/sample", wn(cross(1)));
        if numel(cross) > 1
            f3db = sprintf("%s / %.4f &times;&pi; rad/sample", f3db, wn(cross(end)));
        end
    else
        f3db = "N/A";
    end

    % Render all plots to base64 PNGs
    img_mag   = render_magnitude(wn, H);
    img_phase = render_phase(wn, H);
    img_pz    = render_pz(b, a, stab);
    img_gd    = render_gd(b, a);
    img_imp   = render_impulse(b, a, Fs);
    img_step  = render_step(b, a, Fs);

    if stab.stable
        stab_cls = "stable";
    else
        stab_cls = "unstable";
    end

    % Build HTML
    L = {};
    L{end+1} = "<!DOCTYPE html>";
    L{end+1} = "<html lang='en'>";
    L{end+1} = "<head>";
    L{end+1} = "<meta charset='UTF-8'>";
    L{end+1} = "<title>Filter Design Report - dsppack</title>";
    L{end+1} = "<style>";
    L{end+1} = "body{font-family:'Segoe UI',Arial,sans-serif;color:#333;max-width:1100px;margin:0 auto;padding:40px}";
    L{end+1} = "h1{color:#1a5276;border-bottom:3px solid #1a5276;padding-bottom:10px}";
    L{end+1} = "h2{color:#2c3e50;margin-top:35px;border-bottom:1px solid #bdc3c7;padding-bottom:5px}";
    L{end+1} = "h3{color:#34495e;margin-top:15px}";
    L{end+1} = "table{border-collapse:collapse;width:100%;margin:15px 0}";
    L{end+1} = "th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}";
    L{end+1} = "th{background:#1a5276;color:#fff}";
    L{end+1} = "tr:nth-child(even){background:#f2f2f2}";
    L{end+1} = ".grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin:20px 0}";
    L{end+1} = ".grid img{width:100%;border:1px solid #ddd;border-radius:4px}";
    L{end+1} = ".stable{color:#27ae60;font-weight:bold}";
    L{end+1} = ".unstable{color:#e74c3c;font-weight:bold}";
    L{end+1} = "pre{background:#f8f9fa;border:1px solid #ddd;border-radius:4px;padding:15px;overflow-x:auto;font-size:13px}";
    L{end+1} = ".info{color:#7f8c8d;font-size:14px;margin-top:-5px}";
    L{end+1} = "@media print{.grid{break-inside:avoid}}";
    L{end+1} = "</style>";
    L{end+1} = "</head>";
    L{end+1} = "<body>";

    L{end+1} = "<h1>Filter Design Report</h1>";
    L{end+1} = sprintf("<p class='info'>Generated by <strong>dsppack</strong> | %s</p>", datestr(now()));

    % Specifications table
    L{end+1} = "<h2>Filter Specifications</h2>";
    L{end+1} = "<table>";
    L{end+1} = "<tr><th>Parameter</th><th>Value</th></tr>";
    L{end+1} = sprintf("<tr><td>Architecture</td><td>%s</td></tr>", arch_name);
    L{end+1} = sprintf("<tr><td>Filter Type</td><td>%s</td></tr>", type_name);
    L{end+1} = sprintf("<tr><td>Topology / Method</td><td>%s</td></tr>", topo_name);
    L{end+1} = sprintf("<tr><td>Filter Order</td><td>%d</td></tr>", N);
    L{end+1} = sprintf("<tr><td>Sampling Frequency</td><td>%.0f Hz</td></tr>", Fs);
    L{end+1} = sprintf("<tr><td>Cutoff Frequency (Fc)</td><td>%.1f Hz</td></tr>", Fc);
    if get(h.popup_Type, "value") >= 3
        L{end+1} = sprintf("<tr><td>Cutoff Frequency 2 (Fc2)</td><td>%.1f Hz</td></tr>", Fc2);
    end
    L{end+1} = "</table>";

    % Analysis metrics table
    L{end+1} = "<h2>Analysis Metrics</h2>";
    L{end+1} = "<table>";
    L{end+1} = "<tr><th>Metric</th><th>Value</th></tr>";
    L{end+1} = sprintf("<tr><td>Stability</td><td class='%s'>%s</td></tr>", stab_cls, stab.status);
    L{end+1} = sprintf("<tr><td>Max Pole Radius</td><td>%.6f</td></tr>", stab.max_radius);
    L{end+1} = sprintf("<tr><td>Stability Margin</td><td>%.6f</td></tr>", stab.margin);
    L{end+1} = sprintf("<tr><td>Max Gain</td><td>%.2f dB</td></tr>", max_db);
    L{end+1} = sprintf("<tr><td>DC Gain</td><td>%.2f dB</td></tr>", dc_gain_db);
    L{end+1} = sprintf("<tr><td>-3 dB Crossing(s)</td><td>%s</td></tr>", f3db);
    L{end+1} = "</table>";

    % Plots
    L{end+1} = "<h2>Frequency Response</h2>";
    L{end+1} = "<div class='grid'>";
    L{end+1} = sprintf("<img src='data:image/png;base64,%s' alt='Magnitude Response'>", img_mag);
    L{end+1} = sprintf("<img src='data:image/png;base64,%s' alt='Phase Response'>", img_phase);
    L{end+1} = "</div>";

    L{end+1} = "<h2>Pole-Zero Diagram &amp; Group Delay</h2>";
    L{end+1} = "<div class='grid'>";
    L{end+1} = sprintf("<img src='data:image/png;base64,%s' alt='Pole-Zero Diagram'>", img_pz);
    L{end+1} = sprintf("<img src='data:image/png;base64,%s' alt='Group Delay'>", img_gd);
    L{end+1} = "</div>";

    L{end+1} = "<h2>Time Domain Response</h2>";
    L{end+1} = "<div class='grid'>";
    L{end+1} = sprintf("<img src='data:image/png;base64,%s' alt='Impulse Response'>", img_imp);
    L{end+1} = sprintf("<img src='data:image/png;base64,%s' alt='Step Response'>", img_step);
    L{end+1} = "</div>";

    % Coefficients
    L{end+1} = "<h2>Filter Coefficients</h2>";
    L{end+1} = "<h3>Numerator (b)</h3>";
    L{end+1} = sprintf("<pre>%s</pre>", format_coeff_html(b));
    L{end+1} = "<h3>Denominator (a)</h3>";
    L{end+1} = sprintf("<pre>%s</pre>", format_coeff_html(a));

    L{end+1} = "</body>";
    L{end+1} = "</html>";

    html = strjoin(L, "\n");

    fid = fopen(filepath, "w");
    if fid == -1
        errordlg(sprintf("Cannot write to:\n%s", filepath), "Report Error");
        return;
    end
    fprintf(fid, "%s", html);
    fclose(fid);

    msgbox(sprintf("Report saved to:\n%s", filepath), "Report Generated");
end

% ------------------------------------------------------------------
% Plot renderers — each returns a base64-encoded PNG string
% ------------------------------------------------------------------

function b64 = render_magnitude(wn, H)
    fig = figure("visible", "off");
    plot(wn, 20*log10(abs(H) + eps), "LineWidth", 2, "Color", [0.00, 0.45, 0.74]);
    title("Magnitude Response");
    dsp_freq_xticks();
    ylabel("Magnitude (dB)");
    grid on; xlim([0, 1]);
    b64 = fig_to_base64(fig);
end

function b64 = render_phase(wn, H)
    fig = figure("visible", "off");
    plot(wn, unwrap(angle(H)) * (180/pi), "LineWidth", 2, "Color", [0.85, 0.33, 0.10]);
    title("Phase Response");
    dsp_freq_xticks();
    ylabel("Phase (degrees)");
    grid on; xlim([0, 1]);
    b64 = fig_to_base64(fig);
end

function b64 = render_pz(b, a, stab)
    fig = figure("visible", "off");
    zeros_z = roots(b);
    poles_z = roots(a);
    theta = linspace(0, 2*pi, 256);
    plot(cos(theta), sin(theta), "k--", "LineWidth", 0.8); hold on;
    plot(real(zeros_z), imag(zeros_z), "bo", "MarkerSize", 9, "LineWidth", 2);

    safe = poles_z(stab.radii < 0.95);
    warn = poles_z(stab.radii >= 0.95 & stab.radii < 1 - 1e-10);
    crit = poles_z(stab.radii >= 1 - 1e-10);

    if ~isempty(safe)
        plot(real(safe), imag(safe), "x", "MarkerSize", 9, "LineWidth", 2, "Color", [0.13, 0.55, 0.13]);
    end
    if ~isempty(warn)
        plot(real(warn), imag(warn), "x", "MarkerSize", 10, "LineWidth", 2.5, "Color", [0.93, 0.69, 0.13]);
    end
    if ~isempty(crit)
        plot(real(crit), imag(crit), "x", "MarkerSize", 11, "LineWidth", 2.5, "Color", [0.85, 0.15, 0.15]);
    end

    if stab.max_radius > 0
        r = stab.max_radius;
        if stab.stable
            rad_col = [0.13, 0.55, 0.13];
        else
            rad_col = [0.85, 0.15, 0.15];
        end
        plot(r*cos(theta), r*sin(theta), ":", "LineWidth", 1.2, "Color", rad_col);
    end

    hold off; axis equal; grid on;
    title("Pole-Zero Diagram"); xlabel("Real Part"); ylabel("Imaginary Part");
    b64 = fig_to_base64(fig);
end

function b64 = render_gd(b, a)
    fig = figure("visible", "off");
    ws = warning("off", "all");
    [gd, w_gd] = grpdelay(b, a, 1024);
    warning(ws);
    plot(w_gd / pi, gd, "LineWidth", 2, "Color", [0.13, 0.55, 0.13]);
    title("Group Delay");
    dsp_freq_xticks();
    ylabel("Delay (samples)");
    grid on; xlim([0, 1]);
    b64 = fig_to_base64(fig);
end

function b64 = render_impulse(b, a, Fs)
    fig = figure("visible", "off");
    [h_imp, t_imp] = impz(b, a, [], Fs);
    stem(t_imp, h_imp, "filled", "MarkerSize", 3, "Color", [0.00, 0.45, 0.74]);
    title("Impulse Response"); xlabel("Time (s)"); ylabel("Amplitude");
    grid on;
    b64 = fig_to_base64(fig);
end

function b64 = render_step(b, a, Fs)
    fig = figure("visible", "off");
    [h_imp] = impz(b, a, [], Fs);
    n_pts = length(h_imp);
    step_resp = filter(b, a, ones(n_pts, 1));
    t_step = (0:n_pts-1)' / Fs;
    plot(t_step, step_resp, "LineWidth", 2, "Color", [0.64, 0.08, 0.18]);
    title("Step Response"); xlabel("Time (s)"); ylabel("Amplitude");
    grid on;
    b64 = fig_to_base64(fig);
end

% ------------------------------------------------------------------
% Helpers
% ------------------------------------------------------------------

function b64 = fig_to_base64(fig)
    tmpfile = [tempname(), ".png"];
    drawnow();
    print(fig, tmpfile, "-dpng", "-r150");
    close(fig);
    fid = fopen(tmpfile, "rb");
    raw = fread(fid, Inf, "uint8=>uint8");
    fclose(fid);
    b64 = base64_encode(raw);
    b64(b64 == char(10) | b64 == char(13)) = [];
    unlink(tmpfile);
end

function s = format_coeff_html(v)
    s = "";
    per_line = 6;
    for k = 1:numel(v)
        s = [s, sprintf("% .10g", v(k))];
        if k < numel(v)
            s = [s, ",  "];
            if mod(k, per_line) == 0
                s = [s, "\n"];
            end
        end
    end
end
