'use strict';

// Dependency-free ISO-BMFF remuxer. It copies encoded samples verbatim and rebuilds
// flat sample tables, which gives lossless cuts and turns heavily fragmented captures
// into seek-friendly MP4 files without shipping FFmpeg or another media runtime.

const fs = require('node:fs');
const fsp = require('node:fs/promises');

const U32_MAX = 0xffff_ffff;
const COPY_BUFFER_BYTES = 4 * 1024 * 1024;
const SAMPLE_LIMIT = 50_000_000;

function fail(message) { throw new Error(message); }
function aborted(signal) {
  if (signal?.aborted) fail('Export cancelled');
}

function readU64(buffer, offset) {
  const value = buffer.readBigUInt64BE(offset);
  if (value > BigInt(Number.MAX_SAFE_INTEGER)) fail('MP4 offset exceeds the safe integer range.');
  return Number(value);
}

function writeU64(value) {
  const output = Buffer.alloc(8);
  output.writeBigUInt64BE(BigInt(Math.max(0, Math.round(value))));
  return output;
}

function parseBoxes(buffer, start = 0, end = buffer.length) {
  const boxes = [];
  let position = start;
  while (position + 8 <= end) {
    let size = buffer.readUInt32BE(position);
    const type = buffer.toString('latin1', position + 4, position + 8);
    let header = 8;
    if (size === 1) {
      if (position + 16 > end) fail(`Truncated ${type} box.`);
      size = readU64(buffer, position + 8);
      header = 16;
    } else if (size === 0) size = end - position;
    if (size < header || position + size > end) fail(`Invalid ${type} box size.`);
    boxes.push({ type, start: position, end: position + size, size, header, dataStart: position + header, buffer });
    position += size;
  }
  if (position !== end && end - position > 7) fail('Invalid trailing MP4 box data.');
  return boxes;
}

function children(parent) {
  return parseBoxes(parent.buffer, parent.dataStart, parent.end);
}

function raw(item) { return item.buffer.subarray(item.start, item.end); }
function child(parent, type) { return children(parent).find((item) => item.type === type); }

function box(type, ...parts) {
  const body = Buffer.concat(parts.filter(Boolean));
  const size = body.length + 8;
  if (size > U32_MAX) fail(`${type} metadata is too large.`);
  const header = Buffer.alloc(8);
  header.writeUInt32BE(size, 0);
  header.write(type, 4, 4, 'latin1');
  return Buffer.concat([header, body]);
}

function fullBox(type, version, flags, body) {
  const full = Buffer.alloc(4);
  full[0] = version;
  full.writeUIntBE(flags, 1, 3);
  return box(type, full, body);
}

function patchDuration(item, duration, kind) {
  const output = Buffer.from(raw(item));
  const data = item.header;
  const version = output[data];
  let offset;
  if (kind === 'tkhd') offset = version === 1 ? data + 28 : data + 20;
  else offset = version === 1 ? data + 24 : data + 16;
  if (version === 1) output.writeBigUInt64BE(BigInt(Math.max(0, Math.round(duration))), offset);
  else output.writeUInt32BE(Math.min(U32_MAX, Math.max(0, Math.round(duration))), offset);
  return output;
}

function movieHeader(item) {
  const version = item.buffer[item.dataStart];
  const timescaleOffset = item.dataStart + (version === 1 ? 20 : 12);
  return { timescale: item.buffer.readUInt32BE(timescaleOffset) };
}

function trackIdFromTkhd(item) {
  const version = item.buffer[item.dataStart];
  return item.buffer.readUInt32BE(item.dataStart + (version === 1 ? 20 : 12));
}

function mediaHeader(item) {
  const version = item.buffer[item.dataStart];
  const timescaleOffset = item.dataStart + (version === 1 ? 20 : 12);
  return { timescale: item.buffer.readUInt32BE(timescaleOffset) };
}

function handlerType(item) { return item.buffer.toString('latin1', item.dataStart + 8, item.dataStart + 12); }

