const ppro = require("premierepro");
const { entrypoints, storage } = require("uxp");

const { localFileSystem, formats, fs } = storage;
let v5Imports = null;
let v5LastImportRequest = null;
let decisionDoc = null;
let decisionSignature = null;
let lastAppliedSignature = null;
const $ = (id) => document.getElementById(id);
function log(msg) { $("status").textContent += `\n${msg}`; $("status").scrollTop = $("status").scrollHeight; }
function resetLog(msg) { $("status").textContent = msg; }

const PRESET_TOKEN_KEY = "v5_h264_preset_token_03";
const PRESET_NAME_KEY = "v5_h264_preset_name_03";
const LAST_PACKAGE_TOKEN_KEY = "v5_last_package_token_037";
const LAST_PACKAGE_NAME_KEY = "v5_last_package_name_037";
const PROXY_CHUNK_SECONDS = 300; // proven stable 5-minute source slices
const MAX_SINGLE_PROXY_BYTES = 190 * 1024 * 1024; // hard packaging guard under 200 MB
const TARGET_UPLOAD_ZIP_BYTES = 175 * 1024 * 1024; // headroom for ZIP metadata/context

function cleanName(name) {
  return String(name || "clip")
    .replace(/\.[^/.]+$/, "")
    .replace(/[^a-zA-Z0-9._-]+/g, "_")
    .replace(/^_+|_+$/g, "")
    .slice(0, 80) || "clip";
}

function stamp() {
  const d = new Date();
  const pad = (n) => String(n).padStart(2, "0");
  return `${d.getFullYear()}${pad(d.getMonth()+1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
}


function secondsToTimecodeLike(seconds) {
  const totalMs = Math.max(0, Math.round(Number(seconds || 0) * 1000));
  const totalSec = Math.floor(totalMs / 1000);
  const h = Math.floor(totalSec / 3600);
  const m = Math.floor((totalSec % 3600) / 60);
  const sec = totalSec % 60;
  const ms = totalMs % 1000;
  const pad = (n, w = 2) => String(n).padStart(w, "0");
  return `${pad(h)}:${pad(m)}:${pad(sec)}.${pad(ms, 3)}`;
}

function premiereTranscriptToText(jsonString) {
  try {
    const doc = JSON.parse(jsonString);
    if (!doc || !Array.isArray(doc.segments)) return "";
    const speakerMap = {};
    for (const sp of doc.speakers || []) speakerMap[String(sp.id)] = String(sp.name || "Unknown");
    const blocks = [];
    for (const seg of doc.segments) {
      const start = Number(seg.start || 0);
      const duration = Number(seg.duration || 0);
      const words = Array.isArray(seg.words) ? seg.words : [];
      const text = words.map(w => String(w.text || "")).join(" ")
        .replace(/\s+([,.;!?])/g, "$1")
        .replace(/\s+'/g, "'")
        .trim();
      if (!text) continue;
      const speaker = speakerMap[String(seg.speaker)] || "Unknown";
      blocks.push(`${secondsToTimecodeLike(start)} - ${secondsToTimecodeLike(start + duration)}\n${speaker}\n${text}`);
    }
    return blocks.join("\n\n");
  } catch (_) {
    return "";
  }
}

async function exportTranscriptForClip(clip, transcriptsFolder, base) {
  const result = { status: "none", json_path: null, text_path: null };
  try {
    if (!ppro.Transcript || typeof ppro.Transcript.exportToJSON !== "function") {
      result.status = "api_unavailable";
      return result;
    }
    const transcriptJson = await ppro.Transcript.exportToJSON(clip);
    if (typeof transcriptJson !== "string" || !transcriptJson.trim()) return result;
    try {
      const parsed = JSON.parse(transcriptJson);
      if (!parsed || !Array.isArray(parsed.segments) || parsed.segments.length === 0) return result;
    } catch (_) {
      // If Premiere returns a future JSON shape, keep the raw export rather than dropping it.
    }
    const jsonName = `${base}.transcript.json`;
    const jsonFile = await transcriptsFolder.createFile(jsonName, { overwrite: true });
    await jsonFile.write(transcriptJson);
    result.json_path = `transcripts/${jsonName}`;
    const text = premiereTranscriptToText(transcriptJson);
    if (text) {
      const textName = `${base}.transcript.txt`;
      const textFile = await transcriptsFolder.createFile(textName, { overwrite: true });
      await textFile.write(text);
      result.text_path = `transcripts/${textName}`;
    }
    result.status = "included";
    return result;
  } catch (err) {
    result.status = `export_failed: ${err.message || err}`;
    return result;
  }
}

function crc32(bytes) {
  let c = 0xFFFFFFFF;
  for (let i = 0; i < bytes.length; i++) {
    c ^= bytes[i];
    for (let k = 0; k < 8; k++) c = (c >>> 1) ^ ((c & 1) ? 0xEDB88320 : 0);
  }
  return (c ^ 0xFFFFFFFF) >>> 0;
}

function u16(n) {
  const a = new Uint8Array(2); a[0] = n & 255; a[1] = (n >>> 8) & 255; return a;
}
function u32(n) {
  const a = new Uint8Array(4); a[0] = n & 255; a[1] = (n >>> 8) & 255; a[2] = (n >>> 16) & 255; a[3] = (n >>> 24) & 255; return a;
}
function concatBytes(parts) {
  const size = parts.reduce((s, p) => s + p.length, 0);
  const out = new Uint8Array(size); let off = 0;
  for (const p of parts) { out.set(p, off); off += p.length; }
  return out;
}
function encodeUtf8(text) {
  // Premiere UXP does not expose the browser TextEncoder global.
  // Encode UTF-8 manually so ZIP filenames/context text work in UXP.
  const str = String(text == null ? "" : text);
  const bytes = [];
  for (let i = 0; i < str.length; i++) {
    let cp = str.charCodeAt(i);
    if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < str.length) {
      const low = str.charCodeAt(i + 1);
      if (low >= 0xDC00 && low <= 0xDFFF) {
        cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00);
        i++;
      }
    }
    if (cp <= 0x7F) bytes.push(cp);
    else if (cp <= 0x7FF) {
      bytes.push(0xC0 | (cp >> 6), 0x80 | (cp & 0x3F));
    } else if (cp <= 0xFFFF) {
      bytes.push(0xE0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3F), 0x80 | (cp & 0x3F));
    } else {
      bytes.push(0xF0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3F), 0x80 | ((cp >> 6) & 0x3F), 0x80 | (cp & 0x3F));
    }
  }
  return new Uint8Array(bytes);
}

async function writeStoredZip(zipFile, items) {
  // Store-only ZIP, assembled completely in memory and written ONCE.
  // UXP File.write() does not provide a reliable append stream for binary files;
  // the 0.3.8 chunked-append implementation could create a zero-byte archive.
  // Upload parts are capped below 200 MB, so a one-shot buffer is bounded.
  const localParts = [];
  const central = [];
  let offset = 0;

  for (const item of items) {
    const nameBytes = encodeUtf8(item.name);
    let dataBytes;
    if (item.entry) {
      const raw = await item.entry.read({ format: formats.binary });
      dataBytes = new Uint8Array(raw);
    } else {
      dataBytes = encodeUtf8(item.text || "");
    }

    const crc = crc32(dataBytes);
    const localHeader = concatBytes([
      u32(0x04034b50), u16(20), u16(0x0800), u16(0), u16(0), u16(0),
      u32(crc), u32(dataBytes.length), u32(dataBytes.length), u16(nameBytes.length), u16(0), nameBytes
    ]);

    localParts.push(localHeader, dataBytes);
    central.push({ nameBytes, crc, size: dataBytes.length, offset });
    offset += localHeader.length + dataBytes.length;
  }

  const centralStart = offset;
  const centralParts = [];
  for (const e of central) {
    const hdr = concatBytes([
      u32(0x02014b50), u16(20), u16(20), u16(0x0800), u16(0), u16(0), u16(0),
      u32(e.crc), u32(e.size), u32(e.size), u16(e.nameBytes.length), u16(0), u16(0),
      u16(0), u16(0), u32(0), u32(e.offset), e.nameBytes
    ]);
    centralParts.push(hdr);
    offset += hdr.length;
  }

  const centralSize = offset - centralStart;
  const end = concatBytes([
    u32(0x06054b50), u16(0), u16(0), u16(central.length), u16(central.length),
    u32(centralSize), u32(centralStart), u16(0)
  ]);

  const zipBytes = concatBytes([...localParts, ...centralParts, end]);

  // 0.3.11: Premiere does not expose the lower-level UXP FSAPI used in 0.3.10.
  // Use the documented Entry.write binary path and pass the Uint8Array itself
  // (Adobe's UXP example uses an ArrayBufferView directly). 0.3.8 passed
  // zipBytes.buffer, which produced a zero-byte file in Premiere on this host.
  await zipFile.write(zipBytes, { format: formats.binary });

  // Re-fetch metadata after the write and fail loudly if Premiere did not persist it.
  const meta = await zipFile.getMetadata();
  if (!meta || meta.size !== zipBytes.length) {
    throw new Error(`ZIP write verification failed: expected ${zipBytes.length} bytes, got ${meta && meta.size != null ? meta.size : "unknown"}.`);
  }
}

async function rememberPackageFolder(folder, packageName) {
  const token = await localFileSystem.createPersistentToken(folder);
  localStorage.setItem(LAST_PACKAGE_TOKEN_KEY, token);
  localStorage.setItem(LAST_PACKAGE_NAME_KEY, packageName || folder.name || "V5 package");
}

async function getLastPackageFolder() {
  const token = localStorage.getItem(LAST_PACKAGE_TOKEN_KEY);
  if (!token) throw new Error("No prepared package remembered. Run Prepare footage first.");
  try { return await localFileSystem.getEntryForPersistentToken(token); }
  catch (_) { throw new Error("V5 can no longer access the last package folder. Run Prepare footage again."); }
}

async function finalizeUploadZips() {
  resetLog("Checking the last V5 package…");
  const packageFolder = await getLastPackageFolder();
  const proxiesFolder = await packageFolder.getEntry("proxies");
  const contextFile = await packageFolder.getEntry("V5_UPLOAD_CONTEXT.json");
  const manifestFile = await packageFolder.getEntry("v5-media-manifest.json");
  const briefFile = await packageFolder.getEntry("v5-edit-brief.txt");
  const instructionsFile = await packageFolder.getEntry("UPLOAD_TO_CHATGPT.txt");
  const context = JSON.parse(await contextFile.read());
  const expected = [];
  for (const clip of (context.manifest && context.manifest.clips) || []) {
    for (const part of clip.review_proxy_parts || []) expected.push(String(part.review_proxy || "").replace(/^proxies\//, ""));
  }
  if (!expected.length) throw new Error("Package manifest contains no review proxies.");

  const proxyItems = [];
  for (const name of expected) {
    let entry;
    try { entry = await proxiesFolder.getEntry(name); }
    catch (_) { throw new Error(`Missing proxy ${name}. Wait for AME to finish every queued export, then try again.`); }
    const meta = await entry.getMetadata();
    if (!(meta.size > 0)) throw new Error(`Proxy ${name} is still empty. Wait for AME to finish, then try again.`);
    if (meta.size > MAX_SINGLE_PROXY_BYTES) {
      throw new Error(`${name} is ${(meta.size / 1024 / 1024).toFixed(1)} MB. V5 will not build an upload part with a single proxy over 190 MB. Use the V5 review preset / lower bitrate and prepare again.`);
    }
    proxyItems.push({ name, entry, size: meta.size });
  }

  // Actual-size binning. Upload ZIPs target 175 MB and can never include a proxy
  // over 190 MB, providing margin below the requested 200 MB ceiling.
  const groups = []; let current = []; let currentBytes = 0;
  for (const p of proxyItems) {
    if (current.length && currentBytes + p.size > TARGET_UPLOAD_ZIP_BYTES) {
      groups.push(current); current = []; currentBytes = 0;
    }
    current.push(p); currentBytes += p.size;
  }
  if (current.length) groups.push(current);

  let transcriptEntries = [];
  try {
    const tf = await packageFolder.getEntry("transcripts");
    transcriptEntries = (await tf.getEntries()).filter(e => e && e.isFile).map(e => ({ name: `transcripts/${e.name}`, entry: e }));
  } catch (_) {}

  log(`Found ${proxyItems.length} finished proxy segment(s).`);
  log(`Building ${groups.length} upload ZIP(s), target ≤175 MB each…`);
  for (let i = 0; i < groups.length; i++) {
    const group = groups[i];
    const partName = `UPLOAD_PART_${String(i + 1).padStart(2, "0")}_OF_${String(groups.length).padStart(2, "0")}.zip`;
    const partInfo = {
      schema: "v5-upload-part-0.3.11",
      package_name: context.package_name,
      part: i + 1,
      total_parts: groups.length,
      proxy_files: group.map(p => `proxies/${p.name}`),
      package_complete_instruction: "Upload every UPLOAD_PART zip, then tell ChatGPT: package complete."
    };
    const items = [
      { name: "V5_PART_INFO.json", text: JSON.stringify(partInfo, null, 2) },
      { name: "V5_UPLOAD_CONTEXT.json", entry: contextFile },
      { name: "v5-media-manifest.json", entry: manifestFile },
      { name: "v5-edit-brief.txt", entry: briefFile },
      { name: "UPLOAD_TO_CHATGPT.txt", entry: instructionsFile },
      ...transcriptEntries,
      ...group.map(p => ({ name: `proxies/${p.name}`, entry: p.entry }))
    ];
    const zipFile = await packageFolder.createFile(partName, { overwrite: true });
    await writeStoredZip(zipFile, items);
    const zipMeta = await zipFile.getMetadata();
    if (zipMeta.size >= 200 * 1024 * 1024) {
      throw new Error(`${partName} unexpectedly reached ${(zipMeta.size / 1024 / 1024).toFixed(1)} MB. V5 stopped because upload parts must remain under 200 MB.`);
    }
    log(`✓ ${partName} — ${(zipMeta.size / 1024 / 1024).toFixed(1)} MB — ${group.length} proxy file(s)`);
  }
  log(`DONE — upload the ${groups.length} UPLOAD_PART zip(s), then say "package complete".`);
}

function validate(doc) {
  if (!doc || !["v5-edit-decisions-0.1", "v5-edit-decisions-0.2", "v5-edit-decisions-0.3"].includes(doc.schema)) {
    throw new Error("Unsupported or missing V5 edit-decision schema.");
  }
  if (!Array.isArray(doc.edits) || doc.edits.length === 0) throw new Error("No edits found.");
  if (doc.schema === "v5-edit-decisions-0.3") {
    if (!["reject", "allow_overwrite"].includes(doc.collision_policy)) {
      throw new Error("v5-edit-decisions-0.3 requires collision_policy: reject or allow_overwrite.");
    }
    const missing = ["project_guid", "sequence_name", "sequence_guid", "expected_timeline_state_token", "request_id"]
      .filter(field => typeof doc[field] !== "string" || !doc[field].trim());
    if (missing.length) throw new Error(`v5-edit-decisions-0.3 requires: ${missing.join(", ")}.`);
    if (!/^[A-Za-z0-9._:-]{1,128}$/.test(doc.request_id)) throw new Error("Placement request_id must be 1–128 characters using letters, numbers, dot, underscore, colon, or hyphen.");
  }
  doc.edits.forEach((e, i) => {
    if (!e || typeof e !== "object" || Array.isArray(e)) throw new Error(`Edit ${i}: edit must be an object.`);
    for (const key of ["media_path", "media_match"]) {
      if (Object.prototype.hasOwnProperty.call(e, key) &&
          (typeof e[key] !== "string" || !e[key].trim())) {
        throw new Error(`Edit ${i}: ${key} must be a non-empty string when supplied.`);
      }
    }
    if (!e.media_path && !e.media_match) throw new Error(`Edit ${i}: media_path or media_match required.`);
    for (const k of ["source_in", "source_out", "timeline_start"]) {
      if (typeof e[k] !== "number" || !Number.isFinite(e[k])) throw new Error(`Edit ${i}: ${k} must be a number.`);
    }
    if (e.source_out <= e.source_in) throw new Error(`Edit ${i}: source_out must be after source_in.`);
    if (e.timeline_start < 0) throw new Error(`Edit ${i}: timeline_start cannot be negative.`);
    const supplied = key => Object.prototype.hasOwnProperty.call(e, key);
    for (const key of ["video_track", "audio_track", "source_in_frame", "source_out_frame", "timeline_start_frame"]) {
      if (supplied(key) && (!Number.isSafeInteger(e[key]) || e[key] < 0)) {
        throw new Error(`Edit ${i}: ${key} must be a non-negative safe integer.`);
      }
    }
    for (const key of ["source_fps", "timeline_fps"]) {
      if (supplied(key) && (typeof e[key] !== "number" || !Number.isFinite(e[key]) || e[key] <= 0)) {
        throw new Error(`Edit ${i}: ${key} must be a positive finite number.`);
      }
    }
    if ((supplied("source_in_frame") || supplied("source_out_frame")) && !supplied("source_fps")) {
      throw new Error(`Edit ${i}: source frame timing requires source_fps.`);
    }
    if (supplied("timeline_start_frame") && !supplied("timeline_fps") && !supplied("source_fps")) {
      throw new Error(`Edit ${i}: timeline frame timing requires timeline_fps or source_fps.`);
    }
    const resolved = decisionTimes(e);
    if (!Object.values(resolved).every(Number.isFinite) || resolved.source_in < 0 ||
        resolved.timeline_start < 0 || resolved.source_out <= resolved.source_in) {
      throw new Error(`Edit ${i}: resolved timing must be finite, non-negative and have a positive source duration.`);
    }
  });
  return true;
}

function decisionTimes(e) {
  const sourceFps = Number(e.source_fps || 0);
  const timelineFps = Number(e.timeline_fps || sourceFps || 0);
  const source_in = Number.isInteger(e.source_in_frame) && sourceFps > 0 ? e.source_in_frame / sourceFps : Number(e.source_in);
  const source_out = Number.isInteger(e.source_out_frame) && sourceFps > 0 ? e.source_out_frame / sourceFps : Number(e.source_out);
  const timeline_start = Number.isInteger(e.timeline_start_frame) && timelineFps > 0 ? e.timeline_start_frame / timelineFps : Number(e.timeline_start);
  return { source_in, source_out, timeline_start };
}

function makeDecisionSignature(doc) {
  return JSON.stringify({ request_id: doc.request_id || null, edits: (doc.edits || []).map(e => [e.media_path || e.media_match, e.source_in, e.source_out, e.timeline_start, e.source_in_frame, e.source_out_frame, e.timeline_start_frame, e.video_track, e.audio_track]) });
}

async function clipReviewMetadata(clip) {
  const meta = { source_frame_rate: null, source_duration_seconds: null, media_start_seconds: null };
  try {
    const interpretation = await clip.getFootageInterpretation();
    const fps = interpretation && interpretation.getFrameRate ? interpretation.getFrameRate() : null;
    if (Number.isFinite(Number(fps)) && Number(fps) > 0) meta.source_frame_rate = Number(fps);
  } catch (_) {}
  try {
    const media = await clip.getMedia();
    if (media) {
      const duration = await media.duration;
      const start = await media.start;
      if (duration && Number.isFinite(Number(duration.seconds))) meta.source_duration_seconds = Number(duration.seconds);
      if (start && Number.isFinite(Number(start.seconds))) meta.media_start_seconds = Number(start.seconds);
    }
  } catch (_) {}
  return meta;
}

async function choosePreset() {
  const file = await localFileSystem.getFileForOpening({ types: ["epr"] });
  if (!file) return null;
  const token = await localFileSystem.createPersistentToken(file);
  localStorage.setItem(PRESET_TOKEN_KEY, token);
  localStorage.setItem(PRESET_NAME_KEY, file.name || "AME preset");
  updatePresetStatus();
  return file;
}

async function getPreset() {
  const token = localStorage.getItem(PRESET_TOKEN_KEY);
  if (!token) return await choosePreset();
  try {
    const entry = await localFileSystem.getEntryForPersistentToken(token);
    if (!entry || !entry.isFile) throw new Error("Preset token is not a file.");
    return entry;
  } catch (_) {
    localStorage.removeItem(PRESET_TOKEN_KEY);
    localStorage.removeItem(PRESET_NAME_KEY);
    updatePresetStatus();
    return await choosePreset();
  }
}

function updatePresetStatus() {
  const name = localStorage.getItem(PRESET_NAME_KEY);
  $("presetStatus").textContent = name ? `Saved preset: ${name}` : "No saved AME preset yet.";
}

async function selectedClipItems(project) {
  const selection = await ppro.ProjectUtils.getSelection(project);
  if (!selection) return [];
  const items = await selection.getItems();
  const clips = [];
  for (const item of items || []) {
    try {
      const clip = ppro.ClipProjectItem.cast(item);
      const path = await clip.getMediaFilePath();
      if (path) clips.push({ clip, path });
    } catch (_) {
      // Folder, sequence, graphic, etc. Ignore non-file-backed clip items.
    }
  }
  return clips;
}

async function prepareFootage() {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");

  resetLog("Reading selected Project-panel clips…");
  const selected = await selectedClipItems(project);
  if (!selected.length) {
    throw new Error("Select one or more file-backed clips in Premiere's Project panel first.");
  }
  log(`Found ${selected.length} clip(s).`);

  const manager = ppro.EncoderManager.getManager();
  if (!manager || !manager.isAMEInstalled) {
    throw new Error("Adobe Media Encoder is not installed/detected. V5 uses AME to make review proxies.");
  }

  const preset = await getPreset();
  if (!preset) { log("Cancelled — no preset selected."); return; }
  if (!/\.epr$/i.test(preset.name || "")) throw new Error("Choose an Adobe Media Encoder .epr preset.");

  const destination = await localFileSystem.getFolder();
  if (!destination) { log("Cancelled — no output folder selected."); return; }

  const packageName = `V5_ChatGPT_Package_${stamp()}`;
  const packageFolder = await destination.createFolder(packageName);
  const proxiesFolder = await packageFolder.createFolder("proxies");
  const transcriptsFolder = await packageFolder.createFolder("transcripts");
  const manifestFile = await packageFolder.createFile("v5-media-manifest.json", { overwrite: true });
  const instructionsFile = await packageFolder.createFile("UPLOAD_TO_CHATGPT.txt", { overwrite: true });
  const briefFile = await packageFolder.createFile("v5-edit-brief.txt", { overwrite: true });
  const uploadContextFile = await packageFolder.createFile("V5_UPLOAD_CONTEXT.json", { overwrite: true });


  const manifest = {
    schema: "v5-media-manifest-0.3.10",
    created_at: new Date().toISOString(),
    project_name: project.name || null,
    package_name: packageName,
    proxy_format_note: "Files are queued through Adobe Media Encoder using the user-selected H.264 .epr preset.",
    multipart_review: true,
    review_chunk_seconds: PROXY_CHUNK_SECONDS,
    upload_zip_target_mb: 175,
    upload_zip_hard_ceiling_mb: 200,
    transcript_auto_include: true,
    review_mapping_note: "Each review_proxy_part is a slice of the ORIGINAL source. When choosing footage, add source_offset_seconds to the time within that proxy part. Return source_in/source_out relative to the original Premiere source clip, never relative to the chunk.",
    edit_brief: String($("brief").value || "").trim(),
    clips: []
  };

  log(`Package: ${packageFolder.nativePath}`);
  log(`Preset: ${preset.name}`);
  log(`Proxy mode: ${PROXY_CHUNK_SECONDS / 60}-minute source slices; final ZIPs are actual-size packed under 200 MB.`);
  log("Queueing review proxies in Adobe Media Encoder…");

  let totalParts = 0;
  for (let i = 0; i < selected.length; i++) {
    const { clip, path } = selected[i];
    const base = `${String(i + 1).padStart(3, "0")}_${cleanName(clip.name)}`;
    let existingProxy = null;
    try {
      if (await clip.hasProxy()) existingProxy = await clip.getProxyPath();
    } catch (_) {}

    const reviewMeta = await clipReviewMetadata(clip);
    const duration = Number(reviewMeta.source_duration_seconds || 0);
    const clipEntry = {
      id: `v5_${String(i + 1).padStart(4, "0")}`,
      clip_name: clip.name,
      media_path: path,
      existing_premiere_proxy_path: existingProxy || null,
      source_frame_rate: reviewMeta.source_frame_rate,
      source_duration_seconds: reviewMeta.source_duration_seconds,
      media_start_seconds: reviewMeta.media_start_seconds,
      review_proxy_parts: []
    };
    manifest.clips.push(clipEntry);

    const transcript = await exportTranscriptForClip(clip, transcriptsFolder, base);
    clipEntry.transcript = transcript;
    if (transcript.status === "included") log(`   transcript included for ${clip.name}`);

    // If Premiere cannot report duration, retain the proven 0.3.0 whole-clip path.
    if (!(duration > 0)) {
      const proxyName = `${base}.mp4`;
      const outputPath = `${proxiesFolder.nativePath}/${proxyName}`;
      const ok = await manager.encodeProjectItem(clip, outputPath, preset.nativePath, 0, true, true);
      clipEntry.review_proxy = `proxies/${proxyName}`;
      clipEntry.review_proxy_status = ok ? "queued" : "queue_failed";
      clipEntry.review_proxy_parts.push({
        part: 1,
        review_proxy: `proxies/${proxyName}`,
        source_offset_seconds: 0,
        source_in_seconds: 0,
        source_out_seconds: null,
        status: ok ? "queued" : "queue_failed"
      });
      totalParts++;
      log(`${ok ? "✓" : "✗"} ${i + 1}/${selected.length} ${clip.name} — whole clip (duration unavailable)`);
      continue;
    }

    const partCount = Math.max(1, Math.ceil(duration / PROXY_CHUNK_SECONDS));
    for (let part = 0; part < partCount; part++) {
      const sourceIn = part * PROXY_CHUNK_SECONDS;
      const sourceOut = Math.min(duration, (part + 1) * PROXY_CHUNK_SECONDS);
      const suffix = partCount > 1 ? `_P${String(part + 1).padStart(2, "0")}` : "";
      const proxyName = `${base}${suffix}.mp4`;
      const outputPath = `${proxiesFolder.nativePath}/${proxyName}`;
      const inPoint = ppro.TickTime.createWithSeconds(sourceIn);
      const outPoint = ppro.TickTime.createWithSeconds(sourceOut);

      const ok = await manager.encodeFile(
        path,
        outputPath,
        preset.nativePath,
        inPoint,
        outPoint,
        1, // ENCODE_IN_TO_OUT — 0 means entire source and ignores the requested chunk range
        true,
        true
      );

      clipEntry.review_proxy_parts.push({
        part: part + 1,
        review_proxy: `proxies/${proxyName}`,
        source_offset_seconds: sourceIn,
        source_in_seconds: sourceIn,
        source_out_seconds: sourceOut,
        duration_seconds: sourceOut - sourceIn,
        status: ok ? "queued" : "queue_failed"
      });
      totalParts++;
      log(`${ok ? "✓" : "✗"} ${i + 1}/${selected.length} ${clip.name} — part ${part + 1}/${partCount} (${sourceIn.toFixed(1)}–${sourceOut.toFixed(1)}s)`);
    }

    // Backward-friendly field for single-part consumers.
    if (clipEntry.review_proxy_parts.length === 1) {
      clipEntry.review_proxy = clipEntry.review_proxy_parts[0].review_proxy;
      clipEntry.review_proxy_status = clipEntry.review_proxy_parts[0].status;
    }
  }

  const uploadInstructions =
`V5 AI EDITOR — CHATGPT REVIEW PACKAGE 0.3.10\n\n` +
`1. Wait for Adobe Media Encoder to finish every queued proxy segment.\n` +
`2. Return to V5 and click "Build upload ZIPs after AME finishes".\n` +
`3. V5 packs the finished proxies by ACTUAL FILE SIZE into UPLOAD_PART zip files with a target of 175 MB and a hard ceiling below 200 MB.\n` +
`4. If Premiere already has a transcript for a selected clip, V5 includes Premiere's transcript JSON plus a readable timecoded TXT automatically.\n` +
`5. Upload every UPLOAD_PART zip to ChatGPT. Multiple messages are fine.\n` +
`6. Tell ChatGPT "package complete" after the last part.\n` +
`7. ChatGPT must use source_offset_seconds and return source_in/source_out relative to the ORIGINAL Premiere clip.\n` +
`8. In Premiere, use V5 > Load + validate decisions, then Apply to active sequence.\n\n` +
`Package: ${packageName}\n` +
`Original clips: ${selected.length}\n` +
`Review parts queued: ${totalParts}\n` +
`Proxy slice duration: ${PROXY_CHUNK_SECONDS / 60} minutes max\n` +
`Upload ZIP target: 175 MB; hard ceiling: <200 MB\n` +
`Preset: ${preset.name}\n`;

  const uploadContext = {
    schema: "v5-chatgpt-upload-context-0.3.11",
    package_name: packageName,
    upload_mode: "multipart_zip_under_200mb",
    instructions: uploadInstructions,
    manifest
  };

  await manifestFile.write(JSON.stringify(manifest, null, 2));
  await briefFile.write(manifest.edit_brief || "No edit brief supplied.");
  await instructionsFile.write(uploadInstructions);
  await uploadContextFile.write(JSON.stringify(uploadContext, null, 2));
  await rememberPackageFolder(packageFolder, packageName);

  log(`DONE QUEUEING — ${selected.length} original clip(s) mapped to ${totalParts} review proxy segment(s).`);
  log(`Transcript export: ${manifest.clips.filter(c => c.transcript && c.transcript.status === "included").length}/${manifest.clips.length} clip(s) included.`);
  log(`NEXT: wait for AME to finish, then click "Build upload ZIPs after AME finishes".`);
}

function loadDecisionText(text, sourceLabel = "pasted JSON") {
  const cleaned = String(text || "").trim()
    .replace(/^```(?:json)?\s*/i, "")
    .replace(/\s*```$/, "");
  if (!cleaned) throw new Error("No decisions JSON was provided.");
  const parsed = JSON.parse(cleaned);
  validate(parsed);
  decisionDoc = parsed;
  decisionSignature = makeDecisionSignature(parsed);
  resetLog(`Loaded ${parsed.edits.length} edit decisions from ${sourceLabel}.`);
  if (parsed.sequence_name) log(`Target sequence label: ${parsed.sequence_name}`);
  const applyButton = $("apply");
  if (applyButton) applyButton.disabled = false;
}

function loadPastedDecisions() {
  loadDecisionText($("decisionPaste").value, "paste");
}

async function pasteAndApplyDecisions() {
  // 0.3.19: preserve the intended one-click return handoff. Parse/validate the pasted JSON and
  // immediately run the proven 0.3.6 apply engine. The separate Validate and
  // Apply controls remain available as a safe fallback.
  loadDecisionText($("decisionPaste").value, "paste");
  await applyDecisions();
}

async function readDroppedDecisionFile(file) {
  if (!file) throw new Error("No dropped file was provided.");
  const name = String(file.name || "dropped file");
  if (file.name && !/\.json$/i.test(file.name)) {
    throw new Error(`Drop a .json decision file, not ${name}.`);
  }

  // UXP dropped files expose read(); some host/file implementations expose text().
  // Support both without changing the normal file-picker path.
  let text;
  if (typeof file.read === "function") text = await file.read();
  else if (typeof file.text === "function") text = await file.text();
  else throw new Error("Premiere supplied a dropped file object V5 cannot read. Use Load decisions from file instead.");

  $("decisionPaste").value = String(text || "");
  loadDecisionText(text, `dropped file: ${name}`);
}

function installDecisionDropTarget() {
  const zone = $("decisionDropZone");
  if (!zone) return;

  const cancelDrag = (event) => {
    event.preventDefault();
    event.stopPropagation();
  };

  ["dragenter", "dragover"].forEach(type => {
    zone.addEventListener(type, (event) => {
      cancelDrag(event);
      zone.classList.add("drag-over");
      try { if (event.dataTransfer) event.dataTransfer.dropEffect = "copy"; } catch (_) {}
    });
  });

  ["dragleave", "dragend"].forEach(type => {
    zone.addEventListener(type, (event) => {
      cancelDrag(event);
      zone.classList.remove("drag-over");
    });
  });

  
}

async function chooseDecisionFile() {
  const file = await localFileSystem.getFileForOpening({ types: ["json"] });
  if (!file) return;
  const text = await file.read();
  $("decisionPaste").value = String(text || "");
  loadDecisionText(text, "file");
}

