#include "shine_secret_generation.h"

#include <SD_MMC.h>
#include <Preferences.h>
#include <mbedtls/sha256.h>
#include <mbedtls/base64.h>
#include <string.h>
#include <stdint.h>
#include <driver/gpio.h>

#define PIN_SD_CLK 2
#define PIN_SD_CMD 1
#define PIN_SD_D0  3
#define SD_MEM_FILE "/argon2.bin"

#define A2_T        2u
#define A2_M        65536u
#define A2_P        1u
#define A2_DKLEN    32u
#define A2_SYNC     4u
#define A2_SEG      (A2_M / A2_SYNC)
#define A2_VERSION  0x13u
#define A2_TYPE     2u
#define A2_BLKSZ    1024u
#define TOTAL_FILLS (A2_T * A2_M - 2u)

struct B2State {
  uint64_t h[8], t[2], f[2];
  uint8_t buf[128];
  size_t buflen, outlen;
};

static bool gSdReady = false;
static bool gRunning = false;
static bool gDone = false;
static bool gError = false;
static char gMessage[96] = {};
static uint8_t gSecret[32] = {};
static char gSecretB58[64] = {};
static uint32_t gDoneBlocks = 0;
static uint32_t gStartMs = 0;
static uint32_t gCurPass = 0;
static uint32_t gCurBlock = 2;
static bool gInitDone = false;
static File gSdFile;
static uint8_t *gBufPrev = nullptr;
static uint8_t *gBufRef = nullptr;
static uint8_t *gBufOut = nullptr;
static uint8_t *gBufZero = nullptr;
static uint8_t *gBufAddr = nullptr;
static uint32_t *gJ1Seg = nullptr;
static uint8_t gH0[64] = {};
static B2State gB2S;