function parseStts(item, count) {
  const durations = new Uint32Array(count);
  const entries = item.buffer.readUInt32BE(item.dataStart + 4);
  let cursor = item.dataStart + 8;
  let sample = 0;
  for (let index = 0; index < entries; index += 1) {
    const run = item.buffer.readUInt32BE(cursor);
    const duration = item.buffer.readUInt32BE(cursor + 4);
    cursor += 8;
    if (sample + run > count) fail('stts contains too many samples.');
    durations.fill(duration, sample, sample + run);
    sample += run;
  }
  if (sample !== count) fail('stts sample count does not match stsz.');
  return durations;
}

function parseCtts(item, count) {
  const offsets = new Float64Array(count);
  if (!item) return offsets;
  const version = item.buffer[item.dataStart];
  const entries = item.buffer.readUInt32BE(item.dataStart + 4);
  let cursor = item.dataStart + 8;
  let sample = 0;
  for (let index = 0; index < entries; index += 1) {
    const run = item.buffer.readUInt32BE(cursor);
    const value = version === 1 ? item.buffer.readInt32BE(cursor + 4) : item.buffer.readUInt32BE(cursor + 4);
    cursor += 8;
    if (sample + run > count) fail('ctts contains too many samples.');
    offsets.fill(value, sample, sample + run);
    sample += run;
  }
  if (sample !== count) fail('ctts sample count does not match stsz.');
  return offsets;
}

function parseSizes(item) {
  if (item.type === 'stz2') {
    const fieldSize = item.buffer[item.dataStart + 7];
    const count = item.buffer.readUInt32BE(item.dataStart + 8);
    if (!count || count > SAMPLE_LIMIT || ![4, 8, 16].includes(fieldSize)) fail('Invalid compact MP4 sample-size table.');
    const sizes = new Uint32Array(count);
    let cursor = item.dataStart + 12;
    for (let index = 0; index < count; index += 1) {
      if (fieldSize === 4) sizes[index] = index % 2 ? item.buffer[cursor++] & 0x0f : item.buffer[cursor] >>> 4;
      else if (fieldSize === 8) sizes[index] = item.buffer[cursor++];
      else { sizes[index] = item.buffer.readUInt16BE(cursor); cursor += 2; }
    }
    return sizes;
  }
  const constant = item.buffer.readUInt32BE(item.dataStart + 4);
  const count = item.buffer.readUInt32BE(item.dataStart + 8);
  if (!count || count > SAMPLE_LIMIT) fail('MP4 sample count is missing or unreasonable.');
  const sizes = new Uint32Array(count);
  if (constant) sizes.fill(constant);
  else {
    let cursor = item.dataStart + 12;
    if (cursor + count * 4 > item.end) fail('Truncated stsz table.');
    for (let index = 0; index < count; index += 1, cursor += 4) sizes[index] = item.buffer.readUInt32BE(cursor);
  }
  return sizes;
}

function parseChunkOffsets(item) {
  const count = item.buffer.readUInt32BE(item.dataStart + 4);
  const offsets = new Float64Array(count);
  let cursor = item.dataStart + 8;
  for (let index = 0; index < count; index += 1) {
    offsets[index] = item.type === 'co64' ? readU64(item.buffer, cursor) : item.buffer.readUInt32BE(cursor);
    cursor += item.type === 'co64' ? 8 : 4;
  }
  return offsets;
}

function parseSampleOffsets(stsc, chunkOffsets, sizes) {
  const entries = stsc.buffer.readUInt32BE(stsc.dataStart + 4);
  const map = [];
  let cursor = stsc.dataStart + 8;
  for (let index = 0; index < entries; index += 1, cursor += 12) {
    const description = stsc.buffer.readUInt32BE(cursor + 8);
    if (description !== 1) fail('MP4 tracks with changing sample descriptions are not supported for lossless export.');
    map.push({
      firstChunk: stsc.buffer.readUInt32BE(cursor),
      perChunk: stsc.buffer.readUInt32BE(cursor + 4),
    });
  }
  const offsets = new Float64Array(sizes.length);
  let sample = 0;
  let mapIndex = 0;
  for (let chunkIndex = 0; chunkIndex < chunkOffsets.length; chunkIndex += 1) {
    const oneBased = chunkIndex + 1;
    while (mapIndex + 1 < map.length && map[mapIndex + 1].firstChunk <= oneBased) mapIndex += 1;
    const entry = map[mapIndex];
    if (!entry) fail('stsc does not describe the first chunk.');
    let offset = chunkOffsets[chunkIndex];
    for (let inChunk = 0; inChunk < entry.perChunk && sample < sizes.length; inChunk += 1) {
      offsets[sample] = offset;
      offset += sizes[sample];
      sample += 1;
    }
  }
  if (sample !== sizes.length) fail('Chunk tables do not cover every sample.');
  return offsets;
}

