// FILE: _St/IG_StYellow/test_ext_tab_replacement_v279d.js | ROLE: v279d 새 탭 생성 방식을 v280 2단계 인계로 보호하는 회귀 검사
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');

const root = __dirname;
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8'));
const background = fs.readFileSync(path.join(root, 'background.js'), 'utf8');
const side = fs.readFileSync(path.join(root, 'side.js'), 'utf8');
const inject = fs.readFileSync(path.join(root, 'js', 'instagram_inject.js'), 'utf8');

assert.strictEqual(manifest.version, '2.3.8.2800');
assert.strictEqual(manifest.version_name, '2.3.8.280');
assert.ok(side.includes('const IGBLUE_PACKAGE_VERSION = "v280";'));

for (const token of [
  'function igblueClosePreviousTabAfterReplacement(previousTab, replacementTabId, targetUrl, navigationMeta, callback)',
  'function igblueNavigateActiveWebTab(url, preferredTabId, navigationMeta, callback)',
  '[YELLOW][TAB-REPLACE][NEW_TAB_CREATE_BEFORE]',
  '[YELLOW][TAB-REPLACE][NEW_TAB_CREATED]',
  '[YELLOW][TAB-REPLACE][NEW_TAB_READY]',
  '[YELLOW][TAB-REPLACE][NEW_TAB_ACTIVATED]',
  '[YELLOW][TAB-HANDOFF][WAIT_SIDE_ACK]',
  'chrome.tabs.create(createProperties',
  'active:false',
  'igblueWaitForNavigationReady(newTabId, targetUrl',
  'chrome.tabs.update(newTabId, {active:true}',
  'const handoff = igbluePrepareTabHandoff(previousTab, newTabId, targetUrl, meta, readyResult);',
  'handoff_required:true',
  'previous_tab_closed:false',
  'previous_tab_preserved:true',
]) assert.ok(background.includes(token), `missing replacement token: ${token}`);

const navStart = background.indexOf('function igblueNavigateActiveWebTab(url, preferredTabId, navigationMeta, callback)');
const navEnd = background.indexOf('function igblueEnsureContentScripts', navStart);
const navBlock = background.slice(navStart, navEnd);
const createAt = navBlock.indexOf('chrome.tabs.create(createProperties');
const waitAt = navBlock.indexOf('igblueWaitForNavigationReady(newTabId, targetUrl');
const activateAt = navBlock.indexOf('chrome.tabs.update(newTabId, {active:true}');
const prepareAt = navBlock.indexOf('igbluePrepareTabHandoff(previousTab, newTabId');
const callbackAt = navBlock.indexOf('status:"handoff_ready"', prepareAt);
assert.ok(createAt >= 0 && waitAt > createAt, 'new tab readiness must follow creation');
assert.ok(activateAt > waitAt, 'new tab activation must follow readiness');
assert.ok(prepareAt > activateAt && callbackAt > prepareAt, 'handoff token must be prepared after activation');
assert.ok(!navBlock.includes('igblueClosePreviousTabAfterReplacement(previousTab, newTabId'), 'old tab must not close before Side owner acknowledgement');
assert.ok(!navBlock.includes('chrome.tabs.update(tab.id, {url:targetUrl'), 'in-place URL replacement must remain removed');

const sideNavStart = side.indexOf('async function teAutoPyNavigateAccountUrl');
const sideNavEnd = side.indexOf('async function teAutoPyPrepareAccountQueueForStart', sideNavStart);
const sideNav = side.slice(sideNavStart, sideNavEnd);
assert.ok(sideNav.indexOf('teAutoPySessionTarget.tab_id = tabId;') >= 0);
assert.ok(sideNav.indexOf('const handoff = await teAutoPyCommitTabHandoff') > sideNav.indexOf('teAutoPySessionTarget.tab_id = tabId;'));
assert.ok(sideNav.indexOf('teAutoPyValidateAccountAfterNavigation') > sideNav.indexOf('const handoff = await teAutoPyCommitTabHandoff'));

assert.ok(inject.includes('data-yellow-main-injection-ready","v280"'));
assert.ok(!inject.includes('DOCUMENT_START_CALL'));

for (const token of ['IGBLUE_DOWNLOAD_URL','auto_direct_download: true','ig_media_download_direct','ig_thread_queue_result']) {
  assert.ok(side.includes(token) || background.includes(token), `missing proven direct download token: ${token}`);
}

// VM: navigation returns a handoff token and does not remove the previous tab yet.
const blockStart = background.indexOf('function igblueClosePreviousTabAfterReplacement');
const helperCode = background.slice(blockStart, navEnd) + '\nthis.navigate = igblueNavigateActiveWebTab;';
const events = [];
const context = {
  URL, Number, String, Object, Math,
  console: {log(){}},
  setTimeout() { return 1; },
  igblueIsInstagramUrl(url) { return String(url).startsWith('https://www.instagram.com/'); },
  igblueIsExtensionUrl() { return false; },
  igblueWaitForNavigationReady(tabId, url, guard, meta, cb) { events.push(['wait', tabId]); cb({ok:true,navigation_ready:{content_ready:true,main_injection_ready:true},navigation_ready_ms:120,attempts:2}); },
  igbluePrepareTabHandoff(previousTab, newTabId) { events.push(['prepare', previousTab.id, newTabId]); return {token:'handoff-test',generation:2}; },
  igblueBroadcastTarget(tabId) { events.push(['broadcast', tabId]); },
  igblueLastActiveWebTabId:0, igblueLastInstagramTabId:0, igblueActiveInstagramTabId:0,
  igblueUrlByTabId:new Map(),
  chrome:{runtime:{lastError:null},tabs:{
    get(tabId, cb){ cb({id:tabId,url:'https://www.instagram.com/old/',windowId:1,index:3}); },
    query(opts, cb){ cb([{id:10,url:'https://www.instagram.com/old/',windowId:1,index:3}]); },
    create(props, cb){ events.push(['create',props.active]); cb({id:20,url:props.url,windowId:1,index:4}); },
    update(tabId, props, cb){ events.push(['update',tabId,props.active]); cb({id:tabId,url:'https://www.instagram.com/new/'}); },
    remove(tabId, cb){ events.push(['remove',tabId]); cb(); }
  }}
};
vm.runInNewContext(helperCode, context, {filename:'tab_replacement_v280.js'});
let result = null;
context.navigate('https://www.instagram.com/new/', 10, {reason:'account_queue_next',run_seq:'8',account:'new',account_index:2,account_total:3}, value => { result = value; });
assert.ok(result && result.ok);
assert.strictEqual(result.tabId, 20);
assert.strictEqual(result.previousTabId, 10);
assert.strictEqual(result.previous_tab_closed, false);
assert.strictEqual(result.handoff_required, true);
assert.strictEqual(result.handoff_token, 'handoff-test');
assert.ok(!events.some(row => row[0] === 'remove'), 'previous tab must remain until commit');

console.log('OK test_ext_tab_replacement_v279d');
