const fs = require('fs');
const path = require('path');
const { computeStateRoot } = require('./chain');
const { sha256 } = require('./hash');
const { sanitizeBase1PublicState } = require('./private_vault');

const ROOT = path.resolve(__dirname, '..', '..');
const BASE_DATA_DIR = path.resolve(process.env.MAZEX_DATA_DIR || path.join(ROOT, 'data'));
const STORAGE_ROOT = path.resolve(process.env.MAZEX_SHARDED_CHAIN_DIR || path.join(BASE_DATA_DIR, 'mazexchain'));
const STORAGE_VERSION = 'mazexchain-sharded-storage-1.0';
const DEFAULT_SEGMENT_SIZE = Math.max(10, Math.trunc(Number(process.env.MAZEX_BLOCK_SEGMENT_SIZE || 1000)));
const DEFAULT_SNAPSHOT_INTERVAL = Math.max(
  10,
  Math.trunc(Number(process.env.MAZEX_SNAPSHOT_INTERVAL_BLOCKS || 1000)),
);

function nowIso() {
  return new Date().toISOString();
}

function cloneJson(value) {
  return JSON.parse(JSON.stringify(value));
}

function safeArray(value) {
  return Array.isArray(value) ? value : [];
}

function ensureDir(dir) {
  fs.mkdirSync(dir, { recursive: true });
}

function readJsonIfExists(filePath, fallback = null) {
  if (!fs.existsSync(filePath)) return fallback;
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}

function atomicWriteText(filePath, text) {
  ensureDir(path.dirname(filePath));
  const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
  fs.writeFileSync(tmp, text, 'utf8');
  fs.renameSync(tmp, filePath);
}

function atomicWriteJson(filePath, value) {
  atomicWriteText(filePath, `${JSON.stringify(value, null, 2)}\n`);
}

function jsonLine(value) {
  return `${JSON.stringify(value)}\n`;
}

function storagePaths(root = STORAGE_ROOT) {
  return {
    root,
    meta: path.join(root, 'meta'),
    chain: path.join(root, 'chain'),
    blocks: path.join(root, 'chain', 'blocks'),
    headers: path.join(root, 'chain', 'headers'),
    state: path.join(root, 'state'),
    snapshots: path.join(root, 'snapshots'),
    indexes: path.join(root, 'indexes'),
    samples: path.join(root, 'samples'),
    local: path.join(root, 'local'),
    p2p: path.join(root, 'p2p'),
    journal: path.join(root, 'journal'),
  };
}

function ensureStorageDirs(root = STORAGE_ROOT) {
  const paths = storagePaths(root);
  for (const dir of Object.values(paths)) ensureDir(dir);
  return paths;
}

function segmentBounds(height, segmentSize = DEFAULT_SEGMENT_SIZE) {
  const safeHeight = Math.max(0, Math.trunc(Number(height || 0)));
  const start = Math.floor(safeHeight / segmentSize) * segmentSize;
  const end = start + segmentSize - 1;
  return { start, end };
}

function padHeight(value) {
  return String(Math.max(0, Math.trunc(Number(value || 0)))).padStart(6, '0');
}

function segmentName(prefix, height, segmentSize = DEFAULT_SEGMENT_SIZE) {
  const { start, end } = segmentBounds(height, segmentSize);
  return `${prefix}_${padHeight(start)}_${padHeight(end)}.jsonl`;
}

function blockSegmentPath(height, root = STORAGE_ROOT, segmentSize = DEFAULT_SEGMENT_SIZE) {
  return path.join(storagePaths(root).blocks, segmentName('blk', height, segmentSize));
}

function headerSegmentPath(height, root = STORAGE_ROOT, segmentSize = DEFAULT_SEGMENT_SIZE) {
  return path.join(storagePaths(root).headers, segmentName('hdr', height, segmentSize));
}