async function collectProjectClipMetadata(folder, out = []) {
  // IMPORTANT: do not retain Premiere DOM/script-object handles across a long
  // series of awaits. 0.2.2 proved that those handles can become invalid.
  // We index only plain JS metadata here and reacquire a fresh ClipProjectItem
  // immediately before each edit operation.
  const items = await folder.getItems();
  if (!items || typeof items === "string" || typeof items[Symbol.iterator] !== "function") {
    throw new Error("Media scan incomplete: Premiere did not return a valid bin listing.");
  }
  for (const item of items) {
    let clip = null;
    try { clip = ppro.ClipProjectItem.cast(item); } catch (_) {}
    if (clip) {
      // A recognized clip whose metadata cannot be read makes the scan incomplete.
      const path = await clip.getMediaFilePath();
      if (path) {
        out.push({ path: String(path), name: String(clip.name || "") });
        continue;
      }
    }
    let childFolder = null;
    try { childFolder = ppro.FolderItem.cast(item); } catch (_) {}
    // Catch only type-cast failures, never failures while traversing a real bin.
    if (childFolder) await collectProjectClipMetadata(childFolder, out);
  }
  return out;
}

function metadataMatchesEdit(meta, edit) {
  const wantedPath = String(edit.media_path || "").toLowerCase();
  const wantedMatch = String(edit.media_match || "").toLowerCase();
  const p = String(meta.path || "").toLowerCase();
  const n = String(meta.name || "").toLowerCase();
  if (wantedPath && p !== wantedPath) return false;
  if (wantedMatch && !p.includes(wantedMatch) && !n.includes(wantedMatch)) return false;
  return !!(wantedPath || wantedMatch);
}

async function findFreshClipInFolder(folder, edit) {
  const items = await folder.getItems();
  if (!items || typeof items === "string" || typeof items[Symbol.iterator] !== "function") {
    throw new Error("Media scan incomplete: Premiere did not return a valid bin listing.");
  }
  for (const item of items) {
    let inspectionClip = null;
    try { inspectionClip = ppro.ClipProjectItem.cast(item); } catch (_) {}
    if (inspectionClip) {
      const path = await inspectionClip.getMediaFilePath();
      if (path) {
        const meta = { path: String(path), name: String(inspectionClip.name || "") };
        if (metadataMatchesEdit(meta, edit)) {
          // Cast again immediately before returning; propagate reacquisition errors.
          return ppro.ClipProjectItem.cast(item);
        }
        continue;
      }
    }
    let childFolder = null;
    try { childFolder = ppro.FolderItem.cast(item); } catch (_) {}
    if (childFolder) {
      const found = await findFreshClipInFolder(childFolder, edit);
      if (found) return found;
    }
  }
  return null;
}

async function reacquireProjectItem(project, edit) {
  const metadata = await collectProjectClipMetadata(await project.getRootItem());
  const matches = metadata.filter(meta => metadataMatchesEdit(meta, edit));
  if (!matches.length) throw new Error(`Media not found in Premiere project: ${edit.media_path || edit.media_match}`);
  if (matches.length > 1) throw new Error(`Media target is ambiguous: ${matches.length} project items match ${edit.media_path || edit.media_match}. Use a unique media_path or refine media_match.`);
  // Reacquire the root and clip after scanning; do not retain clip handles.
  const root = await project.getRootItem();
  // Pin the uniquely scanned file so a newly matching name cannot substitute it.
  const resolvedEdit = { ...edit, media_path: matches[0].path };
  const clip = await findFreshClipInFolder(root, resolvedEdit);
  if (!clip) throw new Error(`Media not found in Premiere project: ${edit.media_path || edit.media_match}`);
  return clip;
}

async function getVideoTrackItems(sequence, trackIndex) {
  const track = await sequence.getVideoTrack(trackIndex);
  if (!track) return [];
  const items = await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false);
  return Array.from(items || []);
}

async function getAudioTrackItems(sequence, trackIndex) {
  const track = await sequence.getAudioTrack(trackIndex);
  if (!track) return [];
  const items = await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false);
  return Array.from(items || []);
}

async function describeTrackItem(item) {
  const name = await item.getName();
  const start = await item.getStartTime();
  const end = await item.getEndTime();
  let mediaPath = "";
  let projectItemName = "";
  try {
    const projectItem = await item.getProjectItem();
    if (projectItem) {
      projectItemName = String(projectItem.name || "");
      try {
        const clipProjectItem = ppro.ClipProjectItem.cast(projectItem);
        mediaPath = String((await clipProjectItem.getMediaFilePath()) || "");
      } catch (_) {}
    }
  } catch (_) {}
  return {
    name: String(name || ""),
    projectItemName,
    mediaPath,
    start: Number(start.seconds),
    end: Number(end.seconds)
  };
}

async function findTrackItemForEdit(project, kind, trackIndex, edit, fallbackName) {
  // 0.2.7: NEVER depend on the TrackItem display name or an unchanged start
  // time between trim actions. A trim can alter properties we previously used
  // as our lookup key. Reacquire the active sequence + track each time, then
  // identify candidates by their underlying ProjectItem/media path.
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("active sequence disappeared while trimming");

  const items = kind === "audio"
    ? await getAudioTrackItems(sequence, trackIndex)
    : await getVideoTrackItems(sequence, trackIndex);

  const candidates = [];
  for (const item of items) {
    try {
      const d = await describeTrackItem(item);
      const mediaMeta = { path: d.mediaPath, name: d.projectItemName || d.name };
      const strongMatch = metadataMatchesEdit(mediaMeta, edit);
      const fallback = String(fallbackName || "").toLowerCase();
      const weakNameMatch = !!fallback && (
        d.name.toLowerCase().includes(fallback) ||
        fallback.includes(d.name.toLowerCase()) ||
        d.projectItemName.toLowerCase().includes(fallback) ||
        fallback.includes(d.projectItemName.toLowerCase())
      );
      if (strongMatch || weakNameMatch) {
        candidates.push({ item, d, strongMatch });
      }
    } catch (_) {}
  }

  if (!candidates.length) return null;

  // If the same source is used more than once, choose the occurrence nearest
  // the intended edit location. Unlike 0.2.6, do not reject it merely because
  // a prior trim action changed its exposed start time.
  candidates.sort((a, b) => {
    if (a.strongMatch !== b.strongMatch) return a.strongMatch ? -1 : 1;
    return Math.abs(a.d.start - Number(edit.timeline_start)) - Math.abs(b.d.start - Number(edit.timeline_start));
  });
  return candidates[0].item;
}


async function findAudioTrackIndicesForEdit(project, edit, fallbackName) {
  // 0.2.8: overwrite can fan a multichannel source out across A1/A2/A3...
  // Discover every audio track containing the newly placed source occurrence
  // so all channels/components get trimmed to the same editorial duration.
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("active sequence disappeared while finding audio items");
  const trackCount = await sequence.getAudioTrackCount();
  const matches = [];
  const targetStart = Number(edit.timeline_start);
  const fallback = String(fallbackName || "").toLowerCase();

  for (let trackIndex = 0; trackIndex < trackCount; trackIndex++) {
    const items = await getAudioTrackItems(sequence, trackIndex);
    for (const item of items) {
      try {
        const d = await describeTrackItem(item);
        const mediaMeta = { path: d.mediaPath, name: d.projectItemName || d.name };
        const strongMatch = metadataMatchesEdit(mediaMeta, edit);
        const weakNameMatch = !!fallback && (
          d.name.toLowerCase().includes(fallback) ||
          fallback.includes(d.name.toLowerCase()) ||
          d.projectItemName.toLowerCase().includes(fallback) ||
          fallback.includes(d.projectItemName.toLowerCase())
        );
        // Placement starts every audio component at timeline_start. Keep the
        // tolerance small enough to avoid grabbing an older occurrence of the
        // same source elsewhere in the sequence.
        const nearPlacement = Math.abs(d.start - targetStart) <= 0.25;
        if ((strongMatch || weakNameMatch) && nearPlacement) {
          matches.push(trackIndex);
          break;
        }
      } catch (_) {}
    }
  }
  return [...new Set(matches)];
}

async function runSingleAction(project, objectFactory, actionBuilder, label) {
  // Adobe's 26.x UXP host requires many Action objects to be CREATED while
  // Project.lockedAccess() is active, not merely executed while locked.
  // Resolve/reacquire the host object first, then synchronously construct and
  // commit the Action inside the same lockedAccess callback.
  let hostObject;
  try {
    hostObject = await objectFactory();
  } catch (err) {
    throw new Error(`${label} object acquisition failed: ${err.message || err}`);
  }

  let ok = false;
  try {
    project.lockedAccess(() => {
      let action;
      try {
        action = actionBuilder(hostObject);
      } catch (err) {
        throw new Error(`${label} action creation failed: ${err.message || err}`);
      }
      ok = project.executeTransaction((compoundAction) => {
        compoundAction.addAction(action);
      }, label);
    });
  } catch (err) {
    throw new Error(`${label} locked transaction failed: ${err.message || err}`);
  }
  if (!ok) throw new Error(`${label} transaction returned false.`);
  v5SessionMutations++;
}

async function trimPlacedItem(project, kind, trackIndex, e, fallbackName, editNumber) {
  const inTime = ppro.TickTime.createWithSeconds(Number(e.source_in));
  const outTime = ppro.TickTime.createWithSeconds(Number(e.source_out));
  const startTime = ppro.TickTime.createWithSeconds(Number(e.timeline_start));
  const endTime = ppro.TickTime.createWithSeconds(Number(e.timeline_start + (e.source_out - e.source_in)));

  const itemFor = async () => {
    const item = await findTrackItemForEdit(project, kind, trackIndex, e, fallbackName);
    if (!item) throw new Error(`could not reacquire ${kind} TrackItem for ${e.media_match || e.media_path || fallbackName}`);
    return item;
  };

  // 0.3.5: restore the proven 0.3.3 VIDEO trim path exactly. 0.3.4's
  // all-at-once compound trim dereferenced a nullptr on the very first video
  // edit in Premiere 26.x.
  //
  // AUDIO is handled more conservatively. Overwrite placement already puts
  // each audio TrackItem at timeline_start. Setting the source in/out points is
  // sufficient to establish the desired source range/duration, so we avoid the
  // redundant audio start/end actions that were intermittently returning stale
  // UXP TrackItem wrappers ("The script object is no longer valid"). Each action
  // still reacquires the TrackItem before action creation/transaction.
  await runSingleAction(project, itemFor, (item) => item.createSetInPointAction(inTime), `V5 ${kind} in ${editNumber}`);
  await runSingleAction(project, itemFor, (item) => item.createSetOutPointAction(outTime), `V5 ${kind} out ${editNumber}`);

  if (kind === "video") {
    await runSingleAction(project, itemFor, (item) => item.createSetStartAction(startTime), `V5 ${kind} start ${editNumber}`);
    await runSingleAction(project, itemFor, (item) => item.createSetEndAction(endTime), `V5 ${kind} end ${editNumber}`);
  }
}

async function setProjectItemInOut(project, clip, sourceIn, sourceOut, editNumber) {
  const inTime = ppro.TickTime.createWithSeconds(Number(sourceIn));
  const outTime = ppro.TickTime.createWithSeconds(Number(sourceOut));
  let ok = false;
  try {
    project.lockedAccess(() => {
      const clipProjectItem = ppro.ClipProjectItem.cast(ppro.ProjectItem.cast(clip));
      let action;
      try {
        // IMPORTANT: Premiere UXP applies ProjectItem in/out to NEW placements.
        // This must be its own committed transaction BEFORE overwrite placement;
        // putting both actions in one compound transaction is known to race.
        action = clipProjectItem.createSetInOutPointsAction(inTime, outTime);
      } catch (err) {
        throw new Error(`Edit ${editNumber} SOURCE RANGE action creation failed: ${err.message || err}`);
      }
      ok = project.executeTransaction((compoundAction) => {
        compoundAction.addAction(action);
      }, `V5 source range ${editNumber}`);
    });
  } catch (err) {
    throw new Error(`Edit ${editNumber} SOURCE RANGE locked transaction failed: ${err.message || err}`);
  }
  if (!ok) throw new Error(`Edit ${editNumber} SOURCE RANGE transaction returned false.`);
  v5SessionMutations++;
}

async function clearProjectItemInOut(project, clip, editNumber) {
  let ok = false;
  try {
    project.lockedAccess(() => {
      const clipProjectItem = ppro.ClipProjectItem.cast(ppro.ProjectItem.cast(clip));
      let action;
      try {
        action = clipProjectItem.createClearInOutPointsAction();
      } catch (err) {
        throw new Error(`Edit ${editNumber} CLEAR SOURCE RANGE action creation failed: ${err.message || err}`);
      }
      ok = project.executeTransaction((compoundAction) => {
        compoundAction.addAction(action);
      }, `V5 clear source range ${editNumber}`);
    });
  } catch (err) {
    throw new Error(`Edit ${editNumber} CLEAR SOURCE RANGE locked transaction failed: ${err.message || err}`);
  }
  if (!ok) throw new Error(`Edit ${editNumber} CLEAR SOURCE RANGE transaction returned false.`);
  v5SessionMutations++;
}

async function inspectClipForPreflight(clip) {
  const info = {
    duration_seconds: null,
    offline: null,
    has_video: null,
    has_audio: null,
    media_type: "unknown"
  };
  try {
    const media = await clip.getMedia();
    if (media) {
      let duration = null;
      if (typeof media.getDuration === "function") duration = await media.getDuration();
      else duration = await media.duration;
      const seconds = Number(duration && duration.seconds);
      if (Number.isFinite(seconds) && seconds >= 0) info.duration_seconds = seconds;
    }
  } catch (_) {}
  try { if (typeof clip.isOffline === "function") info.offline = !!(await clip.isOffline()); }
  catch (_) {}
  try {
    const mediaTypes = ppro.Constants && ppro.Constants.MediaType;
    if (clip && typeof clip.getComponentChain === "function" && mediaTypes) {
      try { info.has_video = !!(await clip.getComponentChain(mediaTypes.VIDEO)); } catch (_) {}
      try { info.has_audio = !!(await clip.getComponentChain(mediaTypes.AUDIO)); } catch (_) {}
    }
  } catch (_) {}
  if (info.has_video === true && info.has_audio === true) info.media_type = "video_audio";
  else if (info.has_video === true) info.media_type = "video";
  else if (info.has_audio === true) info.media_type = "audio";
  return info;
}

async function preflightDecisions(project, sequence, doc = decisionDoc) {
  if (!doc) throw new Error("Load decisions first.");
  validate(doc);
  if (!project) throw new Error("No active Premiere project.");
  if (!sequence) throw new Error("No active sequence. Create/open the target sequence first.");

  const videoTrackCount = Number(await sequence.getVideoTrackCount());
  const audioTrackCount = Number(await sequence.getAudioTrackCount());
  const timeline = await getBridgeTimelineInventory(sequence);
  const collisionPolicy = doc.schema === "v5-edit-decisions-0.3" ? doc.collision_policy : "legacy_allow_overwrite";
  let placementRequestGuard = { supplied: false, request_id: null, status: "legacy", retry_safe: null };
  if (doc.schema === "v5-edit-decisions-0.3") {
    if (bridgeGuidString(project.guid) !== doc.project_guid) throw new Error("Active project GUID does not match the placement document.");
    if (String(sequence.name || "") !== doc.sequence_name) throw new Error(`Active sequence is ${String(sequence.name || "")}, not ${doc.sequence_name}.`);
    if (bridgeGuidString(sequence.guid) !== doc.sequence_guid) throw new Error("Active sequence GUID does not match the placement document.");
    if (timeline.state_token !== doc.expected_timeline_state_token) {
      throw new Error(`Timeline state changed since the placement document was authored (expected ${doc.expected_timeline_state_token}, current ${timeline.state_token}). Request a fresh revision_context and rebuild the placement document.`);
    }
    placementRequestGuard = await assertPlacementRequestUnused(doc.request_id);
  }
  const checks = [];

  for (let i = 0; i < doc.edits.length; i++) {
    const raw = doc.edits[i];
    const times = decisionTimes(raw);
    const e = { ...raw, source_in: times.source_in, source_out: times.source_out, timeline_start: times.timeline_start };
    const vTrack = Number.isInteger(e.video_track) ? e.video_track : 0;
    const aTrack = Number.isInteger(e.audio_track) ? e.audio_track : 0;
    const label = `Edit ${i + 1} PREFLIGHT`;

    if (e.source_in < 0) throw new Error(`${label} failed: source_in cannot be negative.`);
    if (vTrack < 0) throw new Error(`${label} failed: video_track cannot be negative.`);
    if (aTrack < 0) throw new Error(`${label} failed: audio_track cannot be negative.`);

    let clip;
    try { clip = await reacquireProjectItem(project, e); }
    catch (err) { throw new Error(`${label} failed: ${String(err.message || err)}`); }
    const clipName = String(clip.name || e.media_match || e.media_path || "clip");
    const resolvedMediaPath = await clip.getMediaFilePath();
    if (typeof resolvedMediaPath !== "string" || !resolvedMediaPath.trim()) {
      throw new Error(`${label} failed: resolved media path is unavailable.`);
    }
    const info = await inspectClipForPreflight(clip);

    if (info.offline === true) throw new Error(`${label} failed: media is offline: ${clipName}.`);
    if (info.duration_seconds != null && info.duration_seconds > 0 && e.source_out > info.duration_seconds + 0.001) {
      throw new Error(`${label} failed: source_out ${e.source_out.toFixed(3)}s exceeds ${clipName} duration ${info.duration_seconds.toFixed(3)}s.`);
    }
    if (info.has_video === false && info.has_audio === false) {
      throw new Error(`${label} failed: ${clipName} exposes neither video nor audio.`);
    }
    if (info.has_video !== false && (!Number.isInteger(videoTrackCount) || vTrack >= videoTrackCount)) {
      throw new Error(`${label} failed: V${vTrack + 1} does not exist; active sequence has ${videoTrackCount} video track(s).`);
    }
    if (info.has_audio !== false && (!Number.isInteger(audioTrackCount) || aTrack >= audioTrackCount)) {
      throw new Error(`${label} failed: A${aTrack + 1} does not exist; active sequence has ${audioTrackCount} audio track(s).`);
    }

    const plannedEnd = e.timeline_start + (e.source_out - e.source_in);
    const collisions = [];
    const inspectTrack = (kind, trackIndex) => {
      const tracks = kind === "video" ? timeline.video_tracks : timeline.audio_tracks;
      const track = tracks.find(candidate => candidate.index === trackIndex);
      (track && track.items || []).forEach(item => {
        const startsBeforeEnd = Number(item.timeline_start_seconds) < plannedEnd - V5_REVISION_COLLISION_EPSILON;
        const endsAfterStart = Number(item.timeline_end_seconds) > e.timeline_start + V5_REVISION_COLLISION_EPSILON;
        if (startsBeforeEnd && endsAfterStart) collisions.push({
          kind,
          track_index: trackIndex,
          name: item.name,
          project_item_id: item.project_item_id || null,
          timeline_start: item.timeline_start_seconds,
          timeline_end: item.timeline_end_seconds,
          disabled: item.disabled === true
        });
      });
      checks.forEach(previous => {
        const previousUsesTrack = kind === "video"
          ? previous.video_track === trackIndex
          : Array.isArray(previous.audio_tracks_checked) && previous.audio_tracks_checked.includes(trackIndex);
        if (!previousUsesTrack) return;
        if (previous.timeline_start < plannedEnd - V5_REVISION_COLLISION_EPSILON && previous.timeline_end > e.timeline_start + V5_REVISION_COLLISION_EPSILON) {
          collisions.push({ kind, track_index: trackIndex, planned_edit: previous.edit, timeline_start: previous.timeline_start, timeline_end: previous.timeline_end });
        }
      });
    };
    if (info.has_video !== false) inspectTrack("video", vTrack);
    const audioTracksChecked = [];
    if (info.has_audio !== false) {
      // Premiere may fan a multichannel source from the requested audio track
      // across every following sequence track. Conservatively inspect that full
      // range because the host API does not expose the placement fan-out count.
      for (let trackIndex = aTrack; trackIndex < audioTrackCount; trackIndex++) {
        audioTracksChecked.push(trackIndex);
        inspectTrack("audio", trackIndex);
      }
    }
    if (collisionPolicy === "reject" && collisions.length) {
      throw new Error(`${label} failed: placement would overwrite ${collisions.length} existing or planned timeline item(s). Use collision_policy allow_overwrite only when replacement is intentional.`);
    }

    checks.push({
      edit: i + 1,
      clip_name: clipName,
      resolved_media_path: resolvedMediaPath,
      media_path: String(e.media_path || "") || null,
      media_match: String(e.media_match || "") || null,
      source_in: e.source_in,
      source_out: e.source_out,
      timeline_start: e.timeline_start,
      timeline_end: plannedEnd,
      duration_seconds: info.duration_seconds,
      media_type: info.media_type,
      offline: info.offline,
      video_track: info.has_video === false ? null : vTrack,
      audio_track: info.has_audio === false ? null : aTrack,
      audio_tracks_checked: audioTracksChecked,
      audio_collision_scope: info.has_audio === false ? "none" : "all_tracks_from_target",
      collisions
    });
  }

  return {
    preflighted: true,
    edits: checks.length,
    sequence: { name: String(sequence.name || "") },
    video_tracks: videoTrackCount,
    audio_tracks: audioTrackCount,
    collision_policy: collisionPolicy,
    collision_count: checks.reduce((sum, check) => sum + check.collisions.length, 0),
    project_guard: doc.schema === "v5-edit-decisions-0.3" ? { supplied: true, matched: true, guid: doc.project_guid } : { supplied: false, matched: null, guid: null },
    sequence_guard: doc.schema === "v5-edit-decisions-0.3" ? { supplied: true, matched: true, name: doc.sequence_name, guid: doc.sequence_guid } : { supplied: false, matched: null, name: null, guid: null },
    timeline_state_guard: doc.schema === "v5-edit-decisions-0.3" ? { supplied: true, matched: true, state_token: doc.expected_timeline_state_token } : { supplied: false, matched: null, state_token: null },
    placement_request_guard: placementRequestGuard,
    session_mutation_guard: describeSessionMutationGuard(checks.length * 3),
    checks
  };
}

async function preflightLoadedDecisions() {
  if (!decisionDoc) throw new Error("No decisions loaded.");
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence. Create/open the target sequence first.");
  return await preflightDecisions(project, sequence, decisionDoc);
}

async function applyDecisions() {
  if (applyDecisions.inProgress || (typeof applyLoadedRevisions === "function" && applyLoadedRevisions.inProgress)) throw new Error("Apply already in progress. Wait for it to finish.");
  if (typeof v5Imports !== "undefined" && v5Imports && v5Imports.busy) throw new Error("Import already in progress.");
  applyDecisions.inProgress = true;
  try {
  const applyDoc = decisionDoc;
  const applySignature = decisionSignature;
  const assertLoadedDecisionsUnchanged = () => {
    if (decisionDoc !== applyDoc || decisionSignature !== applySignature) {
      throw new Error("Loaded decisions changed during Apply. Preflight the current proposal before retrying.");
    }
  };
  if (!applyDoc) throw new Error("Load decisions first.");
  if (applySignature && applySignature === lastAppliedSignature) throw new Error("These exact decisions were already applied in this V5 session. Load a fresh sequence or a changed decision file before applying again.");
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence. Create/open the target sequence first.");

  resetLog(`Preflighting ${applyDoc.edits.length} edits for: ${sequence.name}`);
  const preflight = await preflightDecisions(project, sequence, applyDoc);
  assertLoadedDecisionsUnchanged();
  const placementMutationUnits = applyDoc.edits.length * 3;
  assertSessionMutationBudget(placementMutationUnits, "placement");
  const placementIntent = applyDoc.schema === "v5-edit-decisions-0.3"
    ? await reservePlacementRequest(applyDoc, preflight)
    : null;
  log(`PREFLIGHT PASSED — ${preflight.edits} edit(s), ${preflight.video_tracks} video track(s), ${preflight.audio_tracks} audio track(s).`);
  log(`Applying ${applyDoc.edits.length} edits to: ${sequence.name}`);

  const editor = ppro.SequenceEditor.getEditor(sequence);
  let completed = 0;
  log("Indexing Premiere project media…");
  const root = await project.getRootItem();
  const projectClipMetadata = await collectProjectClipMetadata(root);
  log(`Indexed ${projectClipMetadata.length} file-backed clip(s).`);
  log("0.3.6 strategy: set ClipProjectItem source in/out BEFORE placement, then overwrite only the requested segment. No post-placement TrackItem trimming.");

  for (let i = 0; i < applyDoc.edits.length; i++) {
    const raw = applyDoc.edits[i];
    const times = decisionTimes(raw);
    const e = { ...raw, source_in: times.source_in, source_out: times.source_out, timeline_start: times.timeline_start };
    const vTrack = Number.isInteger(e.video_track) ? e.video_track : 0;
    const aTrack = Number.isInteger(e.audio_track) ? e.audio_track : 0;
    const timelineTime = ppro.TickTime.createWithSeconds(Number(e.timeline_start));

    const resolvedPath = preflight.checks[i] && preflight.checks[i].resolved_media_path;
    if (typeof resolvedPath !== "string" || !resolvedPath.trim()) {
      throw new Error(`Edit ${i + 1} resolved media path is missing from preflight.`);
    }
    const clip = await reacquireProjectItem(project, { ...e, media_path: resolvedPath });
    assertLoadedDecisionsUnchanged();
    const clipName = String(clip.name || e.media_match || e.media_path || "clip");
    log(`→ ${i + 1}: ${clipName}`);
    log(`   source ${e.source_in.toFixed(3)}–${e.source_out.toFixed(3)}s  timeline ${e.timeline_start.toFixed(3)}s  V${vTrack + 1}/A${aTrack + 1}`);

    // 0.3.6: the key fix. A full-length overwrite followed by trimming can
    // destroy/replace previously-built timeline material and leaves Premiere
    // with unreliable TrackItem wrappers. Instead, commit the source range on
    // the ClipProjectItem first, then place that already-trimmed range.
    try {
      await setProjectItemInOut(project, clip, e.source_in, e.source_out, i + 1);
      log("   ✓ source range prepared");
    } catch (err) {
      throw new Error(`Edit ${i + 1} SOURCE RANGE failed: ${err.message || err}`);
    }

    let overwriteOK = false;
    try {
      project.lockedAccess(() => {
        let overwriteAction;
        try {
          const projectItem = ppro.ProjectItem.cast(clip);
          overwriteAction = editor.createOverwriteItemAction(projectItem, timelineTime, vTrack, aTrack);
        } catch (err) {
          throw new Error(`Edit ${i + 1} PLACE action creation failed: ${err.message || err}`);
        }
        overwriteOK = project.executeTransaction((compoundAction) => {
          compoundAction.addAction(overwriteAction);
        }, `V5 place ${i + 1}`);
      });
    } catch (err) {
      throw new Error(`Edit ${i + 1} PLACE locked transaction failed: ${err.message || err}`);
    }
    if (!overwriteOK) throw new Error(`Edit ${i + 1} PLACE transaction returned false.`);
    v5SessionMutations++;
    log("   ✓ trimmed source segment placed");

    // Clear the Project-panel marks after placement so V5 does not leave the
    // user's source clips altered. The timeline occurrence keeps the range it
    // was created with.
    try {
      await clearProjectItemInOut(project, clip, i + 1);
      log("   ✓ source marks cleared");
    } catch (err) {
      // Placement is already valid; make failure explicit rather than silently
      // continuing with a source item whose marks may affect a later edit.
      throw new Error(`Edit ${i + 1} CLEAR SOURCE RANGE failed after placement: ${err.message || err}`);
    }

    completed++;
    log(`✓ ${completed}: ${clipName} complete`);
  }
  log(`DONE — ${completed} edits applied.`);
  lastAppliedSignature = applySignature;
  const placementReceipt = placementIntent
    ? await completePlacementRequest(placementIntent, preflight, completed)
    : null;
  const applyButton = $("apply");
  if (applyButton && decisionDoc === applyDoc) applyButton.disabled = true;
  return { applied: true, edits: completed, placement_receipt: placementReceipt };
  } finally {
    applyDecisions.inProgress = false;
  }
}

entrypoints.setup({
  panels: {
    v5editor: { show() {}, hide() {} }
  }
});

// -----------------------------------------------------------------------------
// Prototype 0.4.63 — focused two-way filesystem bridge for Codex / local agents.
// Premiere UXP blocks plain HTTP localhost on macOS, so this bridge exchanges
// JSON through the user-selected bridge/state folder. The existing validator
// and stable apply engine remain the only placement path.
// -----------------------------------------------------------------------------
const V5_BRIDGE_PLUGIN_VERSION = "0.4.63";
const V5_BRIDGE_SNAPSHOT_SCHEMA = "v5-premiere-snapshot-3.1";
const V5_REVISION_SCHEMA = "v5-timeline-revisions-0.1";
const V5_BRIDGE_POLL_MS = 1500;
const V5_REVISION_TIME_TOLERANCE = 0.02;
const V5_REVISION_COLLISION_EPSILON = 0.001;
// Premiere may invalidate its scripting transaction after a larger mutation run.
// Reject oversized documents before preflight/apply so callers must refresh the
// live timeline token between independently verified batches.
const V5_MAX_REVISIONS_PER_BATCH = 10;
const V5_MAX_SESSION_MUTATIONS = 40;
const V5_BRIDGE_FOLDER_TOKEN_KEY = "v5.bridge.folder.token";
let v5SessionMutations = 0;

function revisionMutationUnits(revision) {
  // A trim commits its source-range compound action, then at most one move
  // transaction to restore the original timeline start. Reserve the true
  // worst case so long selects runs do not exhaust the session prematurely.
  return revision && revision.operation === "trim" ? 2 : 1;
}

function assertSessionMutationBudget(requested, operationLabel) {
  const units = Number(requested);
  if (!Number.isInteger(units) || units < 0) throw new Error("Invalid session mutation estimate.");
  if (v5SessionMutations + units > V5_MAX_SESSION_MUTATIONS) {
    throw new Error(`This panel session has completed ${v5SessionMutations} mutations. The requested ${operationLabel} needs up to ${units} more and would exceed the safe session maximum of ${V5_MAX_SESSION_MUTATIONS}. Reconnect or reload the V5 panel, obtain a fresh context, then preflight again.`);
  }
}

function revisionBatchBudget(revisions, reserveRollback = true) {
  const forward = revisions.reduce((sum, revision) => sum + revisionMutationUnits(revision), 0);
  const rollback = reserveRollback ? revisions.reduce((sum, revision) =>
    sum + (revision.operation === "remove" ? 0 : revisionMutationUnits(revision)), 0) : 0;
  return { forward, rollback, total: forward + rollback };
}

function getSessionMutationBudget() {
  const remaining = Math.max(0, V5_MAX_SESSION_MUTATIONS - v5SessionMutations);
  return {
    maximum: V5_MAX_SESSION_MUTATIONS,
    used: v5SessionMutations,
    remaining,
    revision_batch_maximum: V5_MAX_REVISIONS_PER_BATCH,
    trim_units_per_revision: 2,
    standard_units_per_revision: 1,
    max_trim_revisions_now: Math.min(V5_MAX_REVISIONS_PER_BATCH, Math.floor(remaining / 2)),
    max_standard_revisions_now: Math.min(V5_MAX_REVISIONS_PER_BATCH, remaining),
    max_trim_revisions_then_assembly: Math.min(V5_MAX_REVISIONS_PER_BATCH, Math.floor(Math.max(0, remaining - 1) / 2)),
    max_trim_revisions_with_rollback: Math.min(V5_MAX_REVISIONS_PER_BATCH, Math.floor(remaining / 4)),
    max_standard_revisions_with_rollback: Math.min(V5_MAX_REVISIONS_PER_BATCH, Math.floor(remaining / 2)),
    max_trim_revisions_with_rollback_then_assembly: Math.min(V5_MAX_REVISIONS_PER_BATCH, Math.floor(Math.max(0, remaining - 1) / 4)),
    reset: "Reconnect or reload the V5 panel"
  };
}

function describeSessionMutationGuard(requested) {
  const budget = getSessionMutationBudget();
  const units = Number(requested);
  return {
    requested: units,
    used: budget.used,
    remaining: budget.remaining,
    maximum: budget.maximum,
    allowed: Number.isInteger(units) && units >= 0 && units <= budget.remaining,
    reconnect_required: Number.isInteger(units) && units >= 0 ? units > budget.remaining : true
  };
}

function updateSessionBudgetStatus() {
  const element = $("budgetStatus");
  if (!element) return;
  const budget = getSessionMutationBudget();
  element.textContent = `Session budget: ${budget.remaining} / ${budget.maximum} transactions remaining`;
  element.classList.toggle("warning", budget.remaining <= 10);
}
let v5BridgeEnabled = false;
let v5BridgeTimer = null;
let v5BridgeBusy = false;
let v5BridgeStateFolder = null;
let v5BridgeLastCommandId = null;
let revisionDoc = null;
let revisionSignature = null;
let lastAppliedRevisionSignature = null;
let lastRevisionReceipt = null;
let lastRevisionFailure = null;

