#pragma once

#include <cmath>
#include <cstddef>

/**
 * Largest Triangle Three Buckets (LTTB) downsampling.
 *
 * Reduces n input samples (ts, vs) to at most `threshold` output samples
 * while preserving the visual shape of the signal.
 *
 * - First and last points are always included.
 * - For each intermediate bucket the point forming the largest triangle
 *   with the previous selected point and the centroid of the next bucket
 *   is selected.
 *
 * Returns the number of output samples written (= min(n, threshold)).
 * threshold must be >= 2.
 */
inline size_t lttb(const double* ts, const double* vs, size_t n,
                   double* out_t, double* out_v, size_t threshold)
{
    if (n == 0 || threshold == 0) return 0;
    if (n <= threshold) {
        for (size_t i = 0; i < n; ++i) { out_t[i] = ts[i]; out_v[i] = vs[i]; }
        return n;
    }

    size_t out = 0;

    // Always include first point
    out_t[out] = ts[0];
    out_v[out] = vs[0];
    ++out;

    // threshold-2 intermediate buckets (excluding first and last points)
    const size_t sampled = threshold - 2;
    size_t a = 0;  // index of last selected point

    for (size_t bucket = 0; bucket < sampled; ++bucket) {
        // Logical range for current bucket [b0, b1)
        const size_t b0 = 1 + bucket       * (n - 2) / sampled;
        const size_t b1 = 1 + (bucket + 1) * (n - 2) / sampled;

        // Centroid of the NEXT bucket (used as the "C" point in the triangle)
        const size_t c0 = b1;
        const size_t c1 = (bucket + 1 < sampled)
            ? (1 + (bucket + 2) * (n - 2) / sampled)
            : n;  // final bucket extends to include the last point

        double avg_t = 0.0, avg_v = 0.0;
        const size_t c_len = c1 - c0;
        for (size_t k = c0; k < c1; ++k) { avg_t += ts[k]; avg_v += vs[k]; }
        if (c_len > 0) { avg_t /= c_len; avg_v /= c_len; }

        // Find point in [b0, b1) forming the largest triangle with a and centroid
        double max_area = -1.0;
        size_t best = b0;
        for (size_t k = b0; k < b1; ++k) {
            // Area of triangle (a, k, avg): 0.5 * |cross(a→k, a→avg)|
            double area = std::abs(
                (ts[a] - avg_t) * (vs[k] - vs[a]) -
                (ts[k] - ts[a]) * (avg_v - vs[a])
            ) * 0.5;
            if (area > max_area) { max_area = area; best = k; }
        }

        out_t[out] = ts[best];
        out_v[out] = vs[best];
        ++out;
        a = best;
    }

    // Always include last point
    out_t[out] = ts[n - 1];
    out_v[out] = vs[n - 1];
    ++out;

    return out;
}
