// FILE: _St/IG_StYellow/background.js | ROLE: 확장 Background 메시지·WS8771 직접 저장
(()=>{

  // IGblue DEBUG v70: set IG_DBG_ON=0 or put `return;` at top of dbg() to disable all logs.
  const IG_DBG_ON = 1;
  const IG_DBG_CONSOLE_ON = 0;
  function dbg(fn, loc, line, value) {
    // return; // DEBUG OFF: uncomment this one line later.
    if (!IG_DBG_ON) return;
    try {
      const clip = (v) => {
        const seen = new WeakSet();
        let s = JSON.stringify(v, (k, val) => {
          if (typeof val === "string") return val.length > 500 ? val.slice(0, 500) + `...<${val.length}>` : val;
          if (val && typeof val === "object") { if (seen.has(val)) return "[Circular]"; seen.add(val); }
          return val;
        });
        if (s && s.length > 2200) s = s.slice(0,2200) + `...<${s.length}>`;
        return s || "";
      };
      const payload = { ts:new Date().toISOString(), file:"background.js", fn, loc, line, value };
      const out = clip(payload);
      const key = `IG_dbg/background.js/${fn}/${loc}/${line}`.replace(/\s+/g,"_");
      const obj = {}; obj[key] = out; obj["IG_dbg/background.js/_last"] = out;
      try { chrome.storage.local.set(obj); } catch(e) {}
      if (IG_DBG_CONSOLE_ON) console.log(`[IG_DBG][background][${fn}][${loc}:${line}]`, value);
    } catch (e) {}
  }

  // IGblue local patch: detached popup needs a live Instagram tab target.
  // The popup is not bound to a Chrome sidePanel tab, so the background tracks the currently active Instagram tab.
  let igblueLastInstagramTabId = 0;
  let igblueActiveInstagramTabId = 0;
  let igblueLastActiveWebTabId = 0;
  let igblueLastShopInfo = null;
  let extpay = null;
  const igblueInfoByTabId = new Map();
  const igblueUrlByTabId = new Map();
  const igbluePopupHandoffs = new Map();
  const IGBLUE_TAB_CONTROL_STORAGE_KEY = "YELLOW/tab_control_v280";
  const IGBLUE_TAB_CONTROL_HISTORY_LIMIT = 20;
  const YELLOW_CDP_LIFECYCLE_PRELOAD_URL = "http://127.0.0.1:8707/api/cdp/ig/install_lifecycle_preload";
  const YELLOW_CDP_CLOSE_TARGET_URL = "http://127.0.0.1:8707/api/cdp/ig/close_target";
  const YELLOW_CDP_LIFECYCLE_STORAGE_KEY = "YELLOW/cdp_lifecycle_diag_v281";
  const YELLOW_CDP_LIFECYCLE_HISTORY_LIMIT = 50;
  const YELLOW_CDP_STATE_STORAGE_KEY = "YELLOW/cdp_state_v303";
  // [CDP 상태 원본][회귀 금지]
  // Side/Auto8700이 실제 통신 결과를 갱신하고, Instagram Main DOM은 이 값을 표시만 한다.
  // 새 문서 생성·새로고침·URL 이동만으로 X를 만들지 않는다.
  let yellowCdpState = {schema:"yellow_cdp_state_v303", ok:null, checking:true, source:"background_startup", target_id:"", tab_id:"", failure_count:0, updated_at:0};

  function yellowNormalizeCdpState(raw){
    const d = raw && typeof raw === "object" ? raw : {};
    const hasOk = d.ok === true || d.ok === false;
    return {
      schema:"yellow_cdp_state_v303",
      ok:hasOk ? !!d.ok : null,
      checking:d.checking === true || !hasOk,
      source:String(d.source || "unknown"),
      target_id:String(d.target_id || ""),
      tab_id:String(d.tab_id || d.request_tab_id || ""),
      failure_count:Math.max(0, Number(d.failure_count || 0) || 0),
      updated_at:Math.max(0, Number(d.updated_at || d.ts || Date.now()) || Date.now())
    };
  }

  function yellowPersistCdpState(){
    try { const payload={}; payload[YELLOW_CDP_STATE_STORAGE_KEY]=yellowCdpState; chrome.storage.local.set(payload, function(){ chrome.runtime.lastError; }); } catch(_) {}
  }

  function yellowBroadcastCdpState(){
    try {
      chrome.tabs.query({}, function(tabs){
        if(chrome.runtime.lastError) return;
        (tabs || []).filter(function(tab){ return /(^|\.)instagram\.com$/i.test(String(tab && tab.url || "").replace(/^https?:\/\//i, "").split(/[\/?#]/)[0]); }).forEach(function(tab){
          if(!(Number(tab && tab.id || 0) > 0)) return;
          try { chrome.tabs.sendMessage(Number(tab.id), {type:"IGBLUE_CDP_STATE_CHANGED", state:yellowCdpState}, function(){ chrome.runtime.lastError; }); } catch(_) {}
        });
      });
    } catch(_) {}
  }

  function yellowSetCdpState(raw, broadcast){
    yellowCdpState = yellowNormalizeCdpState(Object.assign({}, yellowCdpState, raw || {}, {updated_at:Date.now()}));
    yellowPersistCdpState();
    if(broadcast !== false) yellowBroadcastCdpState();
    return Object.assign({}, yellowCdpState);
  }

  try {
    chrome.storage.local.get(YELLOW_CDP_STATE_STORAGE_KEY, function(data){
      if(chrome.runtime.lastError) return;
      const saved=data && data[YELLOW_CDP_STATE_STORAGE_KEY];
      if(saved && typeof saved === "object") {
        yellowCdpState=yellowNormalizeCdpState(saved);
        // [CDP 상태 판정] 오래된 저장값은 O/X로 단정하지 않고 다음 Side/Auto8700 확인을 기다린다.
        if(Date.now() - Number(yellowCdpState.updated_at || 0) > 15000) {
          yellowCdpState=yellowNormalizeCdpState({ok:null, checking:true, source:"background_restore_stale", failure_count:0, updated_at:Date.now()});
        }
      }
    });
  } catch(_) {}
  const igblueManagedTabMeta = new Map();
  const igbluePendingTabHandoffs = new Map();
  let igblueTabControlState = {
    schema:"yellow_tab_control_v280",
    owner_tab_id:0,
    owner_generation:0,
    generation_counter:0,
    run_seq:"",
    account:"",
    account_index:0,
    account_total:0,
    handoff_state:"IDLE",
    pending:null,
    recent:[],
    updated_at:0
  };

  function igblueTabControlTrimText(value, max){
    const text = String(value == null ? "" : value);
    return text.length > (max || 500) ? text.slice(0, max || 500) : text;
  }


  function yellowRecordCdpLifecycleDiag(tabId, rawDetail){
    try{
      const detail = rawDetail && typeof rawDetail === "object" ? rawDetail : {};
      const id = Number(tabId || 0) || 0;
      const meta = igblueManagedTabMeta.get(id) || {};
      const row = {
        ts:Number(detail.ts || Date.now()) || Date.now(),
        iso:String(detail.iso || new Date().toISOString()),
        tab_id:id,
        role:String(meta.state || (Number(igblueTabControlState.owner_tab_id || 0) === id ? "OWNER" : "UNMANAGED")),
        generation:Number(meta.generation || 0) || 0,
        run_seq:String(meta.run_seq || igblueTabControlState.run_seq || ""),
        account:String(meta.account || igblueTabControlState.account || ""),
        stage:igblueTabControlTrimText(detail.stage || "LOG", 80),
        event_type:igblueTabControlTrimText(detail.event_type || "", 80),
        target:igblueTabControlTrimText(detail.target || "", 80),
        source_url:igblueTabControlTrimText(detail.source_url || "", 500),
        listener_name:igblueTabControlTrimText(detail.listener_name || "", 120),
        listener:igblueTabControlTrimText(detail.listener || "", 600),
        stack:igblueTabControlTrimText(detail.stack || "", 1800),
        href:igblueTabControlTrimText(detail.href || "", 500),
        blocked_count:Number(detail.blocked_count || 0) || 0,
        persisted:!!detail.persisted,
        visibility_state:String(detail.visibility_state || ""),
        session_id:igblueTabControlTrimText(detail.session_id || "", 160)
      };
      const stage = row.stage;
      const status = stage === "INSTALLED" || stage === "BRIDGE_READY" ? "체크완료" : (stage === "INSTALL_FAILED" || stage === "PROPERTY_HOOK_FAILED" ? "체크실패" : "예외추적");
      if(stage !== "BRIDGE_READY_LATE"){
        console.log(`[CDP수명주기][CL-04][${status}] ${stage} / tab=${id} role=${row.role} event=${row.event_type || '-'} source=${row.source_url || '-'}`, row);
      }
      chrome.storage.local.get(YELLOW_CDP_LIFECYCLE_STORAGE_KEY, function(data){
        if(chrome.runtime.lastError) return;
        const recent = Array.isArray(data && data[YELLOW_CDP_LIFECYCLE_STORAGE_KEY]) ? data[YELLOW_CDP_LIFECYCLE_STORAGE_KEY].slice() : [];
        recent.unshift(row);
        if(recent.length > YELLOW_CDP_LIFECYCLE_HISTORY_LIMIT) recent.length = YELLOW_CDP_LIFECYCLE_HISTORY_LIMIT;
        const payload = {}; payload[YELLOW_CDP_LIFECYCLE_STORAGE_KEY] = recent;
        chrome.storage.local.set(payload, function(){ chrome.runtime.lastError; });
      });
      return row;
    }catch(error){
      console.log("[CDP수명주기][CL-04][체크실패] Background 진단 저장 실패", {tab_id:Number(tabId||0)||0,error:String(error&&error.message||error||"")});
      return null;
    }
  }

  function igblueTabControlPersist(){
    try{
      igblueTabControlState.updated_at = Date.now();
      const payload = {};
      payload[IGBLUE_TAB_CONTROL_STORAGE_KEY] = JSON.parse(JSON.stringify(igblueTabControlState));
      chrome.storage.local.set(payload, function(){ chrome.runtime.lastError; });
    }catch(_){ }
  }

  function igblueTabControlLifecycleState(eventState, managedState){
    const explicit = String(managedState || "");
    if(explicit) return explicit;
    const event = String(eventState || "UNKNOWN");
    return ({
      OWNER_CLAIMED:"OWNER",
      OWNER_COMMITTED:"OWNER",
      HANDOFF_READY:"READY",
      HANDOFF_COMMIT_FAILED:"FAILED",
      OLD_TAB_CLOSED:"CLOSED",
      TAB_REMOVED:"CLOSED",
      OLD_TAB_CLOSE_FAILED:"RETIRED"
    })[event] || event;
  }

  function igblueTabControlRecord(state, detail){
    try{
      const d = detail && typeof detail === "object" ? detail : {};
      const event = {
        event_id:String(Date.now()) + "_" + Math.floor(Math.random()*100000),
        ts:Date.now(),
        state:String(state || "UNKNOWN"),
        tab_id:Number(d.tab_id || 0) || 0,
        previous_tab_id:Number(d.previous_tab_id || 0) || 0,
        replacement_tab_id:Number(d.replacement_tab_id || 0) || 0,
        generation:Number(d.generation || 0) || 0,
        run_seq:igblueTabControlTrimText(d.run_seq || igblueTabControlState.run_seq || "", 80),
        account:igblueTabControlTrimText(d.account || igblueTabControlState.account || "", 120),
        account_index:Number(d.account_index || 0) || 0,
        account_total:Number(d.account_total || 0) || 0,
        reason:igblueTabControlTrimText(d.reason || "", 180),
        url:igblueTabControlTrimText(d.url || "", 500),
        close_reason:igblueTabControlTrimText(d.close_reason || "", 180),
        auto_created:d.auto_created === true,
        cdp_target_id:igblueTabControlTrimText(d.cdp_target_id || "", 160),
        error:igblueTabControlTrimText(d.error || "", 300)
      };
      igblueTabControlState.recent.unshift(event);
      if(igblueTabControlState.recent.length > IGBLUE_TAB_CONTROL_HISTORY_LIMIT){
        igblueTabControlState.recent.length = IGBLUE_TAB_CONTROL_HISTORY_LIMIT;
      }
      if(event.tab_id > 0){
        const prev = igblueManagedTabMeta.get(event.tab_id) || {};
        const lifecycleState = igblueTabControlLifecycleState(event.state, d.managed_state);
        igblueManagedTabMeta.set(event.tab_id, Object.assign({}, prev, event, {state:lifecycleState, event_state:event.state, updated_at:event.ts}));
      }
      igblueTabControlPersist();
      console.log("[YELLOW][TAB-CONTROL][" + event.state + "]", event);
      return event;
    }catch(_){ return null; }
  }

  function igblueRestoreTabControlState(){
    try{
      chrome.storage.local.get(IGBLUE_TAB_CONTROL_STORAGE_KEY, function(data){
        if(chrome.runtime.lastError) return;
        const saved = data && data[IGBLUE_TAB_CONTROL_STORAGE_KEY];
        if(!saved || typeof saved !== "object") return;
        igblueTabControlState = Object.assign({}, igblueTabControlState, saved, {
          schema:"yellow_tab_control_v280",
          recent:Array.isArray(saved.recent) ? saved.recent.slice(0, IGBLUE_TAB_CONTROL_HISTORY_LIMIT) : []
        });
        const latestByTab = new Map();
        igblueTabControlState.recent.slice().reverse().forEach(function(event){
          const id = Number(event && event.tab_id || 0) || 0;
          if(id > 0){
            const restored = Object.assign({}, latestByTab.get(id) || {}, event || {});
            restored.event_state = String(restored.state || "UNKNOWN");
            restored.state = igblueTabControlLifecycleState(restored.event_state, restored.managed_state);
            latestByTab.set(id, restored);
          }
        });
        latestByTab.forEach(function(value, id){ igblueManagedTabMeta.set(id, value); });
        console.log("[YELLOW][TAB-CONTROL][RESTORED]", {
          owner_tab_id:Number(igblueTabControlState.owner_tab_id || 0) || 0,
          owner_generation:Number(igblueTabControlState.owner_generation || 0) || 0,
          recent_count:igblueTabControlState.recent.length
        });
      });
    }catch(_){ }
  }

  function igblueNextTabGeneration(){
    igblueTabControlState.generation_counter = Math.max(0, Number(igblueTabControlState.generation_counter || 0) || 0) + 1;
    return igblueTabControlState.generation_counter;
  }

  function igblueTabControlCandidate(tabs){
    const rows = (Array.isArray(tabs) ? tabs : []).filter(function(tab){ return tab && Number(tab.id || 0) > 0 && igblueIsInstagramUrl(tab.url || ""); }).map(function(tab){
      const meta = igblueManagedTabMeta.get(Number(tab.id || 0)) || {};
      return {tab:tab, meta:meta, generation:Number(meta.generation || 0) || 0, created_at:Number(meta.ts || meta.created_at || 0) || 0};
    });
    const validOwner = rows.find(function(row){ return Number(row.tab.id || 0) === Number(igblueTabControlState.owner_tab_id || 0); });
    if(validOwner) return {row:validOwner, basis:"recorded_owner"};
    const currentRunSeq = String(igblueTabControlState.run_seq || "");
    const managed = rows.filter(function(row){ return row.generation > 0 && ["READY","OWNER"].includes(String(row.meta.state || "")); });
    const sameRun = currentRunSeq ? managed.filter(function(row){ return String(row.meta.run_seq || "") === currentRunSeq; }) : [];
    sameRun.sort(function(a,b){ return b.generation-a.generation || b.created_at-a.created_at || Number(b.tab.id||0)-Number(a.tab.id||0); });
    if(sameRun[0]) return {row:sameRun[0], basis:"same_run_generation_ready"};
    managed.sort(function(a,b){ return b.generation-a.generation || b.created_at-a.created_at || Number(b.tab.id||0)-Number(a.tab.id||0); });
    if(managed[0]) return {row:managed[0], basis:"generation_ready"};
    const active = rows.find(function(row){ return !!row.tab.active; });
    if(active) return {row:active, basis:"active_instagram"};
    rows.sort(function(a,b){ return Number(b.tab.id||0)-Number(a.tab.id||0); });
    return rows[0] ? {row:rows[0], basis:"largest_tab_id_fallback"} : {row:null, basis:"no_instagram_tab"};
  }

  function igblueGetTabControlSnapshot(callback){
    try{
      chrome.tabs.query({}, function(tabs){
        if(chrome.runtime.lastError) return callback({ok:false, error:String(chrome.runtime.lastError.message || "tab_query_failed")});
        const instagramTabs = (tabs || []).filter(function(tab){ return tab && Number(tab.id || 0) > 0 && igblueIsInstagramUrl(tab.url || ""); });
        const selected = igblueTabControlCandidate(instagramTabs);
        const ownerTab = selected.row && selected.row.tab || null;
        const ownerMeta = selected.row && selected.row.meta || {};
        const pending = igblueTabControlState.pending && typeof igblueTabControlState.pending === "object" ? Object.assign({}, igblueTabControlState.pending) : null;
        callback({
          ok:true,
          schema:"yellow_tab_control_v280",
          instagram_tab_count:instagramTabs.length,
          owner_tab_id:Number(ownerTab && ownerTab.id || 0) || 0,
          recorded_owner_tab_id:Number(igblueTabControlState.owner_tab_id || 0) || 0,
          owner_generation:Number(ownerMeta.generation || igblueTabControlState.owner_generation || 0) || 0,
          owner_url:String(ownerTab && ownerTab.url || ""),
          owner_active:!!(ownerTab && ownerTab.active),
          owner_window_id:Number(ownerTab && ownerTab.windowId || 0) || 0,
          run_seq:String(igblueTabControlState.run_seq || ownerMeta.run_seq || ""),
          account:String(igblueTabControlState.account || ownerMeta.account || ""),
          account_index:Number(igblueTabControlState.account_index || ownerMeta.account_index || 0) || 0,
          account_total:Number(igblueTabControlState.account_total || ownerMeta.account_total || 0) || 0,
          handoff_state:String(igblueTabControlState.handoff_state || "IDLE"),
          selection_basis:String(selected.basis || ""),
          pending:pending,
          tabs:instagramTabs.map(function(tab){
            const meta = igblueManagedTabMeta.get(Number(tab.id || 0)) || {};
            return {
              tab_id:Number(tab.id || 0) || 0,
              window_id:Number(tab.windowId || 0) || 0,
              active:!!tab.active,
              url:String(tab.url || ""),
              state:String(meta.state || (Number(tab.id||0) === Number(ownerTab&&ownerTab.id||0) ? "OWNER" : "UNMANAGED")),
              generation:Number(meta.generation || 0) || 0,
              run_seq:String(meta.run_seq || ""),
              account:String(meta.account || ""),
              updated_at:Number(meta.updated_at || meta.ts || 0) || 0
            };
          }),
          recent:(igblueTabControlState.recent || []).slice(0, IGBLUE_TAB_CONTROL_HISTORY_LIMIT),
          updated_at:Number(igblueTabControlState.updated_at || 0) || 0
        });
      });
    }catch(error){ callback({ok:false, error:String(error && error.message || error || "tab_control_snapshot_failed")}); }
  }

  function igblueClaimTabOwner(tabId, meta, callback){
    const id = Number(tabId || 0) || 0;
    const m = meta && typeof meta === "object" ? meta : {};
    if(!(id > 0)) return callback({ok:false, error:"owner_tab_id_missing"});
    chrome.tabs.get(id, function(tab){
      if(chrome.runtime.lastError || !tab || !igblueIsInstagramUrl(tab.url || "")){
        return callback({ok:false, error:String(chrome.runtime.lastError && chrome.runtime.lastError.message || "owner_tab_not_instagram"), tab_id:id});
      }
      const existing = igblueManagedTabMeta.get(id) || {};
      const generation = Number(existing.generation || 0) || igblueNextTabGeneration();
      const detail = {
        tab_id:id,
        generation:generation,
        run_seq:String(m.run_seq || ""),
        account:String(m.account || ""),
        account_index:Number(m.account_index || 0) || 0,
        account_total:Number(m.account_total || 0) || 0,
        reason:String(m.reason || "claim_owner"),
        url:String(tab.url || "")
      };
      igblueTabControlState.owner_tab_id = id;
      igblueTabControlState.owner_generation = generation;
      igblueTabControlState.run_seq = detail.run_seq;
      igblueTabControlState.account = detail.account;
      igblueTabControlState.account_index = detail.account_index;
      igblueTabControlState.account_total = detail.account_total;
      igblueTabControlState.handoff_state = "OWNER";
      igblueTabControlState.pending = null;
      igblueManagedTabMeta.set(id, Object.assign({}, existing, detail, {state:"OWNER", updated_at:Date.now()}));
      igblueTabControlRecord("OWNER_CLAIMED", detail);
      callback({ok:true, tab_id:id, generation:generation, state:"OWNER"});
    });
  }

  function igbluePrepareTabHandoff(previousTab, replacementTabId, targetUrl, navigationMeta, readyResult){
    const previousId = Number(previousTab && previousTab.id || 0) || 0;
    const replacementId = Number(replacementTabId || 0) || 0;
    const meta = navigationMeta && typeof navigationMeta === "object" ? navigationMeta : {};
    const generation = igblueNextTabGeneration();
    const token = "handoff_" + Date.now() + "_" + replacementId + "_" + Math.floor(Math.random()*100000);
    const cdpTargetId = String(readyResult && readyResult.unload_guard && readyResult.unload_guard.target && readyResult.unload_guard.target.id || "");
    const pending = {
      token:token,
      previous_tab_id:previousId,
      replacement_tab_id:replacementId,
      auto_created:true,
      cdp_target_id:cdpTargetId,
      generation:generation,
      run_seq:String(meta.run_seq || ""),
      account:String(meta.account || ""),
      account_index:Number(meta.account_index || 0) || 0,
      account_total:Number(meta.account_total || 0) || 0,
      reason:String(meta.reason || "account_queue"),
      target_url:String(targetUrl || ""),
      created_at:Date.now(),
      navigation_ready_ms:Number(readyResult && readyResult.navigation_ready_ms || 0) || 0
    };
    igbluePendingTabHandoffs.set(token, {pending:pending, previousTab:previousTab, targetUrl:String(targetUrl || ""), navigationMeta:meta});
    igblueTabControlState.handoff_state = "PENDING_SIDE_ACK";
    igblueTabControlState.pending = Object.assign({}, pending);
    igblueManagedTabMeta.set(replacementId, Object.assign({}, pending, {tab_id:replacementId, state:"READY", url:String(targetUrl || ""), updated_at:Date.now()}));
    igblueTabControlRecord("HANDOFF_READY", {
      tab_id:replacementId,
      previous_tab_id:previousId,
      replacement_tab_id:replacementId,
      generation:generation,
      run_seq:pending.run_seq,
      account:pending.account,
      account_index:pending.account_index,
      account_total:pending.account_total,
      auto_created:true,
      cdp_target_id:pending.cdp_target_id,
      reason:pending.reason,
      url:pending.target_url
    });
    return pending;
  }

  function igblueCommitTabHandoff(token, detail, callback){
    const key = String(token || "");
    let entry = igbluePendingTabHandoffs.get(key);
    const d = detail && typeof detail === "object" ? detail : {};
    if((!entry || !entry.pending) && igblueTabControlState.pending && String(igblueTabControlState.pending.token || "") === key){
      const restored = Object.assign({}, igblueTabControlState.pending);
      entry = {
        pending:restored,
        previousTab:{id:Number(restored.previous_tab_id || 0) || 0, url:""},
        targetUrl:String(restored.target_url || ""),
        navigationMeta:{reason:String(restored.reason || "restored_pending_handoff"), run_seq:String(restored.run_seq || ""), account:String(restored.account || ""), account_index:Number(restored.account_index || 0) || 0, account_total:Number(restored.account_total || 0) || 0}
      };
      igbluePendingTabHandoffs.set(key, entry);
      console.log("[YELLOW][TAB-HANDOFF][RESTORED_PENDING]", restored);
    }
    if(!entry || !entry.pending) return callback({ok:false, error:"handoff_not_found", handoff_token:key});
    const pending = entry.pending;
    const replacementId = Number(pending.replacement_tab_id || 0) || 0;
    chrome.tabs.get(replacementId, function(tab){
      if(chrome.runtime.lastError || !tab || !igblueIsInstagramUrl(tab.url || "")){
        igblueTabControlState.handoff_state = "COMMIT_FAILED";
        igblueTabControlRecord("HANDOFF_COMMIT_FAILED", {
          tab_id:replacementId,
          previous_tab_id:Number(pending.previous_tab_id || 0) || 0,
          replacement_tab_id:replacementId,
          generation:Number(pending.generation || 0) || 0,
          run_seq:String(d.run_seq || pending.run_seq || ""),
          reason:String(pending.reason || ""),
          error:String(chrome.runtime.lastError && chrome.runtime.lastError.message || "replacement_tab_missing")
        });
        return callback({ok:false, error:String(chrome.runtime.lastError && chrome.runtime.lastError.message || "replacement_tab_missing"), handoff_token:key});
      }
      const runSeq = String(d.run_seq || pending.run_seq || "");
      const account = String(d.account || pending.account || "");
      igblueTabControlState.owner_tab_id = replacementId;
      igblueTabControlState.owner_generation = Number(pending.generation || 0) || 0;
      igblueTabControlState.run_seq = runSeq;
      igblueTabControlState.account = account;
      igblueTabControlState.account_index = Number(d.account_index || pending.account_index || 0) || 0;
      igblueTabControlState.account_total = Number(d.account_total || pending.account_total || 0) || 0;
      igblueTabControlState.handoff_state = "OWNER_COMMITTED";
      igblueTabControlState.pending = Object.assign({}, pending, {committed_at:Date.now()});
      const newMeta = igblueManagedTabMeta.get(replacementId) || {};
      igblueManagedTabMeta.set(replacementId, Object.assign({}, newMeta, {
        tab_id:replacementId,
        generation:Number(pending.generation || 0) || 0,
        run_seq:runSeq,
        account:account,
        account_index:igblueTabControlState.account_index,
        account_total:igblueTabControlState.account_total,
        auto_created:pending.auto_created === true,
        cdp_target_id:String(pending.cdp_target_id || ""),
        state:"OWNER",
        url:String(tab.url || pending.target_url || ""),
        updated_at:Date.now()
      }));
      const previousId = Number(pending.previous_tab_id || 0) || 0;
      if(previousId > 0){
        const oldMeta = igblueManagedTabMeta.get(previousId) || {};
        if(oldMeta.auto_created === true){
          igblueManagedTabMeta.set(previousId, Object.assign({}, oldMeta, {state:"RETIRED", replacement_tab_id:replacementId, updated_at:Date.now()}));
        }
      }
      igblueTabControlRecord("OWNER_COMMITTED", {
        tab_id:replacementId,
        previous_tab_id:previousId,
        replacement_tab_id:replacementId,
        generation:Number(pending.generation || 0) || 0,
        run_seq:runSeq,
        account:account,
        account_index:igblueTabControlState.account_index,
        account_total:igblueTabControlState.account_total,
        auto_created:true,
        cdp_target_id:String(pending.cdp_target_id || ""),
        reason:String(pending.reason || ""),
        url:String(tab.url || pending.target_url || "")
      });
      igblueLastActiveWebTabId = replacementId;
      igblueLastInstagramTabId = replacementId;
      igblueActiveInstagramTabId = replacementId;
      igblueUrlByTabId.set(replacementId, String(tab.url || pending.target_url || ""));
      igblueBroadcastTarget(replacementId);
      igblueClosePreviousTabAfterReplacement(entry.previousTab, replacementId, entry.targetUrl, entry.navigationMeta, function(closeResult){
        igbluePendingTabHandoffs.delete(key);
        igblueTabControlState.pending = null;
        const previousClosed = !!(closeResult && closeResult.closed);
        const previousPreserved = !!(closeResult && closeResult.preserved);
        igblueTabControlState.handoff_state = previousClosed ? "OK" : (previousPreserved ? "OK_USER_TAB_PRESERVED" : (closeResult && closeResult.ok ? "OK_NO_CLOSE_REQUIRED" : "OWNER_OK_OLD_CLOSE_FAILED"));
        if(previousId > 0){
          const oldMeta = igblueManagedTabMeta.get(previousId) || {};
          if(oldMeta.auto_created === true){
            igblueManagedTabMeta.set(previousId, Object.assign({}, oldMeta, {state:previousClosed ? "CLOSED" : "RETIRED", close_reason:"planned_handoff", updated_at:Date.now()}));
            igblueTabControlRecord(previousClosed ? "OLD_TAB_CLOSED" : "OLD_TAB_CLOSE_FAILED", {
              tab_id:previousId,
              previous_tab_id:previousId,
              replacement_tab_id:replacementId,
              generation:Number(oldMeta.generation || 0) || 0,
              run_seq:runSeq,
              account:String(oldMeta.account || ""),
              auto_created:true,
              cdp_target_id:String(oldMeta.cdp_target_id || ""),
              reason:String(pending.reason || ""),
              close_reason:"planned_handoff",
              error:String(closeResult && closeResult.error || "")
            });
          }else if(previousPreserved){
            console.log("[YELLOW][TAB-HANDOFF][USER_TAB_PRESERVED]", {previous_tab_id:previousId,replacement_tab_id:replacementId,reason:String(pending.reason||"")});
          }
        }
        igblueTabControlPersist();
        callback({
          ok:true,
          status:"owner_committed",
          handoff_token:key,
          tabId:replacementId,
          owner_tab_id:replacementId,
          generation:Number(pending.generation || 0) || 0,
          previousTabId:previousId,
          replacementTabId:replacementId,
          previous_tab_closed:!!(closeResult && closeResult.closed),
          previous_tab_close:closeResult || null,
          handoff_state:String(igblueTabControlState.handoff_state || "")
        });
      });
    });
  }

  igblueRestoreTabControlState();
  const IGBLUE_LOCAL_WS_URL = "ws://127.0.0.1:8771";
  const IGBLUE_LOCAL_WS_TOKEN = "auto_enter_local_SEC_20260623";
  const IGBLUE_WS_OFFLINE_CACHE_MS = 20000;
  let igblueWsHealth = { connected:false, lastOkAt:0, lastFailAt:0, lastCheckedAt:0, error_code:"NOT_CHECKED", message:"RTC 미확인" };
  // OS mouse control, origin calibration and owner-gated automation are not used.

  function igblueWsOfflineResult(act){
    return {
      ok:false,
      connected:false,
      act:act || "",
      error_code:"IGBLUE_RTC_OFFLINE",
      message:"WS 대기중: 로컬 host 미접속, 다음 주기 재시도",
      ws_status:Object.assign({}, igblueWsHealth)
    };
  }



  // v237: 일반 optional 상태보고는 미접속 시 연결을 만들지 않는다. 실제 자동 직접 다운로드는 force_probe로 이 제한을 우회한다.
  function igblueWsOptionalAct(act){
    const a = String(act || "");
    return ["ig_media_exists", "ig_media_downloaded", "ig_auto_cycle_state"].includes(a);
  }

  function igblueWsShouldProbe(act, options){
    options = options || {};
    if(options.force_probe === true) return true;
    if(options.force === true && options.allow_refused_probe === true) return true;
    const now = Date.now();
    if(igblueWsHealth.connected) return true;
    if(igblueWsOptionalAct(act)){
      if(igblueWsHealth.lastFailAt && now - igblueWsHealth.lastFailAt < IGBLUE_WS_OFFLINE_CACHE_MS) return false;
      if(igblueWsHealth.lastOkAt && now - igblueWsHealth.lastOkAt < 15000) return true;
      return false;
    }
    if(igblueWsHealth.lastOkAt) return true;
    return false;
  }

  function igblueWsHealthHint(connected, detail){
    const now = Date.now();
    igblueWsHealth.lastCheckedAt = now;
    if(connected){
      igblueWsHealth.connected = true;
      igblueWsHealth.lastOkAt = now;
      igblueWsHealth.error_code = "";
      igblueWsHealth.message = "WS 연결됨";
    }else{
      igblueWsHealth.connected = false;
      igblueWsHealth.lastFailAt = now;
      igblueWsHealth.error_code = String(detail && detail.error_code || "IGBLUE_RTC_OFFLINE");
      igblueWsHealth.message = String(detail && (detail.message || detail.error) || "WS 대기중 / 로컬 host 미접속");
    }
  }

  const IGBLUE_BG_VERSION = (() => { try { const m = chrome.runtime.getManifest(); return String(m.version_name || m.version || "2.3.8.170"); } catch (_) { return "2.3.8.170"; } })();
  const IGBLUE_BG_TAG = (() => { const m = String(IGBLUE_BG_VERSION || "").match(/(\d+(?:a\d*)?)$/i); return m ? ("v" + m[1]) : String(IGBLUE_BG_VERSION || "v170"); })();
  let igblueAuto8700LastStartRequestId = "";

  function igblueWsLooksOffline(res){
    const code = String(res && res.error_code || "");
    return code === "IGBLUE_RTC_OFFLINE" || code.indexOf("IGBLUE_BG_WS_") === 0 || code === "EMPTY_RESPONSE";
  }

  function igblueNoteWsResult(res, act){
    const now = Date.now();
    igblueWsHealth.lastCheckedAt = now;
    if(res && !igblueWsLooksOffline(res)){
      igblueWsHealth.connected = true;
      igblueWsHealth.lastOkAt = now;
      igblueWsHealth.error_code = "";
      igblueWsHealth.message = "WS 연결됨";
    }else{
      igblueWsHealth.connected = false;
      igblueWsHealth.lastFailAt = now;
      igblueWsHealth.error_code = String(res && res.error_code || "IGBLUE_BG_WS_ERROR");
      igblueWsHealth.message = String(res && (res.message || res.error) || "WS 대기중: 로컬 host 미접속");
    }
    return res;
  }

  function igblueWsStatusPayload(extra){
    const now = Date.now();
    return Object.assign({
      ok:!!igblueWsHealth.connected,
      connected:!!igblueWsHealth.connected,
      status:igblueWsHealth.connected ? "connected" : "offline",
      state:igblueWsHealth.connected ? "connected" : "offline",
      message:igblueWsHealth.connected ? "WS 연결됨" : "WS 대기중 / 로컬 host 미접속",
      lastOkAgoMs:igblueWsHealth.lastOkAt ? now - igblueWsHealth.lastOkAt : null,
      lastFailAgoMs:igblueWsHealth.lastFailAt ? now - igblueWsHealth.lastFailAt : null,
      ws_status:Object.assign({}, igblueWsHealth)
    }, extra || {});
  }

  function igblueWsQueueSnapshot(extra){
    let current = null;
    try{
      current = igblueWsCurrentJob ? {
        act:String(igblueWsCurrentJob.act || ''),
        priority:!!igblueWsCurrentJob.priority,
        age_ms:Date.now() - Number(igblueWsCurrentJob.createdAt || Date.now()),
        timeout_ms:Number(igblueWsCurrentJob.timeoutMs || 0)
      } : null;
    }catch(_){ current = null; }
    let queued = [];
    try{
      queued = igblueWsQueue.slice(0, 8).map(function(j){
        return {act:String(j && j.act || ''), priority:!!(j && j.priority), age_ms:Date.now() - Number(j && j.createdAt || Date.now())};
      });
    }catch(_){ queued = []; }
    return Object.assign({
      bg_version:IGBLUE_BG_VERSION,
      bg_tag:IGBLUE_BG_TAG,
      shared_ready:!!igblueWsReady,
      shared_connecting:!!igblueWsConnecting,
      shared_state:igblueWsShared ? Number(igblueWsShared.readyState) : -1,
      backoff_left_ms:Math.max(0, Number(igblueWsBackoffUntil || 0) - Date.now()),
      queue_len:Array.isArray(igblueWsQueue) ? igblueWsQueue.length : 0,
      current_job:current,
      queue_head:queued,
      ws_health:Object.assign({}, igblueWsHealth)
    }, extra || {});
  }

  let igblueWsShared = null;
  let igblueWsReady = false;
  let igblueWsConnecting = false;
  let igblueWsBackoffUntil = 0;
  let igblueWsQueue = [];
  let igblueWsCurrentJob = null;
  let igblueWsConnectWaiters = [];
  const igblueWsLatestOnlyActs = new Set();

  function igblueWsBackoffMs(){
    try{
      const fails = Math.max(1, Number(igblueWsHealth.failCount || 1));
      return Math.min(30000, 1000 * Math.pow(2, Math.min(5, fails - 1)));
    }catch(_){ return 3000; }
  }

  function igblueWsFailAll(error_code, message){
    const jobs = [];
    try{ if(igblueWsCurrentJob) jobs.push(igblueWsCurrentJob); }catch(_){}
    igblueWsCurrentJob = null;
    try{ jobs.push.apply(jobs, igblueWsQueue.splice(0)); }catch(_){}
    jobs.forEach(function(job){
      try{ job.finish({ok:false, connected:false, error_code:error_code || "IGBLUE_BG_WS_ERROR", message:message || "WS 연결 실패", act:job.act}); }catch(_){}
    });
  }

  function igblueWsDisconnect(reason){
    try{ if(igblueWsShared){ igblueWsShared.onopen = igblueWsShared.onmessage = igblueWsShared.onerror = igblueWsShared.onclose = null; igblueWsShared.close(); } }catch(_){}
    igblueWsShared = null;
    igblueWsReady = false;
    igblueWsConnecting = false;
    try{ igblueWsConnectWaiters.splice(0).forEach(function(w){ w.reject(new Error(reason || "ws_disconnected")); }); }catch(_){}
  }

  function igblueEnsureSharedWs(){
    return new Promise(function(resolve, reject){
      const now = Date.now();
      if(igblueWsShared && igblueWsReady && igblueWsShared.readyState === WebSocket.OPEN){ resolve(igblueWsShared); return; }
      if(now < igblueWsBackoffUntil){ reject(new Error("ws_backoff")); return; }
      igblueWsConnectWaiters.push({resolve, reject});
      if(igblueWsConnecting) return;
      igblueWsConnecting = true;
      let ws = null;
      try{
        // 주의: new WebSocket()의 연결 실패는 throw가 아니라 비동기 콘솔 오류로 찍힌다.
        // 그래서 try/catch로 숨길 수 없고, 공유 연결 + backoff로 새 연결 생성을 최소화한다.
        ws = new WebSocket(IGBLUE_LOCAL_WS_URL);
        igblueWsShared = ws;
        ws.onopen = function(){
          igblueWsReady = true;
          igblueWsConnecting = false;
          igblueWsHealth.failCount = 0;
          igblueWsHealthHint(true, {message:"WS 공유 연결됨"});
          const waiters = igblueWsConnectWaiters.splice(0);
          waiters.forEach(function(w){ try{ w.resolve(ws); }catch(_){} });
          setTimeout(igblueWsProcessQueue, 0);
        };
        ws.onmessage = function(ev){
          const job = igblueWsCurrentJob;
          if(!job) return;
          try{ job.finish(JSON.parse(ev.data)); }catch(e){ job.finish({ok:false, error_code:"IGBLUE_BG_BAD_JSON", error:String(e&&e.message||e), act:job.act}); }
          igblueWsCurrentJob = null;
          setTimeout(igblueWsProcessQueue, 0);
        };
        ws.onerror = function(){
          igblueWsReady = false;
          igblueWsConnecting = false;
          igblueWsHealth.failCount = Number(igblueWsHealth.failCount || 0) + 1;
          igblueWsBackoffUntil = Date.now() + igblueWsBackoffMs();
          igblueWsHealthHint(false, {error_code:"IGBLUE_RTC_OFFLINE", message:"WS 미접속 / backoff 재시도 대기"});
          const waiters = igblueWsConnectWaiters.splice(0);
          waiters.forEach(function(w){ try{ w.reject(new Error("ws_error")); }catch(_){} });
          igblueWsFailAll("IGBLUE_RTC_OFFLINE", "WS 미접속 / 재시도 대기");
          try{ if(ws && ws.readyState !== WebSocket.CLOSED) ws.close(); }catch(_){}
        };
        ws.onclose = function(){
          igblueWsReady = false;
          igblueWsConnecting = false;
          if(igblueWsShared === ws) igblueWsShared = null;
          if(igblueWsCurrentJob){
            const job = igblueWsCurrentJob;
            igblueWsCurrentJob = null;
            job.finish({ok:false, connected:false, error_code:"IGBLUE_BG_WS_CLOSED", message:"WS 연결 종료", act:job.act});
          }
          setTimeout(igblueWsProcessQueue, 250);
        };
      }catch(e){
        igblueWsConnecting = false;
        igblueWsBackoffUntil = Date.now() + 10000;
        igblueWsHealth.failCount = Number(igblueWsHealth.failCount || 0) + 1;
        igblueWsHealthHint(false, {error_code:"IGBLUE_BG_WS_CREATE_FAILED", message:String(e&&e.message||e)});
        const waiters = igblueWsConnectWaiters.splice(0);
        waiters.forEach(function(w){ try{ w.reject(e); }catch(_){} });
      }
    });
  }

  function igblueWsProcessQueue(){
    if(igblueWsCurrentJob || !igblueWsQueue.length) return;
    const job = igblueWsQueue.shift();
    const now = Date.now();
    if(now - job.createdAt > Math.max(1000, job.timeoutMs + 600)){
      job.finish({ok:false, connected:false, error_code:"IGBLUE_BG_WS_QUEUE_STALE", message:"WS 요청 대기 만료", act:job.act});
      setTimeout(igblueWsProcessQueue, 0);
      return;
    }
    igblueWsCurrentJob = job;
    igblueEnsureSharedWs().then(function(ws){
      try{
        if(!ws || ws.readyState !== WebSocket.OPEN) throw new Error("ws_not_open");
        ws.send(JSON.stringify({v:1, id:job.id, act:job.act, token:IGBLUE_LOCAL_WS_TOKEN, from:{endpoint:"background", href:(job.data&&job.data.href)||"", host:(job.data&&job.data.host)||""}, data:job.data||{}}));
      }catch(e){
        igblueWsCurrentJob = null;
        try{ igblueWsDisconnect("send_failed"); }catch(_){}
        job.finish({ok:false, connected:false, error_code:"IGBLUE_BG_WS_SEND_ERROR", error:String(e&&e.message||e), act:job.act});
        setTimeout(igblueWsProcessQueue, 250);
      }
    }).catch(function(err){
      igblueWsCurrentJob = null;
      const code = String(err && err.message || "") === "ws_backoff" ? "IGBLUE_BG_WS_BACKOFF" : "IGBLUE_BG_WS_CONNECT_FAILED";
      job.finish({ok:false, connected:false, error_code:code, message:"WS 대기중: 로컬 host 미접속", act:job.act});
      setTimeout(igblueWsProcessQueue, 250);
    });
  }

  function igblueLocalWsRequest(act, data, timeoutMs, options){
    options = options || {};
    const priority = !!options.priority;
    if(options.force_probe === true && options.force === true){
      igblueWsBackoffUntil = 0;
    }
    data = Object.assign({}, data || {});
    timeoutMs = Math.max(priority ? 1800 : 800, Number(timeoutMs || 2500));
    return new Promise(function(resolve){
      const now0 = Date.now();
      if(!igblueWsShouldProbe(act, options)){
        const res = igblueWsOfflineResult(act);
        igblueNoteWsResult(res, act);
        resolve(res);
        return;
      }
      if(!priority && !options.force && igblueWsOptionalAct(act) && !igblueWsHealth.connected && (!igblueWsHealth.lastOkAt || (now0 - igblueWsHealth.lastOkAt) >= 15000)){
        const res = igblueWsOfflineResult(act);
        try { res.quiet_skip_probe = true; res.message = "WS 미접속 예상: optional 요청은 연결 생성 없이 생략"; } catch (_) {}
        igblueNoteWsResult(res, act);
        resolve(res);
        return;
      }
      if(!priority && !options.force && igblueWsHealth.lastFailAt && (now0 - igblueWsHealth.lastFailAt) < IGBLUE_WS_OFFLINE_CACHE_MS && now0 < igblueWsBackoffUntil){
        resolve(igblueWsOfflineResult(act));
        return;
      }
      if(igblueWsQueue.length > 24){
        if(priority){
          try{ igblueWsQueue = igblueWsQueue.filter(function(j){ return j && j.priority; }).slice(-12); }catch(_){}
        }else{
          const res = {ok:false, connected:!!igblueWsHealth.connected, error_code:"IGBLUE_BG_WS_QUEUE_FULL", message:"WS 요청이 많아 일시 생략", act, bg_queue_snapshot:igblueWsQueueSnapshot({queue_full:true, dropped_action:act})};
          igblueNoteWsResult(res, act);
          resolve(res);
          return;
        }
      }
      let done = false;
      let timer = 0;
      const job = {
        id:"igblue_bg_" + Date.now() + "_" + Math.floor(Math.random() * 100000),
        act,
        data:data || {},
        timeoutMs,
        priority,
        createdAt:now0,
        finish:function(res){
          if(done) return;
          done = true;
          try{ clearTimeout(timer); }catch(_){}
          res = res || {ok:false, error_code:"EMPTY_RESPONSE", act};
          igblueNoteWsResult(res, act);
          resolve(res);
        }
      };
      timer = setTimeout(function(){
        if(igblueWsCurrentJob === job) igblueWsCurrentJob = null;
        try{ igblueWsDisconnect("request_timeout"); }catch(_){}
        job.finish({ok:false, connected:false, error_code:"IGBLUE_BG_WS_TIMEOUT", message:"WS 응답 timeout", act});
        setTimeout(igblueWsProcessQueue, 250);
      }, timeoutMs);
      if(priority) igblueWsQueue.unshift(job); else igblueWsQueue.push(job);
      igblueWsProcessQueue();
    });
  }

  function igblueSendWsAndRespond(act, data, sendResponse, timeoutMs){
    igblueLocalWsRequest(act, data || {}, timeoutMs).then(function(res){
      try{ sendResponse(res || {ok:false, error_code:"EMPTY_RESPONSE", act}); }catch(e){}
    }).catch(function(err){
      try{ sendResponse({ok:false, error_code:"IGBLUE_BG_WS_EXCEPTION", error:String(err&&err.message||err), act}); }catch(e){}
    });
  }

  function igblueSafePart(value, fallback){
    return String(value || fallback || "unknown")
      .replace(/[\\/:*?"<>|]+/g, "_")
      .replace(/\u0000/g, "")
      .replace(/\s+/g, "_")
      .replace(/_+/g, "_")
      .replace(/^_+|_+$/g, "")
      .slice(0, 90) || String(fallback || "unknown");
  }

  // IGblue local patch v20: allow controlled subfolders such as p/SHORTCODE under the download root.
  function igblueSafePath(value, fallback){
    const raw = String(value || fallback || "instagram").replace(/\\+/g, "/");
    const parts = raw.split("/").map(function(part){ return igblueSafePart(part, ""); }).filter(Boolean);
    return parts.length ? parts.join("/") : igblueSafePart(fallback || "instagram", "instagram");
  }

  function igblueGuessExt(url, prefix, fallback){
    const p = String(prefix || "").split(/[?#]/)[0];
    const m0 = p.match(/\.([a-z0-9]{2,5})$/i);
    if(m0) return m0[1].toLowerCase();
    const u = String(url || "").split(/[?#]/)[0].toLowerCase();
    const m = u.match(/\.([a-z0-9]{2,5})$/i);
    if(m && !["com", "net", "org"].includes(m[1])) return m[1];
    if(u.includes("mp4") || u.includes("/video/") || u.includes("video")) return "mp4";
    return fallback || "jpg";
  }

  function igblueJoinDownloadPath(root, account, base, ext, accountFolderMerge){
    const r = igblueSafePart(root || "IGblue", "IGblue");
    const a = igblueSafePath(account || "instagram", "instagram");
    let b = igblueSafePart(base || "media", "media");
    const e = igblueSafePart(ext || "jpg", "jpg");
    if(!new RegExp("\\." + e + "$", "i").test(b)) b += "." + e;
    return accountFolderMerge ? `${r}/${b}` : `${r}/${a}/${b}`;
  }

  function igblueSafeFileName(value, fallback){
    let name = String(value || fallback || "media").split(/[\/]/).pop() || String(fallback || "media");
    try { name = decodeURIComponent(name); } catch(_) {}
    name = name.replace(/[\/:*?"<>|\u0000-\u001f]+/g, "_").replace(/[. ]+$/g, "").trim();
    if(!name) name = String(fallback || "media");
    return name.slice(0, 240);
  }

  function igblueResolvedMediaExt(msg, url){
    msg = msg && typeof msg === "object" ? msg : {};
    const meta = msg.meta && typeof msg.meta === "object" ? msg.meta : {};
    const assetKind = igblueAssetKindFromMeta(meta, url);
    if(assetKind === "video") return "mp4";
    let ext = igblueGuessExt(url, msg.prefix, msg.ext || "jpg");
    ext = String(ext || "jpg").toLowerCase();
    if(assetKind === "image" && !["jpg","jpeg","png","webp","avif"].includes(ext)) return "jpg";
    return ext;
  }

  function igblueSafeFileNameKeepExt(value, ext, fallback){
    const e = String(ext || "jpg").replace(/[^a-z0-9]/gi, "").toLowerCase() || "jpg";
    let raw = String(value || fallback || "media").split(/[\/]/).pop() || "media";
    try { raw = decodeURIComponent(raw); } catch(_) {}
    raw = raw.replace(/[\/:*?"<>|\u0000-\u001f]+/g, "_").replace(/[. ]+$/g, "").trim();
    raw = raw.replace(/\.[a-z0-9]{2,5}$/i, "");
    if(!raw) raw = String(fallback || "media").replace(/\.[a-z0-9]{2,5}$/i, "");
    const suffix = "." + e;
    const keep = Math.max(1, 240 - suffix.length);
    return raw.slice(0, keep) + suffix;
  }

  function igblueOriginalDownloadFileName(msg, url, ext){
    const meta = msg && msg.meta && typeof msg.meta === "object" ? msg.meta : {};
    let name = String(meta.instagram_original_filename_with_ext || meta.original_filename_with_ext || "").trim();
    if(!name){
      try{
        const u = new URL(String(url || ""));
        name = decodeURIComponent(String(u.pathname || "").split("/").filter(Boolean).pop() || "");
      }catch(_){ name = String(url || "").split(/[?#]/)[0].split("/").pop() || ""; }
    }
    return igblueSafeFileNameKeepExt(name, ext || "jpg", "media");
  }

  function igbluePollWsQueuedJob(jobId, options){
    options = options || {};
    const timeoutMs = Math.max(10000, Number(options.timeoutMs || 30 * 60 * 1000));
    const intervalMs = Math.max(200, Number(options.intervalMs || 500));
    const started = Date.now();
    return new Promise(function(resolve){
      function poll(){
        igblueLocalWsRequest("ig_thread_queue_result", {job_id:String(jobId || "")}, 2500, {priority:true, force:true, force_probe:true})
          .then(function(res){
            if(!(res && res.ok === true)){
              if(Date.now() - started >= timeoutMs){
                resolve({ok:false, error_code:"WS_DIRECT_DOWNLOAD_POLL_TIMEOUT", error:String(res && (res.error || res.message) || "queue_poll_failed")});
                return;
              }
              setTimeout(poll, intervalMs);
              return;
            }
            if(res.done === true){
              const result = res.result && typeof res.result === "object" ? res.result : {};
              if(result.ok === false || res.status === "error"){
                resolve(Object.assign({ok:false}, result, {queue_error:String(res.error || "")}));
              }else{
                resolve(Object.assign({ok:true}, result));
              }
              return;
            }
            setTimeout(poll, intervalMs);
          })
          .catch(function(err){
            if(Date.now() - started >= timeoutMs){
              resolve({ok:false, error_code:"WS_DIRECT_DOWNLOAD_POLL_EXCEPTION", error:String(err && err.message || err)});
              return;
            }
            setTimeout(poll, intervalMs);
          });
      }
      poll();
    });
  }

  function igblueSendDownloadProgress(payload){
    const message = Object.assign({type:"ndy_down_progress"}, payload || {});
    try{
      chrome.runtime.sendMessage(message, function(){ chrome.runtime.lastError; });
    }catch(e){}
    // v272: runtime.sendMessage updates Side/extension pages. Content scripts require
    // tabs.sendMessage, so forward the same status to known Instagram tabs as well.
    try{
      const ids = new Set();
      if(igblueActiveInstagramTabId) ids.add(Number(igblueActiveInstagramTabId));
      if(igblueLastInstagramTabId) ids.add(Number(igblueLastInstagramTabId));
      igblueInfoByTabId.forEach(function(_info, tabId){ if(tabId) ids.add(Number(tabId)); });
      ids.forEach(function(tabId){
        chrome.tabs.sendMessage(tabId, message, function(){ chrome.runtime.lastError; });
      });
    }catch(e){}
  }

  function igblueSendFeatureCheck(msg, checkId, status, messageKo, extra){
    try{
      const meta = msg && msg.meta && typeof msg.meta === "object" ? msg.meta : {};
      igblueSendDownloadProgress(Object.assign({
        status:"feature_check",
        feature_check_id:String(checkId || ""),
        feature_check_status:String(status || ""),
        feature_check_message_ko:String(messageKo || ""),
        feature_cycle_id:String(meta.client_batch_key || meta.cycle_id || msg && msg.batch_key || ""),
        rank_no:Number(meta.rank_no || 0) || 0,
        rank_label:String(meta.rank_label || ""),
        shortcode:igblueMetaShortcode(meta),
        asset_kind:igblueAssetKindFromMeta(meta, msg && msg.url),
        error_code:""
      }, extra || {}));
    }catch(_){}
  }

  function igblueMetaShortcode(meta){
    meta = meta && typeof meta === "object" ? meta : {};
    return String(meta.shortcode || meta.code || "").trim();
  }

  function igblueAssetKindFromMeta(meta, url){
    meta = meta && typeof meta === "object" ? meta : {};
    const raw = String(meta.asset_kind || meta.media_kind || meta.download_kind || "").toLowerCase();
    if(raw.includes("video") || raw.includes("mp4")) return "video";
    if(raw.includes("image") || raw.includes("jpg") || raw.includes("jpeg") || raw.includes("png")) return "image";
    const u = String(url || meta.media_url || "").toLowerCase();
    if(u.includes("mp4") || u.includes("video")) return "video";
    return "image";
  }


  function igblueIsPlaceholderAccount(value){
    const v = String(value || "").replace(/^@/, "").trim().toLowerCase();
    return !v || ["instagram", "explore", "explorer", "reel", "reels", "p", "home", "ig", "unknown", "undefined", "null", "none"].includes(v);
  }

  function igblueCleanOwnerAccount(value){
    const safe = igblueSafePart(String(value || "").replace(/^@/, ""), "");
    return igblueIsPlaceholderAccount(safe) ? "" : safe;
  }

  function igblueOwnerFromPrefix(prefix){
    const m = String(prefix || "").match(/^(.*?)_\d{8}_\d{6}(?:_No(?:\d+|없음))?_/);
    return m && m[1] ? igblueCleanOwnerAccount(m[1]) : "";
  }

  function igblueOwnerFromDownloadMsg(msg){
    const meta = msg && msg.meta && typeof msg.meta === "object" ? msg.meta : {};
    const choices = [meta.owner_username, meta.username, meta.ig_account_id, igblueOwnerFromPrefix(msg && msg.prefix)];
    for(const c of choices){
      const owner = igblueCleanOwnerAccount(c);
      if(owner) return owner;
    }
    return "";
  }

  function igblueUploadStampFromMeta(meta){
    meta = meta && typeof meta === "object" ? meta : {};
    const direct = String(meta.upload_stamp || "").trim();
    if(/^\d{8}_\d{6}$/.test(direct) || direct === "upload_unknown") return direct;
    const raw = String(meta.upload_at || meta.created_at || "").trim();
    const m = raw.match(/^(\d{4})[-/]?(\d{2})[-/]?(\d{2})(?:[ T_](\d{2}):?(\d{2}):?(\d{2}))?/);
    if(m) return `${m[1]}${m[2]}${m[3]}_${m[4] || "00"}${m[5] || "00"}${m[6] || "00"}`;
    return "upload_unknown";
  }

  function igblueIsSingleHref(href){
    try{
      const u = new URL(String(href || ""));
      const parts = String(u.pathname || "/").split("/").filter(Boolean);
      return !!(parts.length >= 2 && ["reel", "reels", "p"].includes(parts[0]));
    }catch(e){ return false; }
  }

  function igblueNormalizeDownloadPrefix(msg, ext){
    const meta = msg && msg.meta && typeof msg.meta === "object" ? msg.meta : {};
    const shortcode = igblueSafePart(meta.shortcode || meta.code || "", "");
    if(!shortcode){
      const prefixOwner = igblueOwnerFromPrefix(msg && msg.prefix);
      return prefixOwner ? igblueSafePart(msg && msg.prefix || "media", "media") : "";
    }
    const owner = igblueOwnerFromDownloadMsg(msg);
    if(!owner) return "";
    const upload = igblueUploadStampFromMeta(meta);
    let rankNo = Number(meta.rank_no || 0) || 0;
    if(!rankNo){
      const m = String(meta.rank_label || meta.rank || "").match(/No\s*0*(\d+)/i);
      if(m) rankNo = Number(m[1]) || 0;
    }
    if(!rankNo){
      // v99: final filename may arrive with rank only in the already-built prefix.
      // v170 inherited v169: rank가 없으면 _No없음_으로 정규화한다.
      const m = String(msg && msg.prefix || "").match(/(?:^|_)No\s*0*(\d+)(?:_|$)/i);
      if(m) rankNo = Number(m[1]) || 0;
    }
    const hrefForNo = (msg && (msg.href || meta.href || meta.page_url || meta.current_url || meta.media_url)) || "";
    // v170 inherited v169: rank가 없거나 단일/수동 다운로드이면 _No없음_을 사용한다.
    const noPart = rankNo > 0 ? `_No${String(rankNo).padStart(3, "0")}` : `_No없음`;
    const mediaIndex = Number(meta.media_index || 0) || 0;
    const totalMedia = Number(meta.total_media || 1) || 1;
    const idxPart = totalMedia > 1 ? `_${mediaIndex + 1}` : "";
    return igblueSafePart(`${owner}_${upload}${noPart}_${shortcode}${idxPart}`, "media");
  }

  function igblueLocalFilePathFromExists(existsRes){
    const local = existsRes && existsRes.local && typeof existsRes.local === "object" ? existsRes.local : {};
    if(existsRes && existsRes.file_path) return String(existsRes.file_path);
    if(local.file_path) return String(local.file_path);
    if(Array.isArray(existsRes && existsRes.file_paths) && existsRes.file_paths[0]) return String(existsRes.file_paths[0]);
    if(Array.isArray(local.file_paths) && local.file_paths[0]) return String(local.file_paths[0]);
    return "";
  }

  // v279aa: 수동/Filtered 다운로드는 Auto8700·WS8771 미실행 시에도 동작해야 한다.
  // 페이지 MAIN의 fetch/Blob/a.click()은 사용하지 않고 Background Chrome downloads API만 사용한다.
  // v316: 수동 다운로드에서 WS8771이 실제로 연결되지 않으면 Background가 파일명을 가공하거나 저장하지 않는다.
  // MAIN 원래 인젝션 downloader(shortcode + Content-Type 확장자)로 되돌리라는 신호만 반환한다.
  function igblueDownloadWithChromeFallback(msg, finish, reason){
    msg = msg && typeof msg === "object" ? msg : {};
    const meta = msg.meta && typeof msg.meta === "object" ? msg.meta : {};
    const url = String(msg.url || meta.media_url || meta.video_url || meta.image_url || "").trim();
    const shortcode = String(meta.shortcode || meta.code || "").trim();
    try{
      finish({
        status:"manual_main_fallback",
        ok:false,
        error_code:"IGBLUE_MANUAL_USE_MAIN_DOWNLOADER",
        message:String(reason || "WS8771 unavailable"),
        manual_main_fallback:true,
        url,
        shortcode,
        prefix:String(msg.prefix || ""),
        asset_kind:igblueAssetKindFromMeta(meta, url),
        media_index:meta.media_index != null ? meta.media_index : 0,
        total_media:meta.total_media != null ? meta.total_media : 1,
        meta
      });
    }catch(_){}
  }

  const IGBLUE_EDIT_COPY_ENABLED_KEY = "IGBLUE/edit_copy_enabled_v315";
  const IGBLUE_EDIT_FOLDER_SESSION_KEY = "IGBLUE/edit_folder_runtime_v318";
  function igbluePublishEditFolderRuntime(path){
    const folder = String(path || "").trim();
    const payload = {type:"IGBLUE_EDIT_FOLDER_RUNTIME", path:folder, available:!!folder, ts:new Date().toISOString()};
    try{
      if(chrome.storage && chrome.storage.session){
        if(folder) chrome.storage.session.set({[IGBLUE_EDIT_FOLDER_SESSION_KEY]:{path:folder, ts:payload.ts}}, function(){ chrome.runtime.lastError; });
        else chrome.storage.session.remove(IGBLUE_EDIT_FOLDER_SESSION_KEY, function(){ chrome.runtime.lastError; });
      }
    }catch(_){}
    try{ chrome.runtime.sendMessage(payload, function(){ chrome.runtime.lastError; }); }catch(_){}
  }
  function igblueManualSingleEditCopyEligible(msg, finalPath){
    const meta = msg && msg.meta && typeof msg.meta === "object" ? msg.meta : {};
    const sourceMode = String(meta.source_mode || msg && msg.source_mode || "").toUpperCase();
    const asset = igblueAssetKindFromMeta(meta, msg && msg.url);
    return sourceMode === "MANUAL_SINGLE" && asset === "video" && /\.mp4$/i.test(String(finalPath || ""));
  }
  function igblueMaybeCopyManualSingleToEditFolder(msg, finalPath){
    return new Promise(function(resolve){
      if(!igblueManualSingleEditCopyEligible(msg, finalPath)){ resolve({ok:false, skipped:true, reason:"not_manual_single_mp4"}); return; }
      try{
        chrome.storage.local.get(IGBLUE_EDIT_COPY_ENABLED_KEY, function(data){
          if(chrome.runtime.lastError || !(data && data[IGBLUE_EDIT_COPY_ENABLED_KEY] === true)){ resolve({ok:false, skipped:true, reason:"edit_copy_disabled"}); return; }
          igbluePublishEditFolderRuntime("");
          igblueLocalWsRequest("ig_copy_to_edit_folder", {file_paths:[String(finalPath)], reason:"manual_single_download_complete"}, 11 * 60 * 1000, {priority:true, force:true, force_probe:true}).then(function(res){
            if(res && res.ok && String(res.target_folder || "").trim()) igbluePublishEditFolderRuntime(res.target_folder);
            resolve(res || {ok:false, error:"empty_edit_copy_response"});
          }).catch(function(err){
            resolve({ok:false, error_code:"edit_copy_request_failed", error:String(err && err.message || err)});
          });
        });
      }catch(err){ resolve({ok:false, error_code:"edit_copy_storage_failed", error:String(err && err.message || err)}); }
    });
  }

  function igblueDownloadWithApi(msg, sendResponse){
    msg = msg && typeof msg === "object" ? msg : {};
    const url = String(msg.url || (msg.meta && (msg.meta.media_url || msg.meta.video_url || msg.meta.image_url)) || "").trim();
    if(!url){
      sendResponse({status:"error", message:"download_url_empty", error_code:"download_url_empty"});
      return;
    }
    const ext = igblueResolvedMediaExt(msg, url);
    const normalizedPrefix = igblueNormalizeDownloadPrefix(msg, ext);
    if(!normalizedPrefix){
      dbg("igblueDownloadWithApi", "owner_missing_blocked", 58, {prefix:msg&&msg.prefix, account:msg&&msg.account, meta:msg&&msg.meta});
      sendResponse({status:"error", message:"owner_missing: 계정명을 찾지 못해 다운로드를 보류했습니다. explore/reels/p는 계정명으로 사용하지 않습니다.", error_code:"owner_missing"});
      return;
    }
    const accountFolderMerge = false;
    const ownerAccount = igblueOwnerFromDownloadMsg(msg);
    const originalFileName = igblueOriginalDownloadFileName(msg, url, ext);
    const relativeFile = accountFolderMerge ? originalFileName : `${igblueSafePath(ownerAccount, "instagram")}/${originalFileName}`;
    dbg("igblueDownloadWithApi", "ws_direct_filename", 59, {ext, relativeFile, normalizedPrefix, ownerAccount, originalFileName, logicalFolder:msg.root || ""});
    let responded = false;
    const finish = (res) => {
      if(responded) return;
      responded = true;
      try{ sendResponse(res); }catch(e){}
    };
    igblueSendDownloadProgress({status:"start", file:relativeFile, loaded:0, total:0, percent:0, message:"WS8771 direct download", shortcode:igblueMetaShortcode(msg && msg.meta), asset_kind:igblueAssetKindFromMeta(msg && msg.meta, url), media_index:(msg && msg.meta && msg.meta.media_index != null ? msg.meta.media_index : 0), meta:msg && msg.meta || null});
    igblueLocalWsRequest("ig_media_download_direct", {
      root:"instagram",
      account:ownerAccount,
      account_folder_merge:false,
      filename:originalFileName,
      relative_file:relativeFile,
      prefix:normalizedPrefix || msg.prefix || "",
      url,
      href:msg.href || (msg.meta && (msg.meta.href || msg.meta.page_url)) || "",
      meta:Object.assign({}, msg.meta && typeof msg.meta === "object" ? msg.meta : {}, {account_folder_merge:false, collection_folder_name:"instagram"}),
      client_job_key:String(msg.client_job_key || (msg.meta && msg.meta.client_job_key) || ""),
      batch_key:String(msg.batch_key || (msg.meta && msg.meta.client_batch_key) || ""),
      event_uuid:String(msg.event_uuid || (msg.meta && msg.meta.client_event_id) || ""),
      thread_queue_mode:true
    }, 5000, {priority:true, force:true, force_probe:true}).then(function(queued){
      if(!(queued && queued.ok === true && queued.job_id)){
        const message = String(queued && (queued.error || queued.message) || "WS direct download queue failed");
        if(msg && msg.auto_direct_download){
          igblueSendFeatureCheck(msg, "AD-05", "[체크실패]", "WS8771 다운로드 큐 등록 실패 / 다음점검=ig_media_download_direct", {error_code:String(queued && queued.error_code || "ws_direct_download_queue_failed")});
          igblueSendDownloadProgress({status:"error", file:relativeFile, message});
          finish({status:"error", message, error_code:String(queued && queued.error_code || "ws_direct_download_queue_failed")});
        }else{
          igblueDownloadWithChromeFallback(msg, finish, message);
        }
        return;
      }
      if(msg && msg.auto_direct_download) igblueSendFeatureCheck(msg, "AD-05", "[체크완료]", "WS8771 다운로드 큐 등록 완료", {job_id:String(queued.job_id || "")});
      igbluePollWsQueuedJob(queued.job_id, {timeoutMs:30 * 60 * 1000, intervalMs:500}).then(function(result){
        if(!(result && result.ok === true && result.file_path)){
          const message = String(result && (result.error || result.message || result.detail) || "WS direct download failed");
          dbg("igblueDownloadWithApi", "ws_direct_failed", 100, {relativeFile, message, result});
          igblueSendDownloadProgress({status:"error", file:relativeFile, message});
          finish({status:"error", message, error_code:String(result && result.error_code || "ws_direct_download_failed")});
          return;
        }
        const finalPath = String(result.file_path || "");
        const total = Number(result.file_size || result.bytes_written || 0);
        dbg("igblueDownloadWithApi", "ws_direct_complete", 101, {relativeFile, finalPath, total});
        igblueSendDownloadProgress({
          status:"done",
          file:relativeFile,
          file_path:finalPath,
          shortcode:igblueMetaShortcode(msg && msg.meta),
          asset_kind:igblueAssetKindFromMeta(msg && msg.meta, url),
          media_index:(msg && msg.meta && msg.meta.media_index != null ? msg.meta.media_index : 0),
          loaded:total,
          total,
          percent:100,
          message:"WS8771 direct download complete"
        });
        const finalStatus = String(result.status || "ok") === "partial" ? "partial" : "ok";
        const finalize = result.finalize && typeof result.finalize === "object" ? result.finalize : {};
        const finalPayload = {
          status:finalStatus, id:0, filename:finalPath, file_path:finalPath, file_saved:true,
          storage_ok:result.finalize_ok !== false && finalize.storage_ok !== false,
          json_path:String(finalize.json_path || ""), json_saved:!!finalize.json_path,
          report_ok:!!finalize.report_ok, report_saved:!!(finalize.report_ok && finalize.report_path),
          report_path:String(finalize.report_path || ""), report_error:String(finalize.report_error || ""),
          media_saved:finalize.media_saved || null, notify_aiwk:finalize,
          shortcode:igblueMetaShortcode(msg && msg.meta), asset_kind:igblueAssetKindFromMeta(msg && msg.meta, url),
          media_index:(msg && msg.meta && msg.meta.media_index != null ? msg.meta.media_index : 0),
          meta:msg && msg.meta || null, direct_ws:true, result
        };
        igblueMaybeCopyManualSingleToEditFolder(msg, finalPath).then(function(editCopy){
          finalPayload.edit_copy = editCopy || null;
          finish(finalPayload);
        });
      });
    }).catch(function(err){
      const message = String(err && err.message || err || "WS direct download exception");
      if(msg && msg.auto_direct_download){
        igblueSendFeatureCheck(msg, "AD-05", "[체크실패]", "WS8771 직접 다운로드 요청 예외 / 다음점검=background WS 연결", {error_code:"ws_direct_download_exception"});
        igblueSendDownloadProgress({status:"error", file:relativeFile, message});
        finish({status:"error", message, error_code:"ws_direct_download_exception"});
      }else{
        igblueDownloadWithChromeFallback(msg, finish, message);
      }
    });
  }

  function igblueIsInstagramUrl(url){
    // IGblue local patch v9: treat any instagram.com / www.instagram.com URL as Instagram.
    // SPA navigation can report a new URL before tab.url is fully refreshed, so callers pass changeInfo.url when available.
    try{
      const u = new URL(String(url || ""));
      const host = u.hostname.replace(/^www\./i, "").toLowerCase();
      return (u.protocol === "https:" || u.protocol === "http:") && host === "instagram.com";
    }catch(e){
      return false;
    }
  }

  function igblueIsExtensionUrl(url){
    return String(url || "").startsWith(chrome.runtime.getURL(""));
  }

  function igblueTabState(tabId){
    // IGblue local patch: when tabId is explicitly 0, report disconnected instead of falling back to an old IG tab.
    const hasExplicitTab = arguments.length > 0;
    const id = Number(hasExplicitTab ? tabId : (igblueActiveInstagramTabId || 0));
    return {
      tabId: id || 0,
      url: id ? (igblueUrlByTabId.get(id) || "") : "",
      info: id ? (igblueInfoByTabId.get(id) || null) : null
    };
  }

  function igblueBroadcastTarget(tabId){
    try{
      const state = igblueTabState(tabId);
      chrome.runtime.sendMessage({
        type: "IGBLUE_TARGET_UPDATED",
        tabId: state.tabId,
        url: state.url,
        info: state.info
      }, function(){ chrome.runtime.lastError; });
    }catch(e){}
  }

  function igblueRememberTab(tab, makeActive, forcedUrl){
    if(!tab || !tab.id) return;
    const url = forcedUrl || tab.url || "";
    if(!igblueIsExtensionUrl(url) && /^https?:/i.test(String(url || ""))) igblueLastActiveWebTabId = tab.id;
    if(igblueIsInstagramUrl(url)){
      igblueLastInstagramTabId = tab.id;
      igblueUrlByTabId.set(tab.id, url);
      if(makeActive) igblueActiveInstagramTabId = tab.id;
      igblueBroadcastTarget(tab.id);
    }else if(makeActive && !igblueIsExtensionUrl(url)){
      // IGblue local patch: if the user activates a non-Instagram web tab, the detached popup should show it is not linked.
      igblueActiveInstagramTabId = 0;
      igblueBroadcastTarget(0);
    }
  }

  function igblueRefreshActiveFromFocusedWindow(){
    try{
      chrome.tabs.query({active:true, lastFocusedWindow:true}, function(tabs){
        if(chrome.runtime.lastError) return;
        const tab = tabs && tabs[0];
        igblueRememberTab(tab, true);
      });
    }catch(e){}
  }


  function igblueReadCurrentActiveInstagramTab(callback){
    try{
      chrome.tabs.query({active:true, lastFocusedWindow:true}, function(tabs){
        if(chrome.runtime.lastError) return callback(null);
        const tab = tabs && tabs[0];
        if(!tab || !tab.id || !igblueIsInstagramUrl(tab.url || "")) return callback(null);
        igblueRememberTab(tab, true, tab.url || "");
        callback(tab);
      });
    }catch(e){ callback(null); }
  }
  function igblueGetBestState(callback){
    igblueGetTabControlSnapshot(function(control){
      const ownerId = Number(control && control.owner_tab_id || 0) || 0;
      if(ownerId > 0) return callback(igblueTabState(ownerId));
      igblueReadCurrentActiveInstagramTab(function(tab){
        if(tab && tab.id) return callback(igblueTabState(tab.id));
        callback(igblueTabState(0));
      });
    });
  }

  function igbluePrepareCdpLifecyclePreload(bootstrapUrl, targetUrl, navigationMeta, callback){
    const meta = navigationMeta && typeof navigationMeta === "object" ? navigationMeta : {};
    const sessionId = `yellow_${Date.now()}_${Math.floor(Math.random()*100000)}`;
    const payload = {
      bootstrap_url:String(bootstrapUrl || ""),
      target_url:String(targetUrl || ""),
      session_id:sessionId,
      timeout:3.0,
      run_seq:String(meta.run_seq || ""),
      account:String(meta.account || ""),
      account_index:Number(meta.account_index || 0) || 0,
      account_total:Number(meta.account_total || 0) || 0
    };
    console.log("[CDP수명주기][CL-01][체크시작] Auto8700 선삽입 요청", payload);
    let settled = false;
    const finish = function(result){
      if(settled) return;
      settled = true;
      const out = Object.assign({ok:false, session_id:sessionId}, result || {});
      console.log(`[CDP수명주기][CL-01][${out.ok ? '체크완료' : '체크실패'}] Auto8700 선삽입 결과`, out);
      callback(out);
    };
    const timer = setTimeout(function(){ finish({ok:false,error:"cdp_lifecycle_preload_timeout"}); }, 13000);
    try{
      fetch(YELLOW_CDP_LIFECYCLE_PRELOAD_URL, {
        method:"POST",
        headers:{"Content-Type":"application/json"},
        body:JSON.stringify(payload),
        cache:"no-store"
      }).then(function(response){
        return response.json().then(function(data){ return {http_ok:response.ok,status:response.status,data:data}; });
      }).then(function(result){
        clearTimeout(timer);
        const data = result && result.data && typeof result.data === "object" ? result.data : {};
        finish(Object.assign({}, data, {ok:!!(result.http_ok && data.ok), http_status:Number(result.status || 0) || 0}));
      }).catch(function(error){
        clearTimeout(timer);
        finish({ok:false,error:String(error && error.message || error || "cdp_lifecycle_preload_fetch_failed")});
      });
    }catch(error){
      clearTimeout(timer);
      finish({ok:false,error:String(error && error.message || error || "cdp_lifecycle_preload_exception")});
    }
  }

  function igblueUnloadGuardMainWorld(context){
    const key = "__YELLOW_UNLOAD_GUARD_V279D_CLOSE__";
    const ctx = context && typeof context === "object" ? context : {};
    const pageLog = function(stage, detail){
      try{
        console.log("[YELLOW][UNLOAD-GUARD][" + String(stage || "LOG") + "]", Object.assign({
          href:String(location.href || ""),
          target_url:String(ctx.target_url || ""),
          reason:String(ctx.reason || ""),
          account_index:Number(ctx.account_index || 0) || 0,
          account_total:Number(ctx.account_total || 0) || 0,
          ts:new Date().toISOString()
        }, detail || {}));
      }catch(_){ }
    };
    const callStage = String(ctx.operation || "") === "close_previous_tab" ? "CALL_BEFORE_TAB_CLOSE" : "CALL_BEFORE_URL";
    pageLog(callStage, {phase:String(ctx.operation || "before_navigation")});
    const existing = window[key];
    if(existing && existing.installed){
      pageLog("REUSED", {blocked_add_count:Number(existing.blocked_add_count||0), blocked_property_count:Number(existing.blocked_property_count||0)});
      return {ok:true, installed:true, reused:true, blocked_add_count:Number(existing.blocked_add_count||0), blocked_property_count:Number(existing.blocked_property_count||0), href:String(location.href||"")};
    }
    const state = {installed:false, installed_at:Date.now(), blocked_add_count:0, blocked_property_count:0, add_event_listener_wrapped:false, onunload_blocked:false};
    try{
      pageLog("INSTALL_START", {});
      const proto = EventTarget && EventTarget.prototype;
      const original = proto && proto.addEventListener;
      if(typeof original !== "function"){
        pageLog("INSTALL_FAIL", {error:"add_event_listener_missing"});
        return {ok:false, installed:false, error:"add_event_listener_missing", href:String(location.href||"")};
      }
      if(!original.__yellowUnloadGuardWrapped){
        const wrapped = function(type, listener, options){
          if(String(type || "").toLowerCase() === "unload"){
            state.blocked_add_count += 1;
            try{
              console.log("[YELLOW][UNLOAD-GUARD][BLOCKED_ADD]", {
                href:String(location.href||""),
                count:state.blocked_add_count,
                stack:String(new Error("unload_registration_blocked").stack || ""),
                ts:new Date().toISOString()
              });
            }catch(_){ }
            return;
          }
          return original.call(this, type, listener, options);
        };
        try{ Object.defineProperty(wrapped, "__yellowUnloadGuardWrapped", {value:true}); }catch(_){ }
        try{ Object.defineProperty(wrapped, "__yellowUnloadGuardOriginal", {value:original}); }catch(_){ }
        proto.addEventListener = wrapped;
        state.add_event_listener_wrapped = true;
      }else{
        state.add_event_listener_wrapped = true;
      }
      try{
        Object.defineProperty(window, "onunload", {
          configurable:true,
          enumerable:true,
          get:function(){ return null; },
          set:function(){
            state.blocked_property_count += 1;
            try{
              console.log("[YELLOW][UNLOAD-GUARD][BLOCKED_PROPERTY]", {
                href:String(location.href||""),
                count:state.blocked_property_count,
                stack:String(new Error("onunload_assignment_blocked").stack || ""),
                ts:new Date().toISOString()
              });
            }catch(_){ }
          }
        });
        state.onunload_blocked = true;
      }catch(propertyError){
        state.onunload_error = String(propertyError && propertyError.message || propertyError || "onunload_define_failed");
      }
      state.installed = !!state.add_event_listener_wrapped;
      window[key] = state;
      pageLog(state.installed ? "INSTALL_OK" : "INSTALL_FAIL", {add_event_listener_wrapped:state.add_event_listener_wrapped, onunload_blocked:state.onunload_blocked, onunload_error:String(state.onunload_error||"")});
      return {ok:state.installed, installed:state.installed, reused:false, add_event_listener_wrapped:state.add_event_listener_wrapped, onunload_blocked:state.onunload_blocked, onunload_error:String(state.onunload_error||""), href:String(location.href||"")};
    }catch(error){
      state.error = String(error && error.message || error || "unload_guard_failed");
      try{ window[key] = state; }catch(_){ }
      pageLog("INSTALL_FAIL", {error:state.error});
      return {ok:false, installed:false, error:state.error, href:String(location.href||"")};
    }
  }

  function igblueInstallUnloadGuard(tabId, context, callback){
    try{
      const id = Number(tabId || 0) || 0;
      const ctx = context && typeof context === "object" ? context : {};
      console.log("[YELLOW][NAV][GUARD_REQUEST]", Object.assign({tabId:id}, ctx));
      if(!id || !chrome.scripting || !chrome.scripting.executeScript) return callback({ok:false, installed:false, error:"scripting_unavailable", tabId:id});
      chrome.scripting.executeScript({target:{tabId:id, allFrames:false}, world:"MAIN", func:igblueUnloadGuardMainWorld, args:[ctx]}, function(results){
        if(chrome.runtime.lastError){
          const failed = {ok:false, installed:false, error:String(chrome.runtime.lastError.message || "unload_guard_execute_failed"), tabId:id};
          console.log("[YELLOW][NAV][GUARD_RESULT]", failed);
          return callback(failed);
        }
        const result = results && results[0] && results[0].result || null;
        const payload = Object.assign({ok:!!(result && result.ok), installed:!!(result && result.installed), tabId:id}, result || {error:"unload_guard_empty_result"});
        console.log("[YELLOW][NAV][GUARD_RESULT]", payload);
        dbg("igblueInstallUnloadGuard", "result", 930, payload);
        callback(payload);
      });
    }catch(error){
      const failed = {ok:false, installed:false, error:String(error && error.message || error || "unload_guard_exception"), tabId:Number(tabId||0)||0};
      console.log("[YELLOW][NAV][GUARD_RESULT]", failed);
      callback(failed);
    }
  }

  function igblueNavigationTargetMatches(actualUrl, targetUrl){
    try{
      const actual = new URL(String(actualUrl || ""));
      const target = new URL(String(targetUrl || ""));
      const trim = function(pathname){ const value = String(pathname || "/").replace(/\/{2,}/g, "/"); return value.length > 1 ? value.replace(/\/+$/, "") : value; };
      return actual.origin === target.origin && trim(actual.pathname) === trim(target.pathname);
    }catch(_){ return false; }
  }

  function igblueWaitForNavigationReady(tabId, targetUrl, guardResult, navigationMeta, callback){
    const id = Number(tabId || 0) || 0;
    const meta = navigationMeta && typeof navigationMeta === "object" ? navigationMeta : {};
    const startedAt = Date.now();
    const timeoutMs = 30000;
    let finished = false;
    let attempts = 0;
    let ensureAttempted = false;
    let lastSignature = "";
    let last = {tab_status:"", actual_url:"", probe_error:"not_started", main_injection_ready:false};
    const emitState = function(stage, detail){
      try{
        const payload = Object.assign({stage, tabId:id, target_url:String(targetUrl||""), reason:String(meta.reason||""), attempts, elapsed_ms:Math.max(0,Date.now()-startedAt)}, detail || {});
        const signature = JSON.stringify([payload.stage,payload.tab_status,payload.actual_url,payload.probe_error,payload.main_injection_ready,payload.content_ready]);
        if(stage === "WAIT_STATE" && signature === lastSignature) return;
        lastSignature = signature;
        console.log("[YELLOW][NAV][" + stage + "]", payload);
      }catch(_){ }
    };
    const finish = function(payload){
      if(finished) return;
      finished = true;
      const result = Object.assign({tabId:id, url:String(targetUrl||""), unload_guard:guardResult || null, navigation_meta:meta, navigation_ready_ms:Math.max(0, Date.now()-startedAt), attempts}, payload || {});
      emitState(result.ok ? "READY" : "READY_FAIL", result);
      dbg("igblueWaitForNavigationReady", "finish", 952, result);
      callback(result);
    };
    const schedule = function(){ if(!finished) setTimeout(poll, 350); };
    const poll = function(){
      if(finished) return;
      attempts += 1;
      if(Date.now() - startedAt > timeoutMs){
        return finish({ok:false, status:"navigation_ready_timeout", error:"navigation_ready_timeout", recoverable:true, navigation_ready:last});
      }
      try{
        chrome.tabs.get(id, function(tab){
          if(chrome.runtime.lastError || !tab){
            last = Object.assign({}, last, {probe_error:String(chrome.runtime.lastError && chrome.runtime.lastError.message || "tab_get_failed")});
            emitState("WAIT_STATE", last);
            return schedule();
          }
          const actualUrl = String(tab.url || "");
          const status = String(tab.status || "");
          const sameTarget = igblueNavigationTargetMatches(actualUrl, targetUrl);
          last = Object.assign({}, last, {tab_status:status, actual_url:actualUrl, same_target:sameTarget});
          emitState("WAIT_STATE", last);
          if(status !== "complete" || !sameTarget) return schedule();
          chrome.tabs.sendMessage(id, {type:"igblue_navigation_ready_probe", expected_url:String(targetUrl||"")}, function(probe){
            const runtimeError = chrome.runtime.lastError && chrome.runtime.lastError.message || "";
            if(runtimeError || !(probe && probe.ok)){
              last = Object.assign({}, last, {probe_error:String(runtimeError || probe && probe.error || "navigation_probe_failed")});
              emitState("WAIT_STATE", last);
              if(!ensureAttempted && Date.now() - startedAt > 1800){
                ensureAttempted = true;
                return igblueEnsureContentScripts(id, function(){ schedule(); });
              }
              return schedule();
            }
            last = Object.assign({}, last, probe, {probe_error:""});
            emitState("WAIT_STATE", last);
            if(probe.same_target && probe.content_ready && probe.main_injection_ready){
              return finish({ok:true, status:"ready", navigation_ready:last});
            }
            schedule();
          });
        });
      }catch(error){
        last = Object.assign({}, last, {probe_error:String(error && error.message || error || "navigation_probe_exception")});
        emitState("WAIT_STATE", last);
        schedule();
      }
    };
    emitState("WAIT_START", {});
    poll();
  }

  function igblueRequestCdpTargetClose(targetId, expectedUrl, reason, callback){
    const id = String(targetId || "");
    if(!id) return callback({ok:false, closed:false, error:"managed_target_id_missing"});
    const payload = {target_id:id, expected_url:String(expectedUrl || ""), reason:String(reason || "managed_auto_tab_close")};
    console.log("[CDP탭종료][TC-01][체크시작] Auto8700 강제 종료 요청", payload);
    let settled = false;
    const finish = function(result){
      if(settled) return;
      settled = true;
      const out = Object.assign({ok:false, closed:false, target_id:id}, result || {});
      console.log(`[CDP탭종료][TC-02][${out.ok && out.closed ? '체크완료' : '체크실패'}] Auto8700 강제 종료 결과`, out);
      callback(out);
    };
    const timer = setTimeout(function(){ finish({ok:false, closed:false, error:"cdp_close_target_timeout"}); }, 12000);
    try{
      fetch(YELLOW_CDP_CLOSE_TARGET_URL, {
        method:"POST",
        headers:{"Content-Type":"application/json"},
        body:JSON.stringify(payload),
        cache:"no-store"
      }).then(function(response){
        return response.json().then(function(data){ return {http_ok:response.ok,status:response.status,data:data}; });
      }).then(function(result){
        clearTimeout(timer);
        const data = result && result.data && typeof result.data === "object" ? result.data : {};
        finish(Object.assign({}, data, {ok:!!(result.http_ok && data.ok), http_status:Number(result.status || 0) || 0}));
      }).catch(function(error){
        clearTimeout(timer);
        finish({ok:false, closed:false, error:String(error && error.message || error || "cdp_close_target_fetch_failed")});
      });
    }catch(error){
      clearTimeout(timer);
      finish({ok:false, closed:false, error:String(error && error.message || error || "cdp_close_target_exception")});
    }
  }

  function igblueCloseManagedAutoTab(tabId, replacementTabId, reason, callback){
    const id = Number(tabId || 0) || 0;
    const replacementId = Number(replacementTabId || 0) || 0;
    const meta = igblueManagedTabMeta.get(id) || {};
    if(!(id > 0) || id === replacementId){
      return callback({ok:true, closed:false, skipped:true, reason:"invalid_or_same_tab", tab_id:id, replacement_tab_id:replacementId});
    }
    if(meta.auto_created !== true){
      console.log("[CDP탭종료][TC-01][체크완료] 사용자/비관리 탭 보존", {tab_id:id,replacement_tab_id:replacementId,reason:String(reason||"")});
      return callback({ok:true, closed:false, skipped:true, preserved:true, reason:"not_managed_auto_tab", tab_id:id, replacement_tab_id:replacementId});
    }
    const targetId = String(meta.cdp_target_id || "");
    const expectedUrl = String(meta.url || igblueUrlByTabId.get(id) || "");
    const fallbackRemove = function(cdpResult){
      chrome.tabs.remove(id, function(){
        const removeError = String(chrome.runtime.lastError && chrome.runtime.lastError.message || "");
        if(removeError){
          if(/no tab with id|invalid tab id/i.test(removeError)){
            return callback({ok:true, closed:true, already_closed:true, tab_id:id, replacement_tab_id:replacementId, target_id:targetId, cdp_close:cdpResult || null, fallback:"chrome.tabs.remove"});
          }
          return callback({ok:false, closed:false, error:removeError, tab_id:id, replacement_tab_id:replacementId, target_id:targetId, cdp_close:cdpResult || null, fallback:"chrome.tabs.remove"});
        }
        callback({ok:true, closed:true, tab_id:id, replacement_tab_id:replacementId, target_id:targetId, cdp_close:cdpResult || null, fallback:"chrome.tabs.remove"});
      });
    };
    if(!targetId){
      console.log("[CDP탭종료][TC-01][체크실패] targetId 없음, tabs.remove 보조 종료", {tab_id:id,replacement_tab_id:replacementId,reason:String(reason||"")});
      return fallbackRemove({ok:false,closed:false,error:"managed_target_id_missing"});
    }
    igblueRequestCdpTargetClose(targetId, expectedUrl, reason, function(cdpResult){
      if(cdpResult && cdpResult.ok && cdpResult.closed){
        return setTimeout(function(){
          chrome.tabs.get(id, function(tab){
            const tabError = String(chrome.runtime.lastError && chrome.runtime.lastError.message || "");
            if(tabError || !tab){
              return callback({ok:true, closed:true, tab_id:id, replacement_tab_id:replacementId, target_id:targetId, cdp_close:cdpResult, fallback:"", tab_verified_removed:true});
            }
            console.log("[CDP탭종료][TC-02][예외추적] target 제거 후 Chrome tab 잔존, tabs.remove 보조 종료", {tab_id:id,replacement_tab_id:replacementId,target_id:targetId,url:String(tab.url||"")});
            fallbackRemove(Object.assign({}, cdpResult, {tab_verified_removed:false, tab_still_present:true}));
          });
        }, 180);
      }
      console.log("[CDP탭종료][TC-02][체크실패] CDP 종료 실패, tabs.remove 보조 종료", {tab_id:id,replacement_tab_id:replacementId,target_id:targetId,result:cdpResult||null});
      fallbackRemove(cdpResult || null);
    });
  }

  function igblueClosePreviousTabAfterReplacement(previousTab, replacementTabId, targetUrl, navigationMeta, callback){
    const previousId = Number(previousTab && previousTab.id || 0) || 0;
    const replacementId = Number(replacementTabId || 0) || 0;
    const meta = navigationMeta && typeof navigationMeta === "object" ? navigationMeta : {};
    igblueCloseManagedAutoTab(previousId, replacementId, String(meta.reason || "planned_handoff"), function(result){
      const out = Object.assign({previous_tab_id:previousId,replacement_tab_id:replacementId,target_url:String(targetUrl||"")}, result || {});
      console.log("[YELLOW][TAB-REPLACE][OLD_TAB_FORCE_CLOSE_RESULT]", out);
      callback(out);
    });
  }

  function igblueNavigateActiveWebTab(url, preferredTabId, navigationMeta, callback){
    const targetUrl = String(url || "");
    const meta = navigationMeta && typeof navigationMeta === "object" ? navigationMeta : {};
    if(!igblueIsInstagramUrl(targetUrl)) return callback({ok:false, error:"invalid_instagram_url", tabId:0, url:targetUrl});

    const replaceFromTab = function(previousTab){
      if(!previousTab || !previousTab.id) return callback({ok:false, error:"active_web_tab_not_found", tabId:0, url:targetUrl});
      const previousId = Number(previousTab.id || 0) || 0;
      const bootstrapToken = "yellow_cdp_bootstrap_v281_" + Date.now() + "_" + Math.floor(Math.random()*100000);
      const bootstrapUrl = "http://127.0.0.1:8707/cdp-bootstrap?token=" + encodeURIComponent(bootstrapToken);
      const createProperties = {
        url:bootstrapUrl,
        active:false,
        windowId:Number(previousTab.windowId || 0) || undefined,
        index:Math.max(0, Number(previousTab.index || 0) + 1)
      };
      if(createProperties.windowId === undefined) delete createProperties.windowId;
      console.log("[YELLOW][TAB-REPLACE][NEW_TAB_CREATE_BEFORE]", {
        previous_tab_id:previousId,
        previous_url:String(previousTab.url || ""),
        target_url:targetUrl,
        reason:String(meta.reason || ""),
        account_index:Number(meta.account_index || 0) || 0,
        account_total:Number(meta.account_total || 0) || 0
      });
      chrome.tabs.create(createProperties, function(created){
        if(chrome.runtime.lastError || !created || !created.id){
          return callback({
            ok:false,
            error:String(chrome.runtime.lastError && chrome.runtime.lastError.message || "replacement_tab_create_failed"),
            tabId:previousId,
            previousTabId:previousId,
            url:targetUrl
          });
        }
        const newTabId = Number(created.id || 0) || 0;
        console.log("[YELLOW][TAB-REPLACE][NEW_TAB_CREATED]", {
          previous_tab_id:previousId,
          new_tab_id:newTabId,
          target_url:targetUrl,
          bootstrap_url:bootstrapUrl,
          active:false,
          reason:String(meta.reason || "")
        });
        igbluePrepareCdpLifecyclePreload(bootstrapUrl, targetUrl, meta, function(preloadResult){
          const documentStartGuard = Object.assign({tabId:newTabId, source:"auto8700_cdp_preload_v281"}, preloadResult || {});
          if(!(preloadResult && preloadResult.ok)){
            console.log("[CDP수명주기][CL-02][체크실패] 선삽입 없이 기존 경로 계속", {new_tab_id:newTabId,target_url:targetUrl,result:preloadResult||null});
          }
          chrome.tabs.update(newTabId, {url:targetUrl}, function(navigated){
            if(chrome.runtime.lastError || !navigated){
              const navigationError = String(chrome.runtime.lastError && chrome.runtime.lastError.message || "replacement_tab_navigation_failed");
              console.log("[YELLOW][TAB-REPLACE][NEW_TAB_NAVIGATE_FAIL]", {previous_tab_id:previousId,new_tab_id:newTabId,target_url:targetUrl,error:navigationError,cdp_preload:preloadResult||null});
              chrome.tabs.remove(newTabId, function(){ chrome.runtime.lastError; });
              chrome.tabs.update(previousId, {active:true}, function(){ chrome.runtime.lastError; });
              return callback({ok:false,error:navigationError,tabId:previousId,previousTabId:previousId,replacementTabId:newTabId,previous_tab_preserved:true,url:targetUrl,cdp_preload:preloadResult||null});
            }
            console.log("[CDP수명주기][CL-03][체크완료] managed blank → Instagram 이동", {new_tab_id:newTabId,target_url:targetUrl,cdp_preload:preloadResult||null});
            igblueWaitForNavigationReady(newTabId, targetUrl, documentStartGuard, meta, function(readyResult){
          if(!(readyResult && readyResult.ok)){
            console.log("[YELLOW][TAB-REPLACE][NEW_TAB_READY_FAIL]", {
              previous_tab_id:previousId,
              new_tab_id:newTabId,
              target_url:targetUrl,
              result:readyResult || null
            });
            chrome.tabs.remove(newTabId, function(){ chrome.runtime.lastError; });
            chrome.tabs.update(previousId, {active:true}, function(){ chrome.runtime.lastError; });
            return callback(Object.assign({
              ok:false,
              error:String(readyResult && readyResult.error || "replacement_tab_not_ready"),
              tabId:previousId,
              previousTabId:previousId,
              replacementTabId:newTabId,
              previous_tab_preserved:true,
              url:targetUrl
            }, readyResult || {}));
          }
          console.log("[YELLOW][TAB-REPLACE][NEW_TAB_READY]", {
            previous_tab_id:previousId,
            new_tab_id:newTabId,
            target_url:targetUrl,
            navigation_ready:readyResult.navigation_ready || null,
            navigation_ready_ms:Number(readyResult.navigation_ready_ms || 0) || 0
          });
          chrome.tabs.update(newTabId, {active:true}, function(activated){
            if(chrome.runtime.lastError || !activated){
              const activationError = String(chrome.runtime.lastError && chrome.runtime.lastError.message || "replacement_tab_activate_failed");
              console.log("[YELLOW][TAB-REPLACE][NEW_TAB_ACTIVATE_FAIL]", {previous_tab_id:previousId, new_tab_id:newTabId, error:activationError});
              chrome.tabs.remove(newTabId, function(){ chrome.runtime.lastError; });
              chrome.tabs.update(previousId, {active:true}, function(){ chrome.runtime.lastError; });
              return callback({ok:false, error:activationError, tabId:previousId, previousTabId:previousId, replacementTabId:newTabId, previous_tab_preserved:true, url:targetUrl});
            }
            igblueLastActiveWebTabId = newTabId;
            igblueLastInstagramTabId = newTabId;
            igblueActiveInstagramTabId = newTabId;
            igblueUrlByTabId.set(newTabId, targetUrl);
            igblueBroadcastTarget(newTabId);
            console.log("[YELLOW][TAB-REPLACE][NEW_TAB_ACTIVATED]", {previous_tab_id:previousId, new_tab_id:newTabId, target_url:targetUrl});
            const handoff = igbluePrepareTabHandoff(previousTab, newTabId, targetUrl, meta, readyResult);
            console.log("[YELLOW][TAB-HANDOFF][WAIT_SIDE_ACK]", handoff);
            callback({
              ok:true,
              status:"handoff_ready",
              tabId:newTabId,
              url:targetUrl,
              previousTabId:previousId,
              replacementTabId:newTabId,
              previous_tab_closed:false,
              handoff_required:true,
              handoff_token:String(handoff && handoff.token || ""),
              generation:Number(handoff && handoff.generation || 0) || 0,
              navigation_ready:readyResult.navigation_ready || null,
              navigation_ready_ms:Number(readyResult.navigation_ready_ms || 0) || 0,
              attempts:Number(readyResult.attempts || 0) || 0,
              unload_guard:null,
              navigation_meta:meta
            });
          });
            });
          });
        });
      });
    };

    const preferred = Number(preferredTabId || 0) || 0;
    if(preferred > 0){
      return chrome.tabs.get(preferred, function(tab){
        if(!chrome.runtime.lastError && tab && tab.id) return replaceFromTab(tab);
        chooseActive();
      });
    }
    chooseActive();
    function chooseActive(){
      chrome.tabs.query({active:true, lastFocusedWindow:true}, function(tabs){
        const active = !chrome.runtime.lastError && tabs && tabs.find(function(tab){ return tab && tab.id && !igblueIsExtensionUrl(tab.url || ""); });
        if(active) return replaceFromTab(active);
        const fallback = Number(igblueLastActiveWebTabId || igblueLastInstagramTabId || 0) || 0;
        if(!fallback) return callback({ok:false, error:"active_web_tab_not_found", tabId:0, url:targetUrl});
        chrome.tabs.get(fallback, function(tab){
          if(chrome.runtime.lastError || !tab || !tab.id) return callback({ok:false, error:"active_web_tab_not_found", tabId:0, url:targetUrl});
          replaceFromTab(tab);
        });
      });
    }
  }

  
  function igblueEnsureContentScripts(tabId, callback){
    // IGblue local patch v11: if an Instagram tab was already open when the extension reloaded,
    // declared content scripts may not exist in that tab. Inject both bridge and MAIN script, then retry.
    try{
      tabId = Number(tabId || 0);
      if(!tabId || !chrome.scripting || !chrome.scripting.executeScript) return callback(false);
      chrome.scripting.executeScript({target:{tabId:tabId, allFrames:false}, files:["content_instagram.js"]}, function(){
        if(chrome.runtime.lastError) return callback(false);
        chrome.scripting.executeScript({target:{tabId:tabId, allFrames:true}, files:["js/instagram_inject.js"], world:"MAIN"}, function(){
          if(chrome.runtime.lastError) return callback(false);
          callback(true);
        });
      });
    }catch(e){ callback(false); }
  }

  function igblueRefreshPayState(){
    setTimeout(function(){
      if(!extpay || !extpay.getUser) return;
      extpay.getUser().then(function(user){
        let limit = 3;
        if(user && user.paid) limit = -1;
        chrome.storage.local.set({tg_li: limit}, function(){});
      }).catch(function(){});
    }, 2000);
  }

  chrome.sidePanel.setPanelBehavior({openPanelOnActionClick:true}).catch(function(){});

  chrome.tabs.onActivated.addListener(function(activeInfo){
    try{
      chrome.tabs.get(activeInfo.tabId, function(tab){
        if(chrome.runtime.lastError) return;
        igblueRememberTab(tab, true);
      });
    }catch(e){}
  });

  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
    try{
      if(changeInfo.url || changeInfo.status === "complete"){
        const nextUrl = changeInfo.url || (tab && tab.url) || "";
        if(changeInfo.url) igblueInfoByTabId.delete(tabId);
        if(tab && tab.active) igblueRememberTab(tab, true, nextUrl);
        else if(tab && igblueIsInstagramUrl(nextUrl)){
          igblueLastInstagramTabId = tabId;
          igblueUrlByTabId.set(tabId, nextUrl || "");
        }
      }
    }catch(e){}
  });

  chrome.tabs.onRemoved.addListener(function(tabId){
    const removedWasActive = igblueActiveInstagramTabId === tabId;
    const removedWasLast = igblueLastInstagramTabId === tabId;
    const removedWasOwner = Number(igblueTabControlState.owner_tab_id || 0) === Number(tabId || 0);
    const meta = igblueManagedTabMeta.get(Number(tabId || 0)) || {};
    igblueInfoByTabId.delete(tabId);
    igblueUrlByTabId.delete(tabId);
    if(meta && Object.keys(meta).length){
      igblueManagedTabMeta.set(Number(tabId || 0), Object.assign({}, meta, {state:"CLOSED", updated_at:Date.now()}));
      if(String(meta.state || "") !== "CLOSED"){
        igblueTabControlRecord("TAB_REMOVED", {
          tab_id:Number(tabId || 0) || 0,
          generation:Number(meta.generation || 0) || 0,
          run_seq:String(meta.run_seq || ""),
          account:String(meta.account || ""),
          reason:removedWasOwner ? "owner_tab_removed" : "tab_removed",
          close_reason:String(meta.close_reason || "")
        });
      }
    }
    if(removedWasOwner){
      igblueTabControlState.owner_tab_id = 0;
      igblueTabControlState.owner_generation = 0;
      igblueTabControlState.handoff_state = "OWNER_LOST";
      igblueTabControlPersist();
    }
    if(removedWasActive) igblueActiveInstagramTabId = 0;
    if(removedWasLast) igblueLastInstagramTabId = 0;
    if(igblueActiveInstagramTabId > 0) igblueBroadcastTarget(igblueActiveInstagramTabId);
    else if(removedWasActive) igblueRefreshActiveFromFocusedWindow();
  });

  chrome.windows.onFocusChanged.addListener(function(windowId){
    if(windowId === chrome.windows.WINDOW_ID_NONE) return;
    igblueRefreshActiveFromFocusedWindow();
  });

  chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse){
    dbg("runtime_onMessage", "recv", 266, {type:msg&&msg.type, senderTab:sender&&sender.tab&&sender.tab.id, url:msg&&msg.url, prefix:msg&&msg.prefix, root:msg&&msg.root, account:msg&&msg.account, info_count:msg&&Array.isArray(msg.info)?msg.info.length:null});
    if(msg && msg.type === "IGBLUE_CDP_STATE_UPDATE"){
      const state = yellowSetCdpState(msg.state || msg.detail || {}, true);
      sendResponse({ok:true, state});
      return true;
    }

    if(msg && msg.type === "IGBLUE_GET_CDP_STATE"){
      sendResponse({ok:true, state:Object.assign({}, yellowCdpState)});
      return true;
    }

    if(msg && msg.type === "YELLOW_CDP_LIFECYCLE_DIAG"){
      const tabId = sender && sender.tab && sender.tab.id ? Number(sender.tab.id) : 0;
      const row = yellowRecordCdpLifecycleDiag(tabId, msg.detail || {});
      sendResponse({ok:!!row, tab_id:tabId, recorded:!!row});
      return true;
    }

    if(msg && msg.type === "IGBLUE_CONTENT_READY"){
      const tabId = sender && sender.tab && sender.tab.id ? sender.tab.id : 0;
      if(tabId){
        igblueLastInstagramTabId = tabId;
        if(sender.tab && sender.tab.url) igblueUrlByTabId.set(tabId, sender.tab.url);
        if(sender.tab && sender.tab.active) igblueActiveInstagramTabId = tabId;
        if(sender.tab && sender.tab.active) igblueBroadcastTarget(tabId);
      }
      sendResponse({status:"ok", tabId:tabId || 0});
      return true;
    }



    if(msg && msg.type === "IGBLUE_SAVE_VISUAL_SETTINGS"){
      const settings = msg.settings && typeof msg.settings === "object" ? msg.settings : {};
      try{
        chrome.storage.local.set({"ndy_ig_visual_settings": settings}, function(){
          if(chrome.runtime.lastError){
            sendResponse({ok:false, error:String(chrome.runtime.lastError.message || "visual_settings_save_failed")});
            return;
          }
          sendResponse({ok:true, saved:true});
        });
      }catch(e){
        sendResponse({ok:false, error:String(e && e.message || e)});
      }
      return true;
    }

    if(msg && msg.type === "IGBLUE_AUTO8700_SCHEDULE_MIRROR"){
      try { chrome.storage.local.set({"IGBLUE_AUTO8700_SCHEDULE": msg.schedule || {}, "IGBLUE_AUTO8700_SCHEDULE_MIRRORED_AT": Date.now()}); } catch(e) {}
      sendResponse({ok:true, status:"mirrored"});
      return true;
    }

    if(msg && msg.type === "IGBLUE_AUTO8700_START_REQUEST"){
      const tabId = sender && sender.tab && sender.tab.id ? sender.tab.id : (igblueActiveInstagramTabId || igblueLastInstagramTabId || 0);
      const req = Object.assign({
        type:"IGBLUE_AUTO8700_START_REQUEST",
        source:"Auto8700",
        bg_received_at:new Date().toISOString(),
        request_id:"auto8700_bg_" + Date.now() + "_" + Math.floor(Math.random()*100000),
        tabId:tabId || 0,
        url:(sender && sender.tab && sender.tab.url) || igblueUrlByTabId.get(tabId) || ""
      }, msg || {});
      if(!req.request_id) req.request_id = "auto8700_bg_" + Date.now() + "_" + Math.floor(Math.random()*100000);
      req.tabId = tabId || Number(req.tabId || 0) || 0;
      const isDuplicateAuto8700Start = !!(req.request_id && req.request_id === igblueAuto8700LastStartRequestId);
      igblueAuto8700LastStartRequestId = String(req.request_id || "");
      try { chrome.storage.local.set({"IGBLUE_AUTO8700_START_REQUEST": req}); } catch(e) {}
      try { chrome.runtime.sendMessage(req, function(){ chrome.runtime.lastError; }); } catch(e) {}
      if(!isDuplicateAuto8700Start){
        try {
          if(req.open_side !== false && chrome.sidePanel && chrome.sidePanel.open && req.tabId){
            chrome.sidePanel.open({tabId:req.tabId}).catch(function(){ chrome.runtime.lastError; });
          }
        } catch(e) {}
        try {
          if(req.open_side !== false){
            chrome.windows.create({url:chrome.runtime.getURL("side.html?popup=1&auto8700_start=1"), type:"popup", width:520, height:860}, function(){ chrome.runtime.lastError; });
          }
        } catch(e) {}
      }
      sendResponse({ok:true, status:isDuplicateAuto8700Start ? "duplicate" : "queued", request_id:req.request_id, tabId:req.tabId});
      return true;
    }

    if(msg && msg.type === "IGBLUE_AUTO8700_SIDE_OPEN_REQUEST"){
      const tabId = sender && sender.tab && sender.tab.id ? sender.tab.id : (igblueActiveInstagramTabId || igblueLastInstagramTabId || 0);
      const req = Object.assign({
        type:"IGBLUE_AUTO8700_SIDE_OPEN_REQUEST",
        source:"Auto8700",
        bg_received_at:new Date().toISOString(),
        request_id:"auto8700_side_bg_" + Date.now() + "_" + Math.floor(Math.random()*100000),
        tabId:tabId || 0,
        url:(sender && sender.tab && sender.tab.url) || igblueUrlByTabId.get(tabId) || ""
      }, msg || {});
      req.tabId = tabId || Number(req.tabId || 0) || 0;
      try { chrome.storage.local.set({"IGBLUE_AUTO8700_SIDE_OPEN_REQUEST": req}); } catch(e) {}
      if(chrome.sidePanel && chrome.sidePanel.open && req.tabId){
        try {
          chrome.sidePanel.open({tabId:req.tabId}).then(function(){
            sendResponse({ok:true, status:"side_open_requested", request_id:req.request_id, tabId:req.tabId});
          }).catch(function(err){
            sendResponse({ok:false, status:"side_open_failed", error:String(err && err.message || err), request_id:req.request_id, tabId:req.tabId});
          });
          return true;
        } catch(e) {
          sendResponse({ok:false, status:"side_open_exception", error:String(e && e.message || e), request_id:req.request_id, tabId:req.tabId});
          return true;
        }
      }
      sendResponse({ok:false, status:"side_panel_api_unavailable", request_id:req.request_id, tabId:req.tabId});
      return true;
    }

    if(msg && msg.type === "ndy_bg_ig"){
      dbg("ndy_bg_ig", "entry", 279, {count:Array.isArray(msg.info)?msg.info.length:null, first:Array.isArray(msg.info)&&msg.info[0]?{code:msg.info[0].code,has_video_url:!!msg.info[0].video_url,has_img_origin:!!msg.info[0].img_origin,play_count:msg.info[0].play_count}:null});
      const tabId = sender && sender.tab && sender.tab.id ? sender.tab.id : 0;
      if(tabId){
        igblueLastInstagramTabId = tabId;
        igblueInfoByTabId.set(tabId, msg.info || null);
        if(sender.tab && sender.tab.url) igblueUrlByTabId.set(tabId, sender.tab.url);
        if(sender.tab && sender.tab.active) igblueActiveInstagramTabId = tabId;
      }
      igblueLastShopInfo = msg.info || null;
      try{
        chrome.runtime.sendMessage({type:"ndy_side_shop", info:msg.info, tabId:tabId || 0, url:(sender && sender.tab && sender.tab.url) || igblueUrlByTabId.get(tabId) || "", source_url:(sender && sender.tab && sender.tab.url) || igblueUrlByTabId.get(tabId) || "", sent_at:new Date().toISOString()}, function(){ chrome.runtime.lastError; });
        igblueBroadcastTarget(tabId || igblueActiveInstagramTabId || igblueLastInstagramTabId);
      }catch(e){}
      sendResponse({status:0});
      return true;
    }

    if(msg && msg.type === "down"){
      chrome.storage.local.get("dcount", function(data){
        if(data && data.dcount && data.dcount > 0){
          const count = data.dcount + 1;
          chrome.storage.local.set({dcount:count}, function(){ sendResponse({status:count}); });
        }else{
          chrome.storage.local.set({dcount:1}, function(){ sendResponse({status:1}); });
        }
      });
      return true;
    }

    if(msg && msg.type === "getc"){
      chrome.storage.local.get("dcount", function(data){
        sendResponse({status:data && data.dcount && data.dcount > 0 ? data.dcount : 0});
      });
      return true;
    }

    if(msg && msg.type === "getl"){
      chrome.storage.local.get("tg_li", function(data){ sendResponse({status:data.tg_li}); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_OPEN_POPUP"){
      const requestId = String(msg.request_id || ("side_handoff_" + Date.now() + "_" + Math.floor(Math.random()*100000)));
      const requestedTabId = Number(msg.tabId || 0) || igblueActiveInstagramTabId || igblueLastInstagramTabId || 0;
      const url = chrome.runtime.getURL("side.html?popup=1&handoff=" + encodeURIComponent(requestId));
      igbluePopupHandoffs.set(requestId, { request_id:requestId, tab_id:requestedTabId, created_at:Date.now(), popup_window_id:0 });
      chrome.windows.create({url:url, type:"popup", width:760, height:900, focused:true}, function(win){
        const ok = !chrome.runtime.lastError && !!win;
        if(ok){ const pending = igbluePopupHandoffs.get(requestId); if(pending){ pending.popup_window_id = Number(win.id || 0); igbluePopupHandoffs.set(requestId, pending); } }
        else igbluePopupHandoffs.delete(requestId);
        sendResponse({status: ok ? "ok" : "error", request_id:requestId, popup_window_id:ok ? Number(win.id || 0) : 0});
      });
      return true;
    }

    if(msg && msg.type === "IGBLUE_POPUP_READY"){
      const requestId = String(msg.request_id || "");
      const pending = igbluePopupHandoffs.get(requestId);
      const readyTabId = Number(msg.tabId || 0) || 0;
      if(!pending){ sendResponse({ok:false, status:"handoff_not_found"}); return true; }
      if(!(readyTabId > 0) || (pending.tab_id > 0 && readyTabId !== pending.tab_id)){
        sendResponse({ok:false, status:"handoff_tab_mismatch", expected_tab_id:Number(pending.tab_id || 0), tab_id:readyTabId});
        return true;
      }
      const finish = function(ok, status, error){
        if(ok) igbluePopupHandoffs.delete(requestId);
        sendResponse({ok:!!ok, status:status, error:String(error || ""), request_id:requestId, tab_id:readyTabId});
      };
      try{
        if(chrome.sidePanel && typeof chrome.sidePanel.close === "function"){
          // v277: tab 전용 Side를 먼저 닫고, Chrome 145+에서 global panel이라 tabId close가
          // 거부되는 경우에는 원래 Instagram 탭의 windowId로 한 번만 재시도한다.
          Promise.resolve(chrome.sidePanel.close({tabId:readyTabId})).then(function(){
            finish(true, "popup_ready_side_closed", "");
          }).catch(function(tabErr){
            try{
              chrome.tabs.get(readyTabId, function(tab){
                if(chrome.runtime.lastError || !tab || !(Number(tab.windowId || 0) > 0)){
                  finish(false, "side_close_failed", tabErr && tabErr.message || tabErr);
                  return;
                }
                Promise.resolve(chrome.sidePanel.close({windowId:Number(tab.windowId)})).then(function(){
                  finish(true, "popup_ready_global_side_closed", "");
                }).catch(function(windowErr){
                  finish(false, "side_close_failed", windowErr && windowErr.message || windowErr || tabErr);
                });
              });
            }catch(windowLookupErr){ finish(false, "side_close_failed", windowLookupErr && windowLookupErr.message || windowLookupErr || tabErr); }
          });
          return true;
        }
      }catch(err){ finish(false, "side_close_exception", err && err.message || err); return true; }
      finish(false, "side_close_api_unavailable", "chrome.sidePanel.close unavailable");
      return true;
    }

    if(msg && msg.type === "IGBLUE_RTC_HEALTH_HINT"){
      igblueWsHealthHint(!!msg.connected, msg || {});
      sendResponse(igblueWsStatusPayload({cached:true, hint:true}));
      return true;
    }

    if(msg && msg.type === "IGBLUE_GET_TAB_CONTROL_STATE"){
      igblueGetTabControlSnapshot(function(state){ sendResponse(state); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_CLAIM_TAB_OWNER"){
      igblueClaimTabOwner(msg.tabId, {
        run_seq:String(msg.run_seq || ""),
        account:String(msg.account || ""),
        account_index:Number(msg.account_index || 0) || 0,
        account_total:Number(msg.account_total || 0) || 0,
        reason:String(msg.reason || "side_claim_owner")
      }, function(result){ sendResponse(result); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_COMMIT_TAB_HANDOFF"){
      igblueCommitTabHandoff(msg.handoff_token, {
        run_seq:String(msg.run_seq || ""),
        account:String(msg.account || ""),
        account_index:Number(msg.account_index || 0) || 0,
        account_total:Number(msg.account_total || 0) || 0
      }, function(result){ sendResponse(result); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_CLOSE_MANAGED_AUTO_TAB"){
      igblueCloseManagedAutoTab(msg.tabId, 0, String(msg.reason || "side_requested_close"), function(result){ sendResponse(result); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_NAVIGATE_ACTIVE_TAB"){
      igblueNavigateActiveWebTab(msg.url, msg.tabId, {
        reason:String(msg.reason||""),
        account:String(msg.account || ""),
        run_seq:String(msg.run_seq || ""),
        account_index:Number(msg.account_index||0)||0,
        account_total:Number(msg.account_total||0)||0
      }, function(result){ sendResponse(result); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_GET_TARGET_TAB"){
      igblueGetBestState(function(state){ sendResponse({tabId:state.tabId, url:state.url}); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_GET_TARGET_STATE"){
      igblueGetBestState(function(state){ sendResponse(state); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_GET_LAST_INFO"){
      igblueGetBestState(function(state){ sendResponse({info:state.info || igblueLastShopInfo}); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_ENSURE_CONTENT"){
      const tabId = Number(msg.tabId || 0);
      igblueEnsureContentScripts(tabId, function(ok){ sendResponse({status: ok ? "ok" : "error"}); });
      return true;
    }

    if(msg && msg.type === "IGBLUE_RTC_STATUS"){
      const now = Date.now();
      const freshOk = igblueWsHealth.connected && igblueWsHealth.lastOkAt && (now - igblueWsHealth.lastOkAt) < 5000;
      const freshFail = !igblueWsHealth.connected && igblueWsHealth.lastFailAt && (now - igblueWsHealth.lastFailAt) < IGBLUE_WS_OFFLINE_CACHE_MS;
      if(!msg.force && (freshOk || freshFail)){
        sendResponse(igblueWsStatusPayload({cached:true}));
      }else{
        igblueLocalWsRequest("status", {reason:"rtc_status_check", href:msg.href||"", host:msg.host||""}, 1200, {force_probe:!!msg.force_probe}).then(function(res){
          sendResponse(igblueWsStatusPayload({cached:false, raw:res || null}));
        }).catch(function(err){
          igblueNoteWsResult({ok:false,error_code:"IGBLUE_BG_WS_EXCEPTION",error:String(err&&err.message||err)}, "status");
          sendResponse(igblueWsStatusPayload({cached:false}));
        });
      }
      return true;
    }

    if(msg && msg.type === "IGBLUE_PY_SETTING"){
      const data = Object.assign({bg_received_at:new Date().toISOString()}, msg.data || {});
      igblueSendWsAndRespond("ig_rtc_py_setting", data, sendResponse, 1800);
      return true;
    }

    if(msg && msg.type === "IGBLUE_DOWNLOAD_URL"){
      dbg("runtime_onMessage", "IGBLUE_DOWNLOAD_URL", 361, {url:String(msg.url||"").slice(0,260), prefix:msg.prefix, root:msg.root, account:msg.account});
      // v86: status 선검사로 "로컬 중복관리 없음"을 오판하지 않는다.
      // 먼저 실제 중복 체크 ig_media_exists를 요청하고, 그 요청이 실패할 때만 다운로드를 차단한다.
      if(msg && msg.auto_direct_download){
        igblueSendFeatureCheck(msg, "AD-04", "[체크시작]", "WS8771 중복 확인 연결 시작");
      }
      const precheckOptions = msg && msg.auto_direct_download ? {priority:true, force:true, force_probe:true} : {};
      igblueLocalWsRequest("ig_media_exists", {root:"instagram", account:msg.account||igblueOwnerFromDownloadMsg(msg)||"instagram", account_folder_merge:false, prefix:msg.prefix||"", url:msg.url||"", href:msg.href||"", meta:Object.assign({}, msg.meta||{}, {account_folder_merge:false, collection_folder_name:"instagram"})}, 8000, precheckOptions).then(function(existsRes){
        if(!existsRes || existsRes.ok === false || igblueWsLooksOffline(existsRes)){
          if(msg && msg.auto_direct_download){
            const message = "자동 직접 다운로드: WS 중복/완료 처리가 준비되지 않아 파일 저장을 시작하지 않습니다.";
            igblueSendFeatureCheck(msg, "AD-04", "[체크실패]", "WS8771 중복 확인 연결 실패 / 다음점검=WS8771 실행·8771 포트", {error_code:String(existsRes && existsRes.error_code || "AUTO_DIRECT_WS_NOT_READY")});
            igblueSendDownloadProgress({status:"error", file:msg.prefix || "", message, shortcode:igblueMetaShortcode(msg.meta), asset_kind:igblueAssetKindFromMeta(msg.meta, msg.url), media_index:(msg.meta&&msg.meta.media_index!=null?msg.meta.media_index:0), meta:msg.meta||null});
            try{ sendResponse({status:"error", ok:false, error_code:"AUTO_DIRECT_WS_NOT_READY", message, file_saved:false, json_saved:false, report_saved:false, exists:existsRes||null}); }catch(e){}
            return;
          }
          const message = "WS8771 미실행: 원래 인젝션 수동 다운로드로 전환합니다.";
          igblueDownloadWithChromeFallback(msg, sendResponse, message);
          return;
        }
        if(msg && msg.auto_direct_download){
          igblueSendFeatureCheck(msg, "AD-04", "[체크완료]", "WS8771 중복 확인 연결 정상");
        }
        const local = existsRes && existsRes.local && typeof existsRes.local === "object" ? existsRes.local : {};
        const matchBy = String((existsRes && existsRes.match_by) || local.match_by || "");
        const exists = !!(existsRes && (existsRes.exists === 1 || existsRes.exists === true || local.exists));
        const localFilePathForDup = igblueLocalFilePathFromExists(existsRes);
        // v86: 서버/yellow 중복만으로는 다운로드를 막지 않는다. 로컬 파일/index가 확실할 때만 차단한다.
        const duplicateConfirmed = !(existsRes && existsRes.duplicate_confirmed === false) && !(local && local.duplicate_confirmed === false);
        const strong = duplicateConfirmed && (!!(local && local.exists) || !!localFilePathForDup);
        if(strong){
          const shortcode = String((existsRes && existsRes.shortcode) || igblueMetaShortcode(msg.meta) || "");
          const assetKind = String((existsRes && existsRes.asset_kind) || igblueAssetKindFromMeta(msg.meta, msg.url) || "");
          const mediaIndex = (existsRes && existsRes.media_index != null) ? existsRes.media_index : (msg.meta && msg.meta.media_index != null ? msg.meta.media_index : 0);
          const filePath = localFilePathForDup || igblueLocalFilePathFromExists(existsRes);
          const jsonPath = String((existsRes && existsRes.json_path) || (local && local.json_path) || "");
          const isLocalDup = !!(local && local.exists);
          const message = `${isLocalDup ? "이미 다운로드됨" : "서버 중복 확인됨"}: shortcode=${shortcode || "?"} ${assetKind || "asset"}[${mediaIndex}]${filePath ? " / " + filePath : ""}`;
          const refresh = existsRes && existsRes.metadata_refresh && typeof existsRes.metadata_refresh === "object" ? existsRes.metadata_refresh : {};
          const report = refresh.report && typeof refresh.report === "object" ? refresh.report : {};
          igblueSendDownloadProgress({status:"already_done", file:filePath || msg.prefix || "", file_path:filePath, file_paths:(existsRes&&existsRes.file_paths)||local.file_paths||[], json_path:jsonPath, report_ok:!!report.ok, report_path:String(report.report_path||""), shortcode, asset_kind:assetKind, media_index:mediaIndex, metadata_updated:!!(existsRes&&existsRes.metadata_updated), local, exists:existsRes, message});
          try{ sendResponse({status:"already_done", ok:true, error_code:"IGBLUE_ALREADY_DOWNLOADED", message, shortcode, asset_kind:assetKind, media_index:mediaIndex, file_path:filePath, file_paths:(existsRes&&existsRes.file_paths)||local.file_paths||[], file_saved:!!filePath, json_path:jsonPath, json_saved:!!jsonPath, report_ok:!!report.ok, report_saved:!!(report.ok&&report.report_path), report_path:String(report.report_path||""), report_error:String(report.error||""), metadata_updated:!!(existsRes&&existsRes.metadata_updated), exists:existsRes, meta:msg.meta||null}); }catch(e){}
          return;
        }
        igblueDownloadWithApi(msg, sendResponse);
      }).catch(function(err){
        const detail = String(err&&err.message||err);
        if(msg && msg.auto_direct_download){
          const message = "자동 직접 다운로드 중복 확인 예외: 파일 저장을 시작하지 않습니다. " + detail;
          igblueSendFeatureCheck(msg, "AD-04", "[체크실패]", "WS8771 중복 확인 예외 / 다음점검=background WS 연결", {error_code:"AUTO_DIRECT_PRECHECK_EXCEPTION"});
          igblueSendDownloadProgress({status:"error", file:msg.prefix || "", message, shortcode:igblueMetaShortcode(msg.meta), asset_kind:igblueAssetKindFromMeta(msg.meta, msg.url), media_index:(msg.meta&&msg.meta.media_index!=null?msg.meta.media_index:0), meta:msg.meta||null});
          try{ sendResponse({status:"error", ok:false, error_code:"AUTO_DIRECT_PRECHECK_EXCEPTION", message, file_saved:false, json_saved:false, report_saved:false}); }catch(e){}
          return;
        }
        const message = "WS8771 중복 확인 예외: 원래 인젝션 수동 다운로드로 전환합니다. " + detail;
        igblueDownloadWithChromeFallback(msg, sendResponse, message);
      });
      return true;
    }

    if(msg && (msg.type === "IGBLUE_OPEN_LOCAL_FILE" || msg.type === "IGBLUE_OPEN_LOCAL_FOLDER")){
      const path = String(msg.path || msg.file_path || "");
      const act = msg.type === "IGBLUE_OPEN_LOCAL_FOLDER" ? "ig_open_folder" : "ig_open_file";
      igblueLocalWsRequest(act, {path}, 5000, {force:true}).then(function(res){
        try{ sendResponse(res || {ok:false,error:"empty_response"}); }catch(e){}
      }).catch(function(err){
        try{ sendResponse({ok:false,error:String(err&&err.message||err)}); }catch(e){}
      });
      return true;
    }

    if(msg && msg.target === "BACKGROUND"){
      if(msg.type === "OPEN PAYMENT PAGE" || msg.type === "OPEN LOGIN PAGE" || msg.type === "OPEN TRIAL PAGE"){
        sendResponse({status:"disabled"});
        return true;
      }
    }

    return false;
  });
 


  // IGblue v172-v178 patch: chrome.storage.local -> Auto8700 8707 /event bridge.
  const IGBLUE_AUTO8700_EVENT_URL = "http://127.0.0.1:8707/event";
  const IGBLUE_CACHE8701_SETTINGS_URL = "http://127.0.0.1:8701/api/settings";
  const IGBLUE_AUTO8700_DEVTOOLS_KEYS = new Set(["devtools_network_enabled", "devtools_console", "devtools_file_log", "devtools_capture_body", "devtools_verbose", "devtools_print_media", "devtools_print_json", "devtools_print_graphql", "ig_auto8700_devtools_settings", "ig_manual_click_mode_enabled", "auto_thumbnail_enabled", "auto_thumbnail_explore", "auto_thumbnail_single_post", "auto_thumbnail_profile", "auto_thumbnail_profile_reels", "cache8701_file_logging"]);
  function igblueAuto8700SettingsFromChanges(changes){
    const settings = {};
    try { Object.keys(changes || {}).forEach((key) => { if (!IGBLUE_AUTO8700_DEVTOOLS_KEYS.has(key)) return; const nv = changes[key] && Object.prototype.hasOwnProperty.call(changes[key], "newValue") ? changes[key].newValue : changes[key]; if (key === "ig_auto8700_devtools_settings" && nv && typeof nv === "object") Object.assign(settings, nv); else settings[key] = nv; }); } catch (_) {}
    return settings;
  }
  function igbluePostAuto8700Event(act, settings, extra){
    const payload = Object.assign({ act: act || "ig_devtools_settings_changed", source: "IG_Ext", version: IGBLUE_BG_VERSION, ts: Date.now(), settings: settings || {} }, extra || {});
    try { fetch(IGBLUE_AUTO8700_EVENT_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), cache: "no-store" }).then((res) => { try { dbg("auto8700_8707", "post_ok", 172, {status: res && res.status, settings}); } catch (_) {} }).catch((err) => { try { dbg("auto8700_8707", "post_fail", 172, String(err && err.message || err)); } catch (_) {} }); }
    catch (e) { try { dbg("auto8700_8707", "post_exception", 172, String(e && e.message || e)); } catch (_) {} }
    try { fetch(IGBLUE_CACHE8701_SETTINGS_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({settings: settings || {}, source:("IG_StYellow_Background_"+IGBLUE_BG_TAG), ts:Date.now()}), cache: "no-store" }).catch(() => {}); } catch (_) {}
  }
  try { chrome.storage.onChanged.addListener((changes, areaName) => { if (areaName !== "local") return; const settings = igblueAuto8700SettingsFromChanges(changes); if (!settings || !Object.keys(settings).length) return; igbluePostAuto8700Event("ig_devtools_settings_changed", settings, {changes}); }); } catch (_) {}

  importScripts("ExtPay.js");
  extpay = ExtPay("ig-sorter");
  extpay.startBackground();
  extpay.onPaid.addListener(function(){ igblueRefreshPayState(); });
  igblueRefreshPayState();
  igblueRefreshActiveFromFocusedWindow();   

  chrome.runtime.onInstalled.addListener(function(info){
    if(info.reason === "install") chrome.tabs.create({url:"https://www.instagram.com/explore/"});
  });
})();