function bridgeSetStatus(text, good = false) {
  const el = $("bridgeStatus");
  if (!el) return;
  el.textContent = text;
  el.className = good ? "small good" : "small";
}

async function bridgeChildFolder(parent, name, create = false) {
  const entries = await parent.getEntries();
  let found = entries.find(e => e.isFolder && e.name === name);
  if (!found && create) found = await parent.createFolder(name);
  return found || null;
}

async function bridgeWriteJson(folder, name, value) {
  let file = null;
  try { file = await folder.getEntry(name); } catch (_) {}
  if (!file) file = await folder.createFile(name, { overwrite: true });
  await file.write(JSON.stringify(value, null, 2));
}

async function bridgeReadJson(folder, name) {
  try {
    const file = await folder.getEntry(name);
    if (!file || !file.isFile) return null;
    return JSON.parse(await file.read());
  } catch (_) { return null; }
}

async function placementRequestFolders(create = false) {
  if (!v5BridgeStateFolder) throw new Error("Schema 0.3 placement requests require an active Codex bridge connection.");
  return {
    intents: await bridgeChildFolder(v5BridgeStateFolder, "placement-intents", create),
    receipts: await bridgeChildFolder(v5BridgeStateFolder, "placement-receipts", create)
  };
}

async function getPlacementRequestRecords(requestId) {
  const folders = await placementRequestFolders(false);
  const name = `${encodeURIComponent(requestId)}.json`;
  return {
    intent: folders.intents ? await bridgeReadJson(folders.intents, name) : null,
    receipt: folders.receipts ? await bridgeReadJson(folders.receipts, name) : null
  };
}

async function assertPlacementRequestUnused(requestId) {
  const records = await getPlacementRequestRecords(requestId);
  if (records.receipt) throw new Error(`Placement request_id ${requestId} was already completed at ${records.receipt.completed_at}; do not retry it.`);
  if (records.intent) throw new Error(`Placement request_id ${requestId} already has a durable intent from ${records.intent.started_at}. Its outcome may be uncertain; inspect the timeline before creating a new request.`);
  return { supplied: true, request_id: requestId, status: "unused", retry_safe: true };
}

async function getPlacementRequestStatus(requestId) {
  if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) throw new Error("A valid placement request_id is required.");
  const records = await getPlacementRequestRecords(requestId);
  if (records.receipt) return {
    request_id: requestId,
    status: "applied",
    retry_safe: false,
    completed_at: records.receipt.completed_at || null,
    receipt: records.receipt
  };
  if (records.intent) return {
    request_id: requestId,
    status: "attention",
    retry_safe: false,
    started_at: records.intent.started_at || null,
    reason: "A durable intent exists without a completion receipt; inspect the timeline before creating a new request.",
    intent: records.intent
  };
  return { request_id: requestId, status: "not_found", retry_safe: true };
}

async function readPlacementHistoryFolder(folder) {
  const documents = [];
  if (!folder) return documents;
  const entries = (await folder.getEntries()).filter(entry => entry.isFile && entry.name.endsWith(".json"));
  for (const entry of entries) {
    try { documents.push(JSON.parse(await entry.read())); }
    catch (err) { documents.push({ malformed: true, file: entry.name, error: String(err.message || err) }); }
  }
  return documents;
}

async function auditPlacementHistory() {
  const activeProject = await ppro.Project.getActiveProject();
  const activeSequence = activeProject ? await activeProject.getActiveSequence() : null;
  const activeProjectGuid = activeProject ? bridgeGuidString(activeProject.guid) : null;
  const activeSequenceGuid = activeSequence ? bridgeGuidString(activeSequence.guid) : null;
  const folders = await placementRequestFolders(false);
  const intents = await readPlacementHistoryFolder(folders.intents);
  const receipts = await readPlacementHistoryFolder(folders.receipts);
  const receiptIds = new Set(receipts.map(item => item.request_id).filter(Boolean));
  const intentIds = new Set(intents.map(item => item.request_id).filter(Boolean));
  const issues = [];
  intents.filter(item => item.malformed).forEach(item => issues.push(`Malformed placement intent ${item.file}: ${item.error}`));
  receipts.filter(item => item.malformed).forEach(item => issues.push(`Malformed placement receipt ${item.file}: ${item.error}`));
  const uncertain = intents.filter(item => item.request_id && !receiptIds.has(item.request_id));
  uncertain.forEach(item => issues.push(`Placement request ${item.request_id} has an intent but no completion receipt.`));
  const orphanReceipts = receipts.filter(item => item.request_id && !intentIds.has(item.request_id));
  orphanReceipts.forEach(item => issues.push(`Placement request ${item.request_id} has a completion receipt but no matching intent.`));
  const requestIds = Array.from(new Set([...intentIds, ...receiptIds])).sort();
  const requests = requestIds.map(requestId => {
    const intent = intents.find(item => item.request_id === requestId) || null;
    const receipt = receipts.find(item => item.request_id === requestId) || null;
    const source = (intent && intent.source_document) || (receipt && receipt.source_document) || {};
    const status = intent && receipt ? "completed" : intent ? "uncertain" : "orphan_receipt";
    const scopeStatus = !source.project_guid || !source.sequence_guid
      ? "unbound"
      : source.project_guid !== activeProjectGuid
        ? "other_project"
        : source.sequence_guid !== activeSequenceGuid
          ? "other_sequence"
          : "current_sequence";
    const recommendedAction = status === "completed"
      ? "Do not retry; the placement has a completion receipt."
      : status === "uncertain"
        ? "Do not retry; inspect the bound timeline before creating a new request."
        : "Do not retry; preserve the receipt and inspect placement history before any new request.";
    return {
      request_id: requestId,
      status,
      scope_status: scopeStatus,
      retry_safe: false,
      recommended_action: recommendedAction,
      started_at: (intent && intent.started_at) || (receipt && receipt.started_at) || null,
      completed_at: (receipt && receipt.completed_at) || null,
      project_guid: source.project_guid || null,
      sequence_guid: source.sequence_guid || null,
      sequence_name: source.sequence_name || null,
      expected_timeline_state_token: source.expected_timeline_state_token || null
    };
  });
  const scopeCounts = {
    current_sequence: requests.filter(item => item.scope_status === "current_sequence").length,
    other_sequence: requests.filter(item => item.scope_status === "other_sequence").length,
    other_project: requests.filter(item => item.scope_status === "other_project").length,
    unbound: requests.filter(item => item.scope_status === "unbound").length,
    current_sequence_attention: requests.filter(item => item.scope_status === "current_sequence" && item.status !== "completed").length
  };
  const activeScopeBlockers = requests.filter(item =>
    item.status !== "completed" && (item.scope_status === "current_sequence" || item.scope_status === "unbound")
  ).map(item => item.request_id);
  const activeScopeStatus = !activeProjectGuid || !activeSequenceGuid
    ? "unavailable"
    : activeScopeBlockers.length
      ? "attention"
      : "healthy";
  const newPlacementAdvisory = activeScopeStatus === "healthy"
    ? { safe_to_proceed: true, reason: "No unresolved placement history is bound or potentially bound to the active sequence." }
    : activeScopeStatus === "attention"
      ? { safe_to_proceed: false, reason: `Resolve or inspect placement request(s) ${activeScopeBlockers.join(", ")} before starting a new placement.` }
      : { safe_to_proceed: false, reason: "No active project and sequence are available for placement-history verification." };
  return {
    status: issues.length ? "attention" : "healthy",
    active_scope_status: activeScopeStatus,
    active_scope_blocker_request_ids: activeScopeBlockers,
    new_placement_advisory: newPlacementAdvisory,
    active_scope: {
      project_guid: activeProjectGuid,
      sequence_guid: activeSequenceGuid,
      sequence_name: activeSequence ? String(activeSequence.name || "") : null
    },
    scope_counts: scopeCounts,
    intents: intents.filter(item => !item.malformed).length,
    receipts: receipts.filter(item => !item.malformed).length,
    completed: receipts.filter(item => item.request_id && intentIds.has(item.request_id)).length,
    uncertain: uncertain.length,
    orphan_receipts: orphanReceipts.length,
    uncertain_request_ids: uncertain.map(item => item.request_id),
    orphan_receipt_request_ids: orphanReceipts.map(item => item.request_id),
    requests,
    issues
  };
}

async function reservePlacementRequest(doc, preflight) {
  await assertPlacementRequestUnused(doc.request_id);
  const folders = await placementRequestFolders(true);
  const intent = {
    schema: "v5-placement-intent-0.1",
    request_id: doc.request_id,
    plugin_version: V5_BRIDGE_PLUGIN_VERSION,
    started_at: new Date().toISOString(),
    status: "started",
    decision_signature: decisionSignature,
    source_document: doc,
    preflight
  };
  await bridgeWriteJson(folders.intents, `${encodeURIComponent(doc.request_id)}.json`, intent);
  return intent;
}

async function completePlacementRequest(intent, preflight, edits) {
  const folders = await placementRequestFolders(true);
  const completedAt = new Date().toISOString();
  const receipt = {
    schema: "v5-placement-receipt-0.1",
    request_id: intent.request_id,
    plugin_version: V5_BRIDGE_PLUGIN_VERSION,
    started_at: intent.started_at,
    completed_at: completedAt,
    decision_signature: intent.decision_signature,
    source_document: intent.source_document,
    preflight,
    edits,
    applied: true
  };
  const recordName = `${encodeURIComponent(intent.request_id)}.json`;
  await bridgeWriteJson(folders.receipts, recordName, receipt);
  intent.status = "completed";
  intent.completed_at = completedAt;
  await bridgeWriteJson(folders.intents, recordName, intent);
  return { request_id: receipt.request_id, completed_at: receipt.completed_at, edits: receipt.edits, applied: true };
}

function revisionReceiptSummary(receipt) {
  return {
    schema: receipt.schema,
    receipt_id: receipt.receipt_id,
    plugin_version: receipt.plugin_version,
    completed_at: receipt.completed_at,
    sequence: receipt.sequence,
    request_id: receipt.source_document && receipt.source_document.request_id || null,
    request_attempt: receipt.preflight && receipt.preflight.request_id_guard &&
      receipt.preflight.request_id_guard.attempt || null,
    revisions: Array.isArray(receipt.verifications) ? receipt.verifications.length : 0,
    operations: Array.isArray(receipt.verifications) ? receipt.verifications.map(item => item.operation) : [],
    verified: Array.isArray(receipt.verifications) && receipt.verifications.every(item => item.verified === true),
    recovery_warnings: receipt.recovery && Array.isArray(receipt.recovery.warnings) ? receipt.recovery.warnings.length : 0,
    recovered: !!receipt.recovered_at,
    recovered_at: receipt.recovered_at || null,
    recovery_incomplete: !!receipt.recovery_started_at && !receipt.recovered_at,
    recovery_source_receipt_id: receipt.recovery_source_receipt_id || null
  };
}

async function persistRevisionReceipt(receipt, options = {}) {
  if (!v5BridgeStateFolder || !receipt) return;
  if (options.update_latest !== false) {
    await bridgeWriteJson(v5BridgeStateFolder, "last-revision-receipt.json", receipt);
  }
  const archive = await bridgeChildFolder(v5BridgeStateFolder, "revision-receipts", true);
  await bridgeWriteJson(archive, `${receipt.receipt_id}.json`, receipt);
}

async function getRevisionReceiptById(receiptId = null) {
  if (!receiptId) return lastRevisionReceipt;
  if (!/^[A-Za-z0-9_-]+$/.test(String(receiptId))) throw new Error("Invalid revision receipt ID.");
  if (lastRevisionReceipt && lastRevisionReceipt.receipt_id === receiptId) return lastRevisionReceipt;
  if (!v5BridgeStateFolder) throw new Error("Bridge state folder is unavailable.");
  const archive = await bridgeChildFolder(v5BridgeStateFolder, "revision-receipts", false);
  if (!archive) throw new Error(`Revision receipt not found: ${receiptId}`);
  const receipt = await bridgeReadJson(archive, `${receiptId}.json`);
  if (!receipt) throw new Error(`Revision receipt not found: ${receiptId}`);
  return receipt;
}

async function listRevisionReceipts() {
  const receipts = [];
  if (v5BridgeStateFolder) {
    const archive = await bridgeChildFolder(v5BridgeStateFolder, "revision-receipts", false);
    if (archive) {
      const entries = (await archive.getEntries()).filter(entry => entry.isFile && entry.name.endsWith(".json"));
      for (const entry of entries) {
        try {
          const receipt = JSON.parse(await entry.read());
          if (receipt && receipt.receipt_id) receipts.push(revisionReceiptSummary(receipt));
        } catch (_) {}
      }
    }
  }
  if (lastRevisionReceipt && !receipts.some(item => item.receipt_id === lastRevisionReceipt.receipt_id)) {
    receipts.push(revisionReceiptSummary(lastRevisionReceipt));
  }
  receipts.sort((a, b) => String(b.completed_at || "").localeCompare(String(a.completed_at || "")));
  return { receipts: receipts.length, items: receipts };
}

function revisionFailureSummary(failure) {
  const rollback = failure && failure.rollback ? failure.rollback : {};
  return {
    schema: failure.schema,
    failure_id: failure.failure_id,
    plugin_version: failure.plugin_version,
    failed_at: failure.failed_at,
    sequence: failure.sequence,
    request_id: failure.source_document && failure.source_document.request_id || null,
    request_attempt: failure.preflight && failure.preflight.request_id_guard &&
      failure.preflight.request_id_guard.attempt || null,
    failed_revision: failure.failed_revision,
    error: failure.error,
    completed_revisions: Array.isArray(failure.completed_verifications) ? failure.completed_verifications.length : 0,
    rollback_status: rollback.status || "unknown",
    rollback_verified: rollback.verified === true,
    rollback_operations: Number(rollback.operations || 0)
  };
}

async function persistRevisionFailure(failure) {
  if (!v5BridgeStateFolder || !failure) return;
  await bridgeWriteJson(v5BridgeStateFolder, "last-revision-failure.json", failure);
  const archive = await bridgeChildFolder(v5BridgeStateFolder, "revision-failures", true);
  await bridgeWriteJson(archive, `${failure.failure_id}.json`, failure);
}

async function getRevisionFailureById(failureId = null) {
  if (!failureId) return lastRevisionFailure;
  if (!/^[A-Za-z0-9_-]+$/.test(String(failureId))) throw new Error("Invalid revision failure ID.");
  if (lastRevisionFailure && lastRevisionFailure.failure_id === failureId) return lastRevisionFailure;
  if (!v5BridgeStateFolder) throw new Error("Bridge state folder is unavailable.");
  const archive = await bridgeChildFolder(v5BridgeStateFolder, "revision-failures", false);
  if (!archive) throw new Error(`Revision failure not found: ${failureId}`);
  const failure = await bridgeReadJson(archive, `${failureId}.json`);
  if (!failure) throw new Error(`Revision failure not found: ${failureId}`);
  return failure;
}

async function listRevisionFailures() {
  const failures = [];
  if (v5BridgeStateFolder) {
    const archive = await bridgeChildFolder(v5BridgeStateFolder, "revision-failures", false);
    if (archive) {
      const entries = (await archive.getEntries()).filter(entry => entry.isFile && entry.name.endsWith(".json"));
      for (const entry of entries) {
        try {
          const failure = JSON.parse(await entry.read());
          if (failure && failure.failure_id) failures.push(revisionFailureSummary(failure));
        } catch (_) {}
      }
    }
  }
  if (lastRevisionFailure && !failures.some(item => item.failure_id === lastRevisionFailure.failure_id)) {
    failures.push(revisionFailureSummary(lastRevisionFailure));
  }
  failures.sort((a, b) => String(b.failed_at || "").localeCompare(String(a.failed_at || "")));
  return { failures: failures.length, items: failures };
}

async function getRevisionRequestStatus(requestId) {
  if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) {
    throw new Error("revision_request_status requires a valid request ID.");
  }
  const receiptHistory = await collectRevisionHistoryDocuments(
    "revision-receipts",
    "receipt_id",
    lastRevisionReceipt
  );
  const failureHistory = await collectRevisionHistoryDocuments(
    "revision-failures",
    "failure_id",
    lastRevisionFailure
  );
  const receipts = Array.from(receiptHistory.documents.values())
    .filter(receipt => receipt.source_document && receipt.source_document.request_id === requestId)
    .map(revisionReceiptSummary);
  const failures = Array.from(failureHistory.documents.values())
    .filter(failure => failure.source_document && failure.source_document.request_id === requestId)
    .map(revisionFailureSummary);
  receipts.sort((a, b) => String(b.completed_at || "").localeCompare(String(a.completed_at || "")));
  failures.sort((a, b) => String(b.failed_at || "").localeCompare(String(a.failed_at || "")));

  if (receipts.length) {
    return {
      request_id: requestId,
      status: "applied_verified",
      retry_safe: false,
      receipts: receipts.length,
      failures: failures.length,
      latest_receipt: receipts[0],
      latest_failure: failures[0] || null
    };
  }
  if (failures.length) {
    const retrySafe = failures.every(failure => failure.rollback_verified === true);
    return {
      request_id: requestId,
      status: retrySafe ? "failed_safe" : "attention",
      retry_safe: retrySafe,
      receipts: 0,
      failures: failures.length,
      latest_receipt: null,
      latest_failure: failures[0]
    };
  }
  return {
    request_id: requestId,
    status: "not_found",
    retry_safe: true,
    receipts: 0,
    failures: 0,
    latest_receipt: null,
    latest_failure: null
  };
}

async function collectRevisionHistoryDocuments(folderName, idField, latest) {
  const documents = new Map();
  const invalidFiles = [];
  if (v5BridgeStateFolder) {
    const archive = await bridgeChildFolder(v5BridgeStateFolder, folderName, false);
    if (archive) {
      const entries = (await archive.getEntries()).filter(entry => entry.isFile && entry.name.endsWith(".json"));
      for (const entry of entries) {
        try {
          const document = JSON.parse(await entry.read());
          const id = document && document[idField];
          if (!id) invalidFiles.push(`${folderName}/${entry.name}: missing ${idField}`);
          else if (documents.has(id)) invalidFiles.push(`${folderName}/${entry.name}: duplicate ${idField} ${id}`);
          else documents.set(id, document);
        } catch (err) {
          invalidFiles.push(`${folderName}/${entry.name}: invalid JSON (${String(err.message || err)})`);
        }
      }
    }
  }
  if (latest && latest[idField] && !documents.has(latest[idField])) {
    documents.set(latest[idField], latest);
  }
  return { documents, invalid_files: invalidFiles };
}

async function auditRevisionHistory() {
  if (!v5BridgeStateFolder && !lastRevisionReceipt && !lastRevisionFailure) {
    return {
      status: "unavailable",
      receipts: 0,
      failures: 0,
      recovery_chains: 0,
      incomplete_recoveries: 0,
      incomplete_rollbacks: 0,
      issues: []
    };
  }
  const receiptHistory = await collectRevisionHistoryDocuments(
    "revision-receipts",
    "receipt_id",
    lastRevisionReceipt
  );
  const failureHistory = await collectRevisionHistoryDocuments(
    "revision-failures",
    "failure_id",
    lastRevisionFailure
  );
  const receipts = receiptHistory.documents;
  const failures = failureHistory.documents;
  const issues = [...receiptHistory.invalid_files, ...failureHistory.invalid_files];
  let recoveryChains = 0;
  let incompleteRecoveries = 0;
  let incompleteRollbacks = 0;
  const requestIds = new Map();

  for (const receipt of receipts.values()) {
    const receiptId = receipt.receipt_id;
    const execution = receipt.recovery_execution || {};
    const requestId = receipt.source_document && receipt.source_document.request_id;
    if (requestId) {
      if (requestIds.has(requestId)) {
        issues.push(`Request ID ${requestId} appears in both receipts ${requestIds.get(requestId)} and ${receiptId}.`);
      } else requestIds.set(requestId, receiptId);
    }
    if (receipt.recovery_started_at && !receipt.recovered_at) {
      incompleteRecoveries++;
      issues.push(`Receipt ${receiptId} recovery started but did not complete.`);
    }
    if (receipt.recovered_at && execution.verified !== true) {
      issues.push(`Receipt ${receiptId} is marked recovered without verified recovery execution.`);
    }
    if (receipt.recovered_at && execution.mode === "revision") {
      const recoveryReceiptId = execution.recovery_receipt_id;
      if (!recoveryReceiptId) {
        issues.push(`Receipt ${receiptId} revision recovery is missing recovery_receipt_id.`);
      } else {
        const recoveryReceipt = receipts.get(recoveryReceiptId);
        if (!recoveryReceipt) {
          issues.push(`Receipt ${receiptId} links to missing recovery receipt ${recoveryReceiptId}.`);
        } else if (recoveryReceipt.recovery_source_receipt_id !== receiptId) {
          issues.push(`Recovery receipt ${recoveryReceiptId} does not link back to source receipt ${receiptId}.`);
        }
      }
    }
    if (receipt.recovery_source_receipt_id) {
      recoveryChains++;
      const source = receipts.get(receipt.recovery_source_receipt_id);
      if (!source) {
        issues.push(`Recovery receipt ${receiptId} links to missing source receipt ${receipt.recovery_source_receipt_id}.`);
      } else {
        const sourceExecution = source.recovery_execution || {};
        if (!source.recovered_at || sourceExecution.verified !== true) {
          issues.push(`Recovery receipt ${receiptId} source ${source.receipt_id} is not marked verified and recovered.`);
        }
        if (sourceExecution.recovery_receipt_id !== receiptId) {
          issues.push(`Source receipt ${source.receipt_id} does not link forward to recovery receipt ${receiptId}.`);
        }
      }
    }
  }

  for (const failure of failures.values()) {
    const rollback = failure.rollback || {};
    if (rollback.verified !== true || rollback.status === "failed") {
      incompleteRollbacks++;
      issues.push(`Failure ${failure.failure_id} does not have a verified rollback.`);
    }
  }

  return {
    status: issues.length ? "attention" : "healthy",
    receipts: receipts.size,
    failures: failures.size,
    recovery_chains: recoveryChains,
    incomplete_recoveries: incompleteRecoveries,
    incomplete_rollbacks: incompleteRollbacks,
    issues
  };
}

function bridgeGuidString(value) {
  if (value == null) return null;
  try {
    const text = typeof value.toString === "function" ? value.toString() : String(value);
    return text && text !== "[object Object]" ? String(text) : null;
  } catch (_) { return null; }
}

function bridgeSeconds(time) {
  const seconds = Number(time && time.seconds);
  return Number.isFinite(seconds) ? seconds : null;
}

async function bridgeMediaTime(media, methodName, propertyName) {
  if (!media) return null;
  try {
    const time = typeof media[methodName] === "function"
      ? await media[methodName]()
      : await media[propertyName];
    return bridgeSeconds(time);
  } catch (_) { return null; }
}

async function bridgeHasComponentChain(clip, mediaType) {
  if (!clip || typeof clip.getComponentChain !== "function" || mediaType == null) return null;
  try { return !!(await clip.getComponentChain(mediaType)); }
  catch (_) { return null; }
}

function bridgeMediaType(hasVideo, hasAudio) {
  if (hasVideo === true && hasAudio === true) return "video_audio";
  if (hasVideo === true) return "video";
  if (hasAudio === true) return "audio";
  return "unknown";
}

async function describeBridgeMediaItem(clip, binNames) {
  const name = String(clip.name || "");
  const item = {
    name,
    project_item_id: null,
    stable_identifier: null,
    media_path: null,
    duration_seconds: null,
    media_start_seconds: null,
    source_frame_rate: null,
    media_type: "unknown",
    has_video: null,
    has_audio: null,
    offline: null,
    has_proxy: null,
    proxy_path: null,
    bin_path: binNames.length ? binNames.join("/") : "/",
    bin_names: binNames.slice(),
    decision_reference: null
  };

  try {
    const projectItem = ppro.ProjectItem.cast(clip);
    if (projectItem && typeof projectItem.getId === "function") {
      const id = await projectItem.getId();
      if (id != null && String(id)) item.project_item_id = String(id);
    }
  } catch (_) {}

  try {
    const path = await clip.getMediaFilePath();
    if (path) item.media_path = String(path);
  } catch (_) {}

  try {
    const media = await clip.getMedia();
    item.duration_seconds = await bridgeMediaTime(media, "getDuration", "duration");
    item.media_start_seconds = await bridgeMediaTime(media, "getStart", "start");
  } catch (_) {}

  try {
    const interpretation = await clip.getFootageInterpretation();
    const fps = interpretation && typeof interpretation.getFrameRate === "function"
      ? await interpretation.getFrameRate()
      : null;
    if (Number.isFinite(Number(fps)) && Number(fps) > 0) item.source_frame_rate = Number(fps);
  } catch (_) {}

  const mediaTypes = ppro.Constants && ppro.Constants.MediaType;
  item.has_video = await bridgeHasComponentChain(clip, mediaTypes && mediaTypes.VIDEO);
  item.has_audio = await bridgeHasComponentChain(clip, mediaTypes && mediaTypes.AUDIO);
  item.media_type = bridgeMediaType(item.has_video, item.has_audio);

  try { if (typeof clip.isOffline === "function") item.offline = !!(await clip.isOffline()); }
  catch (_) {}
  try {
    if (typeof clip.hasProxy === "function") item.has_proxy = !!(await clip.hasProxy());
    if (item.has_proxy && typeof clip.getProxyPath === "function") {
      const proxyPath = await clip.getProxyPath();
      if (proxyPath) item.proxy_path = String(proxyPath);
    }
  } catch (_) {}

  item.stable_identifier = item.project_item_id || item.media_path || null;
  item.decision_reference = item.media_path
    ? { media_path: item.media_path }
    : (name ? { media_match: name } : null);
  return item;
}

async function collectBridgeMediaInventory(folder, binNames = [], inventory = null) {
  const result = inventory || { items: [], warnings: [] };
  let entries = [];
  try { entries = await folder.getItems(); }
  catch (err) {
    result.warnings.push(`Could not read bin ${binNames.length ? binNames.join("/") : "/"}: ${String(err.message || err)}`);
    return result;
  }

  for (const rawItem of entries || []) {
    let clip = null;
    try { clip = ppro.ClipProjectItem.cast(rawItem); } catch (_) {}
    if (clip) {
      let contentType = null;
      let contentTypeKnown = false;
      let isSequence = false;
      try {
        contentType = await clip.getContentType();
        contentTypeKnown = true;
      } catch (_) {}
      try { if (typeof clip.isSequence === "function") isSequence = !!(await clip.isSequence()); }
      catch (_) {}

      const mediaContentType = ppro.Constants && ppro.Constants.ContentType
        ? ppro.Constants.ContentType.MEDIA
        : null;
      if (isSequence || (contentTypeKnown && mediaContentType != null && contentType !== mediaContentType)) continue;

      try {
        const described = await describeBridgeMediaItem(clip, binNames);
        // If content type was unavailable, require some media evidence so bins,
        // sequences, and other non-media ProjectItems are not inventoried.
        if (contentTypeKnown || described.media_path || described.duration_seconds != null) {
          result.items.push(described);
        }
      } catch (err) {
        result.warnings.push(`Could not describe ${String(rawItem.name || "project item")}: ${String(err.message || err)}`);
      }
      continue;
    }

    try {
      const childFolder = ppro.FolderItem.cast(rawItem);
      if (childFolder) {
        await collectBridgeMediaInventory(childFolder, [...binNames, String(rawItem.name || "Unnamed bin")], result);
      }
    } catch (err) {
      result.warnings.push(`Could not enter bin ${String(rawItem.name || "Unnamed bin")}: ${String(err.message || err)}`);
    }
  }
  return result;
}

async function getBridgeMediaInventory(project) {
  const root = await project.getRootItem();
  const inventory = await collectBridgeMediaInventory(root);
  inventory.items.sort((a, b) => {
    const left = `${a.bin_path}\u0000${a.name}\u0000${a.project_item_id || ""}`;
    const right = `${b.bin_path}\u0000${b.name}\u0000${b.project_item_id || ""}`;
    return left.localeCompare(right);
  });
  const count = (predicate) => inventory.items.filter(predicate).length;
  return {
    total_items: inventory.items.length,
    file_backed_items: count(item => !!item.media_path),
    offline_items: count(item => item.offline === true),
    video_audio_items: count(item => item.media_type === "video_audio"),
    video_only_items: count(item => item.media_type === "video"),
    audio_only_items: count(item => item.media_type === "audio"),
    unknown_type_items: count(item => item.media_type === "unknown"),
    items: inventory.items,
    warnings: inventory.warnings
  };
}

async function bridgeTrackItemTime(item, methodName) {
  try {
    if (!item || typeof item[methodName] !== "function") return null;
    return bridgeSeconds(await item[methodName]());
  } catch (_) { return null; }
}

async function describeBridgeTimelineItem(item, kind, trackIndex) {
  let name = "";
  try { name = String((await item.getName()) || ""); } catch (_) {}
  const timelineStart = await bridgeTrackItemTime(item, "getStartTime");
  const timelineEnd = await bridgeTrackItemTime(item, "getEndTime");
  let timelineDuration = await bridgeTrackItemTime(item, "getDuration");
  if (timelineDuration == null && timelineStart != null && timelineEnd != null) {
    timelineDuration = Math.max(0, timelineEnd - timelineStart);
  }
  const sourceIn = await bridgeTrackItemTime(item, "getInPoint");
  const sourceOut = await bridgeTrackItemTime(item, "getOutPoint");

  let projectItemName = "";
  let projectItemId = null;
  let mediaPath = null;
  try {
    const projectItem = await item.getProjectItem();
    if (projectItem) {
      projectItemName = String(projectItem.name || "");
      try {
        const baseItem = ppro.ProjectItem.cast(projectItem);
        if (baseItem && typeof baseItem.getId === "function") {
          const id = await baseItem.getId();
          if (id != null && String(id)) projectItemId = String(id);
        }
      } catch (_) {}
      try {
        const clipItem = ppro.ClipProjectItem.cast(projectItem);
        const path = await clipItem.getMediaFilePath();
        if (path) mediaPath = String(path);
      } catch (_) {}
    }
  } catch (_) {}

  let speed = null;
  let speedReversed = null;
  let disabled = null;
  let selected = null;
  let adjustmentLayer = null;
  let matchName = null;
  try { if (typeof item.getSpeed === "function") speed = Number(await item.getSpeed()); }
  catch (_) {}
  if (!Number.isFinite(speed)) speed = null;
  try { if (typeof item.isSpeedReversed === "function") speedReversed = !!(await item.isSpeedReversed()); }
  catch (_) {}
  try { if (typeof item.isDisabled === "function") disabled = !!(await item.isDisabled()); }
  catch (_) {}
  try { if (typeof item.getIsSelected === "function") selected = !!(await item.getIsSelected()); }
  catch (_) {}
  try { if (typeof item.isAdjustmentLayer === "function") adjustmentLayer = !!(await item.isAdjustmentLayer()); }
  catch (_) {}
  try {
    if (typeof item.getMatchName === "function") {
      const value = await item.getMatchName();
      if (value) matchName = String(value);
    }
  } catch (_) {}

  const stableSource = projectItemId || mediaPath || projectItemName || name || "unknown";
  const rangeKey = timelineStart != null && timelineEnd != null
    ? `${timelineStart.toFixed(6)}-${timelineEnd.toFixed(6)}`
    : "unknown-range";
  return {
    kind,
    track_index: trackIndex,
    track_number: trackIndex + 1,
    name,
    project_item_name: projectItemName || null,
    project_item_id: projectItemId,
    media_path: mediaPath,
    stable_identifier: projectItemId || mediaPath || null,
    link_key: `${stableSource}@${rangeKey}`,
    timeline_start_seconds: timelineStart,
    timeline_end_seconds: timelineEnd,
    timeline_duration_seconds: timelineDuration,
    source_in_seconds: sourceIn,
    source_out_seconds: sourceOut,
    source_duration_seconds: sourceIn != null && sourceOut != null ? Math.max(0, sourceOut - sourceIn) : null,
    speed,
    speed_reversed: speedReversed,
    disabled,
    selected,
    adjustment_layer: adjustmentLayer,
    match_name: matchName
  };
}

