const DEFAULT_SETTINGS = {
  host: '127.0.0.1',
  port: '5054',
  path: '/record',
  learnedPhrases: {},
};

const candidatesByTab = new Map();

chrome.runtime.onInstalled.addListener(async () => {
  const current = await chrome.storage.local.get(Object.keys(DEFAULT_SETTINGS));
  const next = {};
  for (const [key, value] of Object.entries(DEFAULT_SETTINGS)) {
    if (current[key] === undefined) next[key] = value;
  }
  if (Object.keys(next).length) await chrome.storage.local.set(next);
  await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  (async () => {

    if (message?.type === 'NGO_UPDATE_CANDIDATES') {
      const tabId = sender.tab?.id;
      if (typeof tabId === 'number') {
        candidatesByTab.set(tabId, {
          updatedAt: Date.now(),
          url: sender.tab?.url || '',
          title: sender.tab?.title || '',
          candidates: Array.isArray(message.candidates) ? message.candidates : [],
        });
      }
      sendResponse({ ok: true });
      return;
    }

    if (message?.type === 'NGO_GET_TAB_STATE') {
      const tabId = message.tabId;
      const settings = await chrome.storage.local.get(Object.keys(DEFAULT_SETTINGS));
      sendResponse({
        ok: true,
        state: candidatesByTab.get(tabId) || { candidates: [], updatedAt: 0, url: '', title: '' },
        settings: { ...DEFAULT_SETTINGS, ...settings },
      });
      return;
    }

    if (message?.type === 'NGO_SAVE_SETTINGS') {
      const payload = {
        host: String(message.settings?.host || DEFAULT_SETTINGS.host).trim(),
        port: String(message.settings?.port || DEFAULT_SETTINGS.port).trim(),
        path: normalizePath(String(message.settings?.path || DEFAULT_SETTINGS.path).trim()),
      };
      await chrome.storage.local.set(payload);
      sendResponse({ ok: true, settings: payload });
      return;
    }

    if (message?.type === 'NGO_CLASSIFY_PHRASE') {
      const settings = await chrome.storage.local.get(['learnedPhrases']);
      const learnedPhrases = settings.learnedPhrases || {};
      const action = String(message.action || '').trim().toLowerCase();
      const phrase = normalizePhrase(String(message.phrase || ''));
      if (!action || !phrase) {
        sendResponse({ ok: false, error: 'missing_phrase_or_action' });
        return;
      }
      const bucket = new Set(Array.isArray(learnedPhrases[action]) ? learnedPhrases[action] : []);
      bucket.add(phrase);
      learnedPhrases[action] = [...bucket].sort();
      await chrome.storage.local.set({ learnedPhrases });
      sendResponse({ ok: true, learnedPhrases });
      return;
    }

    if (message?.type === 'NGO_SEND_MANIFESTS') {
      const settings = await chrome.storage.local.get(Object.keys(DEFAULT_SETTINGS));
      const endpoint = buildEndpoint({ ...DEFAULT_SETTINGS, ...settings });
      const payload = message.payload;
      const response = await fetch(endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
      const text = await response.text();
      let body;
      try { body = JSON.parse(text); } catch { body = text; }
      sendResponse({ ok: response.ok, status: response.status, endpoint, body });
      return;
    }

    // Save project data as local JSON — always overwrites, stays aligned to active project
    if (message?.type === 'NGO_SAVE_PROJECT_JSON') {
      try {
        // chrome.storage is the persistence layer in MV3; write full JSON there
        // and also keep a serialised copy under a known key for easy retrieval
        const json = JSON.stringify(message.data, null, 2);
        await chrome.storage.local.set({
          projectData: message.data,
          projectDataJson: json,
          allFilePaths: flattenFilePaths(message.data?.roots || []),
        });
        sendResponse({ ok: true });
      } catch (err) {
        sendResponse({ ok: false, error: err?.message || String(err) });
      }
      return;
    }

    // Fetch project file tree from /read/tree and cache it
    // Called by sidepanel directly via fetch(), but this handler exists as a
    // fallback route in case the sidepanel needs the background to make the request.
    if (message?.type === 'NGO_FETCH_PROJECT_TREE') {
      const settings = await chrome.storage.local.get(Object.keys(DEFAULT_SETTINGS));
      const merged = { ...DEFAULT_SETTINGS, ...settings };
      const endpoint = `http://${merged.host}:${merged.port}/read/tree`;
      try {
        const response = await fetch(endpoint, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({}),
        });
        const text = await response.text();
        let body;
        try { body = JSON.parse(text); } catch { body = { ok: false, error: text }; }
        if (body?.ok) {
          const allFilePaths = flattenFilePaths(body.roots || []);
          // Overwrite stored project data — always reflects active project
          await chrome.storage.local.set({ projectData: body, allFilePaths });
        }
        sendResponse({ ok: response.ok, status: response.status, endpoint, body });
      } catch (err) {
        sendResponse({ ok: false, error: err?.message || String(err) });
      }
      return;
    }

    sendResponse({ ok: false, error: 'unknown_message_type' });
  })().catch((error) => {
    sendResponse({ ok: false, error: error?.message || String(error) });
  });

  return true; // keep message channel open for async
});

function buildEndpoint(settings) {
  const host = settings.host || DEFAULT_SETTINGS.host;
  const port = settings.port || DEFAULT_SETTINGS.port;
  const path = normalizePath(settings.path || DEFAULT_SETTINGS.path);
  return `http://${host}:${port}${path}`;
}

function normalizePath(pathValue) {
  if (!pathValue) return DEFAULT_SETTINGS.path;
  return pathValue.startsWith('/') ? pathValue : `/${pathValue}`;
}

function normalizePhrase(value) {
  return value.toLowerCase().replace(/\s+/g, ' ').trim();
}

// Flatten the roots tree into a simple array of file paths
function flattenFilePaths(nodes) {
  const paths = [];
  function walk(nodeList) {
    for (const node of nodeList) {
      if (node.type === 'file') paths.push(node.path);
      if (node.children?.length) walk(node.children);
    }
  }
  walk(nodes);
  return paths;
}