function blockHeader(block = {}) {
  return {
    version: block.version,
    height: block.height,
    block_hash: block.block_hash,
    previous_block_hash: block.previous_block_hash,
    created_at: block.created_at,
    producer: block.producer,
    work_score: block.work_score || 0,
    cumulative_work_score: block.cumulative_work_score || 0,
    tx_count: block.tx_count || safeArray(block.transactions).length,
    tx_root: block.tx_root,
    state_root: block.state_root,
    state_after: block.state_after,
  };
}

function publicCacheHash(state = {}) {
  return sha256({
    type: 'mazex.public_non_consensus_cache',
    seen_answer_hashes: safeArray(state.seenAnswerHashes),
    peer_reputation: state.peerReputation || {},
    step_locks: state.stepLocks || {},
    solve_receipts: safeArray(state.solveReceipts).map((receipt) => ({
      receipt_hash: receipt.receipt_hash,
      status: receipt.status,
      layer_no: receipt.layer_no,
      step_no: receipt.step_no,
      parent_block_hash: receipt.parent_block_hash,
      answer_hash: receipt.answer_hash,
      validation_id: receipt.validation_id,
    })),
  });
}

function compactLatestState(state) {
  const latest = sanitizeBase1PublicState(state);
  const blocks = safeArray(latest.chain?.blocks);
  const headBlock = blocks[blocks.length - 1] || null;
  latest.chain ||= {};
  latest.chain.blocks = [];
  latest.chain.height = headBlock?.height ?? latest.chain.height ?? 0;
  latest.chain.head_hash = headBlock?.block_hash || latest.chain.head_hash || null;
  latest.chain.head = headBlock ? blockHeader(headBlock) : null;

  latest.samples = [];
  latest.submissions = [];
  latest.networkMessages = safeArray(latest.networkMessages).slice(-100);
  latest.events = safeArray(latest.events).slice(-500);
  latest.blockSyncInbox = safeArray(latest.blockSyncInbox).slice(-100);
  latest.storage = {
    type: 'compact_latest_state',
    version: STORAGE_VERSION,
    compacted_at: nowIso(),
    block_count_externalized: blocks.length,
    blocks_path: 'chain/blocks',
    headers_path: 'chain/headers',
    cache_hash: publicCacheHash(state),
  };
  return latest;
}

function chainHeadSummary(state = {}) {
  const blocks = safeArray(state.chain?.blocks);
  const head = blocks[blocks.length - 1] || state.chain?.head || null;
  return {
    chain_id: state.chainId || state.chain?.chain_id || null,
    genesis_hash: state.chain?.genesis_hash || state.genesisHash || null,
    height: head?.height ?? state.chain?.height ?? 0,
    head_hash: head?.block_hash || state.chain?.head_hash || null,
    previous_hash: head?.previous_block_hash || null,
    state_root: head?.state_root || null,
    cumulative_work_score: head?.cumulative_work_score || 0,
    current_layer: state.currentLayer || null,
    current_step: state.currentStep || null,
    grid_size: state.gridSize || null,
    updated_at: nowIso(),
  };
}

function writeMeta(state, root = STORAGE_ROOT) {
  const paths = storagePaths(root);
  atomicWriteJson(path.join(paths.meta, 'storage_version.json'), {
    version: STORAGE_VERSION,
    segment_size: DEFAULT_SEGMENT_SIZE,
    snapshot_interval: DEFAULT_SNAPSHOT_INTERVAL,
    updated_at: nowIso(),
  });
  atomicWriteJson(path.join(paths.meta, 'genesis.json'), {
    chain_id: state.chainId || null,
    genesis_hash: state.chain?.genesis_hash || state.genesisHash || null,
    genesis_created_at: state.genesisCreatedAt || null,
    season_seed_hash: state.seasonSeed ? sha256({ season_seed: state.seasonSeed }) : null,
  });
  atomicWriteJson(path.join(paths.meta, 'chain_config.json'), {
    chain_id: state.chainId || null,
    algorithm: state.currentStepCommitment?.algorithm || state.officialStepsByKey?.[state.currentStepKey]?.algorithm || null,
    sample_cost: 0,
    invalid_submission_on_chain: false,
    storage: STORAGE_VERSION,
  });
}