async function getBridgeTimelineTrack(sequence, kind, trackIndex, warnings) {
  const track = kind === "video"
    ? await sequence.getVideoTrack(trackIndex)
    : await sequence.getAudioTrack(trackIndex);
  if (!track) throw new Error(`${kind} track ${trackIndex + 1} is unavailable.`);
  let muted = null;
  try { if (typeof track.isMuted === "function") muted = !!(await track.isMuted()); }
  catch (_) {}
  let entries = [];
  try {
    const raw = await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false);
    entries = Array.from(raw || []);
  } catch (err) {
    warnings.push(`Could not read ${kind} track ${trackIndex + 1}: ${String(err.message || err)}`);
  }
  const items = [];
  for (const entry of entries) {
    try { items.push(await describeBridgeTimelineItem(entry, kind, trackIndex)); }
    catch (err) {
      warnings.push(`Could not describe ${kind} item on track ${trackIndex + 1}: ${String(err.message || err)}`);
    }
  }
  items.sort((a, b) => {
    const aStart = a.timeline_start_seconds == null ? Number.MAX_SAFE_INTEGER : a.timeline_start_seconds;
    const bStart = b.timeline_start_seconds == null ? Number.MAX_SAFE_INTEGER : b.timeline_start_seconds;
    return aStart - bStart || String(a.name).localeCompare(String(b.name));
  });
  return {
    kind,
    index: trackIndex,
    number: trackIndex + 1,
    id: track.id == null ? null : Number(track.id),
    name: String(track.name || `${kind === "video" ? "V" : "A"}${trackIndex + 1}`),
    muted,
    item_count: items.length,
    items
  };
}

async function getBridgeTimelineInventory(sequence) {
  const warnings = [];
  const videoTrackCount = Number(await sequence.getVideoTrackCount());
  const audioTrackCount = Number(await sequence.getAudioTrackCount());
  let durationSeconds = null;
  let playheadSeconds = null;
  let timebase = null;
  try { durationSeconds = bridgeSeconds(await sequence.getEndTime()); } catch (_) {}
  try { playheadSeconds = bridgeSeconds(await sequence.getPlayerPosition()); } catch (_) {}
  try {
    if (typeof sequence.getTimebase === "function") {
      const value = await sequence.getTimebase();
      if (value != null && String(value)) timebase = String(value);
    }
  } catch (_) {}

  const videoTracks = [];
  const audioTracks = [];
  for (let i = 0; i < videoTrackCount; i++) {
    try { videoTracks.push(await getBridgeTimelineTrack(sequence, "video", i, warnings)); }
    catch (err) { warnings.push(`Could not inspect video track ${i + 1}: ${String(err.message || err)}`); }
  }
  for (let i = 0; i < audioTrackCount; i++) {
    try { audioTracks.push(await getBridgeTimelineTrack(sequence, "audio", i, warnings)); }
    catch (err) { warnings.push(`Could not inspect audio track ${i + 1}: ${String(err.message || err)}`); }
  }
  const totalItems = tracks => tracks.reduce((sum, track) => sum + track.item_count, 0);
  const inventory = {
    sequence_name: String(sequence.name || ""),
    sequence_guid: bridgeGuidString(sequence.guid),
    duration_seconds: durationSeconds,
    playhead_seconds: playheadSeconds,
    timebase,
    video_track_count: videoTrackCount,
    audio_track_count: audioTrackCount,
    total_video_items: totalItems(videoTracks),
    total_audio_items: totalItems(audioTracks),
    video_tracks: videoTracks,
    audio_tracks: audioTracks,
    warnings
  };
  inventory.state_token = timelineStateToken(inventory);
  return inventory;
}

function timelineStateToken(inventory) {
  const stable = {
    sequence_guid: inventory.sequence_guid || null,
    video_tracks: (inventory.video_tracks || []).map(track => ({
      index: track.index,
      muted: track.muted,
      items: (track.items || []).map(timelineStateTokenItem)
    })),
    audio_tracks: (inventory.audio_tracks || []).map(track => ({
      index: track.index,
      muted: track.muted,
      items: (track.items || []).map(timelineStateTokenItem)
    }))
  };
  const text = JSON.stringify(stable);
  let hash = 0x811c9dc5;
  for (let index = 0; index < text.length; index++) {
    hash ^= text.charCodeAt(index);
    hash = Math.imul(hash, 0x01000193) >>> 0;
  }
  return `v5ts1-${hash.toString(16).padStart(8, "0")}-${text.length}`;
}

function timelineStateTokenItem(item) {
  const fixed = value => Number.isFinite(Number(value)) ? Number(Number(value).toFixed(6)) : null;
  return {
    kind: item.kind,
    track_index: item.track_index,
    project_item_id: item.project_item_id || null,
    media_path: item.media_path || null,
    timeline_start: fixed(item.timeline_start_seconds),
    timeline_end: fixed(item.timeline_end_seconds),
    source_in: fixed(item.source_in_seconds),
    source_out: fixed(item.source_out_seconds),
    speed: fixed(item.speed),
    speed_reversed: item.speed_reversed === true,
    disabled: item.disabled === true
  };
}

function revisionNumber(value, label, minimum = null) {
  if ((typeof value !== "number" && typeof value !== "string") ||
      (typeof value === "string" && !value.trim())) {
    throw new Error(`${label} must be a finite number.`);
  }
  const number = Number(value);
  if (!Number.isFinite(number)) throw new Error(`${label} must be a finite number.`);
  if (minimum != null && number < minimum) throw new Error(`${label} must be at least ${minimum}.`);
  return number;
}

function validateRevisionDocument(doc) {
  // Omission may use a default; explicit null must never erase a supplied guard.
  const supplied = key => Object.prototype.hasOwnProperty.call(doc, key);
  if (!doc || doc.schema !== V5_REVISION_SCHEMA) {
    throw new Error(`Unsupported or missing revision schema; expected ${V5_REVISION_SCHEMA}.`);
  }
  if (!Array.isArray(doc.revisions) || !doc.revisions.length) {
    throw new Error("Revision document must contain at least one revision.");
  }
  if (doc.revisions.length > V5_MAX_REVISIONS_PER_BATCH) {
    throw new Error(`Revision document contains ${doc.revisions.length} revisions; the safe maximum is ${V5_MAX_REVISIONS_PER_BATCH}. Split the edit into smaller batches and obtain a fresh revision_context before each batch.`);
  }
  if (supplied("sequence_name") && (typeof doc.sequence_name !== "string" || !doc.sequence_name.trim())) {
    throw new Error("sequence_name must be a non-empty string when provided.");
  }
  if (supplied("sequence_guid") && (typeof doc.sequence_guid !== "string" || !doc.sequence_guid.trim())) {
    throw new Error("sequence_guid must be a non-empty string when provided.");
  }
  if (supplied("project_guid") && (typeof doc.project_guid !== "string" || !doc.project_guid.trim())) {
    throw new Error("project_guid must be a non-empty string when provided.");
  }
  if (supplied("safety_policy") && !["standard", "strict"].includes(doc.safety_policy)) {
    throw new Error("safety_policy must be standard or strict.");
  }
  if (doc.safety_policy === "strict") {
    const missing = ["project_guid", "sequence_name", "sequence_guid", "request_id", "expected_timeline_state_token", "linked_av_policy"]
      .filter(field => doc[field] == null || doc[field] === "");
    if (missing.length) {
      throw new Error(`Strict safety policy requires: ${missing.join(", ")}.`);
    }
  }
  if (supplied("linked_av_policy") && !["independent", "require_all"].includes(doc.linked_av_policy)) {
    throw new Error("linked_av_policy must be independent or require_all.");
  }
  if (supplied("expected_timeline_state_token") &&
      (typeof doc.expected_timeline_state_token !== "string" || !doc.expected_timeline_state_token.trim())) {
    throw new Error("expected_timeline_state_token must be a non-empty string when provided.");
  }
  if (supplied("request_id") &&
      (typeof doc.request_id !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(doc.request_id))) {
    throw new Error("request_id must be 1–128 characters using letters, numbers, dot, underscore, colon, or hyphen.");
  }

  doc.revisions.forEach((revision, index) => {
    const label = `Revision ${index + 1}`;
    if (!revision || !["move", "trim", "disable", "remove"].includes(revision.operation)) {
      throw new Error(`${label} operation must be move, trim, disable, or remove.`);
    }
    const target = revision.target;
    const has = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
    const operationFields = {
      move: ["new_timeline_start"], trim: ["source_in", "source_out"],
      disable: ["disabled"], remove: ["ripple", "shift_overlapping"]
    };
    const allowedRevisionFields = ["operation", "target", ...operationFields[revision.operation]];
    for (const key of Object.keys(revision)) {
      if (!allowedRevisionFields.includes(key)) throw new Error(`${label} has unknown field ${key}.`);
    }
    if (!target || !["video", "audio"].includes(target.kind)) {
      throw new Error(`${label} target.kind must be video or audio.`);
    }
    const allowedTargetFields = ["kind", "track_index", "timeline_start", "timeline_end",
      "tolerance_seconds", "project_item_id", "media_path", "project_item_name"];
    for (const key of Object.keys(target)) {
      if (!allowedTargetFields.includes(key)) throw new Error(`${label} target has unknown field ${key}.`);
    }
    if (!Number.isInteger(target.track_index) || target.track_index < 0) {
      throw new Error(`${label} target.track_index must be a non-negative integer.`);
    }
    revisionNumber(target.timeline_start, `${label} target.timeline_start`, 0);
    if (has(target, "timeline_end")) {
      const end = revisionNumber(target.timeline_end, `${label} target.timeline_end`, 0);
      if (end <= Number(target.timeline_start)) throw new Error(`${label} target.timeline_end must be greater than timeline_start.`);
    }
    if (has(target, "tolerance_seconds")) {
      const tolerance = revisionNumber(target.tolerance_seconds, `${label} target.tolerance_seconds`, 0.000001);
      if (tolerance > 0.25) throw new Error(`${label} target.tolerance_seconds cannot exceed 0.25.`);
    }
    const identity = [target.project_item_id, target.media_path, target.project_item_name]
      .some(value => typeof value === "string" && value.trim());
    if (!identity) throw new Error(`${label} target needs project_item_id, media_path, or project_item_name.`);
    for (const key of ["project_item_id", "media_path", "project_item_name"]) {
      if (has(target, key) && (typeof target[key] !== "string" || !target[key].trim())) {
        throw new Error(`${label} target.${key} must be a non-empty string when supplied.`);
      }
    }

    if (revision.operation === "move") {
      revisionNumber(revision.new_timeline_start, `${label} new_timeline_start`, 0);
    } else if (revision.operation === "trim") {
      const sourceIn = revisionNumber(revision.source_in, `${label} source_in`, 0);
      const sourceOut = revisionNumber(revision.source_out, `${label} source_out`, 0);
      if (sourceOut <= sourceIn) throw new Error(`${label} source_out must be greater than source_in.`);
    } else if (revision.operation === "disable") {
      if (typeof revision.disabled !== "boolean") throw new Error(`${label} disabled must be true or false.`);
    } else if (revision.operation === "remove") {
      if (has(revision, "ripple") && typeof revision.ripple !== "boolean") throw new Error(`${label} ripple must be true or false.`);
      if (has(revision, "shift_overlapping") && typeof revision.shift_overlapping !== "boolean") throw new Error(`${label} shift_overlapping must be true or false.`);
    }
  });
  return doc;
}

function revisionIdentityMatches(description, target) {
  const equal = (left, right) => String(left || "").toLowerCase() === String(right || "").toLowerCase();
  if (target.project_item_id && !equal(description.project_item_id, target.project_item_id)) return false;
  if (target.media_path && !equal(description.media_path, target.media_path)) return false;
  if (target.project_item_name && !equal(description.project_item_name, target.project_item_name)) return false;
  return true;
}

async function resolveRevisionTarget(sequence, target, options = {}) {
  const items = target.kind === "audio"
    ? await getAudioTrackItems(sequence, target.track_index)
    : await getVideoTrackItems(sequence, target.track_index);
  const tolerance = target.tolerance_seconds == null
    ? V5_REVISION_TIME_TOLERANCE
    : Number(target.tolerance_seconds);
  const matches = [];
  for (const item of items) {
    try {
      const description = await describeBridgeTimelineItem(item, target.kind, target.track_index);
      if (!revisionIdentityMatches(description, target)) continue;
      if (!Number.isFinite(description.timeline_start_seconds) ||
          Math.abs(Number(description.timeline_start_seconds) - Number(target.timeline_start)) > tolerance) continue;
      if (options.checkEnd !== false && target.timeline_end != null &&
          (!Number.isFinite(description.timeline_end_seconds) ||
           Math.abs(Number(description.timeline_end_seconds) - Number(target.timeline_end)) > tolerance)) continue;
      matches.push({ item, description });
    } catch (_) {}
  }
  if (!matches.length) {
    throw new Error(`No exact ${target.kind} item matched track ${target.track_index + 1} at ${Number(target.timeline_start).toFixed(3)}s.`);
  }
  if (matches.length > 1) {
    throw new Error(`Target is ambiguous: ${matches.length} ${target.kind} items matched track ${target.track_index + 1} at ${Number(target.timeline_start).toFixed(3)}s.`);
  }
  return matches[0];
}

function revisionTargetKey(target) {
  const identity = target.project_item_id || target.media_path || target.project_item_name;
  return `${target.kind}:${target.track_index}:${String(identity).toLowerCase()}:${Number(target.timeline_start).toFixed(6)}`;
}

function revisionTimelineScale(current, label) {
  const timelineDuration = Number(current.timeline_duration_seconds);
  const sourceDuration = Number(current.source_duration_seconds);
  if (!Number.isFinite(timelineDuration) || timelineDuration <= 0 ||
      !Number.isFinite(sourceDuration) || sourceDuration <= 0) {
    throw new Error(`${label} cannot derive reliable trim timing from the current clip.`);
  }
  return timelineDuration / sourceDuration;
}

function revisionDescriptionsReferToSameItem(left, right) {
  const identity = {};
  if (right.project_item_id) identity.project_item_id = right.project_item_id;
  else if (right.media_path) identity.media_path = right.media_path;
  else if (right.project_item_name) identity.project_item_name = right.project_item_name;
  else return false;
  return revisionIdentityMatches(left, identity) &&
    revisionClose(left.timeline_start_seconds, right.timeline_start_seconds) &&
    revisionClose(left.timeline_end_seconds, right.timeline_end_seconds);
}

function revisionRangesOverlap(leftStart, leftEnd, rightStart, rightEnd) {
  return leftStart < rightEnd - V5_REVISION_COLLISION_EPSILON &&
    leftEnd > rightStart + V5_REVISION_COLLISION_EPSILON;
}

async function buildRevisionProjection(sequence, checks) {
  const layouts = new Map();
  for (const check of checks) {
    if (check.planned.operation !== "move" && check.planned.operation !== "trim") continue;
    const key = `${check.target.kind}:${check.target.track_index}`;
    if (layouts.has(key)) continue;
    const kind = check.target.kind;
    const trackIndex = check.target.track_index;
    const items = kind === "audio"
      ? await getAudioTrackItems(sequence, trackIndex)
      : await getVideoTrackItems(sequence, trackIndex);
    const entries = [];
    for (const item of items) {
      let description = null;
      try { description = await describeBridgeTimelineItem(item, kind, trackIndex); }
      catch (err) {
        throw new Error(`Could not inspect a ${kind} item during batch collision preflight: ${String(err.message || err)}`);
      }
      const start = Number(description.timeline_start_seconds);
      const end = Number(description.timeline_end_seconds);
      if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) {
        throw new Error(`Could not verify the timeline range of ${description.name || "another clip"} during batch collision preflight.`);
      }
      const matchingChecks = checks.filter(candidate =>
        candidate.target.kind === kind &&
        candidate.target.track_index === trackIndex &&
        revisionDescriptionsReferToSameItem(description, candidate.target)
      );
      if (matchingChecks.length > 1) {
        throw new Error(`Multiple revisions resolve to ${description.name || "the same clip"} on ${kind === "video" ? "V" : "A"}${trackIndex + 1}.`);
      }
      entries.push({
        name: description.name,
        project_item_id: description.project_item_id,
        timeline_start_seconds: start,
        timeline_end_seconds: end,
        revision: matchingChecks.length ? matchingChecks[0].revision : null,
        present: true
      });
    }
    layouts.set(key, { kind, track_index: trackIndex, entries });
  }
  for (const check of checks) {
    const key = `${check.target.kind}:${check.target.track_index}`;
    if (!layouts.has(key)) continue;
    const matches = layouts.get(key).entries.filter(entry => entry.revision === check.revision);
    if (matches.length !== 1) {
      throw new Error(`Could not uniquely project revision ${check.revision}'s target during batch collision preflight.`);
    }
  }
  return layouts;
}

async function analyzeBatchRevisionCollisions(sequence, checks) {
  if (checks.length > 1) {
    const removal = checks.find(check => check.planned.operation === "remove");
    if (removal) {
      throw new Error(`Revision ${removal.revision} removes a clip; remove must be applied alone so every multi-operation batch remains automatically rollback-safe.`);
    }
  }

  const layouts = await buildRevisionProjection(sequence, checks);
  let collisionCheckedRevisions = 0;
  for (const check of checks) {
    const key = `${check.target.kind}:${check.target.track_index}`;
    const layout = layouts.get(key);
    if (!layout) continue;
    const entry = layout.entries.find(candidate => candidate.revision === check.revision);
    if (check.planned.operation === "remove") {
      entry.present = false;
      continue;
    }
    if (check.planned.operation !== "move" && check.planned.operation !== "trim") continue;

    const plannedStart = Number(check.planned.timeline_start);
    const plannedEnd = Number(check.planned.timeline_end);
    if (!Number.isFinite(plannedStart) || !Number.isFinite(plannedEnd) || plannedEnd <= plannedStart) {
      throw new Error(`Revision ${check.revision} planned timeline range is invalid.`);
    }
    const collisions = [];
    let checkedItems = 0;
    for (const other of layout.entries) {
      if (other === entry || !other.present) continue;
      checkedItems++;
      if (revisionRangesOverlap(
        plannedStart,
        plannedEnd,
        Number(other.timeline_start_seconds),
        Number(other.timeline_end_seconds)
      )) {
        collisions.push({
          name: other.name,
          project_item_id: other.project_item_id,
          timeline_start_seconds: Number(other.timeline_start_seconds),
          timeline_end_seconds: Number(other.timeline_end_seconds),
          projected_from_revision: other.revision
        });
      }
    }
    const collisionCheck = {
      passed: collisions.length === 0,
      mode: "sequential_batch",
      batch_step: check.revision,
      checked_items: checkedItems,
      collisions
    };
    check.planned.collision_check = collisionCheck;
    collisionCheckedRevisions++;
    if (!collisionCheck.passed) {
      const collision = collisionCheck.collisions[0];
      const projected = collision.projected_from_revision == null
        ? ""
        : ` after revision ${collision.projected_from_revision}`;
      throw new Error(
        `Revision ${check.revision} collision: planned ${plannedStart.toFixed(3)}–${plannedEnd.toFixed(3)}s overlaps ${collision.name || "another clip"} at ${collision.timeline_start_seconds.toFixed(3)}–${collision.timeline_end_seconds.toFixed(3)}s${projected} on ${check.target.kind === "video" ? "V" : "A"}${check.target.track_index + 1}.`
      );
    }
    entry.timeline_start_seconds = plannedStart;
    entry.timeline_end_seconds = plannedEnd;
  }
  return {
    mode: "sequential_batch",
    steps: checks.length,
    collision_checked_revisions: collisionCheckedRevisions,
    touched_tracks: layouts.size
  };
}

function linkedRevisionCompatibility(left, right) {
  if (left.planned.operation !== right.planned.operation) return false;
  if (left.planned.operation === "move") {
    return Math.abs(Number(left.planned.shift_seconds) - Number(right.planned.shift_seconds)) <= V5_REVISION_TIME_TOLERANCE;
  }
  if (left.planned.operation === "trim") {
    return revisionClose(left.planned.source_in, right.planned.source_in) &&
      revisionClose(left.planned.source_out, right.planned.source_out);
  }
  if (left.planned.operation === "disable") return left.planned.disabled === right.planned.disabled;
  return false;
}

async function analyzeLinkedAvPolicy(sequence, checks, policy) {
  const result = {
    policy,
    linked_groups_checked: 0,
    linked_components_checked: 0,
    groups: []
  };
  if (policy !== "require_all") return result;

  const inventory = await getBridgeTimelineInventory(sequence);
  const allItems = [...inventory.video_tracks, ...inventory.audio_tracks]
    .flatMap(track => track.items || []);
  const checkedKeys = new Set();
  for (const check of checks) {
    const linkKey = String(check.target.link_key || "");
    if (!linkKey || checkedKeys.has(linkKey)) continue;
    checkedKeys.add(linkKey);
    const components = allItems.filter(item => String(item.link_key || "") === linkKey);
    if (components.length < 2) continue;
    const groupChecks = checks.filter(candidate => String(candidate.target.link_key || "") === linkKey);
    if (groupChecks.length !== components.length) {
      const target = check.target;
      throw new Error(
        `Linked A/V policy requires all ${components.length} components of ${target.name || target.project_item_name || "the linked clip"}; this batch targets ${groupChecks.length}.`
      );
    }
    if (groupChecks.some(candidate => candidate.planned.operation === "remove")) {
      throw new Error("Linked A/V policy cannot use remove because remove must be applied alone.");
    }
    const first = groupChecks[0];
    if (groupChecks.some(candidate => !linkedRevisionCompatibility(first, candidate))) {
      throw new Error(`Linked A/V components for ${first.target.name || "the linked clip"} must use compatible operations and values.`);
    }
    result.linked_groups_checked++;
    result.linked_components_checked += components.length;
    result.groups.push({
      link_key: linkKey,
      components: components.length,
      revisions: groupChecks.map(candidate => candidate.revision),
      operation: first.planned.operation,
      passed: true
    });
  }
  return result;
}

function assertTrimOriginSafety(current, sourceIn, timelineScale, label) {
  // Premiere shifts the timeline start when source-in changes. Either the
  // forward trim or its inverse expands left by this amount. At the origin
  // the host can clamp that expansion and leave a partially restored range.
  const requiredLeftRoom = Math.abs(Number(sourceIn) - Number(current.source_in_seconds)) * timelineScale;
  if (!Number.isFinite(requiredLeftRoom) || !Number.isFinite(Number(current.timeline_start_seconds))) {
    throw new Error(`${label} cannot establish safe source-in rollback timing.`);
  }
  if (requiredLeftRoom > Number(current.timeline_start_seconds) + 0.000001) {
    throw new Error(`${label} source-in change cannot be safely reversed near timeline zero. No changes were made; use an unchanged source-in or a test clip later in the sequence.`);
  }
}

async function preflightRevisions(project, sequence, doc = revisionDoc, options = {}) {
  validateRevisionDocument(doc);
  const requestIdGuard = await assertRevisionRequestUnused(doc);
  if (!project) throw new Error("No active Premiere project.");
  if (!sequence) throw new Error("No active sequence.");
  if (doc.project_guid && bridgeGuidString(project.guid) !== doc.project_guid) {
    throw new Error("Active project GUID does not match the revision document.");
  }
  if (doc.sequence_name && String(sequence.name || "") !== doc.sequence_name) {
    throw new Error(`Active sequence is ${String(sequence.name || "")}, not ${doc.sequence_name}.`);
  }
  if (doc.sequence_guid && bridgeGuidString(sequence.guid) !== doc.sequence_guid) {
    throw new Error("Active sequence GUID does not match the revision document.");
  }
  let timelineState = null;
  if (doc.expected_timeline_state_token) {
    timelineState = await getBridgeTimelineInventory(sequence);
    if (timelineState.state_token !== doc.expected_timeline_state_token) {
      throw new Error(`Timeline state changed since the source snapshot (expected ${doc.expected_timeline_state_token}, current ${timelineState.state_token}). Request a fresh snapshot and rebuild the revision document.`);
    }
  }

  const seen = new Set();
  const checks = [];
  for (let index = 0; index < doc.revisions.length; index++) {
    const revision = doc.revisions[index];
    const key = revisionTargetKey(revision.target);
    if (seen.has(key)) throw new Error(`Revision ${index + 1} targets a clip already used earlier in this batch.`);
    seen.add(key);
    const resolved = await resolveRevisionTarget(sequence, revision.target);
    const current = resolved.description;
    const alreadyResolved = checks.find(check =>
      check.target.kind === revision.target.kind &&
      check.target.track_index === revision.target.track_index &&
      revisionDescriptionsReferToSameItem(current, check.target)
    );
    if (alreadyResolved) throw new Error(`Revision ${index + 1} targets a clip already used earlier in this batch.`);
    const planned = { operation: revision.operation };

    if (revision.operation === "move") {
      const destination = Number(revision.new_timeline_start);
      const delta = destination - Number(current.timeline_start_seconds);
      if (Math.abs(delta) < 0.000001) throw new Error(`Revision ${index + 1} move is a no-op.`);
      planned.new_timeline_start = destination;
      planned.shift_seconds = delta;
      planned.timeline_start = destination;
      planned.timeline_duration = Number(current.timeline_duration_seconds);
      planned.timeline_end = Number(current.timeline_end_seconds) + delta;
    } else if (revision.operation === "trim") {
      const sourceIn = Number(revision.source_in);
      const sourceOut = Number(revision.source_out);
      try {
        const projectItem = await resolved.item.getProjectItem();
        const clip = ppro.ClipProjectItem.cast(projectItem);
        const info = await inspectClipForPreflight(clip);
        if (info.duration_seconds != null && info.duration_seconds > 0 && sourceOut > info.duration_seconds + 0.001) {
          throw new Error(`source_out ${sourceOut.toFixed(3)}s exceeds media duration ${info.duration_seconds.toFixed(3)}s.`);
        }
      } catch (err) {
        if (String(err.message || err).includes("exceeds media duration")) throw new Error(`Revision ${index + 1} ${String(err.message || err)}`);
      }
      // Premiere's getSpeed() is not a reliable duration divisor for every
      // host/media combination. Derive the conversion from the clip's actual
      // exposed source and timeline durations instead.
      const timelineScale = revisionTimelineScale(current, `Revision ${index + 1}`);
      assertTrimOriginSafety(current, sourceIn, timelineScale, `Revision ${index + 1}`);
      const timelineEndOverride = options.timeline_end_overrides &&
        Number.isFinite(Number(options.timeline_end_overrides[index]))
        ? Number(options.timeline_end_overrides[index])
        : null;
      planned.source_in = sourceIn;
      planned.source_out = sourceOut;
      planned.timeline_start = current.timeline_start_seconds;
      planned.timeline_duration = timelineEndOverride == null
        ? (sourceOut - sourceIn) * timelineScale
        : timelineEndOverride - Number(current.timeline_start_seconds);
      planned.timeline_end = timelineEndOverride == null
        ? Number(current.timeline_start_seconds) + planned.timeline_duration
        : timelineEndOverride;
      if (!Number.isFinite(planned.timeline_duration) || planned.timeline_duration <= 0) {
        throw new Error(`Revision ${index + 1} rollback timeline end is invalid.`);
      }
      if (timelineEndOverride != null) planned.rollback_timeline_end_override = true;
      planned.source_to_timeline_ratio = timelineScale;
    } else if (revision.operation === "disable") {
      planned.disabled = revision.disabled;
    } else {
      planned.ripple = revision.ripple === true;
      planned.shift_overlapping = revision.shift_overlapping === true;
      planned.media_type = "unknown";
      try {
        const projectItem = await resolved.item.getProjectItem();
        const clip = ppro.ClipProjectItem.cast(projectItem);
        planned.media_type = (await inspectClipForPreflight(clip)).media_type;
      } catch (_) {}
    }
    checks.push({ revision: index + 1, target: current, planned });
  }
  const collisionModel = await analyzeBatchRevisionCollisions(sequence, checks);
  const linkedAvPolicy = await analyzeLinkedAvPolicy(sequence, checks, doc.linked_av_policy || "independent");
  const batchBudget = revisionBatchBudget(doc.revisions, options.reserve_rollback !== false);
  return {
    preflighted: true,
    revisions: checks.length,
    project: { name: String(project.name || ""), guid: bridgeGuidString(project.guid) },
    sequence: { name: String(sequence.name || ""), guid: bridgeGuidString(sequence.guid) },
    safety_policy: {
      policy: doc.safety_policy || "standard",
      strict_guards_complete: doc.safety_policy === "strict" ? true : null
    },
    request_id_guard: requestIdGuard,
    timeline_state_guard: {
      supplied: !!doc.expected_timeline_state_token,
      matched: doc.expected_timeline_state_token ? true : null,
      state_token: doc.expected_timeline_state_token || null
    },
    collision_model: collisionModel,
    linked_av_policy: linkedAvPolicy,
    session_mutation_guard: {
      ...describeSessionMutationGuard(batchBudget.total),
      forward_units: batchBudget.forward,
      rollback_reserved_units: batchBudget.rollback
    },
    checks
  };
}

async function assertRevisionRequestUnused(doc) {
  if (!doc || !doc.request_id) {
    return { supplied: false, request_id: null, retry: false, prior_safe_failures: 0, attempt: null };
  }
  const receiptHistory = await collectRevisionHistoryDocuments(
    "revision-receipts",
    "receipt_id",
    lastRevisionReceipt
  );
  for (const receipt of receiptHistory.documents.values()) {
    const previousRequestId = receipt.source_document && receipt.source_document.request_id;
    if (previousRequestId === doc.request_id) {
      throw new Error(`Revision request_id ${doc.request_id} was already applied and verified in receipt ${receipt.receipt_id}. Use a new request_id for a new edit intent.`);
    }
  }
  const failureHistory = await collectRevisionHistoryDocuments(
    "revision-failures",
    "failure_id",
    lastRevisionFailure
  );
  const matchingFailures = Array.from(failureHistory.documents.values())
    .filter(failure => failure.source_document && failure.source_document.request_id === doc.request_id);
  const unsafeFailure = matchingFailures.find(failure =>
    !failure.rollback || failure.rollback.verified !== true
  );
  if (unsafeFailure) {
    throw new Error(`Revision request_id ${doc.request_id} has prior failure ${unsafeFailure.failure_id} without a verified rollback. Inspect the timeline before retrying.`);
  }
  return {
    supplied: true,
    request_id: doc.request_id,
    retry: matchingFailures.length > 0,
    prior_safe_failures: matchingFailures.length,
    attempt: matchingFailures.length + 1
  };
}

async function preflightLoadedRevisions() {
  if (!revisionDoc) throw new Error("No timeline revisions loaded.");
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  return await preflightRevisions(project, sequence, revisionDoc);
}

async function getStrictRevisionContext() {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  const timeline = await getBridgeTimelineInventory(sequence);
  const allTargets = [...timeline.video_tracks, ...timeline.audio_tracks]
    .flatMap(track => (track.items || []).map(item => {
      const revisionTarget = {
        kind: item.kind,
        track_index: item.track_index,
        timeline_start: item.timeline_start_seconds,
        timeline_end: item.timeline_end_seconds
      };
      let revisionTargetIdentity = "";
      if (item.project_item_id) {
        revisionTarget.project_item_id = item.project_item_id;
        revisionTargetIdentity = "project_item_id";
      } else if (item.media_path) {
        revisionTarget.media_path = item.media_path;
        revisionTargetIdentity = "media_path";
      } else if (item.project_item_name) {
        revisionTarget.project_item_name = item.project_item_name;
        revisionTargetIdentity = "project_item_name";
      }
      return {
        kind: item.kind,
        track_index: item.track_index,
        track_number: item.track_number,
        name: item.name,
        project_item_id: item.project_item_id,
        media_path: item.media_path,
        project_item_name: item.project_item_name,
        timeline_start: item.timeline_start_seconds,
        timeline_end: item.timeline_end_seconds,
        source_in: item.source_in_seconds,
        source_out: item.source_out_seconds,
        link_key: item.link_key,
        disabled: item.disabled,
        revision_target_ready: !!revisionTargetIdentity,
        revision_target_identity: revisionTargetIdentity || null,
        revision_target: revisionTargetIdentity ? revisionTarget : null
      };
    }));
  const targetLimit = 500;
  const linkedTargetMap = {};
  allTargets.forEach(item => {
    if (!item.link_key || !item.revision_target_ready) return;
    if (!linkedTargetMap[item.link_key]) linkedTargetMap[item.link_key] = [];
    linkedTargetMap[item.link_key].push(item.revision_target);
  });
  const linkedTargetGroups = Object.keys(linkedTargetMap)
    .filter(linkKey => linkedTargetMap[linkKey].length > 1)
    .map(linkKey => ({
      link_key: linkKey,
      components: linkedTargetMap[linkKey].length,
      revision_targets: linkedTargetMap[linkKey]
    }));
  const protectedSequences = await getProtectedSequences();
  const sequenceProtected = protectedSequences.items.some(item => String(item.sequence_guid || "") === bridgeGuidString(sequence.guid));
  return {
    schema: "v5-revision-context-0.1",
    generated_at: new Date().toISOString(),
    project: { name: String(project.name || ""), guid: bridgeGuidString(project.guid) },
    sequence: { name: String(sequence.name || ""), guid: bridgeGuidString(sequence.guid) },
    timeline_state_token: timeline.state_token,
    sequence_protection: {
      protected: sequenceProtected,
      safe_to_mutate: !sequenceProtected,
      reason: sequenceProtected ? `Active sequence ${String(sequence.name || "")} is protected.` : "Active sequence is not protected."
    },
    session_mutation_budget: getSessionMutationBudget(),
    strict_document_base: {
      schema: V5_REVISION_SCHEMA,
      safety_policy: "strict",
      project_guid: bridgeGuidString(project.guid),
      sequence_name: String(sequence.name || ""),
      sequence_guid: bridgeGuidString(sequence.guid),
      expected_timeline_state_token: timeline.state_token,
      linked_av_policy: "require_all"
    },
    required_next: ["request_id", "revisions"],
    target_catalog: {
      total: allTargets.length,
      ready: allTargets.filter(item => item.revision_target_ready).length,
      unready: allTargets.filter(item => !item.revision_target_ready).length,
      returned: Math.min(allTargets.length, targetLimit),
      truncated: allTargets.length > targetLimit,
      items: allTargets.slice(0, targetLimit)
    },
    linked_target_groups: linkedTargetGroups,
    timeline_summary: {
      video_tracks: timeline.video_track_count,
      audio_tracks: timeline.audio_track_count,
      video_items: timeline.total_video_items,
      audio_items: timeline.total_audio_items,
      warnings: timeline.warnings
    }
  };
}