#define ROTR64(x,n) (((x)>>(n))|((x)<<(64-(n))))
static const uint64_t B2IV[8] = {
  0x6A09E667F3BCC908ULL,0xBB67AE8584CAA73BULL,
  0x3C6EF372FE94F82BULL,0xA54FF53A5F1D36F1ULL,
  0x510E527FADE682D1ULL,0x9B05688C2B3E6C1FULL,
  0x1F83D9ABFB41BD6BULL,0x5BE0CD19137E2179ULL
};
static const uint8_t B2SIGMA[12][16] = {
  {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
  {14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
  {11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4},
  {7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8},
  {9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13},
  {2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9},
  {12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11},
  {13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10},
  {6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
  {10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0},
  {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
  {14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3}
};

static const char B58_ALPHA[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

static void setMessage(const char *message) {
  snprintf(gMessage, sizeof(gMessage), "%s", message ? message : "");
}

static void b2_compress(B2State *S, const uint8_t *blk) {
  uint64_t m[16], v[16];
  for (int i = 0; i < 16; i++) m[i] = ((const uint64_t *)blk)[i];
  for (int i = 0; i < 8; i++) v[i] = S->h[i];
  v[8]=B2IV[0];v[9]=B2IV[1];v[10]=B2IV[2];v[11]=B2IV[3];
  v[12]=B2IV[4]^S->t[0];v[13]=B2IV[5]^S->t[1];
  v[14]=B2IV[6]^S->f[0];v[15]=B2IV[7]^S->f[1];
#define BG(r,i,a,b,c,d) \
  v[a]+=v[b]+m[B2SIGMA[r][2*(i)]]; v[d]=ROTR64(v[d]^v[a],32); \
  v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],24); \
  v[a]+=v[b]+m[B2SIGMA[r][2*(i)+1]]; v[d]=ROTR64(v[d]^v[a],16); \
  v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],63)
  for (int r = 0; r < 12; r++) {
    BG(r,0,0,4,8,12);BG(r,1,1,5,9,13);BG(r,2,2,6,10,14);BG(r,3,3,7,11,15);
    BG(r,4,0,5,10,15);BG(r,5,1,6,11,12);BG(r,6,2,7,8,13);BG(r,7,3,4,9,14);
  }
#undef BG
  for (int i = 0; i < 8; i++) S->h[i] ^= v[i] ^ v[i + 8];
}

static void b2_init(B2State *S, size_t outlen) {
  memset(S, 0, sizeof(*S));
  memcpy(S->h, B2IV, 64);
  S->h[0] ^= 0x01010000ULL | outlen;
  S->outlen = outlen;
}

static void b2_update(B2State *S, const uint8_t *in, size_t inlen) {
  while (inlen > 0) {
    size_t fill = 128 - S->buflen;
    size_t use = inlen < fill ? inlen : fill;
    memcpy(S->buf + S->buflen, in, use);
    S->buflen += use;
    in += use;
    inlen -= use;
    if (S->buflen == 128 && inlen > 0) {
      S->t[0] += 128;
      if (S->t[0] < 128) S->t[1]++;
      b2_compress(S, S->buf);
      S->buflen = 0;
    }
  }
}

static void b2_final(B2State *S, uint8_t *digest) {
  S->f[0] = ~0ULL;
  S->t[0] += S->buflen;
  if (S->t[0] < S->buflen) S->t[1]++;
  memset(S->buf + S->buflen, 0, 128 - S->buflen);
  b2_compress(S, S->buf);
  uint8_t tmp[64];
  for (int i = 0; i < 8; i++) {
    uint64_t w = S->h[i];
    for (int j = 0; j < 8; j++) tmp[i * 8 + j] = (uint8_t)(w >> (j * 8));
  }
  memcpy(digest, tmp, S->outlen);
}

static void b2hash(const uint8_t *in, size_t inlen, uint8_t *digest, size_t outlen) {
  b2_init(&gB2S, outlen);
  b2_update(&gB2S, in, inlen);
  b2_final(&gB2S, digest);
}

static void b2long(const uint8_t *in, size_t inlen, uint8_t *digest, uint32_t outlen) {
  uint8_t lenle[4] = {(uint8_t)outlen, (uint8_t)(outlen >> 8), (uint8_t)(outlen >> 16), (uint8_t)(outlen >> 24)};
  if (outlen <= 64) {
    b2_init(&gB2S, outlen);
    b2_update(&gB2S, lenle, 4);
    b2_update(&gB2S, in, inlen);
    b2_final(&gB2S, digest);
    return;
  }
  uint8_t tmp[64];
  b2_init(&gB2S, 64);
  b2_update(&gB2S, lenle, 4);
  b2_update(&gB2S, in, inlen);
  b2_final(&gB2S, tmp);
  memcpy(digest, tmp, 32);
  digest += 32;
  uint32_t rem = outlen - 32;
  while (rem > 64) {
    b2hash(tmp, 64, tmp, 64);
    memcpy(digest, tmp, 32);
    digest += 32;
    rem -= 32;
  }
  b2hash(tmp, 64, tmp, rem);
  memcpy(digest, tmp, rem);
}

#define A2G(a,b,c,d) do{ \
  (a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \
  (d)=ROTR64((d)^(a),32); \
  (c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \
  (b)=ROTR64((b)^(c),24); \
  (a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \
  (d)=ROTR64((d)^(a),16); \
  (c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \
  (b)=ROTR64((b)^(c),63); \
}while(0)
#define A2P(v) do{ \
  A2G((v)[0],(v)[4],(v)[8],(v)[12]);A2G((v)[1],(v)[5],(v)[9],(v)[13]); \
  A2G((v)[2],(v)[6],(v)[10],(v)[14]);A2G((v)[3],(v)[7],(v)[11],(v)[15]); \
  A2G((v)[0],(v)[5],(v)[10],(v)[15]);A2G((v)[1],(v)[6],(v)[11],(v)[12]); \
  A2G((v)[2],(v)[7],(v)[8],(v)[13]);A2G((v)[3],(v)[4],(v)[9],(v)[14]); \
}while(0)

static void fillBlock(uint32_t prevIdx, uint32_t refIdx, uint32_t outIdx, bool xorMode) {
  gSdFile.seek((uint64_t)prevIdx * A2_BLKSZ); gSdFile.read(gBufPrev, A2_BLKSZ);
  gSdFile.seek((uint64_t)refIdx * A2_BLKSZ); gSdFile.read(gBufRef, A2_BLKSZ);
  uint64_t *R = (uint64_t *)gBufOut, *P = (uint64_t *)gBufPrev, *F = (uint64_t *)gBufRef;
  for (int i = 0; i < 128; i++) R[i] = P[i] ^ F[i];
  uint64_t *Q = (uint64_t *)gBufRef; memcpy(Q, R, A2_BLKSZ);
  for (int j = 0; j < 8; j++) A2P(&Q[16 * j]);
  uint64_t row[16];
  for (int i = 0; i < 8; i++) {
    for (int k = 0; k < 8; k++) { row[2 * k] = Q[2 * i + 16 * k]; row[2 * k + 1] = Q[2 * i + 16 * k + 1]; }
    A2P(row);
    for (int k = 0; k < 8; k++) { Q[2 * i + 16 * k] = row[2 * k]; Q[2 * i + 16 * k + 1] = row[2 * k + 1]; }
  }
  for (int i = 0; i < 128; i++) R[i] = Q[i] ^ R[i];
  if (xorMode) {
    gSdFile.seek((uint64_t)outIdx * A2_BLKSZ); gSdFile.read(gBufPrev, A2_BLKSZ);
    uint64_t *O = (uint64_t *)gBufPrev; for (int i = 0; i < 128; i++) R[i] ^= O[i];
  }
  gSdFile.seek((uint64_t)outIdx * A2_BLKSZ); gSdFile.write(gBufOut, A2_BLKSZ);
}

static void generateAddresses(uint32_t pass, uint32_t slice) {
  memset(gBufZero, 0, A2_BLKSZ);
  uint8_t inputBlk[A2_BLKSZ]; memset(inputBlk, 0, A2_BLKSZ);
  uint64_t *iv = (uint64_t *)inputBlk;
  iv[0]=pass;iv[1]=0;iv[2]=slice;iv[3]=A2_M;iv[4]=A2_T;iv[5]=A2_TYPE;
  uint32_t count = 0;
  for (uint32_t b = 0; b < A2_SEG; b += 128) {
    iv[6] = ++count;
    {
      uint64_t *R=(uint64_t*)gBufAddr,*Z=(uint64_t*)gBufZero,*I=iv;
      for(int i=0;i<128;i++)R[i]=Z[i]^I[i];
      uint64_t *Q=R,row[16];
      for(int j=0;j<8;j++)A2P(&Q[16*j]);
      for(int i=0;i<8;i++){
        for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
        A2P(row);
        for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
      }
      for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^I[i]);
    }
    {
      uint64_t *R=(uint64_t*)gBufPrev,*Z=(uint64_t*)gBufZero,*A=(uint64_t*)gBufAddr;
      for(int i=0;i<128;i++)R[i]=Z[i]^A[i];
      uint64_t *Q=R,row[16];
      for(int j=0;j<8;j++)A2P(&Q[16*j]);
      for(int i=0;i<8;i++){
        for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
        A2P(row);
        for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
      }
      for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^A[i]);
      memcpy(gBufAddr,gBufPrev,A2_BLKSZ);
    }
    uint64_t *addr=(uint64_t*)gBufAddr;
    uint32_t cnt=(b+128<=A2_SEG)?128:(A2_SEG-b);
    for(uint32_t j=0;j<cnt;j++)gJ1Seg[b+j]=(uint32_t)(addr[j]&0xFFFFFFFFULL);
  }
}

static uint32_t indexAlpha(uint32_t pass, uint32_t slice, uint32_t posInSlice, uint32_t J1) {
  uint32_t refAreaSize;
  if (pass == 0) {
    if (slice == 0) {
      uint32_t pos = posInSlice;
      refAreaSize = (pos < 2) ? 0 : pos - 1;
    } else {
      refAreaSize = slice * A2_SEG + posInSlice - 1;
    }
  } else {
    refAreaSize = A2_M - A2_SEG + posInSlice - 1;
  }
  if (refAreaSize == 0) refAreaSize = 1;
  uint64_t relPos = (uint64_t)J1 * J1 >> 32;
  relPos = refAreaSize - 1 - ((uint64_t)refAreaSize * relPos >> 32);
  uint32_t startPos = 0;
  if (pass != 0 && slice != A2_SYNC - 1) startPos = (slice + 1) * A2_SEG;
  return (startPos + relPos) % A2_M;
}

static void sha256calc(const uint8_t *in, size_t len, uint8_t *out32) {
  mbedtls_sha256_context ctx;
  mbedtls_sha256_init(&ctx);
  mbedtls_sha256_starts(&ctx, 0);
  mbedtls_sha256_update(&ctx, in, len);
  mbedtls_sha256_finish(&ctx, out32);
  mbedtls_sha256_free(&ctx);
}

static void trimInPlace(char *text) {
  if (!text) return;
  size_t len = strlen(text);
  size_t start = 0;
  while (start < len && (text[start] == ' ' || text[start] == '\t' || text[start] == '\n' || text[start] == '\r')) start++;
  size_t end = len;
  while (end > start && (text[end - 1] == ' ' || text[end - 1] == '\t' || text[end - 1] == '\n' || text[end - 1] == '\r')) end--;
  if (start > 0 && end > start) memmove(text, text + start, end - start);
  if (end <= start) { text[0] = '\0'; return; }
  text[end - start] = '\0';
}

static void lowercaseAsciiInPlace(char *text) {
  if (!text) return;
  for (int i = 0; text[i]; i++) if (text[i] >= 'A' && text[i] <= 'Z') text[i] += 32;
}

static void normalizeLoginInPlace(char *text) {
  trimInPlace(text);
  lowercaseAsciiInPlace(text);
}

static void bytesToBase64Std(const uint8_t *data, size_t len, char *out, size_t outSz) {
  size_t b64len = 0;
  if (outSz == 0) return;
  if (mbedtls_base64_encode((uint8_t*)out, outSz, &b64len, data, len) != 0) {
    out[0] = '\0';
    return;
  }
  if (b64len >= outSz) b64len = outSz - 1;
  out[b64len] = '\0';
}

static void deriveLegacyMasterSecret(const char *password, uint8_t *out32) {
  uint8_t baseHash[32];
  sha256calc((const uint8_t*)password, strlen(password), baseHash);
  char baseB64[64];
  bytesToBase64Std(baseHash, 32, baseB64, sizeof(baseB64));
  char material[96];
  snprintf(material, sizeof(material), "%smaster.secret", baseB64);
  sha256calc((const uint8_t*)material, strlen(material), out32);
}

void shineSecretBase58Encode(const uint8_t *data, size_t len, char *out, size_t outSz) {
  int zeros = 0;
  while (zeros < (int)len && data[zeros] == 0) zeros++;
  uint8_t tmp[128] = {};
  int tmpLen = 0;
  for (int i = zeros; i < (int)len; i++) {
    int carry = data[i];
    for (int j = 0; j < tmpLen; j++) { carry += 256 * tmp[j]; tmp[j] = carry % 58; carry /= 58; }
    while (carry) { tmp[tmpLen++] = carry % 58; carry /= 58; }
  }
  int n = 0;
  for (int i = 0; i < zeros && n < (int)outSz - 1; i++) out[n++] = '1';
  for (int i = tmpLen - 1; i >= 0 && n < (int)outSz - 1; i--) out[n++] = B58_ALPHA[(int)tmp[i]];
  out[n] = '\0';
}

bool shineSecretBase58Decode(const char *input, uint8_t *out, size_t *outLen, size_t maxOutLen, String &error) {
  error = "";
  if (!input || !*input) {
    error = "secret too short";
    return false;
  }
  size_t inLen = strlen(input);
  uint8_t tmp[256] = {};
  size_t tmpLen = 0;
  size_t zeros = 0;
  while (zeros < inLen && input[zeros] == '1') zeros++;

  for (size_t i = zeros; i < inLen; ++i) {
    const char *pos = strchr(B58_ALPHA, input[i]);
    if (!pos) {
      error = "invalid base58";
      return false;
    }
    int carry = (int)(pos - B58_ALPHA);
    for (size_t j = 0; j < tmpLen; ++j) {
      carry += 58 * tmp[j];
      tmp[j] = carry & 0xFF;
      carry >>= 8;
    }
    while (carry > 0) {
      tmp[tmpLen++] = carry & 0xFF;
      carry >>= 8;
      if (tmpLen >= sizeof(tmp)) {
        error = "secret too long";
        return false;
      }
    }
  }

  size_t decodedLen = zeros + tmpLen;
  if (decodedLen > maxOutLen) {
    error = "secret too long";
    return false;
  }
  memset(out, 0, maxOutLen);
  size_t offset = 0;
  for (size_t i = 0; i < zeros; ++i) out[offset++] = 0;
  for (size_t i = 0; i < tmpLen; ++i) out[offset + i] = tmp[tmpLen - 1 - i];
  *outLen = decodedLen;
  return true;
}

static void argon2Init(const char *login, const char *password) {
  char saltSrc[256];
  snprintf(saltSrc, sizeof(saltSrc), "shine-auth-v2|login=%s|suffix=master.secret", login);
  uint8_t saltHash[32]; sha256calc((uint8_t*)saltSrc, strlen(saltSrc), saltHash);
  uint8_t salt[16]; memcpy(salt, saltHash, 16);

  char passBytes[128];
  snprintf(passBytes, sizeof(passBytes), "%s\n%s", login, password);
  uint32_t passLen = strlen(passBytes), saltLen = 16;

  b2_init(&gB2S, 64);
  uint8_t le4[4];
#define B2LE32(val) do{uint32_t _v=(val);le4[0]=_v;le4[1]=_v>>8;le4[2]=_v>>16;le4[3]=_v>>24;b2_update(&gB2S,le4,4);}while(0)
  B2LE32(A2_P);B2LE32(A2_DKLEN);B2LE32(A2_M);B2LE32(A2_T);
  B2LE32(A2_VERSION);B2LE32(A2_TYPE);
  B2LE32(passLen);b2_update(&gB2S,(const uint8_t*)passBytes,passLen);
  B2LE32(saltLen);b2_update(&gB2S,salt,saltLen);
  B2LE32(0);B2LE32(0);
#undef B2LE32
  b2_final(&gB2S, gH0);

  uint8_t input[72]; memcpy(input, gH0, 64);
  for (uint32_t i = 0; i < 2; i++) {
    input[64]=i;input[65]=0;input[66]=0;input[67]=0;
    input[68]=0;input[69]=0;input[70]=0;input[71]=0;
    b2long(input, 72, gBufOut, A2_BLKSZ);
    gSdFile.seek((uint64_t)i * A2_BLKSZ); gSdFile.write(gBufOut, A2_BLKSZ);
  }
  gCurPass = 0;
  gCurBlock = 2;
  gDoneBlocks = 0;
  gStartMs = millis();
  gInitDone = true;
  generateAddresses(0, 0);
}

bool shineSecretInitSd(String &error) {
  error = "";
  if (gSdReady) return true;
  gpio_reset_pin(GPIO_NUM_2);
  pinMode(PIN_SD_CMD, INPUT_PULLUP);
  pinMode(PIN_SD_D0, INPUT_PULLUP);
  delay(20);
  SD_MMC.setPins(PIN_SD_CLK, PIN_SD_CMD, PIN_SD_D0);
  gSdReady = SD_MMC.begin("/sdcard", true);
  if (!gSdReady) {
    error = "SD card error";
    return false;
  }
  if (!gBufPrev) {
    gBufPrev=(uint8_t*)ps_malloc(A2_BLKSZ);
    gBufRef=(uint8_t*)ps_malloc(A2_BLKSZ);
    gBufOut=(uint8_t*)ps_malloc(A2_BLKSZ);
    gBufZero=(uint8_t*)ps_calloc(1, A2_BLKSZ);
    gBufAddr=(uint8_t*)ps_malloc(A2_BLKSZ);
    gJ1Seg=(uint32_t*)ps_malloc(A2_SEG * sizeof(uint32_t));
  }
  if (!gBufPrev || !gBufRef || !gBufOut || !gBufZero || !gBufAddr || !gJ1Seg) {
    error = "PSRAM alloc failed";
    return false;
  }
  return true;
}

bool shineSecretStart(const char *normalizedLogin, const char *password, String &error) {
  error = "";
  if (!shineSecretInitSd(error)) return false;
  if (!normalizedLogin || !*normalizedLogin) {
    error = "login not set";
    return false;
  }

  char loginBuf[64];
  snprintf(loginBuf, sizeof(loginBuf), "%s", normalizedLogin);
  normalizeLoginInPlace(loginBuf);
  if (!loginBuf[0]) {
    error = "login not set";
    return false;
  }

  if (gSdFile) gSdFile.close();
  SD_MMC.remove(SD_MEM_FILE);
  gSdFile = SD_MMC.open(SD_MEM_FILE, "w+");
  if (!gSdFile) {
    error = "SD open failed";
    return false;
  }

  memset(gSecret, 0, sizeof(gSecret));
  gSecretB58[0] = '\0';
  gDone = false;
  gError = false;
  gRunning = true;
  gInitDone = false;
  setMessage("Generating secret...");

  if (!password || !password[0]) {
    deriveLegacyMasterSecret(password ? password : "", gSecret);
    shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
    gDone = true;
    gRunning = false;
    setMessage("Secret generated");
    gSdFile.close();
    SD_MMC.remove(SD_MEM_FILE);
    return true;
  }

  argon2Init(loginBuf, password);
  return true;
}

bool shineSecretStep(uint32_t blocksPerTick) {
  if (!gRunning || !gInitDone) return gDone;
  bool done = false;
  for (uint32_t i = 0; i < blocksPerTick && !done; i++) {
    if (gCurPass >= A2_T) {
      done = true;
      break;
    }
    uint32_t blk = gCurBlock;
    uint32_t slice = blk / A2_SEG;
    uint32_t posInSlice = blk - slice * A2_SEG;
    bool xorMode = (gCurPass > 0);
    uint32_t J1;
    bool useArgon2i = (gCurPass == 0 && slice < 2);
    if (useArgon2i) {
      J1 = gJ1Seg[posInSlice];
    } else {
      uint32_t prevIdx = (blk == 0) ? A2_M - 1 : blk - 1;
      gSdFile.seek((uint64_t)prevIdx * A2_BLKSZ);
      gSdFile.read(gBufRef, 8);
      J1 = ((uint32_t*)gBufRef)[0];
    }
    uint32_t prevIdx = (blk == 0) ? A2_M - 1 : blk - 1;
    uint32_t refIdx = indexAlpha(gCurPass, slice, posInSlice, J1);
    fillBlock(prevIdx, refIdx, blk, xorMode);
    gDoneBlocks++;
    gCurBlock++;
    if (gCurBlock >= A2_M) {
      gCurBlock = 0;
      gCurPass++;
    } else {
      uint32_t newSlice = gCurBlock / A2_SEG;
      if (newSlice != slice && gCurPass == 0 && newSlice < 2) generateAddresses(gCurPass, newSlice);
    }
    done = (gCurPass >= A2_T);
  }

  if (done) {
    gSdFile.seek((uint64_t)(A2_M - 1) * A2_BLKSZ);
    gSdFile.read(gBufOut, A2_BLKSZ);
    b2long(gBufOut, A2_BLKSZ, gSecret, A2_DKLEN);
    shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58));
    gDone = true;
    gRunning = false;
    setMessage("Secret generated");
    gSdFile.close();
    SD_MMC.remove(SD_MEM_FILE);
  }
  return gDone;
}

void shineSecretAbort() {
  gRunning = false;
  gDone = false;
  gError = false;
  gInitDone = false;
  gDoneBlocks = 0;
  if (gSdFile) gSdFile.close();
  if (gSdReady) SD_MMC.remove(SD_MEM_FILE);
  setMessage("Generation cancelled");
}

ShineSecretGenerationStatus shineSecretStatus() {
  ShineSecretGenerationStatus status = {};
  status.running = gRunning;
  status.done = gDone;
  status.error = gError;
  status.doneBlocks = gDoneBlocks;
  status.totalBlocks = TOTAL_FILLS;
  status.elapsedSec = gStartMs == 0 ? 0 : (millis() - gStartMs) / 1000;
  snprintf(status.message, sizeof(status.message), "%s", gMessage);
  snprintf(status.secretBase58, sizeof(status.secretBase58), "%s", gSecretB58);
  return status;
}

const uint8_t *shineSecretBytes() {
  return gSecret;
}

const char *shineSecretBase58() {
  return gSecretB58;
}