function readHead(root = STORAGE_ROOT) {
  return readJsonIfExists(path.join(storagePaths(root).chain, 'head.json'), null);
}

function appendLineWithIndex(filePath, value) {
  ensureDir(path.dirname(filePath));
  const offset = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0;
  const line = jsonLine(value);
  fs.appendFileSync(filePath, line, 'utf8');
  return { file: filePath, offset, length: Buffer.byteLength(line, 'utf8') };
}

function appendBlocks(blocks, root = STORAGE_ROOT, segmentSize = DEFAULT_SEGMENT_SIZE) {
  const paths = storagePaths(root);
  const blockIndexPath = path.join(paths.indexes, 'block_index.jsonl');
  const heightIndexPath = path.join(paths.indexes, 'height_index.jsonl');
  let appended = 0;
  for (const block of blocks) {
    const blockFile = blockSegmentPath(block.height, root, segmentSize);
    const blockWrite = appendLineWithIndex(blockFile, block);
    const relativeBlockFile = path.relative(root, blockWrite.file).replace(/\\/g, '/');

    const header = blockHeader(block);
    const headerFile = headerSegmentPath(block.height, root, segmentSize);
    appendLineWithIndex(headerFile, header);

    const indexRecord = {
      height: block.height,
      block_hash: block.block_hash,
      previous_block_hash: block.previous_block_hash,
      file: relativeBlockFile,
      offset: blockWrite.offset,
      length: blockWrite.length,
      state_root: block.state_root,
      tx_root: block.tx_root,
      cumulative_work_score: block.cumulative_work_score || 0,
    };
    fs.appendFileSync(blockIndexPath, jsonLine(indexRecord), 'utf8');
    fs.appendFileSync(
      heightIndexPath,
      jsonLine({ height: block.height, block_hash: block.block_hash, file: relativeBlockFile }),
      'utf8',
    );
    appended += 1;
  }
  return appended;
}

function clearGeneratedChainFiles(root = STORAGE_ROOT) {
  const paths = storagePaths(root);
  for (const dir of [paths.blocks, paths.headers]) {
    if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
    ensureDir(dir);
  }
  for (const file of ['block_index.jsonl', 'height_index.jsonl']) {
    const p = path.join(paths.indexes, file);
    if (fs.existsSync(p)) fs.rmSync(p, { force: true });
  }
}

function syncBlockSegments(state, root = STORAGE_ROOT, segmentSize = DEFAULT_SEGMENT_SIZE) {
  const blocks = safeArray(state.chain?.blocks);
  if (!blocks.length) return { mode: 'empty', appended: 0, total_blocks: 0 };

  const storedHead = readHead(root);
  const incomingHead = blocks[blocks.length - 1];
  const storedHeight = Number(storedHead?.height);
  const canAppend =
    storedHead &&
    Number.isInteger(storedHeight) &&
    storedHeight >= 0 &&
    storedHeight < blocks.length &&
    blocks[storedHeight]?.block_hash === storedHead.head_hash;

  if (!storedHead || !canAppend || storedHeight > Number(incomingHead.height || 0)) {
    clearGeneratedChainFiles(root);
    const appended = appendBlocks(blocks, root, segmentSize);
    return { mode: 'rebuilt', appended, total_blocks: blocks.length };
  }

  const missing = blocks.slice(storedHeight + 1);
  const appended = appendBlocks(missing, root, segmentSize);
  return { mode: appended ? 'appended' : 'unchanged', appended, total_blocks: blocks.length };
}