function parseSync(item, count, video) {
  const sync = new Uint8Array(count);
  if (!video || !item) { sync.fill(1); return sync; }
  const entries = item.buffer.readUInt32BE(item.dataStart + 4);
  let cursor = item.dataStart + 8;
  for (let index = 0; index < entries; index += 1, cursor += 4) {
    const sample = item.buffer.readUInt32BE(cursor) - 1;
    if (sample >= 0 && sample < count) sync[sample] = 1;
  }
  return sync;
}

function addDecodeTimes(track) {
  track.dts = new Float64Array(track.sizes.length);
  let value = 0;
  for (let index = 0; index < track.sizes.length; index += 1) {
    track.dts[index] = value;
    value += track.durations[index];
  }
  track.inputDuration = value;
}

function parseTrack(trak) {
  const tkhd = child(trak, 'tkhd');
  const mdia = child(trak, 'mdia');
  const mdhd = mdia && child(mdia, 'mdhd');
  const hdlr = mdia && child(mdia, 'hdlr');
  const minf = mdia && child(mdia, 'minf');
  const stbl = minf && child(minf, 'stbl');
  const stsd = stbl && child(stbl, 'stsd');
  if (!tkhd || !mdhd || !hdlr || !minf || !stbl || !stsd) return null;
  const kind = handlerType(hdlr);
  if (kind !== 'vide' && kind !== 'soun') return null;
  return {
    id: trackIdFromTkhd(tkhd),
    video: kind === 'vide',
    timescale: mediaHeader(mdhd).timescale,
    trak, tkhd, mdia, mdhd, minf, stbl, stsd,
  };
}

function loadFlatTables(track) {
  const stsz = child(track.stbl, 'stsz') || child(track.stbl, 'stz2');
  const stts = child(track.stbl, 'stts');
  const stsc = child(track.stbl, 'stsc');
  const offsetBox = child(track.stbl, 'stco') || child(track.stbl, 'co64');
  if (!stsz || !stts || !stsc || !offsetBox) return false;
  if (stsz.buffer.readUInt32BE(stsz.dataStart + 8) === 0) return false;
  track.sizes = parseSizes(stsz);
  track.durations = parseStts(stts, track.sizes.length);
  track.cto = parseCtts(child(track.stbl, 'ctts'), track.sizes.length);
  track.offsets = parseSampleOffsets(stsc, parseChunkOffsets(offsetBox), track.sizes);
  track.sync = parseSync(child(track.stbl, 'stss'), track.sizes.length, track.video);
  addDecodeTimes(track);
  return true;
}

function parseTrex(moov) {
  const defaults = new Map();
  const mvex = child(moov, 'mvex');
  if (!mvex) return defaults;
  for (const item of children(mvex).filter((candidate) => candidate.type === 'trex')) {
    defaults.set(item.buffer.readUInt32BE(item.dataStart + 4), {
      duration: item.buffer.readUInt32BE(item.dataStart + 12),
      size: item.buffer.readUInt32BE(item.dataStart + 16),
      flags: item.buffer.readUInt32BE(item.dataStart + 20),
    });
  }
  return defaults;
}

function readTfhd(item, moofOffset, trex) {
  const flags = item.buffer.readUIntBE(item.dataStart + 1, 3);
  const trackId = item.buffer.readUInt32BE(item.dataStart + 4);
  let cursor = item.dataStart + 8;
  let base = moofOffset;
  if (flags & 0x000001) { base = readU64(item.buffer, cursor); cursor += 8; }
  if (flags & 0x000002) cursor += 4;
  const result = { trackId, base, ...trex.get(trackId) };
  if (flags & 0x000008) { result.duration = item.buffer.readUInt32BE(cursor); cursor += 4; }
  if (flags & 0x000010) { result.size = item.buffer.readUInt32BE(cursor); cursor += 4; }
  if (flags & 0x000020) result.flags = item.buffer.readUInt32BE(cursor);
  return result;
}

