#pragma once
#include <windows.h>
#include <string>
#include <cmath>
#include <algorithm>

struct TimingModel {
    double n=0, sx=0, sy=0, sxx=0, sxy=0;
    double a=0, b=0;
    bool   fitted=false;
    double def_a=0.4, def_b=0.6;

    void recompute() {
        if (n >= 2.0) {
            double denom = n*sxx - sx*sx;
            if (std::fabs(denom) > 1e-9) {
                double bb = (n*sxy - sx*sy) / denom;
                double aa = (sy - bb*sx) / n;
                if (bb < 0.02) bb = def_b;
                if (aa < 0.0)  aa = 0.0;
                a=aa; b=bb; fitted=true; return;
            }
        }
        a=def_a; b=def_b; fitted=false;
    }

    double predict(double audio_sec) const {
        double t = (fitted ? a : def_a) + (fitted ? b : def_b) * audio_sec;
        return std::max(0.4, t);
    }

    void add_sample(double audio_sec, double proc_sec) {
        const double decay = 0.97;
        n*=decay; sx*=decay; sy*=decay; sxx*=decay; sxy*=decay;
        n+=1; sx+=audio_sec; sy+=proc_sec;
        sxx+=audio_sec*audio_sec; sxy+=audio_sec*proc_sec;
        recompute();
    }
};

struct ProgressEstimator {
    double T_hat=1.0, disp_rem=1.0, t=0.0;
    bool   done=false;

    void begin(double T_pred) {
        T_hat = std::max(0.4, T_pred);
        disp_rem = T_hat; t = 0.0; done=false;
    }

    void on_whisper(double t_now, int p) {
        if (done || p < 5) return;
        double T_meas = 100.0 * t_now / (double)p;
        const double alpha = 0.5;
        T_hat = (1.0-alpha)*T_hat + alpha*T_meas;
        if (T_hat < t_now) T_hat = t_now;
    }

    void tick(double dt, float& out_frac, float& out_remaining) {
        if (done) { out_frac=1.0f; out_remaining=0.0f; return; }
        t += dt;
        disp_rem -= dt;
        double raw_rem = std::max(0.0, T_hat - t);
        const double maxCatchUp = 2.5;
        double err = raw_rem - disp_rem;
        if (err < 0) disp_rem += std::max(err, -maxCatchUp*dt);
        if (disp_rem < 0) disp_rem = 0;
        double frac = (t + disp_rem > 1e-6) ? t/(t+disp_rem) : 0.0;
        if (frac > 0.95) frac = 0.95;
        out_frac = (float)frac;
        out_remaining = (float)disp_rem;
    }

    void finish(float& out_frac, float& out_remaining) {
        done=true; out_frac=1.0f; out_remaining=0.0f;
    }

    void reset_busy() {
        T_hat=1.0; disp_rem=1.0; t=0.0; done=false;
    }
};

inline std::wstring TimingIniPath() {
    wchar_t buf[MAX_PATH]; GetModuleFileNameW(nullptr, buf, MAX_PATH);
    std::wstring p(buf); p = p.substr(0, p.find_last_of(L"\\/"));
    return p + L"\\win-dictation.ini";
}
inline std::wstring SectionFor(const std::string& modelPath) {
    std::string base = modelPath.substr(modelPath.find_last_of("\\/")+1);
    return L"timing-" + std::wstring(base.begin(), base.end());
}
inline void PutD(const std::wstring& sec, const wchar_t* k, double v) {
    wchar_t b[64]; swprintf_s(b, L"%.6f", v);
    WritePrivateProfileStringW(sec.c_str(), k, b, TimingIniPath().c_str());
}
inline double GetD(const std::wstring& sec, const wchar_t* k, double d) {
    wchar_t b[64]; swprintf_s(b, L"%.6f", d);
    wchar_t out[64];
    GetPrivateProfileStringW(sec.c_str(), k, b, out, 64, TimingIniPath().c_str());
    return wcstod(out, nullptr);
}
inline void LoadTiming(TimingModel& m, const std::string& modelPath) {
    auto s = SectionFor(modelPath);
    m.n=GetD(s,L"n",0); m.sx=GetD(s,L"sx",0); m.sy=GetD(s,L"sy",0);
    m.sxx=GetD(s,L"sxx",0); m.sxy=GetD(s,L"sxy",0);
    m.recompute();
}
inline void SaveTiming(const TimingModel& m, const std::string& modelPath) {
    auto s = SectionFor(modelPath);
    PutD(s,L"n",m.n); PutD(s,L"sx",m.sx); PutD(s,L"sy",m.sy);
    PutD(s,L"sxx",m.sxx); PutD(s,L"sxy",m.sxy);
}

inline void SeedDefaults(TimingModel& m, const std::string& modelPath) {
    std::string p = modelPath;
    auto has = [&](const char* s){ return p.find(s)!=std::string::npos; };
    if      (has("tiny")) { m.def_a=0.3; m.def_b=0.45; }
    else if (has("base")) { m.def_a=0.5; m.def_b=1.10; }
    else if (has("small")){ m.def_a=0.8; m.def_b=3.00; }
    else                  { m.def_a=0.5; m.def_b=1.00; }
    m.recompute();
}