function writeHeadAndLatest(state, root = STORAGE_ROOT) {
  const paths = storagePaths(root);
  const head = chainHeadSummary(state);
  const latest = compactLatestState(state);
  const latestHash = sha256({ type: 'mazex.latest_state', state: latest });
  atomicWriteJson(path.join(paths.chain, 'head.json'), head);
  atomicWriteJson(path.join(paths.state, 'latest.json'), latest);
  atomicWriteText(path.join(paths.state, 'latest.hash'), `${latestHash}\n`);
  return { head, latest_hash: latestHash };
}

function writeJsonIndex(filePath, value) {
  atomicWriteJson(filePath, {
    version: STORAGE_VERSION,
    updated_at: nowIso(),
    ...value,
  });
}

function writeDerivedIndexes(state, root = STORAGE_ROOT) {
  const paths = storagePaths(root);
  const solvedSteps = safeArray(state.solvedSteps);
  const rewards = safeArray(state.rewardLedger);
  const accounts = state.accounts || {};
  const answerHashes = new Set();
  for (const entry of safeArray(state.seenAnswerHashes)) {
    if (entry.answer_hash) answerHashes.add(entry.answer_hash);
    if (typeof entry === 'string') answerHashes.add(entry);
  }
  for (const step of solvedSteps) {
    if (step.answer_hash) answerHashes.add(step.answer_hash);
  }

  writeJsonIndex(path.join(paths.indexes, 'account_index.json'), { accounts });
  writeJsonIndex(path.join(paths.indexes, 'reward_index.json'), {
    rewards,
    pending_count: rewards.filter((reward) => reward.status === 'pending').length,
    confirmed_count: rewards.filter((reward) => reward.status === 'confirmed').length,
  });
  writeJsonIndex(path.join(paths.indexes, 'step_index.json'), {
    solved_steps: solvedSteps.map((step) => ({
      layer_no: step.layer_no,
      step_no: step.step_no,
      solver: step.solver,
      wallet_address: step.wallet_address,
      answer_hash: step.answer_hash,
      block_height: step.block_height || step.height || null,
      solved_at: step.solved_at,
      reward_amount: step.reward_amount,
      difficulty_score: step.difficulty_score,
      work_score: step.work_score,
    })),
  });
  writeJsonIndex(path.join(paths.indexes, 'answer_hash_index.json'), {
    answer_hashes: Array.from(answerHashes).sort(),
    count: answerHashes.size,
  });
  writeJsonIndex(path.join(paths.indexes, 'solver_index.json'), {
    leaderboard: state.leaderboard || {},
  });
}

function writeSampleIndex(state, root = STORAGE_ROOT) {
  const paths = storagePaths(root);
  const samples = safeArray(state.samples);
  const byStep = {};
  for (const sample of samples) {
    const key = `${sample.layer_id || sample.layer_no || 'unknown'}:${sample.step_id || sample.step_no || 'unknown'}`;
    byStep[key] = (byStep[key] || 0) + 1;
  }
  writeJsonIndex(path.join(paths.samples, 'sample_index.json'), {
    total_samples: samples.length,
    by_step: byStep,
    note: 'Samples are kept outside consensus storage. Full-domain samples are intentionally not stored on-chain.',
  });
}