function parseTrun(item, defaults, base, previousEnd, decodeTime, target) {
  const version = item.buffer[item.dataStart];
  const flags = item.buffer.readUIntBE(item.dataStart + 1, 3);
  const count = item.buffer.readUInt32BE(item.dataStart + 4);
  if (target.sizes.length + count > SAMPLE_LIMIT) fail('Fragmented MP4 sample count is unreasonable.');
  let cursor = item.dataStart + 8;
  let offset = previousEnd;
  if (flags & 0x000001) { offset = base + item.buffer.readInt32BE(cursor); cursor += 4; }
  if (!Number.isFinite(offset)) fail('A fragmented MP4 run has no data offset.');
  let firstFlags;
  if (flags & 0x000004) { firstFlags = item.buffer.readUInt32BE(cursor); cursor += 4; }
  for (let index = 0; index < count; index += 1) {
    const duration = flags & 0x000100 ? item.buffer.readUInt32BE(cursor) : defaults.duration; if (flags & 0x000100) cursor += 4;
    const size = flags & 0x000200 ? item.buffer.readUInt32BE(cursor) : defaults.size; if (flags & 0x000200) cursor += 4;
    let sampleFlags = defaults.flags || 0;
    if (flags & 0x000400) { sampleFlags = item.buffer.readUInt32BE(cursor); cursor += 4; }
    else if (index === 0 && firstFlags !== undefined) sampleFlags = firstFlags;
    let composition = 0;
    if (flags & 0x000800) { composition = version === 1 ? item.buffer.readInt32BE(cursor) : item.buffer.readUInt32BE(cursor); cursor += 4; }
    if (!duration || !size) fail('A fragmented MP4 sample has no duration or size.');
    target.sizes.push(size);
    target.durations.push(duration);
    target.cto.push(composition);
    target.offsets.push(offset);
    target.dts.push(decodeTime);
    target.sync.push((sampleFlags & 0x0001_0000) ? 0 : 1);
    offset += size;
    decodeTime += duration;
  }
  return { offset, decodeTime };
}

async function topLevel(handle, fileSize) {
  const entries = [];
  let position = 0;
  const header = Buffer.alloc(16);
  while (position + 8 <= fileSize) {
    const { bytesRead } = await handle.read(header, 0, 16, position);
    if (bytesRead < 8) fail('Truncated MP4 box header.');
    let size = header.readUInt32BE(0);
    const type = header.toString('latin1', 4, 8);
    let headerSize = 8;
    if (size === 1) {
      if (bytesRead < 16) fail(`Truncated ${type} box header.`);
      size = readU64(header, 8);
      headerSize = 16;
    } else if (size === 0) size = fileSize - position;
    if (size < headerSize || position + size > fileSize) fail(`Invalid top-level ${type} box.`);
    entries.push({ type, offset: position, size, header: headerSize });
    position += size;
  }
  if (position !== fileSize) fail('Trailing bytes after the final MP4 box.');
  return entries;
}

async function readExact(handle, length, position) {
  const buffer = Buffer.alloc(length);
  let completed = 0;
  while (completed < length) {
    const result = await handle.read(buffer, completed, length - completed, position + completed);
    if (!result.bytesRead) fail('The MP4 file ended unexpectedly.');
    completed += result.bytesRead;
  }
  return buffer;
}

