// FILE: _St/IG_StYellow/test_ext_side_control_save_v277.js | ROLE: IG_StYellow 회귀 테스트
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');

const root = __dirname;
const read = (name) => fs.readFileSync(path.join(root, name), 'utf8');
const side = read('side.js');
const html = read('side.html');
const background = read('background.js');
const manifest = JSON.parse(read('manifest.json'));
const wsSource = fs.readFileSync(path.join(root, '..', 'WS8771', 'ws8771_main.py'), 'utf8');
const reportSource = fs.readFileSync(path.join(root, '..', 'WS8771', 'ws8771_html_report.py'), 'utf8');
const wsConfig = JSON.parse(fs.readFileSync(path.join(root, '..', 'WS8771', 'ws8771_config.json'), 'utf8'));

assert.strictEqual(manifest.version, '2.3.8.2880');
assert.strictEqual(manifest.version_name, '2.3.8.288');
assert.ok(side.includes('const IGBLUE_PACKAGE_VERSION = "v288";'));

// v277 save selectors replace the two obsolete options without changing download delay.
assert.ok(html.includes('id="download_video_save"'));
assert.ok(html.includes('<span>Video 저장</span>'));
assert.ok(html.includes('id="download_image_save"'));
assert.ok(html.includes('<span>Image 저장</span>'));
assert.ok(!html.includes('id="download_video_only"'));
assert.ok(!html.includes('id="download_id_date_name"'));
assert.ok(!html.includes('>Video only<'));
assert.ok(!html.includes('>ID-date filename<'));
assert.ok(html.includes('id="download_delay_sec"'));

const cdpText = 'CDP는 자동예약에만 필요합니다. 일반 자동실행과 수동 다운로드의 기본 조건은 Auto8700/WS8771입니다.';
assert.ok(html.includes(cdpText));
assert.ok(side.includes(`const IGBLUE_CDP_REQUIRED_TEXT = "${cdpText}";`));
assert.ok(side.includes('window.__IGBLUE_SET_CDP_READY_V277__ = teAutoPySetCdpReady;'));
assert.ok(!side.includes('if (!teAutoPyCdpReady) { teAutoPyApplyCdpNotice(); n(IGBLUE_CDP_REQUIRED_TEXT); return; }'));
assert.ok(!side.includes('cb.disabled = !teAutoPyCdpReady'));
assert.ok(!side.includes('startBtn.disabled = !teAutoPyCdpReady'));
assert.ok(side.includes('startBtn.disabled = !teAutoPyLastPyOk'));
assert.ok(side.includes('setEnabled(lastStatusOk && cdpReady);'));
assert.ok(side.includes('cdpReady ? (storage.ig_side_active ? "IG 담당" : "예약 준비") : "CDP 실행 필요"'));
// Manual download controls remain present and are not routed through the auto-start guard.
assert.ok(html.includes('id="down_all_medias"'));
assert.ok(html.includes('id="down_filtered_medias"'));
assert.ok(side.includes('getElementById("down_all_medias")'));

// Popup handoff: block while auto is running, confirm the same Instagram tab, then close old Side.
assert.ok(side.includes('자동실행 중에는 새 창으로 전환할 수 없습니다. 중지 후 다시 시도하세요.'));
assert.ok(side.includes('type: "IGBLUE_OPEN_POPUP"'));
assert.ok(side.includes('type:"IGBLUE_POPUP_READY"'));
assert.ok(side.includes('teIsInstagramUrl(state.url)'));
assert.ok(background.includes('msg.type === "IGBLUE_POPUP_READY"'));
assert.ok(background.includes('handoff_tab_mismatch'));
assert.ok(background.includes('chrome.sidePanel.close({tabId:readyTabId})'));
assert.ok(background.includes('chrome.sidePanel.close({windowId:Number(tab.windowId)})'));
assert.ok(background.includes('side_close_api_unavailable'));

