const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');

const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');

function extractFunction(name) {
  const markers = [`async function ${name}(`, `function ${name}(`];
  const start = markers
    .map((marker) => sidepanelSource.indexOf(marker))
    .find((index) => index >= 0);
  if (start < 0) {
    throw new Error(`missing function ${name}`);
  }

  let parenDepth = 0;
  let signatureEnded = false;
  let braceStart = -1;
  for (let i = start; i < sidepanelSource.length; i += 1) {
    const ch = sidepanelSource[i];
    if (ch === '(') {
      parenDepth += 1;
    } else if (ch === ')') {
      parenDepth -= 1;
      if (parenDepth === 0) {
        signatureEnded = true;
      }
    } else if (ch === '{' && signatureEnded) {
      braceStart = i;
      break;
    }
  }

  let depth = 0;
  let end = braceStart;
  for (; end < sidepanelSource.length; end += 1) {
    const ch = sidepanelSource[end];
    if (ch === '{') depth += 1;
    if (ch === '}') {
      depth -= 1;
      if (depth === 0) {
        end += 1;
        break;
      }
    }
  }

  return sidepanelSource.slice(start, end);
}

function createApi({
  refreshImpl,
  runCount = 3,
  persistImpl,
} = {}) {
  const bundle = [
    extractFunction('normalizePendingAutoRunStartRunCount'),
    extractFunction('registerPendingAutoRunStartRunCount'),
    extractFunction('clearPendingAutoRunStartRunCount'),
    extractFunction('startAutoRunFromCurrentSettings'),
  ].join('\n');

  return new Function(`
const events = [];
const latestState = { contributionMode: false };
const inputAutoSkipFailures = { checked: false };
const inputContributionNickname = { value: 'tester' };
const inputContributionQq = { value: '123456' };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '5' };
const inputAutoDelayEnabled = { checked: false };
const inputAutoDelayMinutes = { value: '30' };
const btnAutoRun = { disabled: false, innerHTML: '' };
const inputRunCount = { disabled: false };
let runCountValue = ${Math.max(1, Number(runCount) || 1)};
let pendingAutoRunStartTotalRuns = 0;
let pendingAutoRunStartExpiresAt = 0;
const chrome = {
  runtime: {
    async sendMessage(message) {
      events.push({ type: 'send', message });
      return { ok: true };
    },
  },
};
const console = {
  warn(...args) {
    events.push({ type: 'warn', args });
  },
};
async function persistCurrentSettingsForAction() {
  events.push({ type: 'sync-settings' });
  ${persistImpl ? `return (${persistImpl})(events, {
    setRunCount(value) {
      runCountValue = Math.max(1, Number(value) || 1);
    },
    getRunCount() {
      return runCountValue;
    },
  });` : ''}
}
function getRunCountValue() { return Math.max(1, Number(runCountValue) || 1); }
function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) || 0; }
function shouldOfferAutoModeChoice() { return false; }
async function openAutoStartChoiceDialog() { throw new Error('should not be called'); }
function getFirstUnfinishedStep() { return 1; }
function getRunningSteps() { return []; }
function shouldWarnAutoRunFallbackRisk() { return false; }
function isAutoRunFallbackRiskPromptDismissed() { return false; }
async function openAutoRunFallbackRiskConfirmModal() { throw new Error('should not be called'); }
function setAutoRunFallbackRiskPromptDismissed() {}
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
async function refreshContributionContentHint() {
  events.push({ type: 'refresh' });
  ${refreshImpl ? 'return (' + refreshImpl + ')();' : 'return null;'}
}
async function ensureGpcApiKeyReadyForStart() {
  return true;
}
${bundle}
return {
  startAutoRunFromCurrentSettings,
  getEvents() {
    return events;
  },
};
`)();
}