async function loadFragmentTables(handle, top, tracks, moov, signal) {
  const byId = new Map(tracks.map((track) => [track.id, track]));
  const defaults = parseTrex(moov);
  for (const track of tracks) {
    track.sizes = []; track.durations = []; track.cto = []; track.offsets = []; track.dts = []; track.sync = [];
  }
  for (const entry of top) {
    if (entry.type !== 'moof') continue;
    aborted(signal);
    const buffer = await readExact(handle, entry.size, entry.offset);
    const root = parseBoxes(buffer)[0];
    for (const traf of children(root).filter((item) => item.type === 'traf')) {
      const tfhdBox = child(traf, 'tfhd');
      const tfdtBox = child(traf, 'tfdt');
      if (!tfhdBox) continue;
      const tfhd = readTfhd(tfhdBox, entry.offset, defaults);
      const track = byId.get(tfhd.trackId);
      if (!track) continue;
      let decodeTime = track.dts.length ? track.dts.at(-1) + track.durations.at(-1) : 0;
      if (tfdtBox) decodeTime = tfdtBox.buffer[tfdtBox.dataStart] === 1
        ? readU64(tfdtBox.buffer, tfdtBox.dataStart + 4)
        : tfdtBox.buffer.readUInt32BE(tfdtBox.dataStart + 4);
      let previousEnd = Number.NaN;
      for (const trun of children(traf).filter((item) => item.type === 'trun')) {
        const result = parseTrun(trun, tfhd, tfhd.base, previousEnd, decodeTime, track);
        previousEnd = result.offset;
        decodeTime = result.decodeTime;
      }
    }
  }
  for (const track of tracks) {
    if (!track.sizes.length) fail(`Fragmented MP4 ${track.video ? 'video' : 'audio'} track has no samples.`);
    track.sizes = Uint32Array.from(track.sizes);
    track.durations = Uint32Array.from(track.durations);
    track.cto = Float64Array.from(track.cto);
    track.offsets = Float64Array.from(track.offsets);
    track.dts = Float64Array.from(track.dts);
    track.sync = Uint8Array.from(track.sync);
    track.inputDuration = track.dts.at(-1) + track.durations.at(-1);
  }
}

function lowerBound(track, value) {
  let low = 0, high = track.sizes.length;
  while (low < high) {
    const middle = (low + high) >>> 1;
    if (track.dts[middle] < value) low = middle + 1;
    else high = middle;
  }
  return low;
}

function videoRanges(track, plans) {
  const sync = [];
  for (let index = 0; index < track.sync.length; index += 1) if (track.sync[index]) sync.push(index);
  if (!sync.length) fail('The MP4 video track has no keyframes.');
  const ranges = [];
  for (const plan of plans) {
    const wantedStart = plan.start * track.timescale;
    const wantedEnd = plan.end * track.timescale;
    let start = sync[0];
    for (const index of sync) {
      if (track.dts[index] > wantedStart) break;
      start = index;
    }
    let end = track.sizes.length;
    for (const index of sync) {
      if (index > start && track.dts[index] >= wantedEnd) { end = index; break; }
    }
    if (end <= start) continue;
    const startTime = track.dts[start];
    const endTime = end < track.sizes.length ? track.dts[end] : track.dts[end - 1] + track.durations[end - 1];
    ranges.push({ start, end, startSeconds: startTime / track.timescale, endSeconds: endTime / track.timescale });
  }
  return ranges;
}

function audioRanges(track, video) {
  return video.map((range) => {
    const wantedStart = range.startSeconds * track.timescale;
    const wantedEnd = range.endSeconds * track.timescale;
    let start = Math.max(0, lowerBound(track, wantedStart) - 1);
    while (start < track.sizes.length && track.dts[start] + track.durations[start] <= wantedStart) start += 1;
    let end = lowerBound(track, wantedEnd);
    while (end < track.sizes.length && track.dts[end] < wantedEnd) end += 1;
    return { start, end: Math.max(start, end), targetDuration: Math.max(1, Math.round((range.endSeconds - range.startSeconds) * track.timescale)) };
  }).filter((range) => range.end > range.start);
}

function selectedCount(ranges) { return ranges.reduce((total, range) => total + range.end - range.start, 0); }