function createSnapshot(state, root = STORAGE_ROOT, { force = false } = {}) {
  const blocks = safeArray(state.chain?.blocks);
  const anchorBlock = blocks[blocks.length - 1] || null;
  if (!anchorBlock) return null;
  const shouldWrite = force || Number(anchorBlock.height || 0) === 0 || Number(anchorBlock.height || 0) % DEFAULT_SNAPSHOT_INTERVAL === 0;
  if (!shouldWrite) return null;

  const paths = storagePaths(root);
  const snapshotState = compactLatestState(state);
  snapshotState.chain ||= {};
  snapshotState.chain.blocks = [anchorBlock];
  const stateRoot = computeStateRoot(state);
  const fullStateHash = sha256({
    type: 'mazex.snapshot.full_state',
    state: snapshotState,
  });
  const payload = {
    type: 'mazex.base1.state_snapshot',
    version: `${STORAGE_VERSION}.snapshot`,
    chain_id: state.chainId || null,
    genesis_hash: state.chain?.genesis_hash || state.genesisHash || null,
    anchor: {
      height: anchorBlock.height,
      block_hash: anchorBlock.block_hash,
      state_root: anchorBlock.state_root,
      state_after: anchorBlock.state_after,
      tx_root: anchorBlock.tx_root,
      work_score: anchorBlock.work_score || 0,
      cumulative_work_score: anchorBlock.cumulative_work_score || 0,
      previous_block_hash: anchorBlock.previous_block_hash,
    },
    state_root: stateRoot,
    state_after: anchorBlock.state_after,
    full_state_hash: fullStateHash,
  };
  const snapshot = {
    ...payload,
    created_at: nowIso(),
    state: snapshotState,
    snapshot_hash: sha256(payload),
  };
  const fileName = `snapshot_${padHeight(anchorBlock.height)}.json`;
  atomicWriteJson(path.join(paths.snapshots, fileName), snapshot);

  const indexPath = path.join(paths.snapshots, 'snapshot_index.json');
  const existing = readJsonIfExists(indexPath, { version: STORAGE_VERSION, snapshots: [] });
  const snapshots = safeArray(existing.snapshots).filter((item) => item.height !== anchorBlock.height);
  snapshots.push({
    height: anchorBlock.height,
    block_hash: anchorBlock.block_hash,
    state_root: stateRoot,
    snapshot_hash: snapshot.snapshot_hash,
    file: `snapshots/${fileName}`,
    created_at: snapshot.created_at,
  });
  snapshots.sort((a, b) => a.height - b.height);
  atomicWriteJson(indexPath, {
    version: STORAGE_VERSION,
    updated_at: nowIso(),
    snapshots,
  });
  return snapshot;
}

function writeLocalMetadata(state, root = STORAGE_ROOT) {
  const paths = storagePaths(root);
  const rejected = safeArray(state.submissions).filter((submission) => submission && submission.is_correct === false);
  if (rejected.length) {
    atomicWriteText(
      path.join(paths.local, 'rejected_submissions.jsonl'),
      rejected.map((entry) => jsonLine(entry)).join(''),
    );
  }
  atomicWriteText(
    path.join(paths.local, 'node_events.jsonl'),
    safeArray(state.events)
      .slice(-1000)
      .map((event) => jsonLine(event))
      .join(''),
  );
  atomicWriteJson(path.join(paths.p2p, 'peers.json'), { peers: safeArray(state.peers), updated_at: nowIso() });
  atomicWriteJson(path.join(paths.p2p, 'reputation.json'), {
    peer_reputation: state.peerReputation || {},
    updated_at: nowIso(),
  });
}

