const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');

const root = __dirname;
const side = fs.readFileSync(path.join(root, 'side.js'), 'utf8');
const html = fs.readFileSync(path.join(root, 'side.html'), 'utf8');
const content = fs.readFileSync(path.join(root, 'content_instagram.js'), 'utf8');
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8'));

assert.strictEqual(manifest.version, '2.3.8.276');
assert.strictEqual(manifest.version_name, '2.3.8.276');

function extractFunction(source, name) {
  const start = source.indexOf(`function ${name}(`);
  assert.ok(start >= 0, `${name} must exist`);
  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} closing brace not found`);
}

// Native Analyze8780 link: online sets the current URL, offline removes stale href.
assert.ok(html.includes('target="_blank" rel="noopener noreferrer"'));
const link = {
  href: '', title: '', tabIndex: 0, attrs: {},
  setAttribute(name, value) { this.attrs[name] = String(value); },
  removeAttribute(name) { if (name === 'href') this.href = ''; delete this.attrs[name]; },
};
const linkSandbox = {
  document: { getElementById: (id) => id === 'igblue_analyze8780_open' ? link : null },
  teAnalyze8780OpenUrl: () => 'http://127.0.0.1:8780/?account=morefancy_',
};
vm.createContext(linkSandbox);
vm.runInContext(`${extractFunction(side, 'teAnalyze8780SyncOpenLink')}; teAnalyze8780SyncOpenLink(true);`, linkSandbox);
assert.strictEqual(link.href, 'http://127.0.0.1:8780/?account=morefancy_');
assert.strictEqual(link.attrs['aria-disabled'], 'false');
assert.strictEqual(link.tabIndex, 0);
vm.runInContext('teAnalyze8780SyncOpenLink(false);', linkSandbox);
assert.strictEqual(link.href, '');
assert.strictEqual(link.attrs['aria-disabled'], 'true');
assert.strictEqual(link.tabIndex, -1);

// Current-account add uses one shared normalized state for both label and click target.
const button = { textContent: '', disabled: true, title: '', dataset: {} };
const accountSandbox = {
  document: { getElementById: (id) => id === 'igblue_account_url_add_current' ? button : null },
  teAutoPyNormalizeAccountUsername: (value) => String(value || '').trim().replace(/^@+/, '').toLowerCase(),
  teAnalyze8780SyncOpenLink: () => {},
  teAnalyze8780Online: true,
};
vm.createContext(accountSandbox);
vm.runInContext(`let teCurrentAccountAddTarget = { username: "", source: "", shortcode: "" }; ${extractFunction(side, 'teAutoPySetCurrentAccountAddTarget')}; result = teAutoPySetCurrentAccountAddTarget('@MoreFancy_', 'profile_url', 'ABC'); this.state = teCurrentAccountAddTarget;`, accountSandbox);
assert.strictEqual(accountSandbox.result, 'morefancy_');
assert.strictEqual(button.textContent, 'morefancy_ 계정추가');
assert.strictEqual(button.disabled, false);
assert.strictEqual(button.dataset.accountUsername, 'morefancy_');
assert.deepStrictEqual(JSON.parse(JSON.stringify(accountSandbox.state)), { username: 'morefancy_', source: 'profile_url', shortcode: 'ABC' });
vm.runInContext(`result = teAutoPySetCurrentAccountAddTarget('', 'unresolved', ''); this.state = teCurrentAccountAddTarget;`, accountSandbox);
assert.strictEqual(accountSandbox.result, '');
assert.strictEqual(button.textContent, '계정 확인중');
assert.strictEqual(button.disabled, true);
assert.strictEqual(button.dataset.accountUsername, '');
assert.strictEqual(accountSandbox.state.username, '');
assert.ok(side.includes('const username = teCurrentAccountAddTarget.username;'));

// Main resolver prefers the selected current card, accepts a stored target only
// when its shortcode still exists on the current page, and otherwise clears it.
const currentTargetFn = extractFunction(content, 'igblueReadCurrentAccountTarget');
const activeCard = { getAttribute: () => 'ACTIVE1' };
const activeSandbox = {
  localStorage: { getItem: () => JSON.stringify({ code: 'ACTIVE1' }) },
  document: { querySelector: () => null, querySelectorAll: () => [activeCard] },
  window: { innerWidth: 1000, innerHeight: 800 },
  location: { href: 'https://www.instagram.com/reels/' },
  Math,
};
vm.createContext(activeSandbox);
vm.runInContext(`${currentTargetFn}; result = igblueReadCurrentAccountTarget();`, activeSandbox);
assert.strictEqual(activeSandbox.result.shortcode, 'ACTIVE1');
assert.strictEqual(activeSandbox.result.source, 'active_download_target');

const staleSandbox = {
  localStorage: { getItem: () => JSON.stringify({ code: 'OLD_PAGE' }) },
  document: { querySelector: () => null, querySelectorAll: () => [] },
  window: { innerWidth: 1000, innerHeight: 800 },
  location: { href: 'https://www.instagram.com/explore/' },
  Math,
};
vm.createContext(staleSandbox);
vm.runInContext(`${currentTargetFn}; result = igblueReadCurrentAccountTarget();`, staleSandbox);
assert.strictEqual(staleSandbox.result.ok, false);
assert.strictEqual(staleSandbox.result.shortcode, '');

const selectedSandbox = {
  localStorage: { getItem: () => '' },
  document: {
    querySelector: () => ({ getAttribute: () => 'SELECTED2' }),
    querySelectorAll: () => [],
  },
  window: { innerWidth: 1000, innerHeight: 800 },
  location: { href: 'https://www.instagram.com/explore/' },
  Math,
};
vm.createContext(selectedSandbox);
vm.runInContext(`${currentTargetFn}; result = igblueReadCurrentAccountTarget();`, selectedSandbox);
assert.strictEqual(selectedSandbox.result.shortcode, 'SELECTED2');
assert.strictEqual(selectedSandbox.result.source, 'main_selected_card');

assert.ok(side.includes('teAutoPyProfileUsernameFromCurrentTarget()'));
assert.ok(side.includes('teAutoPyMediaOwnerByShortcode(shortcode)'));
assert.ok(side.includes('teAutoPySinglePayloadOwner()'));
assert.ok(side.includes('return teAutoPySetCurrentAccountAddTarget("", "unresolved", "");'));
assert.ok(!side.includes('chrome.tabs.create({ url: teAnalyze8780OpenUrl()'));
assert.ok(!side.includes('window.open(teAnalyze8780OpenUrl()'));


// v268: Explore/Reels hover follows the injected card data-code and S(code) mapping.
const inject = fs.readFileSync(path.join(root, 'js', 'instagram_inject.js'), 'utf8');
assert.ok(inject.includes('function teMainBindHoverAccountHost'));
assert.ok(inject.includes('const media=S(code)'));
assert.ok(inject.includes('teMainRefreshHoverAccountTargetFromCard(t,"data_code_relinked")'));
assert.ok(inject.includes('igblue_hover_account_target'));
assert.ok(inject.includes('data-igblue-account-hover'));
assert.ok(inject.includes('#06b6d4'));
assert.ok(content.includes('let igblueHoveredAccountTarget'));
assert.ok(!content.includes('source: "content_thumbnail_hover"'));
assert.ok(content.includes('data-code → S(code)'));
assert.ok(content.includes('username: hover.username'));
assert.ok(side.includes('const directOwner = teAutoPyNormalizeAccountUsername(current && (current.username || current.account || current.owner_username));'));
assert.ok(side.includes('teAutoPySetCurrentAccountAddTarget(directOwner'));

console.log('OK test_ext_side_account_link_ui v272');