function materialize(track, ranges) {
  const count = selectedCount(ranges);
  if (!count) fail(`The selected ${track.video ? 'video' : 'audio'} ranges contain no samples.`);
  const output = {
    ...track,
    ranges,
    sizes: new Uint32Array(count),
    durations: new Uint32Array(count),
    cto: new Float64Array(count),
    sync: new Uint8Array(count),
    offsets: new Float64Array(count),
  };
  let destination = 0;
  for (const range of ranges) {
    const rangeStart = destination;
    for (let source = range.start; source < range.end; source += 1, destination += 1) {
      output.sizes[destination] = track.sizes[source];
      output.durations[destination] = track.durations[source];
      output.cto[destination] = track.cto[source];
      output.sync[destination] = track.sync[source];
    }
    if (!track.video && range.targetDuration) {
      let duration = 0;
      for (let index = rangeStart; index < destination; index += 1) duration += output.durations[index];
      const correction = range.targetDuration - duration;
      const last = destination - 1;
      output.durations[last] = Math.max(1, output.durations[last] + correction);
    }
  }
  output.outputDuration = output.durations.reduce((total, duration) => total + duration, 0);
  return output;
}

function runs(values) {
  const result = [];
  for (let index = 0; index < values.length; index += 1) {
    const value = values[index];
    const previous = result.at(-1);
    if (previous?.value === value && previous.count < U32_MAX) previous.count += 1;
    else result.push({ count: 1, value });
  }
  return result;
}

function tableBox(type, entries, signed = false) {
  const body = Buffer.alloc(4 + entries.length * 8);
  body.writeUInt32BE(entries.length, 0);
  let cursor = 4;
  for (const entry of entries) {
    body.writeUInt32BE(entry.count, cursor);
    if (signed) body.writeInt32BE(entry.value, cursor + 4);
    else body.writeUInt32BE(entry.value >>> 0, cursor + 4);
    cursor += 8;
  }
  return fullBox(type, signed ? 1 : 0, 0, body);
}

function buildStbl(track) {
  const stts = tableBox('stts', runs(track.durations));
  const cttsRuns = runs(track.cto);
  const hasCtts = cttsRuns.some((entry) => entry.value !== 0);
  const hasNegative = cttsRuns.some((entry) => entry.value < 0);
  const ctts = hasCtts ? tableBox('ctts', cttsRuns, hasNegative) : null;
  const chunkMap = [];
  for (let index = 0; index < track.chunks.length; index += 1) {
    if (index === 0 || track.chunks[index].samples !== track.chunks[index - 1].samples) {
      chunkMap.push({ firstChunk: index + 1, samples: track.chunks[index].samples });
    }
  }
  const stscBody = Buffer.alloc(4 + chunkMap.length * 12);
  stscBody.writeUInt32BE(chunkMap.length, 0);
  chunkMap.forEach((entry, index) => {
    const cursor = 4 + index * 12;
    stscBody.writeUInt32BE(entry.firstChunk, cursor);
    stscBody.writeUInt32BE(entry.samples, cursor + 4);
    stscBody.writeUInt32BE(1, cursor + 8);
  });
  const stsc = fullBox('stsc', 0, 0, stscBody);
  const stszBody = Buffer.alloc(8 + track.sizes.length * 4);
  stszBody.writeUInt32BE(0, 0); stszBody.writeUInt32BE(track.sizes.length, 4);
  for (let index = 0; index < track.sizes.length; index += 1) stszBody.writeUInt32BE(track.sizes[index], 8 + index * 4);
  const stsz = fullBox('stsz', 0, 0, stszBody);
  const large = track.chunks.some((chunk) => chunk.offset > U32_MAX);
  const offsetsBody = Buffer.alloc(4 + track.chunks.length * (large ? 8 : 4));
  offsetsBody.writeUInt32BE(track.chunks.length, 0);
  for (let index = 0; index < track.chunks.length; index += 1) {
    if (large) offsetsBody.writeBigUInt64BE(BigInt(Math.round(track.chunks[index].offset)), 4 + index * 8);
    else offsetsBody.writeUInt32BE(track.chunks[index].offset, 4 + index * 4);
  }
  const offsets = fullBox(large ? 'co64' : 'stco', 0, 0, offsetsBody);
  let stss = null;
  if (track.video && track.sync.some((value) => !value)) {
    const indices = [];
    for (let index = 0; index < track.sync.length; index += 1) if (track.sync[index]) indices.push(index + 1);
    const body = Buffer.alloc(4 + indices.length * 4);
    body.writeUInt32BE(indices.length, 0);
    indices.forEach((value, index) => body.writeUInt32BE(value, 4 + index * 4));
    stss = fullBox('stss', 0, 0, body);
  }
  return box('stbl', raw(track.stsd), stts, ctts, stsc, stsz, offsets, stss);
}