function writeShardedChainState(state, { root = STORAGE_ROOT, forceSnapshot = false } = {}) {
  if (!state || typeof state !== 'object') return { ok: false, code: 'INVALID_STATE' };
  const paths = ensureStorageDirs(root);
  const targetHead = chainHeadSummary(state);
  const storedHead = readHead(root);
  const latestPath = path.join(paths.state, 'latest.json');
  const blockIndexPath = path.join(paths.indexes, 'block_index.jsonl');
  const storedLatest = fs.existsSync(latestPath) ? readJsonIfExists(latestPath, {}) : {};
  const cacheUnchanged = storedLatest?.storage?.cache_hash === publicCacheHash(state);
  if (
    !forceSnapshot &&
    storedHead?.head_hash &&
    storedHead.head_hash === targetHead.head_hash &&
    fs.existsSync(latestPath) &&
    fs.existsSync(blockIndexPath) &&
    cacheUnchanged
  ) {
    writeLocalMetadata(state, root);
    return {
      ok: true,
      root,
      skipped: true,
      reason: 'chain_head_unchanged',
      head: storedHead,
    };
  }
  const journalPath = path.join(paths.journal, 'pending_write.json');
  const startedAt = nowIso();
  atomicWriteJson(journalPath, {
    version: STORAGE_VERSION,
    status: 'pending',
    started_at: startedAt,
    target_head: targetHead,
  });
  try {
    writeMeta(state, root);
    const segments = syncBlockSegments(state, root);
    const head = writeHeadAndLatest(state, root);
    writeDerivedIndexes(state, root);
    writeSampleIndex(state, root);
    writeLocalMetadata(state, root);
    const snapshot = createSnapshot(state, root, { force: forceSnapshot });
    atomicWriteJson(journalPath, {
      version: STORAGE_VERSION,
      status: 'committed',
      started_at: startedAt,
      committed_at: nowIso(),
      target_head: head.head,
    });
    fs.rmSync(journalPath, { force: true });
    return {
      ok: true,
      root,
      segments,
      head: head.head,
      latest_hash: head.latest_hash,
      snapshot_written: Boolean(snapshot),
    };
  } catch (error) {
    atomicWriteJson(journalPath, {
      version: STORAGE_VERSION,
      status: 'failed',
      started_at: startedAt,
      failed_at: nowIso(),
      message: error.message,
      target_head: chainHeadSummary(state),
    });
    throw error;
  }
}

function readJsonLines(filePath, { tolerateTrailingPartial = true } = {}) {
  if (!fs.existsSync(filePath)) return [];
  const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
  let lastNonEmpty = -1;
  for (let index = lines.length - 1; index >= 0; index -= 1) {
    if (lines[index].trim()) {
      lastNonEmpty = index;
      break;
    }
  }
  const records = [];
  for (let index = 0; index < lines.length; index += 1) {
    const line = lines[index].trim();
    if (!line) continue;
    try {
      records.push(JSON.parse(line));
    } catch (error) {
      if (tolerateTrailingPartial && index === lastNonEmpty) {
        break;
      }
      throw new Error(`Invalid JSONL record in ${filePath} at line ${index + 1}: ${error.message}`);
    }
  }
  return records;
}

function listFilesRecursive(dir, predicate = () => true) {
  if (!fs.existsSync(dir)) return [];
  const out = [];
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const p = path.join(dir, entry.name);
    if (entry.isDirectory()) out.push(...listFilesRecursive(p, predicate));
    else if (predicate(p)) out.push(p);
  }
  return out;
}

function readAllShardedBlocks(root = STORAGE_ROOT, { untilHeight = null, untilHash = null } = {}) {
  const paths = storagePaths(root);
  const files = listFilesRecursive(paths.blocks, (file) => file.endsWith('.jsonl')).sort();
  const blocks = [];
  for (const file of files) blocks.push(...readJsonLines(file));
  blocks.sort((a, b) => Number(a.height || 0) - Number(b.height || 0));
  if (untilHeight !== null && untilHeight !== undefined) {
    const maxHeight = Math.trunc(Number(untilHeight));
    const filtered = blocks.filter((block) => Number(block.height || 0) <= maxHeight);
    if (untilHash) {
      const anchorIndex = filtered.findIndex(
        (block) => Number(block.height || 0) === maxHeight && block.block_hash === untilHash,
      );
      if (anchorIndex >= 0) return filtered.slice(0, anchorIndex + 1);
    }
    return filtered;
  }
  return blocks;
}

