// FILE: _St/IG_StYellow/content_instagram.js | ROLE: Instagram 격리 영역과 MAIN/Background bridge
(() => {
  // v223: CDP remains schedule-only; OS mouse-idle gating is retired.

  // IGblue DEBUG v70: set IG_DBG_ON=0 or put `return;` at top of dbg() to disable all logs.
  const IG_DBG_ON = 1;
  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:"content_instagram.js", fn, loc, line, href:location.href, value };
      const out = clip(payload);
      const key = `IG_dbg/content_instagram.js/${fn}/${loc}/${line}`.replace(/\s+/g,"_");
      localStorage.setItem(key, out);
      localStorage.setItem("IG_dbg/content_instagram.js/_last", out);
      console.log(`[IG_DBG][content][${fn}][${loc}:${line}]`, value);
    } catch (e) {}
  }
  function dbgInfoSummary(info){
    const arr = Array.isArray(info) ? info : [];
    return { count: arr.length, first: arr[0] ? {code:arr[0].code, has_img_origin:!!arr[0].img_origin, has_video_url:!!arr[0].video_url, has_carousel:!!arr[0].carousel_media, play_count:arr[0].play_count, username:arr[0].username} : null };
  }

  const IGBLUE_EXT_VERSION_NAME = (() => { 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_EXT_BUILD_TAG = (() => { try { const m = String(IGBLUE_EXT_VERSION_NAME || "").match(/(\d+(?:a\d*)?)$/i); return m ? ("v" + m[1]) : String(IGBLUE_EXT_VERSION_NAME || "v170"); } catch (_) { return "v170"; } })();


  const YELLOW_CDP_LIFECYCLE_EVENT = "yellow_cdp_lifecycle_diag";
  const YELLOW_CDP_LIFECYCLE_BUFFER_ID = "yellow_cdp_lifecycle_buffer_v281";
  const yellowCdpLifecycleForwarded = new Set();
  function yellowForwardCdpLifecycleDetail(detail) {
    try {
      if (!detail || String(detail.version || "") !== "v281") return;
      const key = [Number(detail.ts || 0) || 0, String(detail.stage || ""), String(detail.event_type || ""), String(detail.source_url || "")].join("|");
      if (yellowCdpLifecycleForwarded.has(key)) return;
      yellowCdpLifecycleForwarded.add(key);
      if (yellowCdpLifecycleForwarded.size > 120) yellowCdpLifecycleForwarded.clear();
      chrome.runtime.sendMessage({type:"YELLOW_CDP_LIFECYCLE_DIAG", detail}, function(){ chrome.runtime.lastError; });
    } catch (error) {
      console.log("[YELLOW][CDP-LIFECYCLE][BRIDGE_ERROR]", {error:String(error && error.message || error || ""), ts:new Date().toISOString()});
    }
  }
  function yellowForwardCdpLifecycleDiag(evt) {
    try {
      const raw = evt && evt.detail;
      yellowForwardCdpLifecycleDetail(typeof raw === "string" ? JSON.parse(raw) : (raw && typeof raw === "object" ? raw : null));
    } catch (error) {
      console.log("[YELLOW][CDP-LIFECYCLE][BRIDGE_ERROR]", {error:String(error && error.message || error || ""), ts:new Date().toISOString()});
    }
  }
  function yellowFlushCdpLifecycleBuffer() {
    try {
      const node = document.getElementById(YELLOW_CDP_LIFECYCLE_BUFFER_ID);
      const rows = node && node.textContent ? JSON.parse(node.textContent) : [];
      if (Array.isArray(rows)) rows.slice().reverse().forEach(yellowForwardCdpLifecycleDetail);
    } catch (_) {}
  }
  try { document.addEventListener(YELLOW_CDP_LIFECYCLE_EVENT, yellowForwardCdpLifecycleDiag, true); } catch (_) {}
  setTimeout(yellowFlushCdpLifecycleBuffer, 0);
  setTimeout(yellowFlushCdpLifecycleBuffer, 300);
  setTimeout(yellowFlushCdpLifecycleBuffer, 1200);

  // v127: version-aware bridge guard.  A simple boolean guard kept old v116/v119
  // listeners alive after extension reloads and made logs appear to jump between builds.
  const IGBLUE_PREV_CONTENT_VERSION = String(window.__IGBLUE_CONTENT_BRIDGE_LOADED_VERSION__ || (window.__IGBLUE_CONTENT_BRIDGE_LOADED__ ? "unknown_old_boolean_guard" : ""));
  if (window.__IGBLUE_CONTENT_BRIDGE_LOADED__ && IGBLUE_PREV_CONTENT_VERSION === IGBLUE_EXT_VERSION_NAME) return;
  window.__IGBLUE_CONTENT_BRIDGE_LOADED__ = true;
  window.__IGBLUE_CONTENT_BRIDGE_LOADED_VERSION__ = IGBLUE_EXT_VERSION_NAME;
  window.__IGBLUE_CONTENT_BRIDGE_BUILD_TAG__ = IGBLUE_EXT_BUILD_TAG;
  window.__IGBLUE_CONTENT_BRIDGE_PREV_VERSION__ = IGBLUE_PREV_CONTENT_VERSION;

  const OVERLAY_MODE_KEY = "ndy_ig_overlay_mode";
  const OVERLAY_MODES = ["detail", "download", "none"];
  const DEFAULT_OVERLAY_MODE = "detail";
  const ER_WEIGHTS_KEY = "ndy_ig_er_weights";
  const ER_KEYS = Object.freeze(["like", "comment", "repost"]);
  const DEFAULT_ER_WEIGHTS = Object.freeze({ like: 1, comment: 4, repost: 4 });
  const VISUAL_SETTINGS_KEY = "ndy_ig_visual_settings";
  // v271: restore the Side -> isolated content -> MAIN-world settings bridge.
  // These helpers existed in the known-good build and must remain defined before
  // runtime messages, storage change events and startup storage reads use them.
  function isOverlayMode(value) {
    return OVERLAY_MODES.includes(value);
  }

  function validWeights(value) {
    return !!value && typeof value === "object" && ER_KEYS.every((key) => {
      const n = Number(value[key]);
      return Number.isFinite(n) && n >= 0;
    });
  }

  function normalizeWeights(value) {
    const out = { ...DEFAULT_ER_WEIGHTS };
    if (value && typeof value === "object") {
      ER_KEYS.forEach((key) => {
        const n = Number(value[key]);
        if (Number.isFinite(n) && n >= 0) out[key] = n;
      });
    }
    return out;
  }

  function postOverlayMode(mode) {
    const value = isOverlayMode(mode) ? mode : DEFAULT_OVERLAY_MODE;
    window.postMessage({ source: "ndy_ig_sorter", type: "ndy_ig_overlay_mode", mode: value }, "*");
  }

  function postErWeights(weights) {
    window.postMessage({ source: "ndy_ig_sorter", type: "ndy_ig_er_weights", weights: normalizeWeights(weights) }, "*");
  }

  function postVisualSettings(value) {
    window.postMessage({ source: "ndy_ig_sorter", type: "ndy_ig_visual_settings", value: value || {} }, "*");
  }

  // OS mouse, origin calibration and physical-click verification are not used.

  // v86: main rank labels는 side.js가 정렬/필터/다운로드 위치 기준으로만 만든다.
  // content bridge는 page-world media 원본만 background/side로 전달한다.
  // 임시 No/download_root/download_account fallback 생성 금지.

  function sendIgInfo(evt) {
    dbg("sendIgInfo", "entry", 48, {visible:document.visibilityState, event:evt&&evt.type, summary:dbgInfoSummary(evt&&evt.detail&&evt.detail.root)});
    if (document.visibilityState !== "visible") return;
    if (!evt || evt.type !== "ndy_ig_info") return;
    const payload = { type: "ndy_bg_ig", info: evt.detail && evt.detail.root };
    try { chrome.runtime.sendMessage(payload, () => { if(chrome.runtime.lastError) dbg("sendIgInfo", "runtime_error", 53, {message:chrome.runtime.lastError.message}); else dbg("sendIgInfo", "sent", 53, {summary:dbgInfoSummary(payload.info)}); }); } catch (e) { dbg("sendIgInfo", "exception", 53, {message:String(e&&e.message||e)}); }
  }

  function sendDownloadQueue(evt) {
    const count = Number(evt && evt.detail && evt.detail.count);
    dbg("sendDownloadQueue", "entry", 57, {count, detail:evt&&evt.detail});
    try { chrome.runtime.sendMessage({ type: "ndy_dq", count: Number.isFinite(count) ? count : 0 }, () => { chrome.runtime.lastError; }); } catch {}
  }

  // IGblue local patch v15: forward byte-level download progress from the page-world downloader to side.js.
  function sendDownloadProgress(evt) {
    const d = (evt && evt.detail) || {};
    dbg("sendDownloadProgress", "entry", 65, {status:d.status,file:d.file,file_path:d.file_path,shortcode:d.shortcode,loaded:d.loaded,total:d.total,percent:d.percent,message:d.message});
    try {
      chrome.runtime.sendMessage({
        type: "ndy_down_progress",
        status: d.status || "",
        file: d.file || "",
        file_path: d.file_path || "",
        file_paths: Array.isArray(d.file_paths) ? d.file_paths : [],
        json_path: d.json_path || "",
        shortcode: d.shortcode || (d.meta && (d.meta.shortcode || d.meta.code)) || "",
        asset_kind: d.asset_kind || "",
        media_index: d.media_index != null ? d.media_index : 0,
        loaded: Number(d.loaded) || 0,
        total: Number(d.total) || 0,
        percent: Number(d.percent) || 0,
        message: d.message || "",
        meta: d.meta || null
      }, () => { chrome.runtime.lastError; });
    } catch {}
  }

  function postMainDevStatus(message, level, detail) {
    try {
      window.postMessage({ source: "ndy_ig_sorter", type: "igblue_dev_status", message: String(message || ""), level: level || "info", detail: detail || null }, "*");
    } catch {}
  }


  // v179a32: CDP field office gate. Auto/Auth proves CDP Runtime.evaluate communication by dispatching a page event; content owns the UI marker and diagnostics.
  let igblueCdpGateState = { ok:false, source:'startup', target_id:'', tab_id:'', cdp_schedule_only:true, ts:0 };
  let igblueCdpGateTip = null;
  let igblueTabControlState = { ok:false, schema:'yellow_tab_control_v280', instagram_tab_count:0, owner_tab_id:0, owner_generation:0, run_seq:'', account:'', account_index:0, account_total:0, handoff_state:'UNKNOWN', selection_basis:'', pending:null, tabs:[], recent:[], updated_at:0 };
  let igblueTabControlLastRequestAt = 0;
  let igblueTabControlRequesting = false;
  function igblueTabIdTail(value){
    const text = String(Number(value || 0) || '');
    return text ? text.slice(-6) : '-';
  }
  function igblueTabControlCompactText(){
    const s = igblueTabControlState || {};
    const count = Math.max(0, Number(s.instagram_tab_count || 0) || 0);
    const pending = s.pending && typeof s.pending === 'object' ? s.pending : null;
    if (pending && Number(pending.previous_tab_id || 0) > 0 && Number(pending.replacement_tab_id || 0) > 0) {
      return `탭 ${count} · ${igblueTabIdTail(pending.previous_tab_id)}→${igblueTabIdTail(pending.replacement_tab_id)}`;
    }
    const owner = Number(s.owner_tab_id || 0) || 0;
    if (!owner) return `탭 ${count} · OWNER 없음`;
    return count > 1 ? `탭 ${count} · OWNER ${igblueTabIdTail(owner)}` : `탭 ${count} · OWNER`;
  }
  function igblueTabControlDiagText(){
    const s = igblueTabControlState || {};
    const pending = s.pending && typeof s.pending === 'object' ? s.pending : null;
    const rows = [
      '탭 제어 v281',
      'CDP: ' + (igblueCdpGateState && igblueCdpGateState.ok ? 'O' : 'X'),
      'Instagram 탭 수: ' + String(Number(s.instagram_tab_count || 0) || 0),
      'OWNER tab_id: ' + String(Number(s.owner_tab_id || 0) || '-'),
      'OWNER generation: ' + String(Number(s.owner_generation || 0) || '-'),
      'OWNER active: ' + String(!!s.owner_active),
      '선택 기준: ' + String(s.selection_basis || '-'),
      'run_seq: ' + String(s.run_seq || '-'),
      '현재 계정: ' + String(s.account || '-'),
      '계정 순서: ' + String(Number(s.account_index || 0) || 0) + '/' + String(Number(s.account_total || 0) || 0),
      '인계 상태: ' + String(s.handoff_state || '-'),
      '최근 갱신: ' + (s.updated_at ? new Date(Number(s.updated_at)).toLocaleString('ko-KR') : '-')
    ];
    if (pending) {
      rows.push('인계 대기: ' + igblueTabIdTail(pending.previous_tab_id) + '→' + igblueTabIdTail(pending.replacement_tab_id));
      rows.push('인계 token: ' + String(pending.token || '-'));
    }
    const tabs = Array.isArray(s.tabs) ? s.tabs.slice(0, 12) : [];
    rows.push('', '현재 Instagram 탭');
    if (!tabs.length) rows.push('- 없음');
    tabs.forEach((tab) => {
      rows.push(`- ${tab.tab_id} · ${String(tab.state || 'UNMANAGED')} · gen ${Number(tab.generation || 0) || 0}${tab.active ? ' · active' : ''}`);
    });
    const recent = Array.isArray(s.recent) ? s.recent.slice(0, 20) : [];
    rows.push('', '최근 탭 이력');
    if (!recent.length) rows.push('- 없음');
    recent.forEach((event) => {
      const at = event && event.ts ? new Date(Number(event.ts)).toLocaleTimeString('ko-KR', {hour12:false}) : '--:--:--';
      const pair = Number(event && event.previous_tab_id || 0) > 0 || Number(event && event.replacement_tab_id || 0) > 0
        ? ` ${igblueTabIdTail(event.previous_tab_id)}→${igblueTabIdTail(event.replacement_tab_id)}`
        : ` ${Number(event && event.tab_id || 0) || '-'}`;
      rows.push(`- ${at} ${String(event && event.state || 'UNKNOWN')}${pair} gen ${Number(event && event.generation || 0) || 0}`);
    });
    return rows.join('\n');
  }
  function igblueApplyTabControlState(value){
    try {
      if (value && typeof value === 'object') igblueTabControlState = Object.assign({}, igblueTabControlState, value);
      const root = document.documentElement;
      if (root) root.setAttribute('data-yellow-tab-control-state', JSON.stringify({
        schema:'yellow_tab_control_v280',
        instagram_tab_count:Number(igblueTabControlState.instagram_tab_count || 0) || 0,
        owner_tab_id:Number(igblueTabControlState.owner_tab_id || 0) || 0,
        owner_generation:Number(igblueTabControlState.owner_generation || 0) || 0,
        run_seq:String(igblueTabControlState.run_seq || ''),
        account:String(igblueTabControlState.account || ''),
        account_index:Number(igblueTabControlState.account_index || 0) || 0,
        account_total:Number(igblueTabControlState.account_total || 0) || 0,
        handoff_state:String(igblueTabControlState.handoff_state || ''),
        selection_basis:String(igblueTabControlState.selection_basis || ''),
        pending:igblueTabControlState.pending || null,
        tabs:Array.isArray(igblueTabControlState.tabs) ? igblueTabControlState.tabs.slice(0, 12) : [],
        recent:Array.isArray(igblueTabControlState.recent) ? igblueTabControlState.recent.slice(0, 20) : [],
        updated_at:Number(igblueTabControlState.updated_at || 0) || 0
      }));
      const toggle = document.getElementById('igblue_main_diag_toggle');
      if (toggle) {
        toggle.textContent = igblueTabControlCompactText();
        toggle.classList.toggle('is-warning', !Number(igblueTabControlState.owner_tab_id || 0) || String(igblueTabControlState.handoff_state || '').includes('FAILED'));
        toggle.classList.toggle('is-handoff', !!igblueTabControlState.pending);
        toggle.title = igblueTabControlDiagText();
      }
      try { igblueRefreshMainDiag(); } catch (_) {}
    } catch (_) {}
  }
  function igblueRequestTabControlState(force){
    const now = Date.now();
    if (igblueTabControlRequesting || (!force && now - igblueTabControlLastRequestAt < 900)) return;
    igblueTabControlRequesting = true;
    igblueTabControlLastRequestAt = now;
    try {
      chrome.runtime.sendMessage({type:'IGBLUE_GET_TAB_CONTROL_STATE'}, (state) => {
        igblueTabControlRequesting = false;
        if (chrome.runtime.lastError) return;
        igblueApplyTabControlState(state || {});
      });
    } catch (_) { igblueTabControlRequesting = false; }
  }
  function igblueCdpGateText() {
    const s = igblueCdpGateState || {};
    return [
      'CDP:' + (s.ok ? 'O' : 'X'),
      'source=' + String(s.source || '-'),
      'target_id=' + String(s.target_id || '-'),
      'tab_id=' + String(s.tab_id || s.request_tab_id || '-'),
      'cdp_scope=schedule_only',
      'ts=' + (s.ts ? new Date(Number(s.ts)).toISOString() : '-'),
      '',
      igblueTabControlDiagText()
    ].join('\n') + '\n\n\n';
  }
  function igbluePlaceCdpGate(el) {
    try {
      if (!el) return;
      const panel = document.getElementById('IGBLUE_NoTop') || document.querySelector('[id*="IGBLUE_NoTop"]');
      const gateW = 58, gateH = 18, pad = 8;
      let left = pad;
      let top = 56;
      if (panel && panel.getBoundingClientRect) {
        const r = panel.getBoundingClientRect();
        if (r && Number.isFinite(r.left) && r.width > 20) left = r.left;
      }
      if (panel && panel.getBoundingClientRect) {
        const r = panel.getBoundingClientRect();
        if (r && Number.isFinite(r.top)) top = Math.max(pad, r.top - gateH - 8);
      }
      const maxX = Math.max(pad, (window.innerWidth || 0) - gateW - pad);
      const maxY = Math.max(pad, (window.innerHeight || 0) - gateH - pad);
      el.style.left = Math.round(Math.max(pad, Math.min(maxX, left))) + 'px';
      el.style.top = Math.round(Math.max(pad, Math.min(maxY, top))) + 'px';
      el.style.right = 'auto';
    } catch (_) {}
  }

  // v239: MAIN 원점/상태 정보는 계속 로딩하되 화면에는 기본 숨김으로 두고,
  // CDP 표시 바로 옆의 작은 정사각형 버튼으로만 열어 본다.
  const IGBLUE_MAIN_DIAG_OPEN_KEY = 'IGBLUE/main_diag_open_v239';
  let igblueMainDiagTimer = 0;
  function igblueEnsureMainDiagStyle() {
    if (document.getElementById('igblue_main_diag_style_v239')) return;
    const st = document.createElement('style');
    st.id = 'igblue_main_diag_style_v239';
    st.textContent = `
      #IGBLUE_NoTop > .igblue-origin-panel,
      #IGBLUE_NoTop > .igblue-status-box { display:none !important; }
      #igblue_main_diag_toggle {
        position:fixed; z-index:2147483646; min-width:78px; width:auto; height:18px;
        box-sizing:border-box; padding:0 6px; border-radius:999px;
        background:rgba(15,23,42,.54); border:1px solid rgba(148,163,184,.55);
        color:#e5e7eb; font:800 12px/16px Consolas,Arial,sans-serif;
        text-align:center; cursor:pointer; user-select:none; pointer-events:auto;
        backdrop-filter:blur(2px); white-space:nowrap;
      }
      #igblue_main_diag_toggle.is-open { background:rgba(30,64,175,.72); border-color:rgba(147,197,253,.88); }
      #igblue_main_diag_toggle.is-handoff { background:rgba(146,64,14,.78); border-color:rgba(251,191,36,.9); }
      #igblue_main_diag_toggle.is-warning { background:rgba(127,29,29,.76); border-color:rgba(252,165,165,.88); }
      #igblue_main_diag_panel {
        position:fixed; z-index:2147483647; width:310px; max-height:64vh;
        box-sizing:border-box; overflow:auto; display:none;
        padding:6px 7px; border-radius:8px;
        background:rgba(4,8,15,.94); border:1px solid rgba(148,163,184,.55);
        box-shadow:0 10px 24px rgba(0,0,0,.34); color:#e5e7eb;
        font:700 9px/1.26 Consolas,"Malgun Gothic",monospace;
        white-space:pre-wrap; overflow-wrap:anywhere; word-break:break-word;
      }
      #igblue_main_diag_panel.is-open { display:block; }
    `;
    (document.head || document.documentElement).appendChild(st);
  }
  function igblueMainDiagText() {
    return igblueTabControlDiagText();
  }
  function igbluePlaceMainDiag(toggle, cdpEl) {
    try {
      if (!toggle) return;
      const cdp = cdpEl || document.getElementById('igblue_cdp_gate');
      const r = cdp && cdp.getBoundingClientRect ? cdp.getBoundingClientRect() : null;
      const pad = 8, gap = 4, size = Math.max(78, Number(toggle.offsetWidth || 0) || 78);
      let left = r ? r.right + gap : pad + 62;
      let top = r ? r.top : 56;
      if (left + size + pad > (window.innerWidth || 0)) left = r ? r.left - size - gap : pad;
      toggle.style.left = Math.round(Math.max(pad, left)) + 'px';
      toggle.style.top = Math.round(Math.max(pad, top)) + 'px';
      const panel = document.getElementById('igblue_main_diag_panel');
      if (panel) {
        const panelW = 310;
        let panelLeft = r ? r.left : pad;
        if (panelLeft + panelW + pad > (window.innerWidth || 0)) panelLeft = Math.max(pad, (window.innerWidth || 0) - panelW - pad);
        panel.style.left = Math.round(panelLeft) + 'px';
        panel.style.top = Math.round(Math.max(pad, top + size + 5)) + 'px';
      }
    } catch (_) {}
  }
  function igblueRefreshMainDiag() {
    try {
      igblueRequestTabControlState(false);
      const panel = document.getElementById('igblue_main_diag_panel');
      const toggle = document.getElementById('igblue_main_diag_toggle');
      if (toggle) toggle.textContent = igblueTabControlCompactText();
      if (panel) panel.textContent = igblueMainDiagText();
      igbluePlaceMainDiag(toggle);
    } catch (_) {}
  }
  function igblueSetMainDiagOpen(open) {
    const on = !!open;
    try { localStorage.setItem(IGBLUE_MAIN_DIAG_OPEN_KEY, on ? '1' : '0'); } catch (_) {}
    const toggle = document.getElementById('igblue_main_diag_toggle');
    const panel = document.getElementById('igblue_main_diag_panel');
    if (toggle) {
      toggle.classList.toggle('is-open', on);
      toggle.setAttribute('aria-expanded', on ? 'true' : 'false');
      toggle.title = igblueTabControlDiagText();
    }
    if (panel) panel.classList.toggle('is-open', on);
    if (on) igblueRefreshMainDiag();
  }
  function igblueEnsureMainDiag(cdpEl) {
    igblueEnsureMainDiagStyle();
    let toggle = document.getElementById('igblue_main_diag_toggle');
    if (!toggle) {
      toggle = document.createElement('button');
      toggle.type = 'button';
      toggle.id = 'igblue_main_diag_toggle';
      toggle.textContent = '탭 0 · OWNER 없음';
      toggle.setAttribute('aria-label', 'Instagram 탭 제어 진단');
      toggle.addEventListener('click', (ev) => {
        ev.preventDefault(); ev.stopPropagation();
        const panel = document.getElementById('igblue_main_diag_panel');
        igblueSetMainDiagOpen(!(panel && panel.classList.contains('is-open')));
      }, true);
      (document.documentElement || document.body).appendChild(toggle);
    }
    let panel = document.getElementById('igblue_main_diag_panel');
    if (!panel) {
      panel = document.createElement('pre');
      panel.id = 'igblue_main_diag_panel';
      panel.setAttribute('role', 'status');
      (document.documentElement || document.body).appendChild(panel);
    }
    igbluePlaceMainDiag(toggle, cdpEl);
    let open = false;
    try { open = localStorage.getItem(IGBLUE_MAIN_DIAG_OPEN_KEY) === '1'; } catch (_) {}
    igblueSetMainDiagOpen(open);
    if (!igblueMainDiagTimer) igblueMainDiagTimer = window.setInterval(igblueRefreshMainDiag, 700);
    return toggle;
  }
  function igblueEnsureCdpGate() {
    let el = document.getElementById('igblue_cdp_gate');
    if (!el) {
      el = document.createElement('div');
      el.id = 'igblue_cdp_gate';
      el.textContent = 'CDP:X';
      el.style.cssText = 'position:fixed;right:10px;top:56px;z-index:2147483646;width:58px;height:18px;box-sizing:border-box;padding:2px 5px;border-radius:999px;background:rgba(15,23,42,.54);border:1px solid rgba(148,163,184,.55);color:#e5e7eb;font:700 11px/13px Consolas,Arial,sans-serif;text-align:center;letter-spacing:.1px;backdrop-filter:blur(2px);cursor:default;user-select:text;pointer-events:auto;white-space:nowrap;overflow:hidden;';
      el.addEventListener('mouseenter', (ev)=>igblueShowCdpGateTip(ev));
      el.addEventListener('mousemove', (ev)=>igbluePlaceCdpGateTip(ev));
      el.addEventListener('mouseleave', ()=>{ if(igblueCdpGateTip) igblueCdpGateTip.style.display='none'; });
      el.addEventListener('dblclick', (ev)=>{ try{ ev.preventDefault(); ev.stopPropagation(); const txt=igblueCdpGateText(); if(navigator.clipboard&&navigator.clipboard.writeText) navigator.clipboard.writeText(txt); else { const ta=document.createElement('textarea'); ta.value=txt; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); } igblueShowCdpGateTip(ev, 'CDP 게이트 진단 복사 완료'); }catch(_){} });
      (document.documentElement || document.body).appendChild(el);
    }
    try { igbluePlaceCdpGate(el); } catch (_) {}
    try { igblueEnsureMainDiag(el); } catch (_) {}
    return el;
  }
  function igblueCdpGateTooltip() {
    if (igblueCdpGateTip && document.body.contains(igblueCdpGateTip)) return igblueCdpGateTip;
    igblueCdpGateTip = document.createElement('div');
    igblueCdpGateTip.style.cssText = 'position:fixed;z-index:2147483647;max-width:440px;max-height:55vh;overflow:auto;padding:8px 10px;border-radius:10px;background:rgba(15,23,42,.94);border:1px solid rgba(148,163,184,.5);color:#e5e7eb;font:12px/1.45 Consolas,monospace;white-space:pre-wrap;box-shadow:0 12px 28px rgba(0,0,0,.28);pointer-events:none;display:none;';
    document.body.appendChild(igblueCdpGateTip);
    return igblueCdpGateTip;
  }
  function igbluePlaceCdpGateTip(ev) {
    const tip = igblueCdpGateTooltip();
    let x = (ev && ev.clientX || 0) + 14, y = (ev && ev.clientY || 0) + 14;
    const pad = 12, w = tip.offsetWidth || 300, h = tip.offsetHeight || 150;
    if (x + w + pad > innerWidth) x = Math.max(pad, innerWidth - w - pad);
    if (y + h + pad > innerHeight) y = Math.max(pad, innerHeight - h - pad);
    tip.style.left = x + 'px'; tip.style.top = y + 'px';
  }
  function igblueShowCdpGateTip(ev, text) {
    const tip = igblueCdpGateTooltip();
    tip.textContent = String(text || igblueCdpGateText()).trimEnd();
    tip.style.display = 'block';
    igbluePlaceCdpGateTip(ev || {clientX: innerWidth-160, clientY: 84});
    if (text) setTimeout(()=>{ if(igblueCdpGateTip) igblueCdpGateTip.style.display='none'; }, 1000);
  }
  function igblueUpdateCdpGate(detail, source) {
    try {
      const d = detail && typeof detail === 'object' ? detail : {};
      igblueCdpGateState = Object.assign({}, igblueCdpGateState, d, {ok: d.ok !== false, source: source || d.source || 'cdp_event', ts: Date.now()});
      const el = igblueEnsureCdpGate();
      try { igbluePlaceCdpGate(el); } catch (_) {}
      el.textContent = igblueCdpGateState.ok ? 'CDP:O' : 'CDP:X';
      el.style.background = igblueCdpGateState.ok ? 'rgba(22,101,52,.58)' : 'rgba(69,44,4,.58)';
      el.title = igblueCdpGateText().trimEnd();
      try { localStorage.setItem('IGBLUE/cdp_gate', JSON.stringify(igblueCdpGateState)); } catch (_) {}
      try { postMainDevStatus('CDP gate ' + (igblueCdpGateState.ok ? 'OK' : 'XX'), igblueCdpGateState.ok ? 'ok' : 'warn', igblueCdpGateState); } catch (_) {}
    } catch (_) {}
  }
  try { setTimeout(()=>{ igblueEnsureCdpGate(); igblueRequestTabControlState(true); }, 300); setInterval(()=>{ try { const gate=document.getElementById('igblue_cdp_gate'); igbluePlaceCdpGate(gate); igblueEnsureMainDiag(gate); igblueRequestTabControlState(false); } catch (_) {} }, 1500); } catch (_) {}
  window.addEventListener('igblue_auto8700_cdp_gate', (ev)=>igblueUpdateCdpGate(ev && ev.detail || {}, 'document_event'));
  window.addEventListener('message', (ev)=>{ const d=ev&&ev.data; if(d && d.source === 'AUTO8700' && d.type === 'IGBLUE_AUTO8700_CDP_GATE') igblueUpdateCdpGate(d.detail || d, 'window_message'); });

  function postRtcStatus(value) {
    try {
      window.postMessage({ source: "ndy_ig_sorter", type: "igblue_rtc_status", value: value || {} }, "*");
    } catch {}
  }

  function postDownloadResult(value) {
    try {
      window.postMessage({ source: "ndy_ig_sorter", type: "igblue_download_result", value: value || {} }, "*");
    } catch {}
  }

  // v272: background/Side automatic downloads report progress back into MAIN world
  // so the exact shortcode and MP4/JPG button can be highlighted in Instagram DOM.
  function postDownloadProgress(value) {
    try {
      window.postMessage({ source: "ndy_ig_sorter", type: "igblue_download_progress", value: value || {} }, "*");
    } catch {}
  }

  function postPyState(value) {
    try {
      window.postMessage({ source: "ndy_ig_sorter", type: "igblue_py_state", value: value || {} }, "*");
    } catch {}
  }

  function checkRtcStatus(force) {
    try {
      chrome.runtime.sendMessage({ type:"IGBLUE_RTC_STATUS", force:!!force, href:location.href, host:location.host }, (res) => {
        if (chrome.runtime.lastError) {
          const st = { connected:false, status:"offline", message:"Auto8700/WS8771 미실행 · 수동 다운로드는 확장 저장 사용", error:chrome.runtime.lastError.message };
          postRtcStatus(st);
          postMainDevStatus("Auto8700/WS8771 미실행 · 수동 다운로드는 확장 저장 사용", "warn", st);
          return;
        }
        const st = Object.assign({ connected:false, status:"offline", message:"Auto8700/WS8771 미실행 · 수동 다운로드는 확장 저장 사용" }, res || {});
        postRtcStatus(st);
        postMainDevStatus(st.connected ? "RTC 연결됨" : "Auto8700/WS8771 미실행 · 수동 다운로드는 확장 저장 사용", st.connected ? "ok" : "warn", st);
      });
    } catch (err) {
      const st = { connected:false, status:"offline", message:"Auto8700/WS8771 미실행 · 수동 다운로드는 확장 저장 사용", error:String(err && err.message || err) };
      postRtcStatus(st);
      postMainDevStatus("Auto8700/WS8771 미실행 · 수동 다운로드는 확장 저장 사용", "warn", st);
    }
  }

  setTimeout(() => checkRtcStatus(false), 900);
  setInterval(() => checkRtcStatus(false), 10000);





  // v251: MAIN-world overlay downloads use the same WS8771 direct path and metadata contract as Side.
  function sendMainBackgroundDownload(evt) {
    const d = (evt && evt.detail) || {};
    const url = String(d.url || "");
    const prefix = String(d.prefix || "media");
    const root = "instagram";
    const account = String(d.account || "instagram");
    dbg("sendMainBackgroundDownload", "entry", 82, {url:url.slice(0,260), prefix, root, account});
    if (!url) return;
    try {
      const flowId = `manual-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
      const flowMeta = Object.assign({href: location.href, page_url: location.href}, d.meta || {}, { source_mode: "MANUAL_SINGLE", client_batch_key: `batch-${flowId}`, client_job_key: `job-${flowId}`, client_event_id: `job-${flowId}`, batch_requested_count: 1 });
      chrome.runtime.sendMessage({
        type: "IGBLUE_DOWNLOAD_URL",
        url,
        prefix,
        root,
        account,
        href: location.href,
        ext: d.ext || "",
        meta: flowMeta,
        notify_aiwk: true
      }, (res) => {
        if (chrome.runtime.lastError) {
          dbg("sendMainBackgroundDownload", "runtime_error", 95, {message: chrome.runtime.lastError.message, prefix});
          return;
        }
        dbg("sendMainBackgroundDownload", "response", 99, {prefix, res});
        if(res && (res.status === "ok" || res.status === "already_done" || res.error_code === "IGBLUE_ALREADY_DOWNLOADED")){
          postDownloadResult(res);
          postMainDevStatus("✅", "ok", res);
        }
        if(res && res.status === "partial"){
          postDownloadResult(res);
          postMainDevStatus("파일 저장 완료\nDB 처리 실패", "warn", res);
        }
        if(res && (res.error_code === "IGBLUE_RTC_OFFLINE" || res.error_code === "IGBLUE_RTC_STATUS_ERROR" || res.error_code === "IGBLUE_DUP_CHECK_ERROR")){
          postRtcStatus({connected:false,status:"offline",message:res.message||"Auto8700/WS8771 미실행 · 수동 다운로드는 확장 저장 사용",raw:res});
          postMainDevStatus(res.error_code === "IGBLUE_DUP_CHECK_ERROR" ? "중복확인 실패\n수동 확장 저장 사용 가능" : "Auto8700/WS8771 미실행 · 수동 다운로드는 확장 저장 사용", "warn", res);
        }
      });
    } catch (err) {
      dbg("sendMainBackgroundDownload", "exception", 103, {message:String(err && err.message || err), prefix});
    }
  }

  async function scrollBottom() {
    window.scrollTo(0, Number.MAX_SAFE_INTEGER);
  }

  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async function swipe(count) {
    for (let i = 0; i < count; i++) {
      await scrollBottom();
      if (i < 5) await sleep(3000);
      else if (i < 10) await sleep(6000);
      else await sleep(10000);
    }
  }


  function sendPySetting(evt) {
    const d = (evt && evt.detail) || {};
    try { chrome.runtime.sendMessage({ type: "IGBLUE_PY_SETTING", data: Object.assign({ host: location.host, href: location.href, forwarded_at: new Date().toISOString(), content_version: IGBLUE_EXT_VERSION_NAME, content_build_tag: IGBLUE_EXT_BUILD_TAG }, d) }, (res) => { chrome.runtime.lastError; if(res) postPyState(res); }); } catch {}
  }

  document.addEventListener("ndy_ig_info", sendIgInfo);
  document.addEventListener("ndy_dq", sendDownloadQueue);
  document.addEventListener("ndy_down_progress", sendDownloadProgress);
  document.addEventListener("ndy_ig_bg_down", sendMainBackgroundDownload);
  document.addEventListener("igblue_py_setting", sendPySetting);

  // v179a39: relay MAIN-world screenX/screenY movement to side/background.
  function sendWindowMoveState(ev) {
    try {
      const detail = Object.assign({}, ev && ev.detail || {});
      chrome.runtime.sendMessage({type:'IGBLUE_WINDOW_MOVE_STATE', window_move:detail, tab_url:String(location.href||''), ts:Date.now()}).catch(()=>{});
    } catch (_) {}
  }
  document.addEventListener('igblue_window_move_state', sendWindowMoveState);



  let igblueHoveredAccountTarget = { username: "", shortcode: "", source: "", href: "", ts: 0 };
  function igblueNormalizeHoveredAccount(value) {
    return String(value || "").trim().replace(/^IG\s+/i, "").replace(/^@+/, "").toLowerCase();
  }
  function igblueSetHoveredAccountTarget(detail) {
    const d = detail && typeof detail === "object" ? detail : {};
    const username = igblueNormalizeHoveredAccount(d.username || d.account || d.owner_username);
    igblueHoveredAccountTarget = username ? {
      username,
      shortcode: String(d.shortcode || d.code || "").trim(),
      source: String(d.source || "main_thumbnail_hover"),
      href: String(location.href || ""),
      ts: Date.now()
    } : { username: "", shortcode: "", source: "", href: String(location.href || ""), ts: Date.now() };
  }
  document.addEventListener("igblue_hover_account_target", (ev) => {
    try { igblueSetHoveredAccountTarget(ev && ev.detail || {}); } catch (_) {}
  }, true);
  // v264: hover 계정은 instagram_inject.js의 data-code → S(code) 결과만 수신한다.



  let igblueCurrentMediaAccountTargetV264 = { username: "", shortcode: "", source: "", href: "", ts: 0 };
  let igblueLastObservedHrefV264 = String(location.href || "");
  function igblueShortcodeFromHrefV264(href) {
    try {
      const parts = String(new URL(String(href || location.href), location.origin).pathname || "").split("/").filter(Boolean);
      return ["p", "reel", "reels"].includes(String(parts[0] || "").toLowerCase()) && parts[1] ? String(parts[1]).trim() : "";
    } catch (_) { return ""; }
  }
  function igblueSetCurrentMediaAccountTargetV264(detail) {
    const d = detail && typeof detail === "object" ? detail : {};
    const href = String(d.href || location.href || "");
    const shortcode = String(d.shortcode || d.code || igblueShortcodeFromHrefV264(href) || "").trim();
    const username = igblueNormalizeHoveredAccount(d.username || d.account || d.owner_username);
    igblueLastObservedHrefV264 = String(location.href || href);
    igblueCurrentMediaAccountTargetV264 = {
      username,
      shortcode,
      source: String(d.source || (username ? "spa_current_media" : "spa_current_media_pending")),
      href,
      ts: Date.now()
    };
  }
  document.addEventListener("igblue_current_media_account_target", (ev) => {
    try { igblueSetCurrentMediaAccountTargetV264(ev && ev.detail || {}); } catch (_) {}
  }, true);
  document.addEventListener("igblue_spa_url_changed", (ev) => {
    try {
      const d = ev && ev.detail || {};
      igblueHoveredAccountTarget = { username: "", shortcode: "", source: "", href: String(d.href || location.href || ""), ts: Date.now() };
      igblueSetCurrentMediaAccountTargetV264({ username: "", shortcode: d.shortcode || igblueShortcodeFromHrefV264(d.href), href: d.href || location.href, source: "spa_url_change_pending" });
    } catch (_) {}
  }, true);
  setInterval(() => {
    try {
      const href = String(location.href || "");
      if (href === igblueLastObservedHrefV264) return;
      igblueLastObservedHrefV264 = href;
      igblueHoveredAccountTarget = { username: "", shortcode: "", source: "", href, ts: Date.now() };
      igblueCurrentMediaAccountTargetV264 = { username: "", shortcode: igblueShortcodeFromHrefV264(href), source: "content_url_change_pending", href, ts: Date.now() };
    } catch (_) {}
  }, 250);

  function igblueReadCurrentAccountTarget() {
    try {
      const href = String(location.href || "");
      const currentCode = igblueShortcodeFromHrefV264(href);
      if (currentCode) {
        const current = igblueCurrentMediaAccountTargetV264;
        if (current && current.username && current.href === href && String(current.shortcode || "") === currentCode) {
          return { ok: true, username: current.username, shortcode: currentCode, source: current.source || "spa_current_media", href };
        }
        return { ok: false, username: "", shortcode: currentCode, source: "current_media_pending", href };
      }
    } catch (_) {}
    try {
      const hover = igblueHoveredAccountTarget;
      if (hover && hover.username && hover.href === String(location.href || "") && Date.now() - Number(hover.ts || 0) < 5000) {
        return { ok: true, username: hover.username, shortcode: hover.shortcode, source: hover.source || "main_thumbnail_hover", href: location.href };
      }
    } catch (_) {}
    try {
      const activeEl = document.querySelector('.ndy_ins_info[data-igblue-active="1"][data-code],.ndy_ins_info[data-igblue-active-index][data-code]');
      const activeCode = String(activeEl && activeEl.getAttribute("data-code") || "").trim();
      if (activeCode) return { ok: true, shortcode: activeCode, source: "main_selected_card", href: location.href };
    } catch (_) {}
    try {
      const raw = localStorage.getItem("IGBLUE/active_download_target") || "";
      const active = raw ? JSON.parse(raw) : null;
      const activeCode = String(active && (active.code || active.shortcode) || "").trim();
      const existsOnCurrentPage = activeCode && Array.from(document.querySelectorAll('.ndy_ins_info[data-code]')).some((el) => String(el && el.getAttribute("data-code") || "").trim() === activeCode);
      if (existsOnCurrentPage) return { ok: true, shortcode: activeCode, source: "active_download_target", href: location.href };
    } catch (_) {}
    try {
      const viewportW = Math.max(1, Number(window.innerWidth || document.documentElement.clientWidth || 1) || 1);
      const viewportH = Math.max(1, Number(window.innerHeight || document.documentElement.clientHeight || 1) || 1);
      const centerX = viewportW / 2;
      const centerY = viewportH / 2;
      let best = null;
      document.querySelectorAll('.ndy_ins_info[data-code]').forEach((el) => {
        try {
          const rect = el.getBoundingClientRect();
          const left = Math.max(0, rect.left), top = Math.max(0, rect.top);
          const right = Math.min(viewportW, rect.right), bottom = Math.min(viewportH, rect.bottom);
          const visibleArea = Math.max(0, right - left) * Math.max(0, bottom - top);
          if (visibleArea <= 0) return;
          const cx = rect.left + rect.width / 2, cy = rect.top + rect.height / 2;
          const centerDistance = Math.hypot(cx - centerX, cy - centerY);
          const score = visibleArea - centerDistance;
          const code = String(el.getAttribute("data-code") || "").trim();
          if (code && (!best || score > best.score)) best = { code, score };
        } catch (_) {}
      });
      if (best && best.code) return { ok: true, shortcode: best.code, source: "viewport_current_card", href: location.href };
    } catch (_) {}
    return { ok: false, shortcode: "", source: "current_media_unresolved", href: location.href };
  }

  function igblueReadAccountPageState(expectedUsername) {
    const href = String(location.href || "");
    let pathname = "";
    let currentUsername = "";
    try {
      const parsed = new URL(href);
      pathname = String(parsed.pathname || "/");
      const parts = pathname.split("/").filter(Boolean);
      const first = String(parts[0] || "").trim().toLowerCase();
      const reserved = new Set(["p", "reel", "reels", "explore", "stories", "accounts", "direct", "challenge", "graphql", "api"]);
      if (first && !reserved.has(first) && (parts.length === 1 || String(parts[1] || "").toLowerCase() === "reels")) currentUsername = first;
    } catch (_) {}
    const expected = String(expectedUsername || "").trim().replace(/^@+/, "").toLowerCase();
    let text = "";
    try { text = String(document.body && (document.body.innerText || document.body.textContent) || "").replace(/\s+/g, " ").trim().toLowerCase(); } catch (_) {}
    const authPath = /^\/(?:accounts\/(?:login|signup)|challenge|oauth)(?:\/|$)/i.test(pathname);
    const authText = ["log in", "sign up", "로그인", "가입하기", "security code", "보안 코드", "confirm it's you", "본인 확인"].some((v) => text.includes(v));
    const privateText = ["this account is private", "비공개 계정입니다", "비공개 계정", "follow to see their photos and videos"].some((v) => text.includes(v));
    const notFoundText = [
      "sorry, this page isn't available",
      "sorry, this page is not available",
      "the link you followed may be broken, or the page may have been removed",
      "page isn't available",
      "page not found",
      "죄송합니다. 페이지를 사용할 수 없습니다",
      "이 페이지를 사용할 수 없습니다",
      "페이지를 찾을 수 없습니다",
      "링크가 잘못되었거나 페이지가 삭제되었습니다",
      "페이지가 삭제되었습니다"
    ].some((v) => text.includes(v));
    const sameExpectedProfile = !!(expected && currentUsername && expected === currentUsername);
    let state = "unknown";
    if (sameExpectedProfile && notFoundText) state = "not_found";
    else if (authPath || authText) state = "auth_required";
    else if (sameExpectedProfile && privateText) state = "private";
    else if (expected && currentUsername && expected !== currentUsername) state = "navigating";
    else if (sameExpectedProfile) state = "profile_loaded";
    return {
      ok: true,
      state,
      explicit_not_found: state === "not_found",
      expected_username: expected,
      current_username: currentUsername,
      same_expected_profile: sameExpectedProfile,
      auth_required: state === "auth_required",
      private_account: state === "private",
      href,
      pathname,
      text_sample: notFoundText || privateText || authText ? text.slice(0, 420) : ""
    };
  }
  try { globalThis.__IGBLUE_V273_ACCOUNT_PAGE_STATE_TEST__ = igblueReadAccountPageState; } catch (_) {}

  chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
    try {
      if (!msg || !msg.type) return false;
      dbg("onMessage", "recv", 95, {type:msg.type, url:msg.url, prefix:msg.prefix, value:msg.value});
      if (msg.type === "igblue_navigation_ready_probe") {
        const expectedUrl = String(msg.expected_url || "");
        let sameTarget = false;
        try {
          const actual = new URL(String(location.href || ""));
          const expected = new URL(expectedUrl || String(location.href || ""));
          const trim = (pathname) => { const value = String(pathname || "/").replace(/\/{2,}/g, "/"); return value.length > 1 ? value.replace(/\/+$/, "") : value; };
          sameTarget = actual.origin === expected.origin && trim(actual.pathname) === trim(expected.pathname);
        } catch (_) {}
        let internalState = null;
        try { const raw = localStorage.getItem("IGBLUE/internal_no_nav_state") || ""; internalState = raw ? JSON.parse(raw) : null; } catch (_) {}
        const panelFound = !!document.querySelector("#IGBLUE_NoTop");
        const mainReadyMarker = String(document.documentElement && document.documentElement.getAttribute("data-yellow-main-injection-ready") || "");
        const mainInjectionReady = !!(mainReadyMarker || panelFound);
        sendResponse({
          ok: true,
          action: "navigation_ready_probe",
          content_ready: true,
          same_target: sameTarget,
          document_ready_state: String(document.readyState || ""),
          main_panel_found: panelFound,
          main_ready_marker: mainReadyMarker,
          main_injection_ready: mainInjectionReady,
          labels_ready: !!(internalState && internalState.labels_ready === true),
          loaded_count: internalState && Array.isArray(internalState.loaded_nos) ? internalState.loaded_nos.length : 0,
          href: String(location.href || "")
        });
        return true;
      }
      if (msg.type === "ndy_swipe") {
        swipe(Number(msg.count) || 0).then(() => {}).catch(() => {});
        sendResponse("ok");
        return true;
      }
      if (msg.type === "ndy_top") {
        window.scrollTo(0, 0);
        sendResponse("ok");
        return true;
      }
      if (msg.type === "ndy_reread") {
        const requestId = String(msg.request_id || ("reread_" + Date.now()));
        window.postMessage({ source: "ndy_ig_sorter", type: "igblue_reread_current_tab", request_id: requestId, requested_at: Date.now() }, "*");
        sendResponse({ status: "ok", requested: true, request_id: requestId });
        return true;
      }
      if (msg.type === "ndy_refresh") {
        window.location.reload();
        sendResponse("ok");
        return true;
      }
      if (msg.type === "ndy_goto") {
        if (msg && msg.newpage === 1) window.open(msg.url, "_blank");
        else window.location.href = msg.url;
        sendResponse("ok");
        return true;
      }
      if (msg.type === "ndy_content_down") {
        // v279aa: 이전 수동 다운로드 메시지도 Background 경로로 복구한다.
        // MAIN fetch/Blob/a.click 경로는 unload 오류 때문에 사용하지 않는다.
        dbg("onMessage", "ndy_content_down_background_redirect", 113, {url:String(msg.url||"").slice(0,260), prefix:msg.prefix});
        sendMainBackgroundDownload({detail:{
          url:msg.url,
          prefix:msg.prefix,
          account:msg.account||"instagram",
          ext:msg.ext||"",
          meta:Object.assign({legacy_message_redirected:true, source_mode:"MANUAL_SINGLE"}, msg.meta||{})
        }});
        sendResponse({status:"queued", route:"content_background_download"});
        return true;
      }
      if (msg.type === "ndy_ig_overlay_mode_changed") {
        const mode = msg.value;
        if (isOverlayMode(mode)) postOverlayMode(mode);
        sendResponse("ok");
        return true;
      }
      if (msg.type === "ndy_ig_er_weights_changed") {
        const weights = msg.value;
        if (validWeights(weights)) postErWeights(weights);
        sendResponse("ok");
        return true;
      }
      if (msg.type === "ndy_ig_visual_settings_changed") {
        postVisualSettings(msg.value || {});
        sendResponse("ok");
        return true;
      }
      if (msg.type === "ndy_ig_main_rank_labels_changed") {
        postMainRankLabels(msg.labels || []);
        sendResponse("ok");
        return true;
      }
      const igblueReadInternalNoNavState = () => {
        try { const raw = localStorage.getItem("IGBLUE/internal_no_nav_state") || ""; return raw ? JSON.parse(raw) : null; } catch (_) { return null; }
      };
      if (msg.type === "igblue_get_main_injection_state") {
        const waitMs = Math.max(40, Math.min(1200, Number(msg.wait_ms || 120) || 120));
        const requestedNo = Math.max(0, Number(msg.requested_no || 0) || 0);
        setTimeout(() => {
          const st = igblueReadInternalNoNavState() || {};
          const loaded = Array.isArray(st.loaded_nos) ? st.loaded_nos.map(Number).filter(n => n > 0) : [];
          let activeTarget = null;
          try { const raw = localStorage.getItem("IGBLUE/active_download_target") || ""; activeTarget = raw ? JSON.parse(raw) : null; } catch (_) {}
          sendResponse({ ok: true, action: "main_injection_state", panel_found: !!document.querySelector("#IGBLUE_NoTop"), current_button_found: false, internal_handler_found: true, labels_ready: st.labels_ready === true, current_rank_no: Number(st.current_no || activeTarget && activeTarget.rank || 0) || 0, requested_no: requestedNo, has_requested: requestedNo > 0 ? loaded.includes(requestedNo) : false, loaded_max_no: loaded.length ? Math.max(...loaded) : 0, loaded_count: loaded.length, loaded_nos_sample: loaded.slice(-40), active_target_found: !!activeTarget, active_rank_no: Number(activeTarget && activeTarget.rank || 0) || 0, active_shortcode: String(activeTarget && (activeTarget.code || activeTarget.shortcode) || st.current_shortcode || ""), href: location.href });
        }, waitMs);
        return true;
      }
      if (msg.type === "igblue_auto_py_loaded_rank_state") {
        const waitMs = Math.max(40, Math.min(1200, Number(msg.wait_ms || 120) || 120));
        const requestedNo = Math.max(0, Number(msg.requested_no || 0) || 0);
        setTimeout(() => {
          const st = igblueReadInternalNoNavState() || {};
          const loaded = Array.isArray(st.loaded_nos) ? st.loaded_nos.map(Number).filter(n => n > 0) : [];
          const de = document.documentElement || document.body || {}, body = document.body || {};
          const scrollY = Math.max(0, Number(window.scrollY || window.pageYOffset || de.scrollTop || body.scrollTop || 0) || 0);
          const innerHeight = Math.max(0, Number(window.innerHeight || de.clientHeight || body.clientHeight || 0) || 0);
          const scrollHeight = Math.max(Number(de.scrollHeight || 0) || 0, Number(body.scrollHeight || 0) || 0, innerHeight);
          sendResponse({ ok: true, action: "loaded_rank_state", internal_handler_found: true, labels_ready: st.labels_ready === true, loaded_max_no: loaded.length ? Math.max(...loaded) : 0, loaded_min_no: loaded.length ? Math.min(...loaded) : 0, loaded_count: loaded.length, loaded_nos_sample: loaded.slice(-40), requested_no: requestedNo, has_requested: requestedNo > 0 ? loaded.includes(requestedNo) : false, current_rank_no: Number(st.current_no || 0) || 0, active_rank_no: Number(st.current_no || 0) || 0, active_hash_no: 0, hash_rank_no: 0, href: location.href, scroll_y: scrollY, inner_height: innerHeight, scroll_height: scrollHeight, at_bottom: !!(scrollHeight > 0 && scrollY + innerHeight >= scrollHeight - 16) });
        }, waitMs);
        return true;
      }
      if (msg.type === "igblue_auto_py_read_current_no") {
        const waitMs = Math.max(80, Math.min(1800, Number(msg.wait_ms || 300) || 300));
        setTimeout(() => {
          const st = igblueReadInternalNoNavState() || {};
          let target = null; try { const raw = localStorage.getItem("IGBLUE/active_download_target") || ""; target = raw ? JSON.parse(raw) : null; } catch (_) {}
          const rank = Number(st.current_no || target && target.rank || 0) || 0;
          sendResponse({ ok: !!rank, action: "read_current", rank_no: rank, rank_label: rank ? ("No" + String(rank).padStart(3, "0")) : "", text: "", shortcode: String(target && target.code || st.current_shortcode || ""), target_found: !!target, internal_handler_found: true, target_age_ms: target && target.ts ? Math.max(0, Date.now() - Number(target.ts || 0)) : 0 });
        }, waitMs);
        return true;
      }
      if (msg.type === "igblue_auto_py_current") {
        const waitMs = Math.max(180, Math.min(3000, Number(msg.wait_ms || 1200) || 1200));
        const st = igblueReadInternalNoNavState() || {};
        const beforeRank = Number(st.current_no || 0) || 0;
        try { localStorage.removeItem("IGBLUE/active_download_target"); } catch (_) {}
        if (beforeRank > 0) window.postMessage({ source: "ndy_ig_sorter", type: "igblue_auto_py_go_no", no: beforeRank }, "*");
        setTimeout(() => {
          const afterSt = igblueReadInternalNoNavState() || {};
          let target = null; try { const raw = localStorage.getItem("IGBLUE/active_download_target") || ""; target = raw ? JSON.parse(raw) : null; } catch (_) {}
          const afterRank = Number(afterSt.current_no || target && target.rank || 0) || 0;
          sendResponse({ ok: !!(afterRank || target), action: "click_current", before_rank: beforeRank, after_rank: afterRank, before_label: beforeRank ? ("No" + String(beforeRank).padStart(3, "0")) : "", after_label: afterRank ? ("No" + String(afterRank).padStart(3, "0")) : "", before_shortcode: String(st.current_shortcode || ""), after_shortcode: String(target && target.code || afterSt.current_shortcode || ""), changed: afterRank !== beforeRank, current_clicked: false, current_button_found: false, internal_handler_found: true, target_found: !!target, confirmed_current: afterRank === beforeRank || !!target });
        }, waitMs);
        return true;
      }
      if (msg.type === "igblue_auto_py_next") {
        const waitMs = Math.max(300, Math.min(3000, Number(msg.wait_ms || 1200) || 1200));
        const st = igblueReadInternalNoNavState() || {};
        const beforeRank = Number(st.current_no || 0) || 0;
        const requested = Math.max(1, beforeRank + 1);
        try { localStorage.removeItem("IGBLUE/active_download_target"); } catch (_) {}
        window.postMessage({ source: "ndy_ig_sorter", type: "igblue_auto_py_go_no", no: requested }, "*");
        setTimeout(() => {
          const afterSt = igblueReadInternalNoNavState() || {};
          let target = null; try { const raw = localStorage.getItem("IGBLUE/active_download_target") || ""; target = raw ? JSON.parse(raw) : null; } catch (_) {}
          const afterRank = Number(afterSt.current_no || target && target.rank || 0) || 0;
          sendResponse({ ok: true, action: "click_next", before_rank: beforeRank, after_rank: afterRank, before_label: beforeRank ? ("No" + String(beforeRank).padStart(3, "0")) : "", after_label: afterRank ? ("No" + String(afterRank).padStart(3, "0")) : "", before_shortcode: String(st.current_shortcode || ""), after_shortcode: String(target && target.code || afterSt.current_shortcode || ""), changed: afterRank !== beforeRank, target_found: !!target, next_button_found: false, internal_handler_found: true });
        }, waitMs);
        return true;
      }
      if (msg.type === "igblue_auto_py_go_no") {
        const requestedNo = Number(msg.no || 1) || 1;
        const waitMs = Math.max(180, Math.min(2200, Number(msg.wait_ms || 750) || 750));
        const readTarget = () => {
          try {
            const raw = localStorage.getItem("IGBLUE/active_download_target") || "";
            return raw ? JSON.parse(raw) : null;
          } catch (err) {
            return null;
          }
        };
        const before = readTarget();
        try { localStorage.removeItem("IGBLUE/active_download_target"); } catch (_) {}
        window.postMessage({ source: "ndy_ig_sorter", type: "igblue_auto_py_go_no", no: requestedNo }, "*");
        setTimeout(() => {
          const after = readTarget();
          const beforeRank = Number(before && before.rank || 0) || 0;
          const afterRank = Number(after && after.rank || 0) || 0;
          const beforeCode = String(before && before.code || "");
          const afterCode = String(after && after.code || "");
          sendResponse({
            ok: true,
            action: "go_no",
            requested_no: requestedNo,
            before_rank: beforeRank,
            after_rank: afterRank,
            before_shortcode: beforeCode,
            after_shortcode: afterCode,
            changed: !!(afterRank && (afterRank !== beforeRank || afterRank === requestedNo || afterCode !== beforeCode)),
            target_found: !!after,
            stale_cleared_before_go: true,
            target_age_ms: after && after.ts ? Math.max(0, Date.now() - Number(after.ts || 0)) : 0
          });
        }, waitMs);
        return true;
      }
      if (msg.type === "igblue_auto_status_update") {
        try {
          const v = msg.value && typeof msg.value === 'object' ? msg.value : {};
          igblueAutoStatusSnapshot = Object.assign({}, v, { text: String(v.text || ''), updated_at: Date.now() });
          try { window.postMessage({ source: "ndy_ig_sorter", type: "igblue_main_panel_auto_state", value: { active: !!v.active, reason: String(v.reason || ""), no_label: String(v.no_label || "") } }, "*"); } catch (_) {}
        } catch (_) {}
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === "igblue_dev_status") {
        window.postMessage({ source: "ndy_ig_sorter", type: "igblue_dev_status", message: String(msg.message || ""), level: msg.level || "info", detail: msg.detail || null }, "*");
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === "igblue_py_state") {
        postPyState(msg.value || msg);
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === "ndy_down_progress") {
        postDownloadProgress(msg.value && typeof msg.value === "object" ? msg.value : msg);
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === "igblue_set_tts_level") {
        const value = msg.value || {};
        window.postMessage({ source: "ndy_ig_sorter", type: "igblue_set_tts_level", value }, "*");
        sendResponse({ ok: true });
        return true;
      }
      if (msg.type === "igblue_get_active_download_target") {
        try {
          const raw = localStorage.getItem("IGBLUE/active_download_target") || "";
          const target = raw ? JSON.parse(raw) : null;
          sendResponse({ ok: !!target, target });
        } catch (err) {
          sendResponse({ ok: false, error: String(err && err.message || err) });
        }
        return true;
      }
      if (msg.type === "igblue_get_account_page_state") {
        sendResponse(igblueReadAccountPageState(msg.expected_username || msg.username || ""));
        return true;
      }
      if (msg.type === "igblue_get_current_account_target") {
        sendResponse(igblueReadCurrentAccountTarget());
        return true;
      }
    } catch {}
    return false;
  });

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area !== "local") return;
    if (Object.prototype.hasOwnProperty.call(changes, OVERLAY_MODE_KEY)) {
      const mode = changes[OVERLAY_MODE_KEY].newValue;
      postOverlayMode(isOverlayMode(mode) ? mode : DEFAULT_OVERLAY_MODE);
    }
    if (Object.prototype.hasOwnProperty.call(changes, ER_WEIGHTS_KEY)) {
      const weights = changes[ER_WEIGHTS_KEY].newValue;
      postErWeights(validWeights(weights) ? normalizeWeights(weights) : { ...DEFAULT_ER_WEIGHTS });
    }
    if (Object.prototype.hasOwnProperty.call(changes, VISUAL_SETTINGS_KEY)) {
      postVisualSettings(changes[VISUAL_SETTINGS_KEY].newValue || {});
    }
  });

  chrome.storage.local.get(OVERLAY_MODE_KEY, (data = {}) => {
    const mode = data[OVERLAY_MODE_KEY];
    const value = isOverlayMode(mode) ? mode : DEFAULT_OVERLAY_MODE;
    if (!isOverlayMode(mode)) chrome.storage.local.set({ [OVERLAY_MODE_KEY]: value });
    postOverlayMode(value);
  });

  chrome.storage.local.get(ER_WEIGHTS_KEY, (data = {}) => {
    const weights = data[ER_WEIGHTS_KEY];
    const value = validWeights(weights) ? normalizeWeights(weights) : { ...DEFAULT_ER_WEIGHTS };
    if (!validWeights(weights)) chrome.storage.local.set({ [ER_WEIGHTS_KEY]: value });
    postErWeights(value);
  });

  chrome.storage.local.get(VISUAL_SETTINGS_KEY, (data = {}) => {
    postVisualSettings(data[VISUAL_SETTINGS_KEY] || {});
  });

  // Wake the background so it can remember this tab even before the first media payload arrives.


  // IGblue v179a14: Auto8700 예약 실행 bridge.
  // Auto8700이 Chrome CDP로 Instagram page에 CustomEvent/window message를 던지면,
  // content script가 background/side로 전달한다. 실제 자동다운로드 루프는 side.js 기존 함수를 재사용한다.
  function igblueForwardAuto8700StartRequest(payload, source) {
    try {
      const d = payload && typeof payload === "object" ? payload : {};
      const req = Object.assign({
        type: "IGBLUE_AUTO8700_START_REQUEST",
        source: "Auto8700",
        bridge_source: source || "content",
        href: location.href,
        url: location.href,
        ts: Date.now()
      }, d || {});
      if (!req.request_id) req.request_id = "auto8700_content_" + Date.now() + "_" + Math.floor(Math.random() * 100000);
      try { window.postMessage({ source: "ndy_ig_sorter", type: "igblue_main_panel_auto_start", value: { active: true, reason: String(req.reason || req.trigger || "auto8700_start") } }, "*"); } catch (_) {}
      chrome.runtime.sendMessage(req, () => {
        try {
          if (chrome.runtime.lastError) postMainDevStatus("Auto8700 예약 요청 전달 실패", "warn", { message: chrome.runtime.lastError.message, request_id: req.request_id });
          else postMainDevStatus("Auto8700 예약 요청 전달", "info", { request_id: req.request_id });
        } catch (_) {}
      });
    } catch (err) {
      try { postMainDevStatus("Auto8700 예약 bridge 예외", "warn", { error: String(err && err.message || err) }); } catch (_) {}
    }
  }


  // IGblue v179a16: Auto8700 CDP-owned schedule storage mirror.
  // Auto8700이 Instagram origin localStorage에 만든 예약 기본틀을 chrome.storage로 복사한다.
  const IGBLUE_AUTO8700_SCHEDULE_KEY = "IGBLUE_AUTO8700_SCHEDULE_V1";
  function igblueMirrorAuto8700ScheduleStorage(reason) {
    try {
      const raw = localStorage.getItem(IGBLUE_AUTO8700_SCHEDULE_KEY);
      if (!raw) return;
      const parsed = JSON.parse(raw);
      if (!parsed || typeof parsed !== "object") return;
      chrome.runtime.sendMessage({ type: "IGBLUE_AUTO8700_SCHEDULE_MIRROR", schedule: parsed, reason: reason || "content_mirror", href: location.href }, () => { chrome.runtime.lastError; });
    } catch (_) {}
  }
  window.addEventListener("igblue_auto8700_schedule_sync", () => igblueMirrorAuto8700ScheduleStorage("cdp_sync_event"));
  window.addEventListener("storage", (ev) => { if (ev && ev.key === IGBLUE_AUTO8700_SCHEDULE_KEY) igblueMirrorAuto8700ScheduleStorage("storage_event"); });
  setTimeout(() => igblueMirrorAuto8700ScheduleStorage("startup"), 800);
  setInterval(() => igblueMirrorAuto8700ScheduleStorage("poll"), 5000);

  document.addEventListener("igblue_auto8700_schedule_start", (ev) => {
    igblueForwardAuto8700StartRequest(ev && ev.detail || {}, "document_event");
  });
  window.addEventListener("message", (ev) => {
    const d = ev && ev.data;
    if (!d || d.source !== "AUTO8700" || d.type !== "IGBLUE_AUTO8700_START_REQUEST") return;
    igblueForwardAuto8700StartRequest(d, "window_message");
  });

  function igblueForwardAuto8700SideOpenRequest(payload, source) {
    try {
      const d = payload && typeof payload === "object" ? payload : {};
      const req = Object.assign({
        type: "IGBLUE_AUTO8700_SIDE_OPEN_REQUEST",
        source: "Auto8700",
        bridge_source: source || "content",
        href: location.href,
        url: location.href,
        ts: Date.now()
      }, d || {});
      if (!req.request_id) req.request_id = "auto8700_side_content_" + Date.now() + "_" + Math.floor(Math.random() * 100000);
      chrome.runtime.sendMessage(req, () => { chrome.runtime.lastError; });
    } catch (_) {}
  }

  document.addEventListener("igblue_auto8700_side_open_request", (ev) => {
    igblueForwardAuto8700SideOpenRequest(ev && ev.detail || {}, "document_event");
  });
  window.addEventListener("message", (ev) => {
    const d = ev && ev.data;
    if (!d || d.source !== "AUTO8700" || d.type !== "IGBLUE_AUTO8700_SIDE_OPEN_REQUEST") return;
    igblueForwardAuto8700SideOpenRequest(d, "window_message");
  });

  try { chrome.runtime.sendMessage({ type: "IGBLUE_CONTENT_READY", url: location.href }, () => { if(chrome.runtime.lastError) dbg("content_ready", "runtime_error", 172, {message:chrome.runtime.lastError.message}); else dbg("content_ready", "sent", 172, {url:location.href}); }); } catch (e) { dbg("content_ready", "exception", 172, {message:String(e&&e.message||e)}); }
})();