async function runRevisionTrackItemAction(project, sequence, target, builder, label) {
  await runSingleAction(
    project,
    async () => (await resolveRevisionTarget(sequence, target, { checkEnd: false })).item,
    builder,
    label
  );
}

async function applyRemoveRevision(project, sequence, revision, label) {
  const resolved = await resolveRevisionTarget(sequence, revision.target, { checkEnd: false });
  await sequence.clearSelection();
  const selection = await sequence.getSelection();
  if (!selection || !selection.addItem(resolved.item, true)) throw new Error(`${label} could not select its target.`);
  const editor = ppro.SequenceEditor.getEditor(sequence);
  let ok = false;
  try {
    project.lockedAccess(() => {
      const mediaType = revision.target.kind === "video"
        ? ppro.Constants.MediaType.VIDEO
        : ppro.Constants.MediaType.AUDIO;
      const action = editor.createRemoveItemsAction(
        selection,
        revision.ripple === true,
        mediaType,
        revision.shift_overlapping === true
      );
      ok = project.executeTransaction((compoundAction) => compoundAction.addAction(action), label);
    });
  } finally {
    try { await sequence.clearSelection(); } catch (_) {}
  }
  if (!ok) throw new Error(`${label} transaction returned false.`);
  v5SessionMutations++;
}

function revisionClose(actual, expected, tolerance = V5_REVISION_TIME_TOLERANCE) {
  return Number.isFinite(Number(actual)) && Math.abs(Number(actual) - Number(expected)) <= tolerance;
}

async function verifyAppliedRevision(sequence, revision, check) {
  const planned = check.planned;
  if (revision.operation === "remove") {
    let remaining = null;
    try { remaining = await resolveRevisionTarget(sequence, revision.target); }
    catch (err) {
      if (!String(err.message || err).startsWith("No exact ")) throw err;
    }
    if (remaining) throw new Error("Removed target is still present.");
    return { verified: true, operation: "remove", removed: true, previous_target: check.target };
  }

  const verificationTarget = { ...revision.target };
  if (revision.operation === "move") {
    verificationTarget.timeline_start = planned.new_timeline_start;
    verificationTarget.timeline_end = Number(check.target.timeline_end_seconds) + Number(planned.shift_seconds);
  } else if (revision.operation === "trim") {
    verificationTarget.timeline_end = planned.timeline_end;
  }
  const resolved = await resolveRevisionTarget(sequence, verificationTarget);
  const resulting = resolved.description;

  if (revision.operation === "disable" && resulting.disabled !== planned.disabled) {
    throw new Error(`Expected disabled=${planned.disabled}, got ${resulting.disabled}.`);
  }
  if (revision.operation === "move") {
    if (!revisionClose(resulting.timeline_start_seconds, planned.new_timeline_start) ||
        !revisionClose(resulting.timeline_end_seconds, verificationTarget.timeline_end)) {
      throw new Error("Moved target did not reach its planned timeline range.");
    }
  }
  if (revision.operation === "trim") {
    if (!revisionClose(resulting.source_in_seconds, planned.source_in) ||
        !revisionClose(resulting.source_out_seconds, planned.source_out) ||
        !revisionClose(resulting.timeline_end_seconds, planned.timeline_end)) {
      throw new Error("Trimmed target did not reach its planned source/timeline range.");
    }
  }
  return { verified: true, operation: revision.operation, resulting_target: resulting };
}

function recoveryTarget(description) {
  const target = {
    kind: description.kind,
    track_index: description.track_index,
    timeline_start: description.timeline_start_seconds,
    timeline_end: description.timeline_end_seconds
  };
  if (description.project_item_id) target.project_item_id = description.project_item_id;
  else if (description.media_path) target.media_path = description.media_path;
  else target.project_item_name = description.project_item_name || description.name;
  return target;
}

function buildInverseRevision(revision, before, after) {
  if (!revision || !before || !after) return null;
  if (revision.operation === "disable") {
    return typeof before.disabled === "boolean"
      ? { operation: "disable", target: recoveryTarget(after), disabled: before.disabled }
      : null;
  }
  if (revision.operation === "move") {
    return { operation: "move", target: recoveryTarget(after), new_timeline_start: before.timeline_start_seconds };
  }
  if (revision.operation === "trim") {
    if (!Number.isFinite(Number(before.source_in_seconds)) ||
        !Number.isFinite(Number(before.source_out_seconds)) ||
        Number(before.source_out_seconds) <= Number(before.source_in_seconds)) return null;
    return {
      operation: "trim",
      target: recoveryTarget(after),
      source_in: before.source_in_seconds,
      source_out: before.source_out_seconds
    };
  }
  return null;
}

function buildRevisionRecoveryPlans(doc, preflight, verifications) {
  const inverseRevisions = [];
  const placementDecisions = [];
  const warnings = [];

  for (let index = doc.revisions.length - 1; index >= 0; index--) {
    const revision = doc.revisions[index];
    const check = preflight.checks[index];
    const verification = verifications[index];
    const before = check.target;
    const after = verification.resulting_target;

    if (revision.operation !== "remove") {
      const inverse = buildInverseRevision(revision, before, after);
      if (inverse) inverseRevisions.push(inverse);
      else warnings.push(`Revision ${index + 1} previous state was unavailable; no automatic inverse was generated.`);
    } else {
      const mediaReference = before.media_path
        ? { media_path: before.media_path }
        : { media_match: before.project_item_name || before.name };
      const edit = {
        ...mediaReference,
        source_in: before.source_in_seconds,
        source_out: before.source_out_seconds,
        timeline_start: before.timeline_start_seconds,
        video_track: before.kind === "video" ? before.track_index : 0,
        audio_track: before.kind === "audio" ? before.track_index : 0,
        note: `Recovery placement for removed ${before.kind} item from revision ${index + 1}`
      };
      const componentSafe = (before.kind === "video" && check.planned.media_type === "video") ||
        (before.kind === "audio" && check.planned.media_type === "audio");
      const rippleSafe = check.planned.ripple !== true;
      const timingSafe = Number.isFinite(before.source_in_seconds) &&
        Number.isFinite(before.source_out_seconds) &&
        before.source_out_seconds > before.source_in_seconds;
      placementDecisions.push({
        revision: index + 1,
        safe_to_apply_automatically: componentSafe && rippleSafe && timingSafe,
        media_type: check.planned.media_type,
        decision_document: timingSafe ? {
          schema: "v5-edit-decisions-0.2",
          sequence_name: preflight.sequence.name,
          edits: [edit]
        } : null
      });
      if (!componentSafe) warnings.push(`Removed revision ${index + 1} recovery may also place linked media components; inspect before applying.`);
      if (!rippleSafe) warnings.push(`Removed revision ${index + 1} used ripple; replacement alone cannot restore other shifted timeline items.`);
      if (!timingSafe) warnings.push(`Removed revision ${index + 1} did not expose a recoverable source range.`);
    }
  }

  return {
    revision_document: inverseRevisions.length ? {
      schema: V5_REVISION_SCHEMA,
      project_guid: preflight.project.guid,
      sequence_name: preflight.sequence.name,
      sequence_guid: preflight.sequence.guid,
      linked_av_policy: doc.linked_av_policy || "independent",
      revisions: inverseRevisions
    } : null,
    placement_decisions: placementDecisions,
    warnings
  };
}

async function applyRevisionStep(project, sequence, revision, check, index, labelPrefix = "V5 revision", expectedSequenceGuid = null, expectedProjectGuid = null) {
  const label = `${labelPrefix} ${index + 1} ${revision.operation}`;
  const freshContext = async () => {
    const liveProject = await ppro.Project.getActiveProject();
    if (!liveProject) throw new Error(`${label} could not reacquire the active project.`);
    const liveProjectGuid = bridgeGuidString(liveProject.guid);
    if (expectedProjectGuid && liveProjectGuid !== expectedProjectGuid) {
      throw new Error(`${label} active project changed during the revision batch.`);
    }
    const liveSequence = await liveProject.getActiveSequence();
    if (!liveSequence) throw new Error(`${label} could not reacquire the active sequence.`);
    const liveGuid = bridgeGuidString(liveSequence.guid);
    if (expectedSequenceGuid && liveGuid !== expectedSequenceGuid) {
      throw new Error(`${label} active sequence changed during the revision batch.`);
    }
    return { project: liveProject, sequence: liveSequence };
  };
  if (revision.operation === "move") {
    let live = await freshContext();
    const current = (await resolveRevisionTarget(live.sequence, revision.target)).description;
    const delta = Number(revision.new_timeline_start) - Number(current.timeline_start_seconds);
    const shift = ppro.TickTime.createWithSeconds(delta);
    live = await freshContext();
    await runRevisionTrackItemAction(live.project, live.sequence, revision.target, item => item.createMoveAction(shift), label);
  } else if (revision.operation === "disable") {
    const live = await freshContext();
    await runRevisionTrackItemAction(live.project, live.sequence, revision.target, item => item.createSetDisabledAction(revision.disabled), label);
  } else if (revision.operation === "remove") {
    const live = await freshContext();
    await applyRemoveRevision(live.project, live.sequence, revision, label);
  } else {
    const live = await freshContext();
    const resolved = await resolveRevisionTarget(live.sequence, revision.target);
    const current = resolved.description;
    const sourceIn = Number(revision.source_in);
    const sourceOut = Number(revision.source_out);
    const operations = sourceIn >= Number(current.source_out_seconds)
      ? [["out", sourceOut], ["in", sourceIn]]
      : [["in", sourceIn], ["out", sourceOut]];
    // Changing a source-in can move the item's exposed timeline start before a
    // later action gets a chance to reacquire it. Keep the resolved item: set
    // both source bounds together, then move that same live object back to its
    // planned timeline start. Creating a set-start action for an unchanged
    // pre-transaction value can return null in Premiere, so do not add one to
    // the source-bound compound action.
    let ok = false;
    live.project.lockedAccess(() => {
      ok = live.project.executeTransaction((compoundAction) => {
        for (const [name, seconds] of operations) {
          const time = ppro.TickTime.createWithSeconds(seconds);
          compoundAction.addAction(name === "in"
            ? resolved.item.createSetInPointAction(time)
            : resolved.item.createSetOutPointAction(time));
        }
      }, `${label} source range`);
    });
    if (!ok) throw new Error(`${label} source-range transaction returned false.`);
    v5SessionMutations++;
    const shiftedStart = bridgeSeconds(await resolved.item.getStartTime());
    const delta = Number(check.planned.timeline_start) - Number(shiftedStart);
    if (Math.abs(delta) > V5_REVISION_TIME_TOLERANCE) {
      // The timing read yields to Premiere. Recheck both scope IDs before
      // committing the second transaction, retaining the exact resolved item.
      const positionContext = await freshContext();
      ok = false;
      positionContext.project.lockedAccess(() => {
        ok = positionContext.project.executeTransaction((compoundAction) => {
          compoundAction.addAction(resolved.item.createMoveAction(ppro.TickTime.createWithSeconds(delta)));
        }, `${label} restore position`);
      });
      if (!ok) throw new Error(`${label} position transaction returned false.`);
      v5SessionMutations++;
    }
  }
  const verificationContext = await freshContext();
  return await verifyAppliedRevision(verificationContext.sequence, revision, check);
}

async function executeRevisionBatch(project, sequence, doc, preflight, options = {}) {
  const verifications = Array.isArray(options.verifications) ? options.verifications : [];
  for (let index = 0; index < doc.revisions.length; index++) {
    if (typeof options.onStepStart === "function") options.onStepStart(index);
    const revision = doc.revisions[index];
    const verification = await applyRevisionStep(
      project,
      sequence,
      revision,
      preflight.checks[index],
      index,
      options.label_prefix || "V5 revision",
      preflight.sequence.guid,
      preflight.project.guid
    );
    verifications.push({ revision: index + 1, ...verification });
    log(`${options.log_prefix || "REVISION"} ✓ ${index + 1}: ${revision.operation} — verified`);
    if (Number(options.inject_failure_after_step) === index + 1) {
      throw new Error(`Injected rollback validation failure after verified revision ${index + 1}.`);
    }
  }
  return verifications;
}

function revisionStateMatchesBefore(revision, current, before) {
  if (!current || !before) return false;
  if (revision.operation === "disable") return current.disabled === before.disabled;
  if (revision.operation === "move") {
    return revisionClose(current.timeline_start_seconds, before.timeline_start_seconds) &&
      revisionClose(current.timeline_end_seconds, before.timeline_end_seconds);
  }
  if (revision.operation === "trim") {
    return revisionClose(current.timeline_start_seconds, before.timeline_start_seconds) &&
      revisionClose(current.timeline_end_seconds, before.timeline_end_seconds) &&
      revisionClose(current.source_in_seconds, before.source_in_seconds) &&
      revisionClose(current.source_out_seconds, before.source_out_seconds);
  }
  return true;
}

async function describeRevisionStateAfterFailure(sequence, revision, check) {
  if (revision.operation === "remove") {
    try { return (await resolveRevisionTarget(sequence, revision.target)).description; }
    catch (err) {
      if (String(err.message || err).startsWith("No exact ")) return null;
      throw err;
    }
  }
  if (revision.operation === "move") {
    const plannedTarget = {
      ...revision.target,
      timeline_start: check.planned.timeline_start,
      timeline_end: check.planned.timeline_end
    };
    try { return (await resolveRevisionTarget(sequence, plannedTarget)).description; }
    catch (err) {
      if (!String(err.message || err).startsWith("No exact ")) throw err;
    }
  }
  try { return (await resolveRevisionTarget(sequence, revision.target, { checkEnd: false })).description; }
  catch (err) {
    if (String(err.message || err).startsWith("No exact ")) return null;
    throw err;
  }
}

async function rollbackFailedRevisionBatch(project, sequence, doc, preflight, attemptedIndex, completedVerifications) {
  const inverseRevisions = [];
  const timelineEndOverrides = [];
  const verifiedByRevision = new Map(completedVerifications.map(item => [item.revision, item]));
  try {
    for (let index = attemptedIndex; index >= 0; index--) {
      const revision = doc.revisions[index];
      const before = preflight.checks[index].target;
      const completed = verifiedByRevision.get(index + 1);
      const liveProject = await ppro.Project.getActiveProject();
      if (!liveProject) throw new Error("Automatic rollback could not reacquire the active project.");
      if (bridgeGuidString(liveProject.guid) !== preflight.project.guid) {
        throw new Error("Automatic rollback stopped: active project changed; original state has not been verified.");
      }
      const liveSequence = await liveProject.getActiveSequence();
      if (!liveSequence) throw new Error("Automatic rollback could not reacquire the active sequence.");
      if (bridgeGuidString(liveSequence.guid) !== preflight.sequence.guid) {
        throw new Error("Automatic rollback stopped: active sequence changed; original state has not been verified.");
      }
      const current = completed && completed.resulting_target
        ? completed.resulting_target
        : await describeRevisionStateAfterFailure(liveSequence, revision, preflight.checks[index]);

      if (revision.operation === "remove") {
        if (current) continue;
        return {
          status: "failed",
          verified: false,
          operations: inverseRevisions.length,
          error: `Revision ${index + 1} removed its target and cannot be restored by the revision rollback engine.`
        };
      }
      if (!current) {
        return {
          status: "failed",
          verified: false,
          operations: inverseRevisions.length,
          error: `Revision ${index + 1} current state could not be found for automatic rollback.`
        };
      }
      if (revisionStateMatchesBefore(revision, current, before)) continue;
      const inverse = buildInverseRevision(revision, before, current);
      if (!inverse) {
        return {
          status: "failed",
          verified: false,
          operations: inverseRevisions.length,
          error: `Revision ${index + 1} did not expose enough state for an automatic inverse.`
        };
      }
      inverseRevisions.push(inverse);
      timelineEndOverrides.push(revision.operation === "trim" ? Number(before.timeline_end_seconds) : null);
    }

    if (!inverseRevisions.length) {
      return { status: "not_needed", verified: true, operations: 0, document: null, preflight: null, verifications: [] };
    }
    const rollbackDocument = {
      schema: V5_REVISION_SCHEMA,
      project_guid: preflight.project.guid,
      sequence_name: preflight.sequence.name,
      sequence_guid: preflight.sequence.guid,
      linked_av_policy: "independent",
      revisions: inverseRevisions
    };
    const rollbackProject = await ppro.Project.getActiveProject();
    if (!rollbackProject) throw new Error("Automatic rollback could not reacquire the active project for preflight.");
    const rollbackSequence = await rollbackProject.getActiveSequence();
    if (!rollbackSequence) throw new Error("Automatic rollback could not reacquire the active sequence for preflight.");
    const rollbackPreflight = await preflightRevisions(rollbackProject, rollbackSequence, rollbackDocument, {
      reserve_rollback: false,
      timeline_end_overrides: timelineEndOverrides
    });
    const rollbackVerifications = await executeRevisionBatch(rollbackProject, rollbackSequence, rollbackDocument, rollbackPreflight, {
      label_prefix: "V5 automatic rollback",
      log_prefix: "ROLLBACK"
    });
    return {
      status: "completed",
      verified: rollbackVerifications.every(item => item.verified === true),
      operations: inverseRevisions.length,
      document: rollbackDocument,
      preflight: rollbackPreflight,
      verifications: rollbackVerifications
    };
  } catch (err) {
    return {
      status: "failed",
      verified: false,
      operations: inverseRevisions.length,
      error: String(err.message || err)
    };
  }
}

async function applyLoadedRevisions(options = {}) {
  if (applyLoadedRevisions.inProgress || (typeof applyDecisions === "function" && applyDecisions.inProgress)) throw new Error("Apply already in progress. Wait for it to finish.");
  if (typeof v5Imports !== "undefined" && v5Imports && v5Imports.busy) throw new Error("Import already in progress.");
  applyLoadedRevisions.inProgress = true;
  try {
  const sourceDocument = revisionDoc;
  const sourceSignature = revisionSignature;
  const assertLoadedRevisionsUnchanged = () => {
    if (revisionDoc !== sourceDocument || revisionSignature !== sourceSignature) {
      throw new Error("Loaded revisions changed during Apply. Preflight the current proposal before retrying.");
    }
  };
  if (!revisionDoc) throw new Error("No timeline revisions loaded.");
  if (revisionSignature && revisionSignature === lastAppliedRevisionSignature) {
    throw new Error("These exact timeline revisions were already applied in this V5 session.");
  }
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  assertLoadedRevisionsUnchanged();
  const requestedBudget = revisionBatchBudget(sourceDocument.revisions);
  assertSessionMutationBudget(requestedBudget.total, "revision batch including rollback reserve");
  const previousReceipt = lastRevisionReceipt;
  const preflight = await preflightRevisions(project, sequence, sourceDocument);
  assertLoadedRevisionsUnchanged();
  log(`REVISION PREFLIGHT PASSED — ${preflight.revisions} revision(s).`);
  let attemptedIndex = -1;
  let verifications = [];

  try {
    await executeRevisionBatch(project, sequence, sourceDocument, preflight, {
      onStepStart: index => { attemptedIndex = index; },
      verifications,
      inject_failure_after_step: options.inject_failure_after_step
    });
    const completedProject = await ppro.Project.getActiveProject();
    if (!completedProject) throw new Error("Could not reacquire the active project after revision execution.");
    if (bridgeGuidString(completedProject.guid) !== preflight.project.guid) {
      throw new Error("Active project changed before revision receipt finalization.");
    }
    const completedSequence = await completedProject.getActiveSequence();
    if (!completedSequence) throw new Error("Could not reacquire the active sequence after revision execution.");
    if (bridgeGuidString(completedSequence.guid) !== preflight.sequence.guid) {
      throw new Error("Active sequence changed before revision receipt finalization.");
    }
    const recoveryExpectedTimelineStateToken = (await getBridgeTimelineInventory(completedSequence)).state_token;
    const recovery = buildRevisionRecoveryPlans(sourceDocument, preflight, verifications);
    const completedAt = new Date().toISOString();
    const receipt = {
      schema: "v5-revision-receipt-0.1",
      receipt_id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
      plugin_version: V5_BRIDGE_PLUGIN_VERSION,
      completed_at: completedAt,
      sequence: preflight.sequence,
      revision_signature: sourceSignature,
      source_document: sourceDocument,
      preflight,
      recovery_expected_timeline_state_token: recoveryExpectedTimelineStateToken,
      verifications,
      recovery
    };
    lastRevisionReceipt = receipt;
    await persistRevisionReceipt(receipt);
    lastAppliedRevisionSignature = sourceSignature;
    return {
      applied: true,
      verified: true,
      revisions: sourceDocument.revisions.length,
      receipt_id: receipt.receipt_id,
      verifications,
      recovery
    };
  } catch (err) {
    lastRevisionReceipt = previousReceipt;
    lastAppliedRevisionSignature = null;
    const executionError = String(err.message || err);
    const rollback = await rollbackFailedRevisionBatch(
      project,
      sequence,
      sourceDocument,
      preflight,
      attemptedIndex,
      verifications
    );
    const failure = {
      schema: "v5-revision-failure-0.1",
      failure_id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
      plugin_version: V5_BRIDGE_PLUGIN_VERSION,
      failed_at: new Date().toISOString(),
      sequence: preflight.sequence,
      revision_signature: sourceSignature,
      source_document: sourceDocument,
      preflight,
      failed_revision: attemptedIndex >= 0 ? attemptedIndex + 1 : null,
      error: executionError,
      completed_verifications: verifications,
      rollback
    };
    lastRevisionFailure = failure;
    try { await persistRevisionFailure(failure); }
    catch (persistErr) { failure.persistence_error = String(persistErr.message || persistErr); }
    if (rollback.verified) {
      const restored = rollback.operations === 1 ? "1 change" : `${rollback.operations} changes`;
      throw new Error(`Revision batch failed at revision ${failure.failed_revision}: ${executionError} Automatic rollback restored ${restored}. Failure ID: ${failure.failure_id}.`);
    }
    throw new Error(`Revision batch failed at revision ${failure.failed_revision}: ${executionError} AUTOMATIC ROLLBACK INCOMPLETE: ${rollback.error || rollback.status}. Failure ID: ${failure.failure_id}.`);
  }
  } finally {
    applyLoadedRevisions.inProgress = false;
  }
}

async function preflightReceiptRecovery(receipt = lastRevisionReceipt) {
  if (!receipt) throw new Error("No revision receipt is available.");
  if (receipt.recovered_at) {
    throw new Error(`Receipt ${receipt.receipt_id} was already recovered at ${receipt.recovered_at}.`);
  }
  if (receipt.recovery_started_at) {
    throw new Error(`Receipt recovery started at ${receipt.recovery_started_at} but did not complete; inspect the timeline before retrying.`);
  }
  const recovery = receipt.recovery;
  if (!recovery) throw new Error("The latest receipt has no recovery plan.");
  if (Array.isArray(recovery.warnings) && recovery.warnings.length) {
    throw new Error(`Recovery has ${recovery.warnings.length} warning(s); inspect the receipt and recover manually.`);
  }

  const revisionRecovery = recovery.revision_document || null;
  const placementRecoveries = Array.isArray(recovery.placement_decisions) ? recovery.placement_decisions : [];
  if (revisionRecovery && placementRecoveries.length) {
    throw new Error("Mixed revision and placement recovery is not automatic; inspect and apply the plans separately.");
  }
  if (!revisionRecovery && !placementRecoveries.length) throw new Error("The receipt contains no recoverable operations.");

  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  const expected = receipt.sequence || {};
  if (expected.name && String(sequence.name || "") !== expected.name) {
    throw new Error(`Active sequence is ${String(sequence.name || "")}, not receipt sequence ${expected.name}.`);
  }
  if (expected.guid && bridgeGuidString(sequence.guid) !== expected.guid) {
    throw new Error("Active sequence GUID does not match the receipt.");
  }
  if (receipt.recovery_expected_timeline_state_token) {
    const currentTimelineStateToken = (await getBridgeTimelineInventory(sequence)).state_token;
    if (currentTimelineStateToken !== receipt.recovery_expected_timeline_state_token) {
      throw new Error(`Timeline state changed since receipt ${receipt.receipt_id} was created (expected ${receipt.recovery_expected_timeline_state_token}, current ${currentTimelineStateToken}). Inspect the timeline and request a fresh snapshot before recovery.`);
    }
  }

  if (revisionRecovery) {
    const preflight = await preflightRevisions(project, sequence, revisionRecovery);
    return {
      preflighted: true,
      receipt_id: receipt.receipt_id,
      mode: "revision",
      operations: revisionRecovery.revisions.length,
      document: revisionRecovery,
      preflight
    };
  }

  for (const plan of placementRecoveries) {
    if (!plan.safe_to_apply_automatically || !plan.decision_document) {
      throw new Error(`Removal recovery for revision ${plan.revision} is not safe to apply automatically.`);
    }
  }
  const decisionDocument = {
    schema: "v5-edit-decisions-0.2",
    sequence_name: expected.name || String(sequence.name || ""),
    edits: placementRecoveries.flatMap(plan => plan.decision_document.edits || [])
  };
  validate(decisionDocument);
  const preflight = await preflightDecisions(project, sequence, decisionDocument);
  return {
    preflighted: true,
    receipt_id: receipt.receipt_id,
    mode: "placement",
    operations: decisionDocument.edits.length,
    document: decisionDocument,
    placement_plans: placementRecoveries,
    preflight
  };
}

async function verifyPlacementRecovery(sequence, receipt, placementPlans) {
  const verifications = [];
  for (const plan of placementPlans) {
    const check = receipt.preflight && receipt.preflight.checks
      ? receipt.preflight.checks[Number(plan.revision) - 1]
      : null;
    if (!check || !check.target) throw new Error(`Receipt is missing the original target for revision ${plan.revision}.`);
    const original = check.target;
    const resolved = await resolveRevisionTarget(sequence, recoveryTarget(original));
    const result = resolved.description;
    if (!revisionClose(result.source_in_seconds, original.source_in_seconds) ||
        !revisionClose(result.source_out_seconds, original.source_out_seconds) ||
        !revisionClose(result.timeline_start_seconds, original.timeline_start_seconds) ||
        !revisionClose(result.timeline_end_seconds, original.timeline_end_seconds)) {
      throw new Error(`Recovered placement for revision ${plan.revision} did not match the original range.`);
    }
    verifications.push({ revision: plan.revision, verified: true, resulting_target: result });
  }
  return verifications;
}

async function applyReceiptRecovery(receipt = lastRevisionReceipt) {
  const sourceReceipt = receipt;
  const recoveryPreflight = await preflightReceiptRecovery(sourceReceipt);
  if (recoveryPreflight.mode === "revision") {
    sourceReceipt.recovery_started_at = new Date().toISOString();
    sourceReceipt.recovery_execution = {
      mode: "revision",
      status: "in_progress",
      verified: false,
      operations: recoveryPreflight.operations
    };
    lastRevisionReceipt = sourceReceipt;
    await persistRevisionReceipt(sourceReceipt);
    revisionDoc = recoveryPreflight.document;
    revisionSignature = JSON.stringify(revisionDoc);
    lastAppliedRevisionSignature = null;
    let result = null;
    try {
      result = await applyLoadedRevisions();
    } catch (err) {
      const message = String(err.message || err);
      const incomplete = message.includes("AUTOMATIC ROLLBACK INCOMPLETE");
      if (!incomplete) delete sourceReceipt.recovery_started_at;
      sourceReceipt.recovery_execution = {
        mode: "revision",
        status: incomplete ? "failed_incomplete" : "failed_safe",
        verified: false,
        operations: recoveryPreflight.operations,
        error: message
      };
      lastRevisionReceipt = sourceReceipt;
      await persistRevisionReceipt(sourceReceipt);
      throw err;
    }

    const recoveryReceipt = lastRevisionReceipt;
    sourceReceipt.recovered_at = new Date().toISOString();
    sourceReceipt.recovery_execution = {
      mode: "revision",
      status: "completed",
      verified: result.verified === true,
      operations: result.revisions,
      recovery_receipt_id: result.receipt_id
    };
    await persistRevisionReceipt(sourceReceipt, { update_latest: false });
    if (recoveryReceipt && recoveryReceipt.receipt_id === result.receipt_id) {
      recoveryReceipt.recovery_source_receipt_id = sourceReceipt.receipt_id;
      lastRevisionReceipt = recoveryReceipt;
      await persistRevisionReceipt(recoveryReceipt);
    }
    return {
      recovered: true,
      verified: result.verified === true,
      mode: "revision",
      source_receipt_id: sourceReceipt.receipt_id,
      recovery_receipt_id: result.receipt_id,
      recovery_result: result
    };
  }

  decisionDoc = recoveryPreflight.document;
  decisionSignature = makeDecisionSignature(decisionDoc);
  lastAppliedSignature = null;
  const paste = $("decisionPaste");
  if (paste) paste.value = JSON.stringify(decisionDoc, null, 2);
  const applyButton = $("apply");
  if (applyButton) applyButton.disabled = false;
  sourceReceipt.recovery_started_at = new Date().toISOString();
  lastRevisionReceipt = sourceReceipt;
  await persistRevisionReceipt(lastRevisionReceipt);
  await applyDecisions();

  const project = await ppro.Project.getActiveProject();
  const sequence = project ? await project.getActiveSequence() : null;
  if (!sequence) throw new Error("Active sequence disappeared before recovery verification.");
  const verifications = await verifyPlacementRecovery(sequence, sourceReceipt, recoveryPreflight.placement_plans);
  sourceReceipt.recovered_at = new Date().toISOString();
  sourceReceipt.recovery_execution = {
    mode: "placement",
    verified: true,
    operations: verifications.length,
    verifications
  };
  lastRevisionReceipt = sourceReceipt;
  await persistRevisionReceipt(lastRevisionReceipt);
  return {
    recovered: true,
    verified: true,
    mode: "placement",
    source_receipt_id: sourceReceipt.receipt_id,
    verifications
  };
}

function systemValidationScopeFilename(projectGuid, sequenceGuid) {
  if (!projectGuid || !sequenceGuid) return null;
  return `${encodeURIComponent(String(projectGuid))}--${encodeURIComponent(String(sequenceGuid))}.json`;
}

async function readScopedSystemValidation(project, sequence) {
  if (!project || !sequence) return null;
  const filename = systemValidationScopeFilename(bridgeGuidString(project.guid), bridgeGuidString(sequence.guid));
  if (!filename) return null;
  const folder = await bridgeChildFolder(v5BridgeStateFolder, "system-validations", true);
  const scoped = await bridgeReadJson(folder, filename);
  if (scoped) return scoped;
  const legacy = await bridgeReadJson(v5BridgeStateFolder, "last-system-validation.json");
  const legacyMatches = !!(legacy && legacy.project && legacy.sequence && String(legacy.project.guid || "") === String(bridgeGuidString(project.guid) || "") && String(legacy.sequence.guid || "") === String(bridgeGuidString(sequence.guid) || ""));
  if (legacyMatches) {
    await bridgeWriteJson(folder, filename, legacy);
    return legacy;
  }
  return null;
}

async function writeSystemValidation(report) {
  await bridgeWriteJson(v5BridgeStateFolder, "last-system-validation.json", report);
  const filename = systemValidationScopeFilename(report && report.project && report.project.guid, report && report.sequence && report.sequence.guid);
  if (!filename) throw new Error("System validation report is missing its project or sequence identity.");
  const folder = await bridgeChildFolder(v5BridgeStateFolder, "system-validations", true);
  await bridgeWriteJson(folder, filename, report);
}