function rebuildTrack(track, movieTimescale) {
  const stbl = buildStbl(track);
  const minfParts = children(track.minf).map((item) => item.type === 'stbl' ? stbl : raw(item));
  const minf = box('minf', ...minfParts);
  const mdiaParts = children(track.mdia).map((item) => {
    if (item.type === 'mdhd') return patchDuration(item, track.outputDuration, 'mdhd');
    if (item.type === 'minf') return minf;
    return raw(item);
  });
  const mdia = box('mdia', ...mdiaParts);
  const movieDuration = track.outputDuration / track.timescale * movieTimescale;
  const trakParts = children(track.trak).filter((item) => item.type !== 'edts').map((item) => {
    if (item.type === 'tkhd') return patchDuration(item, movieDuration, 'tkhd');
    if (item.type === 'mdia') return mdia;
    return raw(item);
  });
  return { bytes: box('trak', ...trakParts), movieDuration };
}

function rebuildMoov(moov, tracks, movieTimescale) {
  const rebuilt = new Map(tracks.map((track) => [track.id, rebuildTrack(track, movieTimescale)]));
  const movieDuration = Math.max(...[...rebuilt.values()].map((item) => item.movieDuration));
  const parts = [];
  for (const item of children(moov)) {
    if (item.type === 'mvhd') parts.push(patchDuration(item, movieDuration, 'mvhd'));
    else if (item.type === 'trak') {
      const tkhd = child(item, 'tkhd');
      const replacement = tkhd && rebuilt.get(trackIdFromTkhd(tkhd));
      if (replacement) parts.push(replacement.bytes);
    } else if (item.type !== 'mvex') parts.push(raw(item));
  }
  return box('moov', ...parts);
}

async function writeAll(handle, buffer, position) {
  let written = 0;
  while (written < buffer.length) {
    const result = await handle.write(buffer, written, buffer.length - written, position + written);
    if (!result.bytesWritten) fail('Could not write the MP4 output.');
    written += result.bytesWritten;
  }
}

async function copyBytes(input, output, source, destination, length, state) {
  const buffer = Buffer.allocUnsafe(Math.min(COPY_BUFFER_BYTES, length));
  let copied = 0;
  while (copied < length) {
    aborted(state.signal);
    const count = Math.min(buffer.length, length - copied);
    const read = await input.read(buffer, 0, count, source + copied);
    if (read.bytesRead !== count) fail('The MP4 sample data is truncated.');
    await writeAll(output, buffer.subarray(0, count), destination + copied);
    copied += count;
    state.copied += count;
    state.onProgress?.(Math.min(1, state.copied / state.total));
  }
}

async function copyTrack(input, output, sourceTrack, outputTrack, position, state) {
  let destinationSample = 0;
  let outputPosition = position;
  outputTrack.chunks = [];
  for (const range of outputTrack.ranges) {
    let source = range.start;
    while (source < range.end) {
      aborted(state.signal);
      const runStart = source;
      let length = sourceTrack.sizes[source];
      while (source + 1 < range.end && sourceTrack.offsets[source + 1] === sourceTrack.offsets[source] + sourceTrack.sizes[source] && length < 64 * 1024 * 1024) {
        source += 1;
        length += sourceTrack.sizes[source];
      }
      outputTrack.chunks.push({ offset: outputPosition, samples: source - runStart + 1 });
      for (let index = runStart; index <= source; index += 1) {
        outputTrack.offsets[destinationSample] = outputPosition;
        outputPosition += sourceTrack.sizes[index];
        destinationSample += 1;
      }
      await copyBytes(input, output, sourceTrack.offsets[runStart], outputPosition - length, length, state);
      source += 1;
    }
  }
  return outputPosition;
}

function normalizePlans(plans, duration) {
  if (!Array.isArray(plans) || !plans.length) fail('No retained ranges were supplied.');
  return plans.map((plan) => {
    const start = Math.max(0, Number(plan.start) || 0);
    const end = Math.min(duration, Number(plan.end) || 0);
    if (!(end > start)) fail('A retained range is empty or invalid.');
    return { start, end };
  });
}