function loadLatestFromShardedStorage({ root = STORAGE_ROOT, hydrateBlocks = true } = {}) {
  const paths = storagePaths(root);
  const latestPath = path.join(paths.state, 'latest.json');
  const latestHashPath = path.join(paths.state, 'latest.hash');
  if (!fs.existsSync(latestPath)) return null;
  const latest = readJsonIfExists(latestPath);
  const expectedHash = fs.existsSync(latestHashPath) ? fs.readFileSync(latestHashPath, 'utf8').trim() : null;
  const actualHash = sha256({ type: 'mazex.latest_state', state: latest });
  if (expectedHash && expectedHash !== actualHash) {
    throw new Error(`Latest sharded state hash mismatch: expected ${expectedHash}, got ${actualHash}`);
  }
  if (hydrateBlocks) {
    latest.chain ||= {};
    const compactHead = latest.chain.head || null;
    const hydrateHeight = compactHead?.height ?? latest.chain.height ?? null;
    const hydrateHash = compactHead?.block_hash || latest.chain.head_hash || null;
    latest.chain.blocks = readAllShardedBlocks(root, {
      untilHeight: hydrateHeight,
      untilHash: hydrateHash,
    });
    const head = latest.chain.blocks[latest.chain.blocks.length - 1] || null;
    if (head) {
      latest.chain.height = head.height;
      latest.chain.head_hash = head.block_hash;
    }
  }
  return latest;
}

function directorySizeBytes(dir) {
  if (!fs.existsSync(dir)) return 0;
  let total = 0;
  for (const file of listFilesRecursive(dir)) total += fs.statSync(file).size;
  return total;
}

function shardedStorageStatus(root = STORAGE_ROOT) {
  const paths = storagePaths(root);
  const head = readHead(root);
  const journalPath = path.join(paths.journal, 'pending_write.json');
  const journal = readJsonIfExists(journalPath, null);
  const blockFiles = listFilesRecursive(paths.blocks, (file) => file.endsWith('.jsonl'));
  const headerFiles = listFilesRecursive(paths.headers, (file) => file.endsWith('.jsonl'));
  const snapshotFiles = listFilesRecursive(paths.snapshots, (file) => file.startsWith(paths.snapshots) && file.endsWith('.json'));
  const rawBlocks = readAllShardedBlocks(root);
  const committedBlocks = head
    ? readAllShardedBlocks(root, {
        untilHeight: head.height,
        untilHash: head.head_hash,
      })
    : rawBlocks;
  return {
    ok: true,
    version: STORAGE_VERSION,
    root,
    exists: fs.existsSync(root),
    journal: journal
      ? {
          status: journal.status || 'unknown',
          started_at: journal.started_at || null,
          failed_at: journal.failed_at || null,
          committed_at: journal.committed_at || null,
          target_head: journal.target_head || null,
        }
      : null,
    head,
    block_count: committedBlocks.length,
    raw_block_count: rawBlocks.length,
    uncommitted_tail_count: Math.max(0, rawBlocks.length - committedBlocks.length),
    block_files: blockFiles.length,
    header_files: headerFiles.length,
    snapshot_files: snapshotFiles.length,
    bytes: {
      total: directorySizeBytes(root),
      blocks: directorySizeBytes(paths.blocks),
      headers: directorySizeBytes(paths.headers),
      state: directorySizeBytes(paths.state),
      snapshots: directorySizeBytes(paths.snapshots),
      indexes: directorySizeBytes(paths.indexes),
      samples: directorySizeBytes(paths.samples),
    },
    paths: {
      root,
      blocks: paths.blocks,
      headers: paths.headers,
      latest: path.join(paths.state, 'latest.json'),
      head: path.join(paths.chain, 'head.json'),
    },
  };
}

module.exports = {
  DEFAULT_SEGMENT_SIZE,
  DEFAULT_SNAPSHOT_INTERVAL,
  STORAGE_ROOT,
  STORAGE_VERSION,
  blockHeader,
  blockSegmentPath,
  chainHeadSummary,
  compactLatestState,
  createSnapshot,
  headerSegmentPath,
  loadLatestFromShardedStorage,
  readAllShardedBlocks,
  shardedStorageStatus,
  storagePaths,
  writeShardedChainState,
};