async function listSystemValidations() {
  const items = [];
  const invalidFiles = [];
  const scopeFiles = new Map();
  const folder = await bridgeChildFolder(v5BridgeStateFolder, "system-validations", true);
  for (const entry of await folder.getEntries()) {
    if (!entry.isFile || !entry.name.endsWith(".json")) continue;
    try {
      const report = JSON.parse(await entry.read());
      const projectGuid = report && report.project && report.project.guid;
      const sequenceGuid = report && report.sequence && report.sequence.guid;
      const expectedFilename = systemValidationScopeFilename(projectGuid, sequenceGuid);
      const required = !!(report && report.validation_id && report.validated_at && report.plugin_version && projectGuid && sequenceGuid && report.timeline_state_token && typeof report.passed === "boolean");
      if (!required || !expectedFilename || entry.name !== expectedFilename) {
        invalidFiles.push({ file: entry.name, reason: !required ? "Missing required validation fields." : "Filename does not match report scope." });
        continue;
      }
      const scopeKey = `${String(projectGuid)}::${String(sequenceGuid)}`;
      if (!scopeFiles.has(scopeKey)) scopeFiles.set(scopeKey, []);
      scopeFiles.get(scopeKey).push(entry.name);
      items.push({
        validation_id: report.validation_id,
        validated_at: report.validated_at || null,
        plugin_version: report.plugin_version || null,
        plugin_version_current: String(report.plugin_version || "") === V5_BRIDGE_PLUGIN_VERSION,
        passed: report.passed === true,
        project: report.project || null,
        sequence: report.sequence || null,
        timeline_state_token: report.timeline_state_token || null,
        failed_checks: Array.isArray(report.failed_checks) ? report.failed_checks : []
      });
    } catch (err) {
      invalidFiles.push({ file: entry.name, reason: `Invalid JSON: ${String(err.message || err)}` });
    }
  }
  items.sort((a, b) => String(b.validated_at || "").localeCompare(String(a.validated_at || "")));
  const duplicateScopes = [];
  for (const [scope, files] of scopeFiles.entries()) {
    if (files.length > 1) duplicateScopes.push({ scope, files });
  }
  const issues = [];
  if (invalidFiles.length) issues.push(`${invalidFiles.length} invalid scoped validation file(s).`);
  if (duplicateScopes.length) issues.push(`${duplicateScopes.length} duplicate validation scope(s).`);
  return {
    schema: "v5-system-validation-index-0.1",
    plugin_version: V5_BRIDGE_PLUGIN_VERSION,
    status: issues.length ? "attention" : "healthy",
    total: items.length,
    passed: items.filter(item => item.passed).length,
    failed: items.filter(item => !item.passed).length,
    current_plugin_version: items.filter(item => item.plugin_version_current).length,
    stale_plugin_version: items.filter(item => !item.plugin_version_current).length,
    invalid_files: invalidFiles,
    duplicate_scopes: duplicateScopes,
    issues,
    items,
    timeline_changed: false
  };
}

async function getSystemValidationStatus(context = null) {
  const project = context && context.project ? context.project : await ppro.Project.getActiveProject();
  const sequence = context && context.sequence ? context.sequence : (project ? await project.getActiveSequence() : null);
  const timelineInventory = context && context.timeline_inventory ? context.timeline_inventory : (sequence ? await getBridgeTimelineInventory(sequence) : null);
  const lastValidation = context && Object.prototype.hasOwnProperty.call(context, "last_system_validation")
    ? context.last_system_validation
    : await readScopedSystemValidation(project, sequence);
  const sequenceGuid = sequence ? bridgeGuidString(sequence.guid) : null;
  const timelineToken = timelineInventory ? String(timelineInventory.state_token || "") : "";
  const validationSequenceGuid = lastValidation ? String(lastValidation.sequence && lastValidation.sequence.guid || "") : "";
  const validationTimelineToken = lastValidation ? String(lastValidation.timeline_state_token || "") : "";
  const validationPluginVersion = lastValidation ? String(lastValidation.plugin_version || "") : "";
  const sequenceMatches = !!(lastValidation && sequenceGuid && validationSequenceGuid === sequenceGuid);
  const timelineTokenMatches = !!(lastValidation && timelineToken && validationTimelineToken === timelineToken);
  const pluginVersionMatches = !!(lastValidation && validationPluginVersion === V5_BRIDGE_PLUGIN_VERSION);
  const current = sequenceMatches && timelineTokenMatches && pluginVersionMatches && lastValidation.passed === true;
  return {
    current,
    passed: !!(lastValidation && lastValidation.passed === true),
    reason: current
      ? "Latest system validation matches the active sequence and timeline token."
      : !lastValidation
        ? "No system validation is available."
        : !sequence
          ? "No active sequence is available."
          : !sequenceMatches
            ? "Latest validation belongs to another sequence."
            : !timelineTokenMatches
              ? "Timeline changed after the latest validation."
              : !pluginVersionMatches
                ? `Latest validation was produced by plugin ${validationPluginVersion || "unknown"}; run validation with ${V5_BRIDGE_PLUGIN_VERSION}.`
              : "Latest validation did not pass.",
    active_sequence: sequence ? { name: String(sequence.name || ""), guid: sequenceGuid } : null,
    active_timeline_state_token: timelineToken || null,
    validation_id: lastValidation ? lastValidation.validation_id || null : null,
    validation_plugin_version: validationPluginVersion || null,
    active_plugin_version: V5_BRIDGE_PLUGIN_VERSION,
    plugin_version_matches: pluginVersionMatches,
    validated_at: lastValidation ? lastValidation.validated_at || null : null,
    validation_sequence_guid: validationSequenceGuid || null,
    validation_timeline_state_token: validationTimelineToken || null,
    timeline_changed: !!(lastValidation && sequenceMatches && !timelineTokenMatches)
  };
}

async function getPremiereBridgeSnapshot() {
  const snapshot = {
    schema: V5_BRIDGE_SNAPSHOT_SCHEMA,
    plugin_version: V5_BRIDGE_PLUGIN_VERSION,
    generated_at: new Date().toISOString(),
    active_project: null,
    active_sequence: null,
    session_mutation_budget: getSessionMutationBudget(),
    media_inventory: null,
    timeline_inventory: null,
    continuity_audit: null,
    last_best_clips_receipt: null,
    best_clips_history_health: null,
    protected_sequences: null,
    sequence_protection_history: null,
    sequence_protection_health: null,
    active_sequence_protected: false,
    mutation_advisory: null,
    unified_safety_status: null,
    last_system_validation: null,
    system_validation_fresh: false,
    system_validation_advisory: null,
    loaded_decisions: decisionDoc ? {
      schema: decisionDoc.schema,
      edits: Array.isArray(decisionDoc.edits) ? decisionDoc.edits.length : 0,
      signature: decisionSignature
    } : null,
    loaded_revisions: revisionDoc ? {
      schema: revisionDoc.schema,
      revisions: revisionDoc.revisions.length,
      signature: revisionSignature
    } : null,
    last_revision_receipt: lastRevisionReceipt ? revisionReceiptSummary(lastRevisionReceipt) : null,
    last_revision_failure: lastRevisionFailure ? revisionFailureSummary(lastRevisionFailure) : null,
    revision_history_health: null,
    placement_history_health: null
  };
  try { snapshot.placement_history_health = await auditPlacementHistory(); }
  catch (err) { snapshot.placement_history_health = { status: "attention", active_scope_status: "unavailable", active_scope_blocker_request_ids: [], new_placement_advisory: { safe_to_proceed: false, reason: "Placement history audit failed; do not start a new placement." }, active_scope: { project_guid: null, sequence_guid: null, sequence_name: null }, scope_counts: { current_sequence: 0, other_sequence: 0, other_project: 0, unbound: 0, current_sequence_attention: 0 }, intents: 0, receipts: 0, completed: 0, uncertain: 0, orphan_receipts: 0, uncertain_request_ids: [], orphan_receipt_request_ids: [], requests: [], issues: [`Placement history audit failed: ${String(err.message || err)}`] }; }
  try { snapshot.last_best_clips_receipt = await bridgeReadJson(v5BridgeStateFolder, "last-best-clips-receipt.json"); }
  catch (_) {}
  try { snapshot.best_clips_history_health = await auditBestClipsHistory(); }
  catch (err) { snapshot.best_clips_history_health = { status: "attention", safe_to_start_new: false, issues: [String(err.message || err)] }; }
  try { snapshot.protected_sequences = await getProtectedSequences(); }
  catch (err) { snapshot.protected_sequences = { schema: "v5-protected-sequences-0.1", items: [], error: String(err.message || err) }; }
  try {
    const protectionHistory = await getSequenceProtectionHistory();
    snapshot.sequence_protection_history = { total: protectionHistory.events.length, latest: protectionHistory.events.length ? protectionHistory.events[protectionHistory.events.length - 1] : null };
  } catch (err) { snapshot.sequence_protection_history = { total: 0, latest: null, error: String(err.message || err) }; }
  try { snapshot.sequence_protection_health = await auditSequenceProtection(); }
  catch (err) { snapshot.sequence_protection_health = { status: "attention", issues: [String(err.message || err)] }; }
  try { snapshot.revision_history_health = await auditRevisionHistory(); }
  catch (err) {
    snapshot.revision_history_health = {
      status: "attention",
      receipts: 0,
      failures: 0,
      recovery_chains: 0,
      incomplete_recoveries: 0,
      incomplete_rollbacks: 0,
      issues: [`History audit failed: ${String(err.message || err)}`]
    };
  }
  try {
    const project = await ppro.Project.getActiveProject();
    if (!project) return snapshot;
    snapshot.active_project = {
      name: String(project.name || ""),
      path: project.path ? String(project.path) : null,
      guid: bridgeGuidString(project.guid)
    };
    const sequence = await project.getActiveSequence();
    if (sequence) {
      snapshot.active_sequence = {
        name: String(sequence.name || ""),
        guid: bridgeGuidString(sequence.guid)
      };
      try { snapshot.timeline_inventory = await getBridgeTimelineInventory(sequence); }
      catch (err) { snapshot.timeline_inventory_error = String(err.message || err); }
      try {
        const audit = await validateTimelineContinuity();
        if (audit.sequence.guid !== snapshot.active_sequence.guid) throw new Error("Active sequence changed during snapshot continuity audit.");
        snapshot.continuity_audit = audit;
      } catch (err) {
        snapshot.continuity_audit = { continuous: false, av_ends_aligned: false, error: String(err.message || err) };
      }
    }
    try { snapshot.media_inventory = await getBridgeMediaInventory(project); }
    catch (err) { snapshot.inventory_error = String(err.message || err); }
  } catch (err) { snapshot.warning = String(err.message || err); }
  snapshot.active_sequence_protected = !!(snapshot.active_sequence && snapshot.protected_sequences && Array.isArray(snapshot.protected_sequences.items) && snapshot.protected_sequences.items.some(item => String(item.sequence_guid || "") === String(snapshot.active_sequence.guid || "")));
  snapshot.mutation_advisory = snapshot.active_sequence_protected
    ? { safe_to_mutate: false, reason: `Active sequence ${snapshot.active_sequence.name || ""} is protected.` }
    : snapshot.active_sequence
      ? { safe_to_mutate: true, reason: "Active sequence is not protected." }
      : { safe_to_mutate: false, reason: "No active sequence is available." };
  try { snapshot.unified_safety_status = await getUnifiedSafetyStatus(); }
  catch (err) { snapshot.unified_safety_status = { safe_to_mutate: false, blockers: [String(err.message || err)], timeline_changed: false }; }
  try {
    const project = await ppro.Project.getActiveProject();
    const sequence = project ? await project.getActiveSequence() : null;
    snapshot.last_system_validation = await readScopedSystemValidation(project, sequence);
  }
  catch (_) {}
  const validationStatus = await getSystemValidationStatus({
    project: null,
    sequence: snapshot.active_sequence ? { name: snapshot.active_sequence.name, guid: snapshot.active_sequence.guid } : null,
    timeline_inventory: snapshot.timeline_inventory,
    last_system_validation: snapshot.last_system_validation
  });
  snapshot.system_validation_fresh = validationStatus.current;
  snapshot.system_validation_advisory = validationStatus;
  return snapshot;
}

async function bridgePostResult(commandId, ok, result = null, error = null) {
  if (!v5BridgeStateFolder) return;
  const results = await bridgeChildFolder(v5BridgeStateFolder, "results", true);
  await bridgeWriteJson(results, `${commandId}.json`, { command_id: commandId, ok, result, error, completed_at: new Date().toISOString(), plugin_version: V5_BRIDGE_PLUGIN_VERSION });
}

function validateVisualSampleRequest(request) {
  if (!request || typeof request !== "object") throw new Error("visual_samples requires a request object.");
  for (const field of ["project_guid", "sequence_guid", "expected_timeline_state_token"]) {
    if (typeof request[field] !== "string" || !request[field]) throw new Error(`visual_samples requires ${field}.`);
  }
  if (!Array.isArray(request.samples) || request.samples.length < 1 || request.samples.length > 250) {
    throw new Error("visual_samples requires 1 to 250 samples.");
  }
  const width = request.width === undefined ? 640 : Number(request.width);
  const height = request.height === undefined ? 360 : Number(request.height);
  if (!Number.isInteger(width) || width < 64 || width > 1920 || !Number.isInteger(height) || height < 64 || height > 1080) {
    throw new Error("visual_samples width/height must be integers within 64–1920 by 64–1080.");
  }
  const ids = new Set();
  request.samples.forEach((sample, index) => {
    if (!sample || typeof sample !== "object") throw new Error(`visual_samples sample ${index + 1} is invalid.`);
    if (typeof sample.id !== "string" || !/^[A-Za-z0-9_-]{1,80}$/.test(sample.id)) {
      throw new Error(`visual_samples sample ${index + 1} requires a unique safe id.`);
    }
    if (ids.has(sample.id)) throw new Error(`visual_samples duplicate sample id: ${sample.id}`);
    ids.add(sample.id);
    const seconds = Number(sample.time_seconds);
    if (!Number.isFinite(seconds) || seconds < 0) throw new Error(`visual_samples sample ${sample.id} has invalid time_seconds.`);
  });
  return { width, height };
}

async function exportVisualSamples(commandId, request) {
  const size = validateVisualSampleRequest(request);
  if (!ppro.Exporter || typeof ppro.Exporter.exportSequenceFrame !== "function") {
    throw new Error("Premiere Exporter.exportSequenceFrame is unavailable; Premiere 25.6 or newer is required.");
  }
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  const projectGuid = bridgeGuidString(project.guid);
  const sequenceGuid = bridgeGuidString(sequence.guid);
  if (projectGuid !== request.project_guid) throw new Error(`Visual-sample project GUID mismatch: expected ${request.project_guid}, active ${projectGuid}.`);
  if (sequenceGuid !== request.sequence_guid) throw new Error(`Visual-sample sequence GUID mismatch: expected ${request.sequence_guid}, active ${sequenceGuid}.`);
  const timeline = await getBridgeTimelineInventory(sequence);
  if (timeline.state_token !== request.expected_timeline_state_token) {
    throw new Error(`Visual-sample timeline state is stale: expected ${request.expected_timeline_state_token}, active ${timeline.state_token}.`);
  }
  const root = await bridgeChildFolder(v5BridgeStateFolder, "visual-samples", true);
  const outputFolder = await bridgeChildFolder(root, commandId, true);
  const folderPath = String(outputFolder.nativePath || "");
  if (!folderPath) throw new Error("Visual-sample output folder has no native path.");
  const items = [];
  for (const sample of request.samples) {
    const filename = `${sample.id}.jpg`;
    const outputPath = `${folderPath}/${filename}`;
    const time = ppro.TickTime.createWithSeconds(Number(sample.time_seconds));
    let exportAttempts = 0;
    let verifiedFile = false;
    while (exportAttempts < 3 && !verifiedFile) {
      exportAttempts += 1;
      const ok = await ppro.Exporter.exportSequenceFrame(sequence, time, outputPath, `${folderPath}/`, size.width, size.height);
      if (!ok) continue;
      for (let check = 0; check < 20 && !verifiedFile; check += 1) {
        try {
          const entry = await outputFolder.getEntry(filename);
          verifiedFile = !!(entry && entry.isFile);
        } catch (_) {}
        if (!verifiedFile) await new Promise(resolve => setTimeout(resolve, 100));
      }
    }
    if (!verifiedFile) throw new Error(`Premiere did not write visual sample ${sample.id} after ${exportAttempts} attempts.`);
    items.push({
      id: sample.id,
      time_seconds: Number(sample.time_seconds),
      label: sample.label === undefined ? null : String(sample.label),
      target: sample.target || null,
      filename,
      relative_path: `visual-samples/${commandId}/${filename}`,
      native_path: outputPath,
      verified_file: true,
      export_attempts: exportAttempts
    });
  }
  const manifest = {
    schema: "v5-visual-samples-0.1",
    generated_at: new Date().toISOString(),
    plugin_version: V5_BRIDGE_PLUGIN_VERSION,
    project: { name: String(project.name || ""), guid: projectGuid },
    sequence: { name: String(sequence.name || ""), guid: sequenceGuid },
    timeline_state_token: timeline.state_token,
    width: size.width,
    height: size.height,
    count: items.length,
    output_folder: folderPath,
    items
  };
  await bridgeWriteJson(outputFolder, "manifest.json", manifest);
  return manifest;
}

function bridgeSerializableEffectValue(value) {
  if (value == null || ["string", "number", "boolean"].includes(typeof value)) return value;
  try { return JSON.parse(JSON.stringify(value)); }
  catch (_) { return String(value); }
}

function bridgeSelectionToken(projectGuid, sequenceGuid, items) {
  const stable = items.map(item => ({
    track_number: item.track_number,
    name: item.name,
    timeline_start_seconds: Number(Number(item.timeline_start_seconds).toFixed(6)),
    timeline_end_seconds: Number(Number(item.timeline_end_seconds).toFixed(6)),
    disabled: item.disabled === true
  })).sort((a, b) => a.track_number - b.track_number || a.timeline_start_seconds - b.timeline_start_seconds || a.name.localeCompare(b.name));
  const text = JSON.stringify({ project_guid: projectGuid, sequence_guid: sequenceGuid, items: stable });
  let hash = 0x811c9dc5;
  for (let index = 0; index < text.length; index += 1) {
    hash ^= text.charCodeAt(index);
    hash = Math.imul(hash, 0x01000193) >>> 0;
  }
  return `v5sel1-${hash.toString(16).padStart(8, "0")}-${text.length}`;
}

async function inspectSelectedVideoEffects() {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  const matchNames = Array.from(await ppro.VideoFilterFactory.getMatchNames() || []);
  const displayNames = Array.from(await ppro.VideoFilterFactory.getDisplayNames() || []);
  const availableColorEffects = matchNames.map((match_name, index) => ({
    match_name: String(match_name),
    display_name: displayNames[index] == null ? null : String(displayNames[index])
  })).filter(item => /lumetri|color|colour/i.test(`${item.match_name} ${item.display_name || ""}`));
  const selectedItems = [];
  const videoTrackCount = Number(await sequence.getVideoTrackCount());
  for (let trackIndex = 0; trackIndex < videoTrackCount; trackIndex += 1) {
    const track = await sequence.getVideoTrack(trackIndex);
    const items = Array.from(await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false) || []);
    for (const item of items) {
      if (!(await item.getIsSelected())) continue;
      const described = await describeBridgeTimelineItem(item, "video", trackIndex);
      const chain = await item.getComponentChain();
      const componentCount = Number(await chain.getComponentCount());
      const components = [];
      const sampleTime = ppro.TickTime.createWithSeconds(Number(described.timeline_start_seconds || 0));
      for (let componentIndex = 0; componentIndex < componentCount; componentIndex += 1) {
        const component = await chain.getComponentAtIndex(componentIndex);
        const paramCount = Number(await component.getParamCount());
        const params = [];
        for (let paramIndex = 0; paramIndex < paramCount; paramIndex += 1) {
          const param = await component.getParam(paramIndex);
          let value = null;
          try { value = bridgeSerializableEffectValue(await param.getValueAtTime(sampleTime)); } catch (_) {}
          params.push({ index: paramIndex, display_name: String(param.displayName || ""), value });
        }
        components.push({ index: componentIndex, display_name: String(await component.getDisplayName()), match_name: String(await component.getMatchName()), params });
      }
      selectedItems.push({ track_number: trackIndex + 1, name: described.name, timeline_start_seconds: described.timeline_start_seconds, timeline_end_seconds: described.timeline_end_seconds, disabled: described.disabled, components });
    }
  }
  const projectGuid = bridgeGuidString(project.guid);
  const sequenceGuid = bridgeGuidString(sequence.guid);
  return { project_guid: projectGuid, sequence_guid: sequenceGuid, sequence_name: String(sequence.name || ""), selection_token: bridgeSelectionToken(projectGuid, sequenceGuid, selectedItems), selected_video_items: selectedItems, available_color_effects: availableColorEffects, timeline_changed: false };
}

async function inspectEnabledVideoLumetri() {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  const timeline = await getBridgeTimelineInventory(sequence);
  const parameterIndexes = [14, 15, 16, 19, 20, 21, 22, 23, 24, 41, 42];
  const items = [];
  const videoTrackCount = Number(await sequence.getVideoTrackCount());
  for (let trackIndex = 0; trackIndex < videoTrackCount; trackIndex += 1) {
    const track = await sequence.getVideoTrack(trackIndex);
    const trackItems = Array.from(await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false) || []);
    for (const item of trackItems) {
      if (await item.isDisabled()) continue;
      const described = await describeBridgeTimelineItem(item, "video", trackIndex);
      const chain = await item.getComponentChain();
      const componentCount = Number(await chain.getComponentCount());
      const lumetriComponents = [];
      for (let componentIndex = 0; componentIndex < componentCount; componentIndex += 1) {
        const component = await chain.getComponentAtIndex(componentIndex);
        if (String(await component.getMatchName()) !== "AE.ADBE Lumetri") continue;
        const values = {};
        const startTime = await item.getStartTime();
        for (const paramIndex of parameterIndexes) {
          const param = await component.getParam(paramIndex);
          values[String(paramIndex)] = bridgeEffectScalar(await param.getValueAtTime(startTime));
        }
        lumetriComponents.push({ component_index: componentIndex, values });
      }
      items.push({
        track_number: trackIndex + 1,
        name: described.name,
        project_item_id: described.project_item_id,
        timeline_start_seconds: described.timeline_start_seconds,
        timeline_end_seconds: described.timeline_end_seconds,
        lumetri_components: lumetriComponents
      });
    }
  }
  return {
    project_guid: bridgeGuidString(project.guid),
    sequence_guid: bridgeGuidString(sequence.guid),
    sequence_name: String(sequence.name || ""),
    timeline_state_token: timeline.state_token,
    enabled_video_items: items.length,
    items,
    timeline_changed: false
  };
}

async function ensureEnabledVideoLumetri(expectedTimelineStateToken) {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  const timeline = await getBridgeTimelineInventory(sequence);
  if (!expectedTimelineStateToken || timeline.state_token !== expectedTimelineStateToken) {
    throw new Error(`Timeline state changed since Lumetri inspection (expected ${expectedTimelineStateToken || "no token"}, current ${timeline.state_token}).`);
  }
  const missing = [];
  const videoTrackCount = Number(await sequence.getVideoTrackCount());
  for (let trackIndex = 0; trackIndex < videoTrackCount; trackIndex += 1) {
    const track = await sequence.getVideoTrack(trackIndex);
    const items = Array.from(await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false) || []);
    for (const item of items) {
      if (await item.isDisabled()) continue;
      const chain = await item.getComponentChain();
      const componentCount = Number(await chain.getComponentCount());
      let lumetriCount = 0;
      for (let componentIndex = 0; componentIndex < componentCount; componentIndex += 1) {
        const component = await chain.getComponentAtIndex(componentIndex);
        if (String(await component.getMatchName()) === "AE.ADBE Lumetri") lumetriCount += 1;
      }
      if (lumetriCount > 1) throw new Error(`Enabled clip ${await item.getName()} has multiple Lumetri effects; preparation refused.`);
      if (!lumetriCount) missing.push({ chain, component: await ppro.VideoFilterFactory.createComponent("AE.ADBE Lumetri"), track_number: trackIndex + 1, name: String(await item.getName()) });
    }
  }
  if (!missing.length) return { applied: false, already_ready: true, added: [], timeline_state_token: timeline.state_token, session_mutation_budget: getSessionMutationBudget() };
  assertSessionMutationBudget(1, "add missing Lumetri effects");
  let transactionOK = false;
  project.lockedAccess(() => {
    transactionOK = project.executeTransaction(compoundAction => {
      missing.forEach(target => compoundAction.addAction(target.chain.createAppendComponentAction(target.component)));
    }, `V5 add Lumetri to ${missing.length} enabled clips`);
  });
  if (!transactionOK) throw new Error("Premiere rejected the add-Lumetri transaction.");
  v5SessionMutations += 1;
  return { applied: true, already_ready: false, added: missing.map(target => ({ track_number: target.track_number, name: target.name })), previous_timeline_state_token: timeline.state_token, session_mutation_budget: getSessionMutationBudget() };
}

async function applyWarpStabilizerLastVideo(expectedTimelineStateToken, requestedTarget = null) {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  const timeline = await getBridgeTimelineInventory(sequence);
  if (!expectedTimelineStateToken || timeline.state_token !== expectedTimelineStateToken) throw new Error("Timeline changed since the Warp Stabilizer request was authored.");
  const candidates = [];
  const videoTrackCount = Number(await sequence.getVideoTrackCount());
  for (let trackIndex = 0; trackIndex < videoTrackCount; trackIndex += 1) {
    const track = await sequence.getVideoTrack(trackIndex);
    const items = Array.from(await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false) || []);
    for (const item of items) {
      if (await item.isDisabled()) continue;
      const described = await describeBridgeTimelineItem(item, "video", trackIndex);
      candidates.push({ item, described, track_number: trackIndex + 1 });
    }
  }
  if (!candidates.length) throw new Error("No enabled video clip is available.");
  let target = null;
  if (requestedTarget) {
    const matches = candidates.filter(entry => entry.track_number === Number(requestedTarget.track_number) && entry.described.name === String(requestedTarget.name || "") && Math.abs(Number(entry.described.timeline_start_seconds) - Number(requestedTarget.timeline_start_seconds)) < 0.0001 && Math.abs(Number(entry.described.timeline_end_seconds) - Number(requestedTarget.timeline_end_seconds)) < 0.0001);
    if (matches.length !== 1) throw new Error(`Expected one exact Warp Stabilizer target, found ${matches.length}; no mutation performed.`);
    target = matches[0];
  } else {
    candidates.sort((a, b) => Number(b.described.timeline_end_seconds) - Number(a.described.timeline_end_seconds) || Number(b.described.timeline_start_seconds) - Number(a.described.timeline_start_seconds));
    target = candidates[0];
    const tied = candidates.filter(item => Math.abs(Number(item.described.timeline_end_seconds) - Number(target.described.timeline_end_seconds)) < 0.0001);
    if (tied.length !== 1) throw new Error("The last video clip is ambiguous across tracks; Warp Stabilizer was not added.");
  }
  const chain = await target.item.getComponentChain();
  const componentCount = Number(await chain.getComponentCount());
  for (let index = 0; index < componentCount; index += 1) {
    const component = await chain.getComponentAtIndex(index);
    if (/warp.*stabili/i.test(`${await component.getMatchName()} ${await component.getDisplayName()}`)) throw new Error("The last clip already has Warp Stabilizer; duplicate effect refused.");
  }
  const matchNames = Array.from(await ppro.VideoFilterFactory.getMatchNames() || []);
  const displayNames = Array.from(await ppro.VideoFilterFactory.getDisplayNames() || []);
  const matches = matchNames.map((match_name, index) => ({ match_name: String(match_name), display_name: String(displayNames[index] || "") })).filter(item => /warp.*stabili/i.test(`${item.match_name} ${item.display_name}`));
  if (matches.length !== 1) throw new Error(`Expected one Warp Stabilizer effect, found ${matches.length}; no mutation performed.`);
  assertSessionMutationBudget(1, "add Warp Stabilizer to last clip");
  const factoryComponent = await ppro.VideoFilterFactory.createComponent(matches[0].match_name);
  let appendOK = false;
  project.lockedAccess(() => {
    appendOK = project.executeTransaction(compoundAction => {
      compoundAction.addAction(chain.createAppendComponentAction(factoryComponent));
    }, "V5 add Warp Stabilizer to last clip");
  });
  if (!appendOK) throw new Error("Premiere rejected the Warp Stabilizer attachment.");
  const appendedCount = Number(await chain.getComponentCount());
  const component = appendedCount === componentCount + 1 ? await chain.getComponentAtIndex(appendedCount - 1) : null;
  const rollback = () => {
    if (!component) return false;
    let rollbackOK = false;
    project.lockedAccess(() => {
      rollbackOK = project.executeTransaction(compoundAction => {
        compoundAction.addAction(chain.createRemoveComponentAction(component));
      }, "V5 remove unverified Warp Stabilizer");
    });
    return rollbackOK;
  };
  if (!component || !/warp.*stabili/i.test(`${component ? await component.getMatchName() : ""} ${component ? await component.getDisplayName() : ""}`)) {
    const removed = rollback();
    throw new Error(`Attached effect could not be verified as Warp Stabilizer; rollback ${removed ? "succeeded" : "failed"}.`);
  }
  const params = [];
  const paramCount = Number(await component.getParamCount());
  for (let index = 0; index < paramCount; index += 1) {
    const param = await component.getParam(index);
    if (/smoothness/i.test(String(param.displayName || ""))) params.push({ index, param, display_name: String(param.displayName || "") });
  }
  if (params.length !== 1) {
    const removed = rollback();
    throw new Error(`Expected one Warp Stabilizer Smoothness parameter, found ${params.length}; rollback ${removed ? "succeeded" : "failed"}.`);
  }
  let setOK = false;
  project.lockedAccess(() => {
    setOK = project.executeTransaction(compoundAction => {
      compoundAction.addAction(params[0].param.createSetValueAction(params[0].param.createKeyframe(1), true));
    }, "V5 set Warp Stabilizer Smoothness to 1%");
  });
  if (!setOK) {
    const removed = rollback();
    throw new Error(`Premiere rejected Warp Stabilizer Smoothness; rollback ${removed ? "succeeded" : "failed"}.`);
  }
  const verifiedValue = bridgeEffectScalar(await params[0].param.getValueAtTime(await target.item.getStartTime()));
  if (!Number.isFinite(verifiedValue) || Math.abs(verifiedValue - 1) > 0.0001) {
    const removed = rollback();
    throw new Error(`Warp Stabilizer Smoothness verification returned ${verifiedValue}; rollback ${removed ? "succeeded" : "failed"}.`);
  }
  v5SessionMutations += 1;
  return { applied: true, verified: true, track_number: target.track_number, name: target.described.name, timeline_start_seconds: target.described.timeline_start_seconds, timeline_end_seconds: target.described.timeline_end_seconds, effect_match_name: matches[0].match_name, effect_display_name: matches[0].display_name, smoothness_parameter_index: params[0].index, smoothness_percent: verifiedValue, previous_timeline_state_token: timeline.state_token, undo_labels: ["V5 add Warp Stabilizer to last clip", "V5 set Warp Stabilizer Smoothness to 1%"], session_mutation_budget: getSessionMutationBudget() };
}