test('startAutoRunFromCurrentSettings refreshes contribution content hint before starting auto run', async () => {
  const api = createApi();

  const result = await api.startAutoRunFromCurrentSettings();

  assert.equal(result, true);
  assert.deepEqual(
    api.getEvents().map((entry) => entry.type),
    ['refresh', 'sync-settings', 'send']
  );
  assert.equal(api.getEvents()[2].message.type, 'AUTO_RUN');
});

test('startAutoRunFromCurrentSettings continues auto run when contribution content refresh fails', async () => {
  const api = createApi({
    refreshImpl: 'async () => { throw new Error("refresh failed"); }',
  });

  const result = await api.startAutoRunFromCurrentSettings();
  const events = api.getEvents();

  assert.equal(result, true);
  assert.deepEqual(
    events.map((entry) => entry.type),
    ['refresh', 'warn', 'sync-settings', 'send']
  );
  assert.match(String(events[1].args[0]), /Failed to refresh contribution content hint before auto run/);
  assert.equal(events[3].message.type, 'AUTO_RUN');
});

test('startAutoRunFromCurrentSettings does not block auto run when contribution content has updates', async () => {
  const api = createApi({
    refreshImpl: `async () => ({
      promptVersion: 'questionnaire:2026-04-23T00:00:00Z',
      items: [{ slug: 'questionnaire', isVisible: true }],
    })`,
  });

  const result = await api.startAutoRunFromCurrentSettings();

  assert.equal(result, true);
  assert.deepEqual(
    api.getEvents().map((entry) => entry.type),
    ['refresh', 'sync-settings', 'send']
  );
});

test('startAutoRunFromCurrentSettings freezes run count before async settings sync can repaint it', async () => {
  const api = createApi({
    runCount: 20,
    persistImpl: `(events, controls) => {
      controls.setRunCount(1);
      events.push({ type: 'stale-status-reset', runCount: controls.getRunCount() });
    }`,
  });

  const result = await api.startAutoRunFromCurrentSettings();
  const events = api.getEvents();

  assert.equal(result, true);
  assert.deepEqual(
    events.map((entry) => entry.type),
    ['refresh', 'sync-settings', 'stale-status-reset', 'send']
  );
  assert.equal(events[3].message.payload.totalRuns, 20);
});

test('persistCurrentSettingsForAction forces a silent save even when settings are not marked dirty', async () => {
  const bundle = [
    extractFunction('waitForSettingsSaveIdle'),
    extractFunction('saveSettings'),
    extractFunction('persistCurrentSettingsForAction'),
  ].join('\n');

  const api = new Function(`
let settingsAutoSaveTimer = 123;
let clearedTimer = null;
let settingsSaveInFlight = false;
let settingsDirty = false;
let settingsSaveRevision = 0;
let phonePersistCalls = 0;
const saveCalls = [];
function clearTimeout(value) {
  clearedTimer = value;
}
async function persistSignupPhoneInputForAction() {
  phonePersistCalls += 1;
}
function updateSaveButtonState() {}
function collectSettingsPayload() {
  return { luckmailApiKey: 'autofilled-key' };
}
function syncLatestState() {}
function updatePanelModeUI() {}
function updateMailProviderUI() {}
function updateButtonStates() {}
function markSettingsDirty() {}
function applySettingsState() {}
const chrome = {
  runtime: {
    async sendMessage(message) {
      saveCalls.push(message.payload);
      return { state: { luckmailApiKey: message.payload.luckmailApiKey } };
    },
  },
};
${bundle}
return {
  persistCurrentSettingsForAction,
  getSnapshot() {
    return { clearedTimer, phonePersistCalls, saveCalls };
  },
};
`)();

  await api.persistCurrentSettingsForAction();
  const snapshot = api.getSnapshot();

  assert.equal(snapshot.clearedTimer, 123);
  assert.equal(snapshot.phonePersistCalls, 1);
  assert.deepStrictEqual(snapshot.saveCalls, [{ luckmailApiKey: 'autofilled-key' }]);
});