// Metadata-only path and status vocabulary are preserved across Side, WS JSON and HTML report.
for (const token of ['metadata_collected', 'video_requested', 'video_downloaded', 'image_requested', 'image_downloaded']) {
  assert.ok(side.includes(token), `Side missing ${token}`);
  assert.ok(wsSource.includes(token), `WS missing ${token}`);
  assert.ok(reportSource.includes(token), `report missing ${token}`);
}
assert.ok(side.includes('ig_media_metadata_collect'));
assert.ok(wsSource.includes('def handle_ig_media_metadata_collect(self, req):'));
assert.ok(wsSource.includes('def upsert_folder_metadata(folder, meta):'));
assert.ok((wsConfig.thread_queue_mode.actions || []).includes('ig_media_metadata_collect'));
assert.ok(reportSource.includes('item["_kind"] = "metadata"'));
assert.ok(reportSource.includes('MP4 직접링크'));
assert.ok(reportSource.includes('JPG 직접링크'));
assert.ok(reportSource.includes('파일 미저장 · metadata 수집'));
assert.ok(side.includes('if (e && e.metadata_only) return await teCollectMetadataOnlyEntry(e);'));
assert.ok(side.includes('async function teCollectMetadataOnlyEntry(entry)'));
assert.ok(!side.slice(side.indexOf('function teAutoPyDownloadableKindsFromCache(media)'), side.indexOf('function teAutoPyCacheTargetForMedia(media, rankNo)')).includes('metadata')); // 자동 Top에서는 metadata-only 금지

function extractFunction(source, name) {
  const start = source.indexOf(`function ${name}(`);
  assert.ok(start >= 0, `${name} missing`);
  const open = source.indexOf('{', start);
  let depth = 0;
  for (let i = open; i < source.length; i += 1) {
    if (source[i] === '{') depth += 1;
    else if (source[i] === '}') {
      depth -= 1;
      if (depth === 0) return source.slice(start, i + 1);
    }
  }
  throw new Error(`${name} close missing`);
}

const elements = {
  download_video_save: { checked: true },
  download_image_save: { checked: true },
};
const sandbox = {
  String, Object,
  document: { getElementById(id) { return elements[id] || null; } },
};
vm.createContext(sandbox);
vm.runInContext([
  extractFunction(side, 'teDownloadVideoSave'),
  extractFunction(side, 'teDownloadImageSave'),
  extractFunction(side, 'teDownloadSavePolicy'),
  extractFunction(side, 'teDownloadStatusFlags'),
  extractFunction(side, 'teAutoPyDownloadableKindsFromCache'),
].join('\n'), sandbox);

assert.strictEqual(sandbox.teDownloadSavePolicy(), 'video_image');
assert.deepStrictEqual(Array.from(sandbox.teAutoPyDownloadableKindsFromCache({code:'A', video_url:'a.mp4', img_origin:'a.jpg'})), ['mp4', 'jpg']);
let flags = sandbox.teDownloadStatusFlags('video');
assert.strictEqual(flags.metadata_collected, 1);
assert.strictEqual(flags.video_requested, 1);
assert.strictEqual(flags.video_downloaded, 1);
assert.strictEqual(flags.image_requested, 1);
assert.strictEqual(flags.image_downloaded, 0);

elements.download_image_save.checked = false;
assert.strictEqual(sandbox.teDownloadSavePolicy(), 'video_only');
assert.deepStrictEqual(Array.from(sandbox.teAutoPyDownloadableKindsFromCache({code:'B', video_url:'b.mp4', img_origin:'b.jpg'})), ['mp4', 'jpg']);
assert.deepStrictEqual(Array.from(sandbox.teAutoPyDownloadableKindsFromCache({code:'C', img_origin:'c.jpg'})), ['jpg']);

elements.download_video_save.checked = false;
elements.download_image_save.checked = true;
assert.strictEqual(sandbox.teDownloadSavePolicy(), 'image_only');
assert.deepStrictEqual(Array.from(sandbox.teAutoPyDownloadableKindsFromCache({code:'D', video_url:'d.mp4', img_origin:'d.jpg'})), ['mp4', 'jpg']);

elements.download_image_save.checked = false;
assert.strictEqual(sandbox.teDownloadSavePolicy(), 'metadata_only');
assert.deepStrictEqual(Array.from(sandbox.teAutoPyDownloadableKindsFromCache({code:'E', video_url:'e.mp4', img_origin:'e.jpg'})), ['mp4', 'jpg']);
assert.deepStrictEqual(Array.from(sandbox.teAutoPyDownloadableKindsFromCache({code:'F'})), []);
flags = sandbox.teDownloadStatusFlags('');
assert.strictEqual(flags.video_requested, 0);
assert.strictEqual(flags.image_requested, 0);
assert.strictEqual(flags.metadata_collected, 1);

console.log('OK test_ext_side_control_save_v277');