async function applyLumetriGradePlan(plan) {
  if (!plan || plan.schema !== "v5-lumetri-grade-plan-0.1") throw new Error("Unsupported or missing Lumetri grade-plan schema.");
  for (const field of ["request_id", "project_guid", "sequence_guid", "expected_timeline_state_token"]) {
    if (typeof plan[field] !== "string" || !plan[field]) throw new Error(`Lumetri grade plan requires ${field}.`);
  }
  if (!Array.isArray(plan.grades) || !plan.grades.length || plan.grades.length > 250) throw new Error("Lumetri grade plan requires 1 to 250 grades.");
  const allowedIndexes = new Set([14, 15, 16, 19, 20, 21, 22, 23, 24, 41, 42]);
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  if (bridgeGuidString(project.guid) !== plan.project_guid) throw new Error("Lumetri grade-plan project GUID does not match the active project.");
  if (bridgeGuidString(sequence.guid) !== plan.sequence_guid) throw new Error("Lumetri grade-plan sequence GUID does not match the active sequence.");
  const timeline = await getBridgeTimelineInventory(sequence);
  if (timeline.state_token !== plan.expected_timeline_state_token) throw new Error(`Timeline changed since grade analysis (expected ${plan.expected_timeline_state_token}, current ${timeline.state_token}).`);
  const targets = [];
  const targetKeys = new Set();
  for (let gradeIndex = 0; gradeIndex < plan.grades.length; gradeIndex += 1) {
    const grade = plan.grades[gradeIndex];
    const trackNumber = Number(grade.track_number);
    const start = Number(grade.timeline_start_seconds);
    const end = Number(grade.timeline_end_seconds);
    if (!Number.isInteger(trackNumber) || trackNumber < 1 || !Number.isFinite(start) || !Number.isFinite(end) || end <= start || typeof grade.name !== "string" || !grade.name) throw new Error(`Lumetri grade ${gradeIndex + 1} has an invalid target.`);
    const key = `${trackNumber}|${start.toFixed(6)}|${end.toFixed(6)}|${grade.name}`;
    if (targetKeys.has(key)) throw new Error(`Lumetri grade plan contains duplicate target ${key}.`);
    targetKeys.add(key);
    if (!grade.expected_values || !grade.values) throw new Error(`Lumetri grade ${gradeIndex + 1} requires expected_values and values.`);
    const indexes = Object.keys(grade.values).map(Number);
    if (!indexes.length || indexes.some(index => !allowedIndexes.has(index) || !Object.prototype.hasOwnProperty.call(grade.expected_values, String(index)))) throw new Error(`Lumetri grade ${gradeIndex + 1} contains unsupported or unguarded parameters.`);
    const track = await sequence.getVideoTrack(trackNumber - 1);
    if (!track) throw new Error(`Lumetri grade ${gradeIndex + 1} video track ${trackNumber} is unavailable.`);
    const items = Array.from(await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false) || []);
    let item = null;
    for (const candidate of items) {
      const candidateStart = bridgeSeconds(await candidate.getStartTime());
      const candidateEnd = bridgeSeconds(await candidate.getEndTime());
      if (String(await candidate.getName()) === grade.name && Math.abs(candidateStart - start) < 0.0001 && Math.abs(candidateEnd - end) < 0.0001) { item = candidate; break; }
    }
    if (!item) throw new Error(`Lumetri grade target not found: V${trackNumber} ${grade.name} at ${start.toFixed(3)}s.`);
    if (await item.isDisabled()) throw new Error(`Lumetri grade target is disabled: V${trackNumber} ${grade.name}.`);
    const chain = await item.getComponentChain();
    const componentCount = Number(await chain.getComponentCount());
    let lumetri = null;
    for (let componentIndex = 0; componentIndex < componentCount; componentIndex += 1) {
      const component = await chain.getComponentAtIndex(componentIndex);
      if (String(await component.getMatchName()) === "AE.ADBE Lumetri") {
        if (lumetri) throw new Error(`Lumetri grade target has duplicate Lumetri effects: ${grade.name}.`);
        lumetri = component;
      }
    }
    if (!lumetri) throw new Error(`Lumetri grade target has no Lumetri effect: ${grade.name}.`);
    const startTime = await item.getStartTime();
    const parameters = [];
    for (const index of indexes) {
      const expected = Number(grade.expected_values[String(index)]);
      const value = Number(grade.values[String(index)]);
      if (!Number.isFinite(expected) || !Number.isFinite(value)) throw new Error(`Lumetri grade ${gradeIndex + 1} parameter ${index} must be finite.`);
      const param = await lumetri.getParam(index);
      const current = bridgeEffectScalar(await param.getValueAtTime(startTime));
      if (!Number.isFinite(current) || Math.abs(current - expected) > 0.0001) throw new Error(`Lumetri grade target ${grade.name} parameter ${index} changed since analysis (expected ${expected}, current ${current}).`);
      parameters.push({ index, param, value, before: current });
    }
    targets.push({ track_number: trackNumber, name: grade.name, timeline_start_seconds: start, timeline_end_seconds: end, parameters, analysis: grade.analysis || null });
  }
  assertSessionMutationBudget(1, "individualized Lumetri grade plan");
  let transactionOK = false;
  project.lockedAccess(() => {
    transactionOK = project.executeTransaction(compoundAction => {
      targets.forEach(target => target.parameters.forEach(parameter => compoundAction.addAction(parameter.param.createSetValueAction(parameter.param.createKeyframe(parameter.value), true))));
    }, `V5 individualized Lumetri grade (${targets.length} clips)`);
  });
  if (!transactionOK) throw new Error("Premiere rejected the individualized Lumetri grade transaction.");
  v5SessionMutations += 1;
  return {
    applied: true,
    request_id: plan.request_id,
    method: plan.method || null,
    clips: targets.map(target => ({ track_number: target.track_number, name: target.name, timeline_start_seconds: target.timeline_start_seconds, timeline_end_seconds: target.timeline_end_seconds, values: target.parameters.reduce((values, parameter) => { values[String(parameter.index)] = parameter.value; return values; }, {}), analysis: target.analysis })),
    undo_label: `V5 individualized Lumetri grade (${targets.length} clips)`,
    session_mutation_budget: getSessionMutationBudget()
  };
}

function bridgeEffectScalar(value) {
  if (value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, "value")) return Number(value.value);
  return Number(value);
}

async function applyCleanNaturalGradeSelected(revert = false, expectedSelectionToken = null) {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active sequence.");
  const presetSettings = [
    { index: 16, name: "Saturation", neutral: 100, graded: 102 },
    { index: 20, name: "Contrast", neutral: 0, graded: 8 },
    { index: 21, name: "Highlights", neutral: 0, graded: -12 },
    { index: 22, name: "Shadows", neutral: 0, graded: 8 },
    { index: 23, name: "Whites", neutral: 0, graded: 3 },
    { index: 24, name: "Blacks", neutral: 0, graded: -3 },
    { index: 41, name: "Sharpen", neutral: 0, graded: 5 },
    { index: 42, name: "Vibrance", neutral: 0, graded: 8 }
  ];
  const settings = presetSettings.map(setting => ({ ...setting, expected: revert ? setting.graded : setting.neutral, value: revert ? setting.neutral : setting.graded }));
  const targets = [];
  const videoTrackCount = Number(await sequence.getVideoTrackCount());
  for (let trackIndex = 0; trackIndex < videoTrackCount; trackIndex += 1) {
    const track = await sequence.getVideoTrack(trackIndex);
    const items = Array.from(await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false) || []);
    for (const item of items) {
      if (!(await item.getIsSelected())) continue;
      if (await item.isDisabled()) throw new Error(`Selected clip ${await item.getName()} is disabled; enable it or deselect it before grading.`);
      const chain = await item.getComponentChain();
      const componentCount = Number(await chain.getComponentCount());
      let lumetri = null;
      for (let componentIndex = 0; componentIndex < componentCount; componentIndex += 1) {
        const component = await chain.getComponentAtIndex(componentIndex);
        if (String(await component.getMatchName()) === "AE.ADBE Lumetri") {
          if (lumetri) throw new Error(`Selected clip ${await item.getName()} has multiple Lumetri effects; grade manually to avoid changing the wrong instance.`);
          lumetri = component;
        }
      }
      if (!lumetri) throw new Error(`Selected clip ${await item.getName()} has no Lumetri Color effect.`);
      const startTime = await item.getStartTime();
      const parameters = [];
      const before = {};
      for (const setting of settings) {
        const param = await lumetri.getParam(setting.index);
        const current = bridgeEffectScalar(await param.getValueAtTime(startTime));
        if (!Number.isFinite(current) || Math.abs(current - setting.expected) > 0.0001) {
          throw new Error(`Selected clip ${await item.getName()} has ${setting.name}=${current}, expected ${setting.expected}; clean-natural ${revert ? "revert" : "grade"} was not applied.`);
        }
        before[setting.name] = current;
        parameters.push({ param, setting });
      }
      targets.push({ item, track_number: trackIndex + 1, name: String(await item.getName()), timeline_start_seconds: bridgeSeconds(startTime), timeline_end_seconds: bridgeSeconds(await item.getEndTime()), parameters, before });
    }
  }
  if (!targets.length) throw new Error("No selected video clips. Select one or more enabled video clips and try again.");
  const selectionItems = targets.map(target => ({ track_number: target.track_number, name: target.name, timeline_start_seconds: target.timeline_start_seconds, timeline_end_seconds: target.timeline_end_seconds, disabled: false }));
  const selectionToken = bridgeSelectionToken(bridgeGuidString(project.guid), bridgeGuidString(sequence.guid), selectionItems);
  if (!expectedSelectionToken || selectionToken !== expectedSelectionToken) {
    throw new Error(`Selected clips changed since inspection (expected ${expectedSelectionToken || "no token"}, current ${selectionToken}); inspect again before grading.`);
  }
  assertSessionMutationBudget(1, revert ? "clean-natural color revert" : "clean-natural color grade");
  const undoLabel = `V5 ${revert ? "revert" : "apply"} clean natural grade (${targets.length} clips)`;
  let transactionOK = false;
  project.lockedAccess(() => {
    transactionOK = project.executeTransaction(compoundAction => {
      targets.forEach(target => target.parameters.forEach(({ param, setting }) => {
        compoundAction.addAction(param.createSetValueAction(param.createKeyframe(setting.value), true));
      }));
    }, undoLabel);
  });
  if (!transactionOK) throw new Error(`Premiere rejected the clean-natural color ${revert ? "revert" : "grade"} transaction.`);
  v5SessionMutations += 1;
  return {
    applied: !revert,
    reverted: revert,
    preset: "clean_natural_v1",
    selection_token: selectionToken,
    clips: targets.map(target => ({ track_number: target.track_number, name: target.name, before: target.before })),
    values: settings.reduce((result, setting) => { result[setting.name] = setting.value; return result; }, {}),
    undo_label: undoLabel,
    session_mutation_budget: getSessionMutationBudget()
  };
}

async function applyBestClipsAssembly(plan, options = {}) {
  if (!plan || !Array.isArray(plan.keep_link_keys) || !plan.keep_link_keys.length) throw new Error("apply_best_clips_assembly requires keep_link_keys.");
  if (typeof plan.request_id !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(plan.request_id)) throw new Error("Best-clips plan requires a valid unique request_id.");
  const receiptFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-receipts", true);
  const intentFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-intents", true);
  const existingReceipt = await bridgeReadJson(receiptFolder, `${encodeURIComponent(plan.request_id)}.json`);
  if (existingReceipt) throw new Error(`Best-clips request_id ${plan.request_id} already completed; duplicate application refused.`);
  const existingIntent = await bridgeReadJson(intentFolder, `${encodeURIComponent(plan.request_id)}.json`);
  if (existingIntent) throw new Error(`Best-clips request_id ${plan.request_id} has an existing ${existingIntent.status || "uncertain"} intent; retry refused until its status is resolved.`);
  const historyHealth = await auditBestClipsHistory();
  if (!historyHealth.safe_to_start_new) {
    throw new Error(`Best-clips history is not safe for a new assembly: ${historyHealth.issues.join("; ") || "unresolved history requires attention"}.`);
  }
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active Premiere sequence.");
  const protectedSequences = await getProtectedSequences();
  if (protectedSequences.items.some(item => String(item.sequence_guid || "") === bridgeGuidString(sequence.guid))) {
    throw new Error(`Best-clips mutation refused: active sequence ${String(sequence.name || "")} is protected.`);
  }
  if (bridgeGuidString(project.guid) !== String(plan.project_guid || "")) throw new Error("Best-clips plan targets a different project.");
  if (bridgeGuidString(sequence.guid) !== String(plan.sequence_guid || "")) throw new Error("Best-clips plan targets a different sequence.");
  const before = await getBridgeTimelineInventory(sequence);
  if (before.state_token !== String(plan.expected_timeline_state_token || "")) throw new Error("Timeline changed since the best-clips plan was authored.");
  const keepOrder = plan.keep_link_keys.map(String);
  if (new Set(keepOrder).size !== keepOrder.length) throw new Error("keep_link_keys contains a duplicate.");
  const keepSet = new Set(keepOrder);
  const live = [];
  for (let trackIndex = 0; trackIndex < before.video_track_count; trackIndex++) {
    for (const item of await getVideoTrackItems(sequence, trackIndex)) live.push({ item, description: await describeBridgeTimelineItem(item, "video", trackIndex) });
  }
  for (let trackIndex = 0; trackIndex < before.audio_track_count; trackIndex++) {
    for (const item of await getAudioTrackItems(sequence, trackIndex)) live.push({ item, description: await describeBridgeTimelineItem(item, "audio", trackIndex) });
  }
  const foundKeys = new Set(live.map(entry => String(entry.description.link_key || "")));
  const missing = keepOrder.filter(key => !foundKeys.has(key));
  if (missing.length) throw new Error(`Best-clips plan contains ${missing.length} missing link key(s).`);
  const chronologicalKeepOrder = live
    .filter(entry => entry.description.kind === "video" && keepSet.has(String(entry.description.link_key || "")))
    .sort((left, right) => Number(left.description.timeline_start_seconds) - Number(right.description.timeline_start_seconds))
    .map(entry => String(entry.description.link_key || ""));
  const allowReorder = options.allow_reorder === true;
  if (!allowReorder && (chronologicalKeepOrder.length !== keepOrder.length || chronologicalKeepOrder.some((key, index) => key !== keepOrder[index]))) {
    throw new Error("Unsafe reorder refused before mutation. Best-clips assembly 0.4.63 only compacts a chronological shortlist; use a dedicated reorder workflow for shot-order changes.");
  }
  const kept = live.filter(entry => keepSet.has(String(entry.description.link_key || "")));
  const removed = live.filter(entry => !keepSet.has(String(entry.description.link_key || "")));
  if (allowReorder) {
    const liveVideoKeys = live.filter(entry => entry.description.kind === "video").map(entry => String(entry.description.link_key || ""));
    if (removed.length || keepOrder.length !== liveVideoKeys.length || liveVideoKeys.some(key => !keepSet.has(key))) {
      throw new Error("Linked reorder requires every live video/audio group exactly once; use best-clips assembly for removals.");
    }
  }
  const videoByKey = new Map();
  kept.filter(entry => entry.description.kind === "video").forEach(entry => videoByKey.set(String(entry.description.link_key), entry.description));
  let cursor = 0;
  const destinations = new Map();
  for (const key of keepOrder) {
    const video = videoByKey.get(key);
    if (!video) throw new Error(`Kept link key ${key} has no video component.`);
    const duration = Number(video.timeline_end_seconds) - Number(video.timeline_start_seconds);
    if (!(duration > 0)) throw new Error(`Kept link key ${key} has invalid duration.`);
    destinations.set(key, cursor);
    cursor += duration;
  }
  if (options.preflight_only === true) {
    return {
      accepted: true,
      timeline_changed: false,
      request_id: plan.request_id,
      request_id_available: true,
      history_gate_passed: true,
      chronological_order_verified: !allowReorder,
      complete_linked_reorder_verified: allowReorder,
      predicted_kept_shots: keepOrder.length,
      predicted_removed_items: removed.length,
      predicted_duration_seconds: cursor,
      timeline_state_token: before.state_token,
      session_mutation_budget: getSessionMutationBudget()
    };
  }
  const intent = { schema: "v5-best-clips-intent-0.1", request_id: plan.request_id, plugin_version: V5_BRIDGE_PLUGIN_VERSION, status: "started", started_at: new Date().toISOString(), project_guid: bridgeGuidString(project.guid), sequence_guid: bridgeGuidString(sequence.guid), sequence_name: String(sequence.name || ""), expected_timeline_state_token: before.state_token, keep_link_keys: keepOrder, predicted_removed_items: removed.length, predicted_duration_seconds: cursor };
  await bridgeWriteJson(intentFolder, `${encodeURIComponent(plan.request_id)}.json`, intent);
  assertSessionMutationBudget(1, "best-clips assembly");
  await sequence.clearSelection();
  const selection = await sequence.getSelection();
  removed.forEach(entry => {
    if (!selection.addItem(entry.item, true)) throw new Error(`Could not select rejected item ${entry.description.name || "unnamed"}.`);
  });
  const editor = ppro.SequenceEditor.getEditor(sequence);
  let transactionOK = false;
  try {
    project.lockedAccess(() => {
      transactionOK = project.executeTransaction(compoundAction => {
        if (removed.length) compoundAction.addAction(editor.createRemoveItemsAction(selection, false, ppro.Constants.MediaType.VIDEO, false));
        kept.forEach(entry => {
          const delta = destinations.get(String(entry.description.link_key)) - Number(entry.description.timeline_start_seconds);
          if (Math.abs(delta) > V5_REVISION_TIME_TOLERANCE) compoundAction.addAction(entry.item.createMoveAction(ppro.TickTime.createWithSeconds(delta)));
        });
      }, allowReorder ? `V5 reorder linked shots (${keepOrder.length} shots)` : `V5 assemble best clips (${keepOrder.length} shots)`);
    });
  } finally {
    try { await sequence.clearSelection(); } catch (_) {}
  }
  if (!transactionOK) throw new Error("Premiere rejected the best-clips assembly transaction.");
  v5SessionMutations += 1;
  if (allowReorder) {
    const placementErrors = [];
    for (const entry of kept) {
      const current = await describeBridgeTimelineItem(entry.item, entry.description.kind, entry.description.track_index);
      const expectedStart = destinations.get(String(entry.description.link_key));
      if (Math.abs(Number(current.timeline_start_seconds) - Number(expectedStart)) > 0.12) {
        placementErrors.push({ kind: entry.description.kind, track_index: entry.description.track_index, expected_start: expectedStart, actual_start: current.timeline_start_seconds });
      }
    }
    if (placementErrors.length) throw new Error(`Linked reorder verification found ${placementErrors.length} misplaced item(s). Use Premiere Undo once.`);
  }
  const after = await getBridgeTimelineInventory(sequence);
  const survivingVideo = after.video_tracks.flatMap(track => track.items || []).sort((left, right) => Number(left.timeline_start_seconds) - Number(right.timeline_start_seconds));
  const actualVideoEnd = Math.max(0, ...survivingVideo.map(item => Number(item.timeline_end_seconds) || 0));
  if (Math.abs(actualVideoEnd - cursor) > 0.12) throw new Error(`Assembly verification found video end ${actualVideoEnd.toFixed(3)}s instead of ${cursor.toFixed(3)}s. Use Premiere Undo once.`);
  const continuityGaps = [];
  survivingVideo.forEach((item, index) => {
    const expectedStart = index === 0 ? 0 : Number(survivingVideo[index - 1].timeline_end_seconds);
    const actualStart = Number(item.timeline_start_seconds);
    if (Math.abs(actualStart - expectedStart) > 0.12) continuityGaps.push({ index, expected_start: expectedStart, actual_start: actualStart });
  });
  if (continuityGaps.length) throw new Error(`Assembly continuity verification found ${continuityGaps.length} internal video gap(s). Use Premiere Undo once.`);
  const audioContinuityErrors = [];
  after.audio_tracks.forEach((track, trackIndex) => {
    const items = (track.items || []).slice().sort((left, right) => Number(left.timeline_start_seconds) - Number(right.timeline_start_seconds));
    items.forEach((item, index) => {
      const expectedStart = index === 0 ? 0 : Number(items[index - 1].timeline_end_seconds);
      const actualStart = Number(item.timeline_start_seconds);
      if (Math.abs(actualStart - expectedStart) > 0.12) audioContinuityErrors.push({ track_index: trackIndex, index, expected_start: expectedStart, actual_start: actualStart });
    });
  });
  if (audioContinuityErrors.length) throw new Error(`Assembly continuity verification found ${audioContinuityErrors.length} internal audio gap/overlap(s). Use Premiere Undo once.`);
  const actualAudioItems = after.audio_tracks.flatMap(track => track.items || []);
  const actualAudioEnd = Math.max(0, ...actualAudioItems.map(item => Number(item.timeline_end_seconds) || 0));
  if (actualAudioItems.length > 0 && Math.abs(actualVideoEnd - actualAudioEnd) > 0.12) throw new Error(`Assembly A/V verification found video end ${actualVideoEnd.toFixed(3)}s and audio end ${actualAudioEnd.toFixed(3)}s. Use Premiere Undo once.`);
  const receipt = { schema: "v5-best-clips-receipt-0.1", receipt_id: `best-clips-${Date.now()}`, request_id: plan.request_id, plugin_version: V5_BRIDGE_PLUGIN_VERSION, completed_at: new Date().toISOString(), project_guid: bridgeGuidString(project.guid), sequence_guid: bridgeGuidString(sequence.guid), sequence_name: String(sequence.name || ""), applied: true, verified: true, identity_order_verified: allowReorder, continuity_verified: true, av_ends_aligned: true, internal_video_gaps: 0, internal_audio_gaps_or_overlaps: 0, kept_shots: keepOrder.length, kept_link_keys: keepOrder, removed_items: removed.length, duration_seconds: actualVideoEnd, timeline_state_token_before: before.state_token, timeline_state_token_after: after.state_token, undo_label: allowReorder ? `V5 reorder linked shots (${keepOrder.length} shots)` : `V5 assemble best clips (${keepOrder.length} shots)`, session_mutation_budget: getSessionMutationBudget() };
  await bridgeWriteJson(v5BridgeStateFolder, "last-best-clips-receipt.json", receipt);
  await bridgeWriteJson(receiptFolder, `${encodeURIComponent(plan.request_id)}.json`, receipt);
  await bridgeWriteJson(intentFolder, `${encodeURIComponent(plan.request_id)}.json`, { ...intent, status: "completed", completed_at: receipt.completed_at, receipt_id: receipt.receipt_id });
  return receipt;
}

async function getProtectedSequences() {
  const document = await bridgeReadJson(v5BridgeStateFolder, "protected-sequences.json");
  return document && Array.isArray(document.items) ? document : { schema: "v5-protected-sequences-0.1", items: [] };
}

async function getSequenceProtectionHistory() {
  const document = await bridgeReadJson(v5BridgeStateFolder, "sequence-protection-history.json");
  return document && Array.isArray(document.events) ? document : { schema: "v5-sequence-protection-history-0.1", events: [] };
}

async function auditSequenceProtection() {
  const protectedSequences = await getProtectedSequences();
  const history = await getSequenceProtectionHistory();
  const issues = [];
  const eventIds = new Set();
  const latestBySequence = new Map();
  history.events.forEach(event => {
    if (!event.event_id || eventIds.has(event.event_id)) issues.push(`Duplicate or missing protection event ID: ${event.event_id || "missing"}`);
    else eventIds.add(event.event_id);
    if (!event.sequence_guid || !["protect", "unprotect"].includes(event.action)) issues.push(`Malformed protection event: ${event.event_id || "unknown"}`);
    if (event.sequence_guid) latestBySequence.set(String(event.sequence_guid), event);
  });
  latestBySequence.forEach((event, sequenceGuid) => {
    const currentlyProtected = protectedSequences.items.some(item => String(item.sequence_guid || "") === sequenceGuid);
    if ((event.action === "protect") !== currentlyProtected) issues.push(`Protection state does not match latest event for sequence ${sequenceGuid}.`);
  });
  protectedSequences.items.forEach(item => {
    if (!latestBySequence.has(String(item.sequence_guid || ""))) issues.push(`Protected sequence ${item.sequence_guid || "unknown"} has no protection-history event.`);
  });
  return { status: issues.length ? "attention" : "healthy", protected_sequences: protectedSequences.items.length, events: history.events.length, issues, timeline_changed: false };
}

async function setActiveSequenceProtection(protectedState, expectedTimelineStateToken = null) {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active Premiere sequence.");
  if (!protectedState) {
    const timeline = await getBridgeTimelineInventory(sequence);
    if (!expectedTimelineStateToken || String(expectedTimelineStateToken) !== timeline.state_token) throw new Error("Unprotect refused: provide the current exact timeline-state token from revision_context.");
  }
  const document = await getProtectedSequences();
  const projectGuid = bridgeGuidString(project.guid);
  const sequenceGuid = bridgeGuidString(sequence.guid);
  const alreadyProtected = document.items.some(item => String(item.sequence_guid || "") === sequenceGuid);
  if (protectedState === alreadyProtected) {
    return { protected: protectedState, already_in_requested_state: true, project_guid: projectGuid, sequence_guid: sequenceGuid, sequence_name: String(sequence.name || ""), protected_sequences: document.items.length, protection_event: null, timeline_changed: false };
  }
  const remaining = document.items.filter(item => String(item.sequence_guid || "") !== sequenceGuid);
  if (protectedState) remaining.push({ project_guid: projectGuid, sequence_guid: sequenceGuid, sequence_name: String(sequence.name || ""), protected_at: new Date().toISOString() });
  const updated = { schema: "v5-protected-sequences-0.1", updated_at: new Date().toISOString(), items: remaining };
  await bridgeWriteJson(v5BridgeStateFolder, "protected-sequences.json", updated);
  const history = await getSequenceProtectionHistory();
  const event = { event_id: `protection-${Date.now()}`, action: protectedState ? "protect" : "unprotect", occurred_at: new Date().toISOString(), project_guid: projectGuid, sequence_guid: sequenceGuid, sequence_name: String(sequence.name || ""), expected_timeline_state_token: protectedState ? null : expectedTimelineStateToken };
  const events = [...history.events, event].slice(-200);
  await bridgeWriteJson(v5BridgeStateFolder, "sequence-protection-history.json", { schema: "v5-sequence-protection-history-0.1", updated_at: event.occurred_at, events });
  return { protected: protectedState, project_guid: projectGuid, sequence_guid: sequenceGuid, sequence_name: String(sequence.name || ""), protected_sequences: remaining.length, protection_event: event, timeline_changed: false };
}

async function assertActiveSequenceMutationAllowed(operation) {
  const project = await ppro.Project.getActiveProject();
  const sequence = project ? await project.getActiveSequence() : null;
  if (!sequence) throw new Error(`${operation} requires an active Premiere sequence.`);
  const protectionHealth = await auditSequenceProtection();
  if (protectionHealth.status !== "healthy") throw new Error(`${operation} refused: sequence-protection history requires attention (${protectionHealth.issues.join("; ") || "unknown integrity issue"}).`);
  const protectedSequences = await getProtectedSequences();
  if (protectedSequences.items.some(item => String(item.sequence_guid || "") === bridgeGuidString(sequence.guid))) {
    throw new Error(`${operation} refused: active sequence ${String(sequence.name || "")} is protected.`);
  }
}

async function validateTimelineContinuity() {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active Premiere sequence.");
  const timeline = await getBridgeTimelineInventory(sequence);
  const auditKind = (tracks) => {
    const gaps = [];
    const overlaps = [];
    const allItems = tracks.flatMap(track => track.items || []);
    tracks.forEach((track, trackIndex) => {
      const items = (track.items || []).slice().sort((left, right) => Number(left.timeline_start_seconds) - Number(right.timeline_start_seconds));
      items.forEach((item, index) => {
        const expected = index === 0 ? 0 : Number(items[index - 1].timeline_end_seconds);
        const actual = Number(item.timeline_start_seconds);
        const delta = actual - expected;
        if (delta > 0.12) gaps.push({ track_index: trackIndex, after_index: index, start_seconds: expected, end_seconds: actual, duration_seconds: delta });
        if (delta < -0.12) overlaps.push({ track_index: trackIndex, at_index: index, previous_end_seconds: expected, next_start_seconds: actual, duration_seconds: -delta });
      });
    });
    return { items: allItems.length, end_seconds: Math.max(0, ...allItems.map(item => Number(item.timeline_end_seconds) || 0)), gaps, overlaps };
  };
  const video = auditKind(timeline.video_tracks);
  const audio = auditKind(timeline.audio_tracks);
  const avAlignmentApplicable = video.items > 0 && audio.items > 0;
  return {
    timeline_changed: false,
    project: { name: String(project.name || ""), guid: bridgeGuidString(project.guid) },
    sequence: { name: String(sequence.name || ""), guid: bridgeGuidString(sequence.guid) },
    timeline_state_token: timeline.state_token,
    continuous: video.gaps.length === 0 && video.overlaps.length === 0,
    av_alignment_applicable: avAlignmentApplicable,
    av_ends_aligned: !avAlignmentApplicable || Math.abs(video.end_seconds - audio.end_seconds) <= 0.12,
    video,
    audio,
    warnings: timeline.warnings
  };
}

async function deleteAllAudio(expectedTimelineStateToken) {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("No active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("No active Premiere sequence.");
  const before = await getBridgeTimelineInventory(sequence);
  if (!expectedTimelineStateToken || before.state_token !== expectedTimelineStateToken) throw new Error("Timeline changed since the delete-all-audio request was authored.");
  const audioItems = [];
  for (let trackIndex = 0; trackIndex < before.audio_track_count; trackIndex++) {
    const track = await sequence.getAudioTrack(trackIndex);
    for (const item of Array.from(await track.getTrackItems(ppro.Constants.TrackItemType.CLIP, false) || [])) audioItems.push(item);
  }
  if (!audioItems.length) return { applied: false, already_empty: true, removed_audio_items: 0, timeline_state_token: before.state_token, timeline_changed: false };
  const videoIdentity = before.video_tracks.flatMap(track => track.items || []).map(item => `${item.track_index}|${item.project_item_id || item.media_path || item.name}|${Number(item.timeline_start_seconds).toFixed(6)}|${Number(item.timeline_end_seconds).toFixed(6)}`).sort();
  await sequence.clearSelection();
  const selection = await sequence.getSelection();
  if (!selection) throw new Error("Premiere did not provide a track-item selection.");
  for (const item of audioItems) if (!selection.addItem(item, true)) throw new Error("Could not select every audio item; no deletion was attempted.");
  assertSessionMutationBudget(1, "delete all audio");
  const editor = ppro.SequenceEditor.getEditor(sequence);
  let ok = false;
  try {
    project.lockedAccess(() => {
      const action = editor.createRemoveItemsAction(selection, false, ppro.Constants.MediaType.AUDIO, false);
      ok = project.executeTransaction(compoundAction => compoundAction.addAction(action), `V5 delete all audio (${audioItems.length} items)`);
    });
  } finally {
    try { await sequence.clearSelection(); } catch (_) {}
  }
  if (!ok) throw new Error("Premiere rejected the delete-all-audio transaction.");
  const after = await getBridgeTimelineInventory(sequence);
  const remainingAudio = after.audio_tracks.flatMap(track => track.items || []).length;
  const afterVideoIdentity = after.video_tracks.flatMap(track => track.items || []).map(item => `${item.track_index}|${item.project_item_id || item.media_path || item.name}|${Number(item.timeline_start_seconds).toFixed(6)}|${Number(item.timeline_end_seconds).toFixed(6)}`).sort();
  if (remainingAudio !== 0 || JSON.stringify(videoIdentity) !== JSON.stringify(afterVideoIdentity)) throw new Error(`Delete-all-audio verification failed (remaining audio ${remainingAudio}, video unchanged ${JSON.stringify(videoIdentity) === JSON.stringify(afterVideoIdentity)}). Use Premiere Undo once.`);
  v5SessionMutations += 1;
  return { applied: true, verified: true, removed_audio_items: audioItems.length, remaining_audio_items: 0, video_items_unchanged: afterVideoIdentity.length, timeline_state_token_before: before.state_token, timeline_state_token_after: after.state_token, undo_label: `V5 delete all audio (${audioItems.length} items)`, session_mutation_budget: getSessionMutationBudget() };
}

async function getBestClipsRequestStatus(requestId) {
  if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) throw new Error("best_clips_request_status requires a valid request_id.");
  const receiptFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-receipts", true);
  const intentFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-intents", true);
  const receipt = await bridgeReadJson(receiptFolder, `${encodeURIComponent(requestId)}.json`);
  const intent = await bridgeReadJson(intentFolder, `${encodeURIComponent(requestId)}.json`);
  const status = receipt && receipt.externally_undone === true ? "undone" : receipt ? "completed" : intent && intent.status === "cleared_no_mutation" ? "cleared_no_mutation" : intent ? "uncertain" : "available";
  return {
    request_id: requestId,
    status,
    safe_to_submit: status === "available",
    intent: intent || null,
    receipt: receipt || null,
    timeline_changed: false
  };
}

async function resolveBestClipsIntentNoChange(requestId) {
  if (typeof requestId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(requestId)) throw new Error("resolve_best_clips_intent_no_change requires a valid request_id.");
  const intentFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-intents", true);
  const receiptFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-receipts", true);
  const intent = await bridgeReadJson(intentFolder, `${encodeURIComponent(requestId)}.json`);
  if (!intent) throw new Error(`No best-clips intent exists for ${requestId}.`);
  const receipt = await bridgeReadJson(receiptFolder, `${encodeURIComponent(requestId)}.json`);
  if (receipt) return { request_id: requestId, status: "completed", resolved: false, timeline_changed: false, receipt_id: receipt.receipt_id };
  if (intent.status !== "started") throw new Error(`Best-clips intent ${requestId} has status ${intent.status || "unknown"}; no-change resolution is not applicable.`);
  const project = await ppro.Project.getActiveProject();
  if (!project || bridgeGuidString(project.guid) !== String(intent.project_guid || "")) throw new Error("Active project does not match the uncertain intent; resolution refused.");
  const sequence = await project.getActiveSequence();
  if (!sequence || bridgeGuidString(sequence.guid) !== String(intent.sequence_guid || "")) throw new Error("Active sequence does not match the uncertain intent; resolution refused.");
  const timeline = await getBridgeTimelineInventory(sequence);
  if (timeline.state_token !== String(intent.expected_timeline_state_token || "")) throw new Error("Timeline differs from the uncertain intent's pre-mutation token; no-change resolution refused.");
  const resolvedIntent = { ...intent, status: "cleared_no_mutation", resolved_at: new Date().toISOString(), resolution_timeline_state_token: timeline.state_token };
  await bridgeWriteJson(intentFolder, `${encodeURIComponent(requestId)}.json`, resolvedIntent);
  return { request_id: requestId, status: "cleared_no_mutation", resolved: true, timeline_changed: false, timeline_state_token: timeline.state_token };
}