async function remuxMp4({ input, output, segments, signal, onProgress } = {}) {
  if (!input || !output || input === output) fail('Lossless remux requires different input and output paths.');
  const inputHandle = await fsp.open(input, 'r');
  let outputHandle;
  try {
    const stat = await inputHandle.stat();
    const top = await topLevel(inputHandle, stat.size);
    const ftypEntry = top.find((item) => item.type === 'ftyp');
    const moovEntry = top.find((item) => item.type === 'moov');
    if (!moovEntry || moovEntry.size > 512 * 1024 * 1024) fail('The MP4 movie index is missing or unreasonable.');
    const moovBuffer = await readExact(inputHandle, moovEntry.size, moovEntry.offset);
    const moov = parseBoxes(moovBuffer)[0];
    const mvhd = child(moov, 'mvhd');
    if (!mvhd) fail('The MP4 movie header is missing.');
    const movieTimescale = movieHeader(mvhd).timescale;
    const tracks = children(moov).filter((item) => item.type === 'trak').map(parseTrack).filter(Boolean);
    const video = tracks.find((track) => track.video);
    if (!video) fail('Lossless export requires an MP4 video track.');
    const flat = tracks.every(loadFlatTables);
    if (!flat) await loadFragmentTables(inputHandle, top, tracks, moov, signal);
    for (const track of tracks) {
      for (let index = 0; index < track.sizes.length; index += 1) {
        if (!Number.isFinite(track.offsets[index]) || track.offsets[index] < 0 || track.offsets[index] + track.sizes[index] > stat.size) {
          fail('An MP4 sample points outside the source file.');
        }
      }
    }
    const duration = video.inputDuration / video.timescale;
    const plans = normalizePlans(segments, duration);
    const selectedVideoRanges = videoRanges(video, plans);
    const outputTracks = [];
    for (const track of tracks) {
      const ranges = track.video ? selectedVideoRanges : audioRanges(track, selectedVideoRanges);
      if (ranges.length) outputTracks.push(materialize(track, ranges));
    }
    const ftyp = ftypEntry
      ? await readExact(inputHandle, ftypEntry.size, ftypEntry.offset)
      : box('ftyp', Buffer.from('isom\x00\x00\x02\x00isomiso2avc1mp41', 'latin1'));
    const mediaBytes = outputTracks.reduce((total, track) => total + track.sizes.reduce((sum, size) => sum + size, 0), 0);
    const mdatHeader = Buffer.concat([Buffer.from('\x00\x00\x00\x01mdat', 'latin1'), writeU64(mediaBytes + 16)]);
    await fsp.rm(output, { force: true }).catch(() => {});
    outputHandle = await fsp.open(output, 'wx');
    await writeAll(outputHandle, ftyp, 0);
    await writeAll(outputHandle, mdatHeader, ftyp.length);
    let position = ftyp.length + mdatHeader.length;
    const state = { copied: 0, total: Math.max(1, mediaBytes), signal, onProgress };
    for (const outputTrack of outputTracks) {
      const sourceTrack = tracks.find((track) => track.id === outputTrack.id);
      position = await copyTrack(inputHandle, outputHandle, sourceTrack, outputTrack, position, state);
    }
    const rebuilt = rebuildMoov(moov, outputTracks, movieTimescale);
    await writeAll(outputHandle, rebuilt, position);
    await outputHandle.sync();
    await outputHandle.close();
    outputHandle = null;
    onProgress?.(1);
    return {
      ok: true,
      path: output,
      fragmentedInput: !flat,
      inputBytes: stat.size,
      outputBytes: position + rebuilt.length,
      duration: Math.max(...outputTracks.map((track) => track.outputDuration / track.timescale)),
      tracks: outputTracks.map((track) => ({ type: track.video ? 'video' : 'audio', samples: track.sizes.length })),
    };
  } catch (error) {
    await outputHandle?.close().catch(() => {});
    await fsp.rm(output, { force: true }).catch(() => {});
    throw error;
  } finally {
    await inputHandle.close();
  }
}

module.exports = { remuxMp4 };
