// 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 cdp = igblueCdpGateState && igblueCdpGateState.ok ? 'O' : 'X';
    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 `CDP:${cdp} · 탭 ${count} · ${igblueTabIdTail(pending.previous_tab_id)}→${igblueTabIdTail(pending.replacement_tab_id)}`;
    }
    const owner = Number(s.owner_tab_id || 0) || 0;
    if (!owner) return `CDP:${cdp} · 탭 ${count} · OWNER 없음`;
    return `CDP:${cdp} · 탭 ${count} · OWNER tab ${igblueTabIdTail(owner)}`;
  }
  function igblueTabControlDiagText(){
    const s = igblueTabControlState || {};
    const pending = s.pending && typeof s.pending === 'object' ? s.pending : null;
    const rows = [
      '탭 제어 v288',
      '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
      }));
      try { igblueUpdateMainDiagStatus(); } catch (_) {}
    } catch (_) {}
  }
  function igblueRequestTabControlState(force, done){
    const now = Date.now();
    if (igblueTabControlRequesting || (!force && now - igblueTabControlLastRequestAt < 900)) {
      if (typeof done === 'function') done(igblueTabControlState || {});
      return;
    }
    igblueTabControlRequesting = true;
    igblueTabControlLastRequestAt = now;
    try {
      chrome.runtime.sendMessage({type:'IGBLUE_GET_TAB_CONTROL_STATE'}, (state) => {
        igblueTabControlRequesting = false;
        if (!chrome.runtime.lastError) igblueApplyTabControlState(state || {});
        if (typeof done === 'function') done(igblueTabControlState || {});
      });
    } catch (_) {
      igblueTabControlRequesting = false;
      if (typeof done === 'function') done(igblueTabControlState || {});
    }
  }
  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_v282';
  const IGBLUE_MAIN_DIAG_COLLAPSED_KEY = 'IGBLUE/main_diag_collapsed_v282';
  let igblueMainDiagCopyTimer = 0;
  function igblueEnsureMainDiagStyle() {
    if (document.getElementById('igblue_main_diag_style_v282')) return;
    const st = document.createElement('style');
    st.id = 'igblue_main_diag_style_v282';
    st.textContent = `
      html body #IGBLUE_NoTop > .igblue-eye-toggle,
      html body #IGBLUE_NoTop > .igblue-dom-copy,
      html body #IGBLUE_NoTop > .igblue-origin-panel,
      html body #IGBLUE_NoTop > .igblue-status-box { display:none !important; }
      #igblue_cdp_gate { display:none !important; }
      #igblue_main_diag_control {
        position:fixed; z-index:2147483646; display:flex; align-items:center; gap:3px;
        pointer-events:auto; white-space:nowrap;
      }
      #igblue_main_diag_toggle {
        min-width:148px; width:auto; height:20px; box-sizing:border-box; padding:0 7px;
        border-radius:999px; background:rgba(22,101,52,.62); border:1px solid rgba(134,239,172,.68);
        color:#f8fafc; font:800 11px/18px Consolas,"Malgun Gothic",Arial,sans-serif;
        text-align:center; cursor:pointer; user-select:text; pointer-events:auto;
        backdrop-filter:blur(2px); white-space:nowrap;
      }
      #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_control.is-collapsed #igblue_main_diag_toggle { display:none; }
      .igblue-main-diag-icon {
        width:20px; min-width:20px; height:20px; box-sizing:border-box; padding:0;
        border-radius:6px; border:1px solid rgba(148,163,184,.58); background:rgba(15,23,42,.68);
        color:#e5e7eb; font:900 12px/18px Consolas,Arial,sans-serif; text-align:center;
        cursor:pointer; user-select:none; pointer-events:auto;
      }
      .igblue-main-diag-icon:hover { background:rgba(30,64,175,.78); border-color:rgba(147,197,253,.88); }
      #igblue_main_diag_panel {
        position:fixed; z-index:2147483647; width:340px; max-height:68vh;
        box-sizing:border-box; overflow:hidden; display:none; padding:0; border-radius:9px;
        background:rgba(4,8,15,.96); border:1px solid rgba(148,163,184,.58);
        box-shadow:0 10px 24px rgba(0,0,0,.36); color:#e5e7eb;
      }
      #igblue_main_diag_panel.is-open { display:flex; flex-direction:column; }
      #igblue_main_diag_toolbar { display:flex; justify-content:flex-end; gap:5px; padding:5px; border-bottom:1px solid rgba(148,163,184,.28); }
      #igblue_main_diag_toolbar button {
        min-height:24px; padding:2px 7px; border-radius:6px; border:1px solid rgba(148,163,184,.55);
        background:rgba(30,41,59,.88); color:#f8fafc; font:800 10px/1.2 "Malgun Gothic",Arial,sans-serif; cursor:pointer;
      }
      #igblue_main_diag_text {
        margin:0; padding:7px 8px; overflow:auto; user-select:text; white-space:pre-wrap;
        overflow-wrap:anywhere; word-break:break-word; font:700 10px/1.34 Consolas,"Malgun Gothic",monospace;
      }
    `;
    (document.head || document.documentElement).appendChild(st);
  }
  function igblueMainDiagText() {
    return igblueCdpGateText().trimEnd();
  }
  function igblueCopyText(text, done) {
    const value = String(text || '');
    const finish = (ok) => { if (typeof done === 'function') done(!!ok); };
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(value).then(() => finish(true)).catch(() => {
          try {
            const ta = document.createElement('textarea');
            ta.value = value; ta.style.position = 'fixed'; ta.style.opacity = '0';
            (document.body || document.documentElement).appendChild(ta); ta.select();
            const ok = document.execCommand('copy'); ta.remove(); finish(ok);
          } catch (_) { finish(false); }
        });
        return;
      }
      const ta = document.createElement('textarea');
      ta.value = value; ta.style.position = 'fixed'; ta.style.opacity = '0';
      (document.body || document.documentElement).appendChild(ta); ta.select();
      const ok = document.execCommand('copy'); ta.remove(); finish(ok);
    } catch (_) { finish(false); }
  }
  function igblueUpdateMainDiagStatus() {
    const toggle = document.getElementById('igblue_main_diag_toggle');
    if (!toggle || toggle.dataset.copyNotice === '1') return;
    const next = igblueTabControlCompactText();
    if (toggle.textContent !== next) toggle.textContent = next;
    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 = '클릭: 전체 진단 복사';
  }
  function igblueShowMainDiagCopyResult(ok) {
    const toggle = document.getElementById('igblue_main_diag_toggle');
    if (!toggle) return;
    if (igblueMainDiagCopyTimer) clearTimeout(igblueMainDiagCopyTimer);
    toggle.dataset.copyNotice = '1';
    toggle.textContent = ok ? '복사됨' : '복사 실패';
    igblueMainDiagCopyTimer = setTimeout(() => {
      delete toggle.dataset.copyNotice;
      igblueUpdateMainDiagStatus();
    }, 900);
  }
  function igbluePlaceMainDiag(control) {
    try {
      if (!control) return;
      const host = document.getElementById('IGBLUE_NoTop');
      const r = host && host.getBoundingClientRect ? host.getBoundingClientRect() : null;
      const pad = 8;
      const width = Math.max(44, Number(control.offsetWidth || 0) || 210);
      let top = r && Number.isFinite(r.top) ? Math.max(pad, r.top - 25) : 56;
      let left = r && Number.isFinite(r.right) ? r.right - width : (window.innerWidth || 0) - width - pad;
      left = Math.max(pad, Math.min((window.innerWidth || 0) - width - pad, left));
      control.style.left = Math.round(left) + 'px';
      control.style.top = Math.round(top) + 'px';
      const panel = document.getElementById('igblue_main_diag_panel');
      if (panel) {
        const panelW = 340;
        let panelLeft = left + width - panelW;
        panelLeft = Math.max(pad, Math.min((window.innerWidth || 0) - panelW - pad, panelLeft));
        panel.style.left = Math.round(panelLeft) + 'px';
        panel.style.top = Math.round(Math.max(pad, top + 24)) + 'px';
      }
    } catch (_) {}
  }
  function igblueRenderMainDiagSnapshot(fetchLatest) {
    const render = () => {
      const text = document.getElementById('igblue_main_diag_text');
      if (text) text.textContent = igblueMainDiagText();
      igbluePlaceMainDiag(document.getElementById('igblue_main_diag_control'));
    };
    if (fetchLatest) igblueRequestTabControlState(true, render);
    else render();
  }
  function igblueSetMainDiagOpen(open) {
    const on = !!open;
    try { localStorage.setItem(IGBLUE_MAIN_DIAG_OPEN_KEY, on ? '1' : '0'); } catch (_) {}
    const panel = document.getElementById('igblue_main_diag_panel');
    const detail = document.getElementById('igblue_main_diag_expand');
    if (panel) panel.classList.toggle('is-open', on);
    if (detail) {
      detail.setAttribute('aria-expanded', on ? 'true' : 'false');
      detail.title = on ? '상세 닫기' : '상세 열기';
    }
    if (on) igblueRenderMainDiagSnapshot(true);
  }
  function igblueSetMainDiagCollapsed(collapsed) {
    const on = !!collapsed;
    try { localStorage.setItem(IGBLUE_MAIN_DIAG_COLLAPSED_KEY, on ? '1' : '0'); } catch (_) {}
    const control = document.getElementById('igblue_main_diag_control');
    const minimize = document.getElementById('igblue_main_diag_minimize');
    if (control) control.classList.toggle('is-collapsed', on);
    if (minimize) minimize.title = on ? '상태 펼치기' : '상태 축소';
    if (on) igblueSetMainDiagOpen(false);
    igbluePlaceMainDiag(control);
  }
  function igblueEnsureMainDiag() {
    igblueEnsureMainDiagStyle();
    try {
      ['IGBLUE_MainPanelEye','IGBLUE_DomDiagnosticCopy'].forEach((id) => {
        const legacy = document.getElementById(id);
        if (legacy) legacy.style.setProperty('display', 'none', 'important');
      });
    } catch (_) {}
    let control = document.getElementById('igblue_main_diag_control');
    if (!control) {
      control = document.createElement('div');
      control.id = 'igblue_main_diag_control';
      const toggle = document.createElement('button');
      toggle.type = 'button';
      toggle.id = 'igblue_main_diag_toggle';
      toggle.textContent = 'CDP:X · 탭 0 · OWNER 없음';
      toggle.setAttribute('aria-label', 'CDP와 Instagram 탭 제어 상태');
      toggle.title = '클릭: 전체 진단 복사';
      toggle.addEventListener('click', (ev) => {
        const selection = window.getSelection ? window.getSelection() : null;
        const selectedInside = !!(selection && String(selection.toString() || '').trim() && (toggle.contains(selection.anchorNode) || toggle.contains(selection.focusNode)));
        if (selectedInside) return;
        ev.preventDefault(); ev.stopPropagation();
        igblueCopyText(igblueMainDiagText(), igblueShowMainDiagCopyResult);
      }, true);
      const minimize = document.createElement('button');
      minimize.type = 'button'; minimize.id = 'igblue_main_diag_minimize'; minimize.className = 'igblue-main-diag-icon'; minimize.textContent = '_';
      minimize.addEventListener('click', (ev) => {
        ev.preventDefault(); ev.stopPropagation();
        igblueSetMainDiagCollapsed(!control.classList.contains('is-collapsed'));
      }, true);
      const detail = document.createElement('button');
      detail.type = 'button'; detail.id = 'igblue_main_diag_expand'; detail.className = 'igblue-main-diag-icon'; detail.textContent = '□';
      detail.addEventListener('click', (ev) => {
        ev.preventDefault(); ev.stopPropagation();
        const panel = document.getElementById('igblue_main_diag_panel');
        igblueSetMainDiagOpen(!(panel && panel.classList.contains('is-open')));
      }, true);
      control.append(toggle, minimize, detail);
      (document.documentElement || document.body).appendChild(control);
    }
    let panel = document.getElementById('igblue_main_diag_panel');
    if (!panel) {
      panel = document.createElement('div');
      panel.id = 'igblue_main_diag_panel';
      panel.setAttribute('role', 'dialog');
      const toolbar = document.createElement('div');
      toolbar.id = 'igblue_main_diag_toolbar';
      const refresh = document.createElement('button');
      refresh.type = 'button'; refresh.id = 'igblue_main_diag_refresh'; refresh.textContent = '새로고침';
      refresh.addEventListener('click', (ev) => { ev.preventDefault(); ev.stopPropagation(); igblueRenderMainDiagSnapshot(true); }, true);
      const copy = document.createElement('button');
      copy.type = 'button'; copy.id = 'igblue_main_diag_copy'; copy.textContent = '전체 복사';
      copy.addEventListener('click', (ev) => {
        ev.preventDefault(); ev.stopPropagation();
        const shown = document.getElementById('igblue_main_diag_text');
        igblueCopyText(shown ? shown.textContent : igblueMainDiagText(), igblueShowMainDiagCopyResult);
      }, true);
      toolbar.append(refresh, copy);
      const pre = document.createElement('pre'); pre.id = 'igblue_main_diag_text';
      panel.append(toolbar, pre);
      (document.documentElement || document.body).appendChild(panel);
    }
    if (control.dataset.initialized !== '1') {
      let collapsed = false, open = false;
      try {
        collapsed = localStorage.getItem(IGBLUE_MAIN_DIAG_COLLAPSED_KEY) === '1';
        open = localStorage.getItem(IGBLUE_MAIN_DIAG_OPEN_KEY) === '1';
      } catch (_) {}
      control.dataset.initialized = '1';
      igblueSetMainDiagCollapsed(collapsed);
      if (!collapsed) igblueSetMainDiagOpen(open);
    }
    igblueUpdateMainDiagStatus();
    igbluePlaceMainDiag(control);
    return control;
  }
  function igblueEnsureCdpGate() {
    let marker = document.getElementById('igblue_cdp_gate');
    if (!marker) {
      marker = document.createElement('span');
      marker.id = 'igblue_cdp_gate';
      marker.setAttribute('aria-hidden', 'true');
      (document.documentElement || document.body).appendChild(marker);
    }
    igblueEnsureMainDiag();
    return marker;
  }
  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()});
      igblueEnsureCdpGate();
      igblueUpdateMainDiagStatus();
      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 { igblueEnsureMainDiag(); 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)}); }
})();