async function auditBestClipsHistory() {
  const intentFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-intents", true);
  const receiptFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-receipts", true);
  const readFolder = async (folder) => {
    const records = [];
    for (const entry of await folder.getEntries()) {
      if (!entry.isFile || !entry.name.endsWith(".json")) continue;
      try { records.push(JSON.parse(await entry.read())); }
      catch (err) { records.push({ malformed: true, file: entry.name, error: String(err.message || err) }); }
    }
    return records;
  };
  const intents = await readFolder(intentFolder);
  const receipts = await readFolder(receiptFolder);
  const receiptIds = new Set(receipts.filter(item => item.request_id).map(item => item.request_id));
  const uncertain = intents.filter(item => item.request_id && !["completed", "cleared_no_mutation"].includes(item.status) && !receiptIds.has(item.request_id));
  const clearedNoMutation = intents.filter(item => item.request_id && item.status === "cleared_no_mutation" && !receiptIds.has(item.request_id));
  const orphanReceipts = receipts.filter(item => item.request_id && !intents.some(intent => intent.request_id === item.request_id));
  const receiptMismatches = [];
  receipts.forEach(receipt => {
    const intent = intents.find(candidate => candidate.request_id && candidate.request_id === receipt.request_id);
    if (!intent) return;
    const mismatchedFields = [];
    if (String(intent.project_guid || "") !== String(receipt.project_guid || "")) mismatchedFields.push("project_guid");
    if (String(intent.sequence_guid || "") !== String(receipt.sequence_guid || "")) mismatchedFields.push("sequence_guid");
    if (String(intent.expected_timeline_state_token || "") !== String(receipt.timeline_state_token_before || "")) mismatchedFields.push("timeline_state_token_before");
    if (mismatchedFields.length) receiptMismatches.push({ request_id: receipt.request_id, mismatched_fields: mismatchedFields });
  });
  const malformed = [...intents, ...receipts].filter(item => item.malformed);
  const issues = [
    ...uncertain.map(item => `Uncertain best-clips request: ${item.request_id}`),
    ...orphanReceipts.map(item => `Best-clips receipt has no matching intent: ${item.request_id}`),
    ...receiptMismatches.map(item => `Best-clips intent/receipt mismatch for ${item.request_id}: ${item.mismatched_fields.join(", ")}`),
    ...malformed.map(item => `Malformed best-clips history file: ${item.file}`)
  ];
  let activeProjectGuid = null;
  let activeSequenceGuid = null;
  let activeSequenceName = null;
  try {
    const activeProject = await ppro.Project.getActiveProject();
    const activeSequence = activeProject ? await activeProject.getActiveSequence() : null;
    activeProjectGuid = activeProject ? bridgeGuidString(activeProject.guid) : null;
    activeSequenceGuid = activeSequence ? bridgeGuidString(activeSequence.guid) : null;
    activeSequenceName = activeSequence ? String(activeSequence.name || "") : null;
  } catch (_) {}
  const inActiveScope = item => activeProjectGuid && activeSequenceGuid && String(item.project_guid || "") === activeProjectGuid && String(item.sequence_guid || "") === activeSequenceGuid;
  const activeScopeUncertain = uncertain.filter(inActiveScope);
  const activeScopeCompleted = receipts.filter(item => item.externally_undone !== true && inActiveScope(item));
  return {
    status: issues.length ? "attention" : "healthy",
    safe_to_start_new: uncertain.length === 0 && malformed.length === 0 && receiptMismatches.length === 0,
    intents: intents.filter(item => !item.malformed).length,
    receipts: receipts.filter(item => !item.malformed).length,
    completed: receipts.filter(item => item.externally_undone !== true && item.request_id && intents.some(intent => intent.request_id === item.request_id)).length,
    externally_undone: receipts.filter(item => item.externally_undone === true).length,
    externally_undone_request_ids: receipts.filter(item => item.externally_undone === true).map(item => item.request_id),
    cleared_no_mutation: clearedNoMutation.length,
    cleared_no_mutation_request_ids: clearedNoMutation.map(item => item.request_id),
    uncertain: uncertain.length,
    orphan_receipts: orphanReceipts.length,
    receipt_mismatches: receiptMismatches.length,
    receipt_mismatch_details: receiptMismatches,
    active_scope: { project_guid: activeProjectGuid, sequence_guid: activeSequenceGuid, sequence_name: activeSequenceName },
    active_scope_completed: activeScopeCompleted.length,
    active_scope_completed_request_ids: activeScopeCompleted.map(item => item.request_id),
    active_scope_uncertain: activeScopeUncertain.length,
    active_scope_uncertain_request_ids: activeScopeUncertain.map(item => item.request_id),
    other_scope_completed: receipts.filter(item => item.externally_undone !== true && !inActiveScope(item)).length,
    active_scope_status: !activeSequenceGuid ? "unavailable" : activeScopeUncertain.length ? "attention" : "healthy",
    new_assembly_advisory: { safe_to_proceed: issues.length === 0 && activeScopeUncertain.length === 0, reason: issues.length ? "Best-clips history requires attention before another assembly." : activeScopeUncertain.length ? "The active sequence has an uncertain best-clips request." : "Best-clips history is healthy for the active sequence." },
    uncertain_request_ids: uncertain.map(item => item.request_id),
    issues,
    timeline_changed: false
  };
}

async function listBestClipsReceipts() {
  const receiptFolder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-receipts", true);
  const receipts = [];
  for (const entry of await receiptFolder.getEntries()) {
    if (!entry.isFile || !entry.name.endsWith(".json")) continue;
    try { receipts.push(JSON.parse(await entry.read())); }
    catch (_) {}
  }
  receipts.sort((left, right) => String(right.completed_at || "").localeCompare(String(left.completed_at || "")));
  let activeProjectGuid = null;
  let activeSequenceGuid = null;
  try {
    const project = await ppro.Project.getActiveProject();
    const sequence = project ? await project.getActiveSequence() : null;
    activeProjectGuid = project ? bridgeGuidString(project.guid) : null;
    activeSequenceGuid = sequence ? bridgeGuidString(sequence.guid) : null;
  } catch (_) {}
  const scopeStatus = item => !activeProjectGuid || !activeSequenceGuid
    ? "unavailable"
    : String(item.project_guid || "") !== activeProjectGuid
      ? "other_project"
      : String(item.sequence_guid || "") === activeSequenceGuid
        ? "active_sequence"
        : "other_sequence";
  const summaries = receipts.map(item => ({ request_id: item.request_id, receipt_id: item.receipt_id, completed_at: item.completed_at, project_guid: item.project_guid, sequence_guid: item.sequence_guid, sequence_name: item.sequence_name, scope_status: scopeStatus(item), kept_shots: item.kept_shots, removed_items: item.removed_items, duration_seconds: item.duration_seconds, verified: item.verified === true, externally_undone: item.externally_undone === true, restored_timeline_state_token: item.restored_timeline_state_token || null }));
  return {
    receipts: summaries,
    total: receipts.length,
    active_sequence: summaries.filter(item => item.scope_status === "active_sequence").length,
    other_sequence: summaries.filter(item => item.scope_status === "other_sequence").length,
    other_project: summaries.filter(item => item.scope_status === "other_project").length,
    timeline_changed: false
  };
}

async function getUnifiedSafetyStatus() {
  const continuity = await validateTimelineContinuity();
  const bestClipsHistory = await auditBestClipsHistory();
  const protectionHealth = await auditSequenceProtection();
  const protectedSequences = await getProtectedSequences();
  const activeProtected = protectedSequences.items.some(item => String(item.sequence_guid || "") === String(continuity.sequence.guid || ""));
  const blockers = [];
  if (!continuity.continuous) blockers.push("Timeline has video gaps or overlaps.");
  if (!continuity.av_ends_aligned) blockers.push("Audio and video endpoints are not aligned.");
  if (bestClipsHistory.status !== "healthy") blockers.push("Best-clips history requires attention.");
  if (protectionHealth.status !== "healthy") blockers.push("Sequence-protection history requires attention.");
  if (activeProtected) blockers.push(`Active sequence ${continuity.sequence.name || ""} is protected.`);
  return { safe_to_mutate: blockers.length === 0, blockers, timeline_changed: false, project: continuity.project, sequence: continuity.sequence, timeline_state_token: continuity.timeline_state_token, continuity: { continuous: continuity.continuous, av_ends_aligned: continuity.av_ends_aligned, video_end_seconds: continuity.video.end_seconds, audio_end_seconds: continuity.audio.end_seconds }, best_clips_history: { status: bestClipsHistory.status, uncertain: bestClipsHistory.uncertain, receipt_mismatches: bestClipsHistory.receipt_mismatches }, sequence_protection: { protected: activeProtected, history_status: protectionHealth.status } };
}

async function runSystemValidation() {
  const project = await ppro.Project.getActiveProject();
  if (!project) throw new Error("System validation requires an active Premiere project.");
  const sequence = await project.getActiveSequence();
  if (!sequence) throw new Error("System validation requires an active Premiere sequence.");
  const before = await getBridgeTimelineInventory(sequence);
  const continuity = await validateTimelineContinuity();
  const revisionContext = await getStrictRevisionContext();
  const bestClipsHistory = await auditBestClipsHistory();
  const receiptList = await listBestClipsReceipts();
  const protectionHealth = await auditSequenceProtection();
  const protectedSequences = await getProtectedSequences();
  const safety = await getUnifiedSafetyStatus();
  const receiptStatusChecks = [];
  for (const receipt of receiptList.receipts) {
    const status = await getBestClipsRequestStatus(receipt.request_id);
    const expectedStatus = receipt.externally_undone === true ? "undone" : "completed";
    receiptStatusChecks.push({ request_id: receipt.request_id, passed: status.status === expectedStatus && status.receipt && status.receipt.receipt_id === receipt.receipt_id });
  }
  const after = await getBridgeTimelineInventory(sequence);
  const activeProtected = protectedSequences.items.some(item => String(item.sequence_guid || "") === bridgeGuidString(sequence.guid));
  const checks = [
    { name: "timeline_token_stable", passed: before.state_token === after.state_token },
    { name: "continuity_token_matches", passed: continuity.timeline_state_token === before.state_token },
    { name: "revision_context_token_matches", passed: revisionContext.timeline_state_token === before.state_token },
    { name: "sequence_identity_matches", passed: continuity.sequence.guid === bridgeGuidString(sequence.guid) && revisionContext.sequence.guid === bridgeGuidString(sequence.guid) },
    { name: "timeline_continuous", passed: continuity.continuous === true },
    { name: "av_ends_aligned", passed: continuity.av_ends_aligned === true },
    { name: "target_count_matches", passed: revisionContext.target_catalog.total === before.total_video_items + before.total_audio_items },
    { name: "best_clips_history_healthy", passed: bestClipsHistory.status === "healthy" },
    { name: "receipt_count_matches", passed: receiptList.total === bestClipsHistory.receipts },
    { name: "receipt_statuses_match", passed: receiptStatusChecks.every(item => item.passed) },
    { name: "protection_history_healthy", passed: protectionHealth.status === "healthy" },
    { name: "protected_count_matches", passed: protectionHealth.protected_sequences === protectedSequences.items.length },
    { name: "protection_verdict_matches", passed: activeProtected ? safety.safe_to_mutate === false && safety.blockers.some(item => item.includes("is protected")) : safety.sequence_protection.protected === false }
  ];
  const failed = checks.filter(check => !check.passed);
  const report = { schema: "v5-system-validation-0.1", validation_id: `validation-${Date.now()}`, validated_at: new Date().toISOString(), passed: failed.length === 0, timeline_changed: false, plugin_version: V5_BRIDGE_PLUGIN_VERSION, project: { name: String(project.name || ""), guid: bridgeGuidString(project.guid) }, sequence: { name: String(sequence.name || ""), guid: bridgeGuidString(sequence.guid) }, timeline_state_token: before.state_token, checks, failed_checks: failed.map(check => check.name), receipt_status_checks: receiptStatusChecks, summary: { video_items: before.total_video_items, audio_items: before.total_audio_items, duration_seconds: continuity.video.end_seconds, protected: activeProtected, receipts: receiptList.total } };
  await writeSystemValidation(report);
  return report;
}

async function executeBridgeCommand(command) {
  const id = command && command.id;
  const type = command && command.type;
  if (!id || !type || id === v5BridgeLastCommandId) return;
  v5BridgeLastCommandId = id;
  try {
    const protectedMutationTypes = new Set([
      "apply_best_clips_assembly", "apply_loaded", "load_and_apply", "apply_revisions",
      "apply_linked_reorder",
      "validate_revision_rollback", "apply_receipt_recovery", "ensure_enabled_video_lumetri",
      "apply_lumetri_grade_plan", "apply_clean_natural_grade_selected", "revert_clean_natural_grade_selected", "delete_all_audio"
    ]);
    if (v5Imports && v5Imports.busy && type !== "import_xml_status") throw new Error("Import in progress; wait for its receipt.");
    if (protectedMutationTypes.has(type)) await assertActiveSequenceMutationAllowed(type);
    let result = null;
    if (type === "import_xml" || type === "preflight_import_xml") {
      result = await getV5Imports()[type === "import_xml" ? "run" : "preflight"](command.request);
    } else if (type === "import_xml_status") {
      result = await getV5Imports().status(command.request_id);
      if (!result) throw new Error("No import receipt found.");
    } else if (type === "ping" || type === "snapshot") {
      result = await getPremiereBridgeSnapshot();
      if (type === "snapshot") await bridgeWriteJson(v5BridgeStateFolder, "premiere-snapshot.json", result);
    } else if (type === "session_status") {
      result = { session_mutation_budget: getSessionMutationBudget() };
    } else if (type === "validate_timeline_continuity") {
      result = await validateTimelineContinuity();
    } else if (type === "safety_status") {
      result = await getUnifiedSafetyStatus();
    } else if (type === "run_system_validation") {
      result = await runSystemValidation();
    } else if (type === "delete_all_audio") {
      result = await deleteAllAudio(command.expected_timeline_state_token);
    } else if (type === "last_system_validation") {
      result = await bridgeReadJson(v5BridgeStateFolder, "last-system-validation.json");
      if (!result) throw new Error("No system-validation report is available in this bridge state.");
    } else if (type === "system_validation_status") {
      result = await getSystemValidationStatus();
    } else if (type === "list_system_validations") {
      result = await listSystemValidations();
    } else if (type === "list_protected_sequences") {
      result = { ...(await getProtectedSequences()), timeline_changed: false };
    } else if (type === "sequence_protection_history") {
      result = { ...(await getSequenceProtectionHistory()), timeline_changed: false };
    } else if (type === "audit_sequence_protection") {
      result = await auditSequenceProtection();
    } else if (type === "protect_active_sequence") {
      result = await setActiveSequenceProtection(true);
    } else if (type === "unprotect_active_sequence") {
      result = await setActiveSequenceProtection(false, command.expected_timeline_state_token);
    } else if (type === "best_clips_receipt") {
      if (command.request_id) {
        const folder = await bridgeChildFolder(v5BridgeStateFolder, "best-clips-receipts", true);
        result = await bridgeReadJson(folder, `${encodeURIComponent(command.request_id)}.json`);
      } else result = await bridgeReadJson(v5BridgeStateFolder, "last-best-clips-receipt.json");
      if (!result) throw new Error("No best-clips receipt is available in this bridge state.");
    } else if (type === "list_best_clips_receipts") {
      result = await listBestClipsReceipts();
    } else if (type === "best_clips_request_status") {
      result = await getBestClipsRequestStatus(command.request_id);
    } else if (type === "resolve_best_clips_intent_no_change") {
      result = await resolveBestClipsIntentNoChange(command.request_id);
    } else if (type === "audit_best_clips_history") {
      result = await auditBestClipsHistory();
    } else if (type === "validate_session_budget") {
      const units = Number(command.mutation_units);
      if (!Number.isInteger(units) || units < 0) throw new Error("validate_session_budget requires a non-negative integer mutation-unit count.");
      assertSessionMutationBudget(units, "read-only validation request");
      result = { accepted: true, requested_mutation_units: units, session_mutation_budget: getSessionMutationBudget(), timeline_changed: false };
    } else if (type === "placement_request_status") {
      result = await getPlacementRequestStatus(command.request_id);
    } else if (type === "audit_placement_history") {
      result = await auditPlacementHistory();
    } else if (type === "inspect_selected_video_effects") {
      result = await inspectSelectedVideoEffects();
    } else if (type === "inspect_enabled_video_lumetri") {
      result = await inspectEnabledVideoLumetri();
    } else if (type === "ensure_enabled_video_lumetri") {
      result = await ensureEnabledVideoLumetri(command.expected_timeline_state_token);
    } else if (type === "apply_warp_stabilizer_last_video") {
      result = await applyWarpStabilizerLastVideo(command.expected_timeline_state_token);
    } else if (type === "apply_warp_stabilizer_target") {
      const plan = command.plan;
      if (!plan || plan.schema !== "v5-warp-stabilizer-target-0.1" || typeof plan.expected_timeline_state_token !== "string" || !plan.target) throw new Error("Invalid Warp Stabilizer target plan.");
      result = await applyWarpStabilizerLastVideo(plan.expected_timeline_state_token, plan.target);
    } else if (type === "apply_lumetri_grade_plan") {
      result = await applyLumetriGradePlan(command.plan);
    } else if (type === "apply_best_clips_assembly") {
      result = await applyBestClipsAssembly(command.plan);
    } else if (type === "preflight_best_clips_assembly") {
      result = await applyBestClipsAssembly(command.plan, { preflight_only: true });
    } else if (type === "preflight_linked_reorder") {
      throw new Error("Linked reorder is disabled: Premiere host timing readback can disagree with the visible timeline. Use manual reorder until a visually verifiable host API is available.");
    } else if (type === "apply_linked_reorder") {
      throw new Error("Linked reorder is disabled: Premiere host timing readback can disagree with the visible timeline. Use manual reorder until a visually verifiable host API is available.");
    } else if (type === "apply_clean_natural_grade_selected") {
      result = await applyCleanNaturalGradeSelected(false, command.selection_token);
    } else if (type === "revert_clean_natural_grade_selected") {
      result = await applyCleanNaturalGradeSelected(true, command.selection_token);
    } else if (type === "load_decisions") {
      decisionDoc = null;
      decisionSignature = null;
      if ($("apply")) $("apply").disabled = true;
      if ($("decisionPaste")) $("decisionPaste").value = "";
      const doc = command.decisions;
      validate(doc);
      decisionDoc = doc;
      decisionSignature = makeDecisionSignature(doc);
      lastAppliedSignature = null;
      const paste = $("decisionPaste");
      if (paste) paste.value = JSON.stringify(doc, null, 2);
      const applyButton = $("apply");
      if (applyButton) applyButton.disabled = false;
      result = { loaded: true, edits: doc.edits.length, schema: doc.schema };
      log(`BRIDGE — loaded ${doc.edits.length} validated edit(s).`);
    } else if (type === "clear_loaded_decisions") {
      const cleared = !!decisionDoc;
      decisionDoc = null;
      decisionSignature = null;
      lastAppliedSignature = null;
      const paste = $("decisionPaste");
      if (paste) paste.value = "";
      const applyButton = $("apply");
      if (applyButton) applyButton.disabled = true;
      result = { cleared, loaded_decisions: null, timeline_changed: false };
      log("BRIDGE — cleared loaded edit decisions without changing Premiere.");
    } else if (type === "apply_loaded") {
      if (!decisionDoc) throw new Error("No decisions loaded.");
      result = await applyDecisions();
    } else if (type === "preflight_loaded") {
      result = await preflightLoadedDecisions();
      log(`BRIDGE — preflight passed for ${result.edits} edit(s).`);
    } else if (type === "load_and_apply") {
      decisionDoc = null;
      decisionSignature = null;
      if ($("apply")) $("apply").disabled = true;
      if ($("decisionPaste")) $("decisionPaste").value = "";
      const doc = command.decisions;
      validate(doc);
      decisionDoc = doc;
      decisionSignature = makeDecisionSignature(doc);
      lastAppliedSignature = null;
      const paste = $("decisionPaste");
      if (paste) paste.value = JSON.stringify(doc, null, 2);
      const applyButton = $("apply");
      if (applyButton) applyButton.disabled = false;
      result = await applyDecisions();
    } else if (type === "load_revisions") {
      // Fail closed: an invalid replacement must not leave an older batch armed.
      // Keep the last-applied marker until a valid replacement is accepted.
      revisionDoc = null;
      revisionSignature = null;
      const doc = command.revisions;
      validateRevisionDocument(doc);
      revisionDoc = doc;
      revisionSignature = JSON.stringify(doc);
      lastAppliedRevisionSignature = null;
      result = { loaded: true, revisions: doc.revisions.length, schema: doc.schema };
      log(`BRIDGE — loaded ${doc.revisions.length} validated timeline revision(s).`);
    } else if (type === "clear_loaded_revisions") {
      const cleared = !!revisionDoc;
      revisionDoc = null;
      revisionSignature = null;
      lastAppliedRevisionSignature = null;
      result = { cleared, loaded_revisions: null, timeline_changed: false };
      log("BRIDGE — cleared loaded timeline revisions without changing Premiere.");
    } else if (type === "preflight_revisions") {
      result = await preflightLoadedRevisions();
      log(`BRIDGE — revision preflight passed for ${result.revisions} revision(s).`);
    } else if (type === "revision_context") {
      result = await getStrictRevisionContext();
    } else if (type === "visual_samples") {
      result = await exportVisualSamples(id, command.request);
    } else if (type === "apply_revisions") {
      result = await applyLoadedRevisions();
    } else if (type === "validate_revision_rollback") {
      const step = Number(command.fail_after_revision);
      if (!Number.isInteger(step) || step < 1 || !revisionDoc || step > revisionDoc.revisions.length) {
        throw new Error("validate_revision_rollback requires a fail-after revision number within the loaded batch.");
      }
      result = await applyLoadedRevisions({ inject_failure_after_step: step });
    } else if (type === "revision_receipt") {
      result = await getRevisionReceiptById(command.receipt_id || null);
      if (!result) throw new Error("No revision receipt is available in this bridge session.");
    } else if (type === "list_revision_receipts") {
      result = await listRevisionReceipts();
    } else if (type === "revision_failure") {
      result = await getRevisionFailureById(command.failure_id || null);
      if (!result) throw new Error("No revision failure is available in this bridge session.");
    } else if (type === "list_revision_failures") {
      result = await listRevisionFailures();
    } else if (type === "revision_request_status") {
      result = await getRevisionRequestStatus(command.request_id);
    } else if (type === "audit_revision_history") {
      result = await auditRevisionHistory();
    } else if (type === "preflight_receipt_recovery") {
      const receipt = await getRevisionReceiptById(command.receipt_id || null);
      result = await preflightReceiptRecovery(receipt);
      log(`BRIDGE — receipt recovery preflight passed in ${result.mode} mode.`);
    } else if (type === "apply_receipt_recovery") {
      const receipt = await getRevisionReceiptById(command.receipt_id || null);
      result = await applyReceiptRecovery(receipt);
      log(`BRIDGE — receipt recovery applied and verified in ${result.mode} mode.`);
    } else throw new Error(`Unsupported bridge command: ${type}`);
    await bridgePostResult(id, true, result, null);
  } catch (err) {
    const message = String(err.message || err);
    log(`BRIDGE ERROR: ${message}`);
    await bridgePostResult(id, false, null, message);
  }
  updateSessionBudgetStatus();
}

async function pollV5Bridge() {
  if (!v5BridgeEnabled || v5BridgeBusy || !v5BridgeStateFolder) return;
  v5BridgeBusy = true;
  try {
    const commands = await bridgeChildFolder(v5BridgeStateFolder, "commands", true);
    const entries = (await commands.getEntries())
      .filter(e => e.isFile && e.name.endsWith(".json"))
      .sort((a,b) => a.name.localeCompare(b.name));
    bridgeSetStatus(`Connected — ${v5BridgeStateFolder.nativePath || v5BridgeStateFolder.name}`, true);

    // Consume one command per poll. Removing it after execution is critical:
    // otherwise the first command would remain at the head of the queue forever
    // and block every later Codex command.
    if (entries.length) {
      const entry = entries[0];
      let command = null;
      try {
        const text = await entry.read();
        command = JSON.parse(text);
        await executeBridgeCommand(command);
      } finally {
        try { await entry.delete(); }
        catch (deleteErr) {
          log(`BRIDGE WARNING: processed command could not be removed: ${String(deleteErr.message || deleteErr)}`);
        }
      }
    }
  } catch (err) { bridgeSetStatus(`Bridge error — ${String(err.message || err)}`); }
  finally { v5BridgeBusy = false; }
}

async function activateV5BridgeFolder(folder) {
  if (!folder || !folder.isFolder) throw new Error("The saved bridge location is not a folder.");
  v5BridgeStateFolder = folder;
  lastRevisionReceipt = await bridgeReadJson(folder, "last-revision-receipt.json");
  if (lastRevisionReceipt) await persistRevisionReceipt(lastRevisionReceipt);
  lastRevisionFailure = await bridgeReadJson(folder, "last-revision-failure.json");
  if (lastRevisionFailure) await persistRevisionFailure(lastRevisionFailure);
  await bridgeChildFolder(folder, "commands", true);
  await bridgeChildFolder(folder, "results", true);
  v5BridgeEnabled = true;
  const button = $("bridgeToggle");
  if (button) button.textContent = "Disconnect Codex bridge";
  bridgeSetStatus(`Connected — ${folder.nativePath || folder.name}`, true);
  updateSessionBudgetStatus();
  await pollV5Bridge();
  v5BridgeTimer = setInterval(pollV5Bridge, V5_BRIDGE_POLL_MS);
}

async function startV5Bridge() {
  if (v5BridgeEnabled) return;
  const fs = require("uxp").storage.localFileSystem;
  const folder = await fs.getFolder();
  if (!folder) { bridgeSetStatus("Bridge folder selection cancelled."); return; }
  const token = await fs.createPersistentToken(folder);
  localStorage.setItem(V5_BRIDGE_FOLDER_TOKEN_KEY, token);
  await activateV5BridgeFolder(folder);
}

async function restoreV5Bridge() {
  if (v5BridgeEnabled) return true;
  const token = localStorage.getItem(V5_BRIDGE_FOLDER_TOKEN_KEY);
  if (!token) return false;
  const fs = require("uxp").storage.localFileSystem;
  try {
    const folder = await fs.getEntryForPersistentToken(token);
    await activateV5BridgeFolder(folder);
    log("BRIDGE — restored saved connection automatically.");
    return true;
  } catch (err) {
    localStorage.removeItem(V5_BRIDGE_FOLDER_TOKEN_KEY);
    bridgeSetStatus(`Saved bridge unavailable — reconnect once (${String(err.message || err)})`);
    return false;
  }
}

function stopV5Bridge() {
  v5BridgeEnabled = false;
  if (v5BridgeTimer) clearInterval(v5BridgeTimer);
  v5BridgeTimer = null;
  v5BridgeStateFolder = null;
  localStorage.removeItem(V5_BRIDGE_FOLDER_TOKEN_KEY);
  const button = $("bridgeToggle");
  if (button) button.textContent = "Connect Codex bridge";
  bridgeSetStatus("Disconnected — choose the bridge/state folder");
  updateSessionBudgetStatus();
}

window.addEventListener("load", () => {
  updateSessionBudgetStatus();
  const toggle = $("bridgeToggle");
  if (toggle) toggle.addEventListener("click", async () => {
    try { v5BridgeEnabled ? stopV5Bridge() : await startV5Bridge(); }
    catch (err) { bridgeSetStatus(`Bridge error — ${String(err.message || err)}`); }
  });
  const snapshot = $("bridgeSnapshot");
  if (snapshot) snapshot.addEventListener("click", async () => {
    try {
      if (!v5BridgeStateFolder) throw new Error("Choose / connect the bridge folder first.");
      const data = await getPremiereBridgeSnapshot();
      await bridgeWriteJson(v5BridgeStateFolder, "premiere-snapshot.json", data);
      bridgeSetStatus("Connected — snapshot written", true);
      log("BRIDGE — Premiere snapshot written.");
    } catch (err) { bridgeSetStatus(`Bridge error — ${String(err.message || err)}`); }
  });
  restoreV5Bridge().catch(err => bridgeSetStatus(`Automatic bridge reconnect failed — ${String(err.message || err)}`));
});

// 0.4.63: imports use only files staged inside the already-authorized bridge folder.
async function importFolder(name) {
  if (!v5BridgeStateFolder) throw new Error("Connect the Codex bridge first.");
  return await bridgeChildFolder(v5BridgeStateFolder, name, true);
}
function getV5Imports() {
  if (!v5Imports) v5Imports = require("./import-controls.js")({
    project: () => ppro.Project.getActiveProject(), guid: bridgeGuidString,
    otherBusy: () => !!(applyDecisions.inProgress || applyLoadedRevisions.inProgress),
    file: async name => (await importFolder("imports")).getEntry(name),
    read: async id => {
      const folder = await importFolder("import-receipts");
      const entries = await folder.getEntries();
      const file = entries.find(e => e.name === id + ".json");
      if (!file) return null;
      return JSON.parse(await file.read()); // Corrupt or unreadable receipts fail closed.
    },
    write: async (id, value) => bridgeWriteJson(await importFolder("import-receipts"), id + ".json", value)
  });
  return v5Imports;
}
window.addEventListener("load", () => {
  const button = $("importXml");
  if (button) button.addEventListener("click", async () => {
    button.disabled = true;
    try {
      const folder = await importFolder("imports");
      const project = await ppro.Project.getActiveProject();
      if (!project) throw new Error("Open the destination Premiere project first.");
      const guid = bridgeGuidString(project.guid);
      const file = await localFileSystem.getFileForOpening({types:["xml"],allowMultiple:false});
      if (!file) { $("importStatus").textContent = "Import cancelled."; return; }
      const id = "panel-" + Date.now() + "-" + Math.random().toString(16).slice(2,10);
      const staged = await folder.createFile(id + ".xml", {overwrite:false});
      await staged.write(await file.read());
      v5LastImportRequest = id;
      if (v5BridgeBusy) throw new Error("Bridge is processing a command; wait before importing.");
      const result = await getV5Imports().run({request_id:id,filename:id+".xml",expected_project_guid:guid});
      $("importStatus").textContent = result.status + ": " + (result.added_sequences || []).map(s=>s.name).join(", ") + ". Review the imported timelines.";
    } catch (e) { $("importStatus").textContent = "Import stopped: " + String(e.message || e); }
    finally {button.disabled=false;}
  });
  const open = $("importOpen");
  if (open) open.addEventListener("click", async () => {
    try {
      if (!v5LastImportRequest) throw new Error("No panel import requested this session.");
      if (v5BridgeBusy || (v5Imports && v5Imports.busy)) throw new Error("Wait for the current command to finish.");
      const receipt = await getV5Imports().status(v5LastImportRequest);
      if (!receipt || receipt.status !== "imported" || !receipt.added_sequences.length) throw new Error("No verified imported sequence to open.");
      const project = await ppro.Project.getActiveProject();
      if (!project || bridgeGuidString(project.guid) !== receipt.project_guid) throw new Error("Open the import destination project first.");
      const sequences = await project.getSequences();
      const seq = sequences.find(s => bridgeGuidString(s.guid) === receipt.added_sequences[0].guid);
      if (!seq) throw new Error("Imported sequence no longer exists.");
      const active = await ppro.Project.getActiveProject();
      if (!active || bridgeGuidString(active.guid) !== receipt.project_guid) throw new Error("Destination project changed.");
      if (!await project.openSequence(seq)) throw new Error("Premiere could not open the imported sequence.");
      if (!await project.setActiveSequence(seq)) throw new Error("Sequence opened but could not be activated.");
      $("importStatus").textContent = "Opened " + seq.name;
    } catch (e) { $("importStatus").textContent = String(e.message || e); }
  });
  const status = $("importReceipt");
  if (status) status.addEventListener("click", async () => {
    try {
      if (!v5LastImportRequest) throw new Error("No panel import requested this session.");
      const receipt = await getV5Imports().status(v5LastImportRequest);
      $("importStatus").textContent = JSON.stringify(receipt);
    } catch (e) { $("importStatus").textContent = String(e.message || e); }
  });
});
