/*
 * H7 RECHECK · in-process curriculum check · no per-lesson fork
 * Reads packed mega.bin (from mega.tsv) · calls linked iron limbs · fail list
 *
 *   ./Build/h7-recheck out/h7fast/mega.bin out/h7fast/fails
 * exit 0 = FULL · exit 1 = fails · prints pass/fail/N/ms
 *
 * Pack format (little-endian), one record per lesson:
 *   u32 id | u16 limb_idx | i32 a | i32 b | i32 c | i32 want   (22 bytes + 2 pad = 24)
 */
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

typedef int32_t (*limb_fn)(int32_t, int32_t, int32_t);

/* table filled by generated h7_recheck_table.c */
typedef struct {
  const char *name;
  limb_fn fn;
} LimbEnt;

extern const LimbEnt h7_limbs[];
extern const int h7_nlimbs;

#pragma pack(push, 1)
typedef struct {
  uint32_t id;
  uint16_t limb_idx;
  int32_t a, b, c, want;
  uint16_t _pad;
} Rec; /* 24 bytes */
#pragma pack(pop)

static double now_ms(void) {
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  return ts.tv_sec * 1000.0 + ts.tv_nsec / 1e6;
}

int main(int argc, char **argv) {
  const char *binpath = argc > 1 ? argv[1] : "out/h7fast/mega.bin";
  const char *faildir = argc > 2 ? argv[2] : "out/h7fast/fails";
  int clear_fails = 1;
  if (argc > 3 && strcmp(argv[3], "--keep-fails") == 0) clear_fails = 0;

  FILE *f = fopen(binpath, "rb");
  if (!f) {
    fprintf(stderr, "h7-recheck: cannot open %s\n", binpath);
    return 2;
  }
  fseek(f, 0, SEEK_END);
  long sz = ftell(f);
  fseek(f, 0, SEEK_SET);
  if (sz < 4 || (sz % (long)sizeof(Rec)) != 0) {
    fprintf(stderr, "h7-recheck: bad size %ld (want multiple of %zu)\n", sz, sizeof(Rec));
    fclose(f);
    return 2;
  }
  size_t n = (size_t)sz / sizeof(Rec);
  Rec *recs = (Rec *)malloc(sz);
  if (!recs || fread(recs, sizeof(Rec), n, f) != n) {
    fprintf(stderr, "h7-recheck: read fail\n");
    free(recs);
    fclose(f);
    return 2;
  }
  fclose(f);

  if (clear_fails) {
    /* best-effort clear fails dir contents */
    char cmd[1024];
    snprintf(cmd, sizeof cmd, "mkdir -p '%s' && find '%s' -mindepth 1 -delete 2>/dev/null", faildir,
             faildir);
    (void)system(cmd);
  } else {
    char cmd[512];
    snprintf(cmd, sizeof cmd, "mkdir -p '%s'", faildir);
    (void)system(cmd);
  }

  double t0 = now_ms();
  size_t pass = 0, fail = 0;
  uint32_t first_fail_id = UINT32_MAX;
  char first_msg[256] = "";

  for (size_t i = 0; i < n; i++) {
    Rec *r = &recs[i];
    int32_t got;
    if (r->limb_idx >= (uint16_t)h7_nlimbs || !h7_limbs[r->limb_idx].fn) {
      got = (int32_t)0x7fffffff; /* ERR sentinel */
    } else {
      got = h7_limbs[r->limb_idx].fn(r->a, r->b, r->c);
    }
    if (got == r->want) {
      pass++;
    } else {
      fail++;
      if (first_fail_id == UINT32_MAX) {
        first_fail_id = r->id;
        snprintf(first_msg, sizeof first_msg, "FAIL id=%u got=%d want=%d limb=%s a=%d b=%d c=%d",
                 r->id, got, r->want,
                 (r->limb_idx < (uint16_t)h7_nlimbs) ? h7_limbs[r->limb_idx].name : "?", r->a, r->b,
                 r->c);
      }
      /* write fail file only (sparse) */
      char path[640];
      snprintf(path, sizeof path, "%s/%u", faildir, r->id);
      FILE *of = fopen(path, "w");
      if (of) {
        fprintf(of, "FAIL got=%d want=%d limb=%s a=%d b=%d c=%d\n", got, r->want,
                (r->limb_idx < (uint16_t)h7_nlimbs) ? h7_limbs[r->limb_idx].name : "?", r->a, r->b,
                r->c);
        fclose(of);
      }
    }
  }
  double ms = now_ms() - t0;
  free(recs);

  printf("H7_RECHECK N=%zu pass=%zu fail=%zu ms=%.3f limbs=%d %s\n", n, pass, fail, ms, h7_nlimbs,
         fail == 0 ? "FULL" : "PARTIAL");
  if (fail && first_msg[0])
    printf("first_fail: %s\n", first_msg);
  /* machine line for bash */
  printf("PASS=%zu FAIL=%zu N=%zu MS=%.3f\n", pass, fail, n, ms);
  return fail == 0 ? 0 : 1;
}
