/// Credential capture module.
///
/// Detects login form submissions and prompts the user to save or update
/// credentials in the vault. Supports bar and toast prompt styles.
///
/// The prompt renders inside a closed Shadow DOM so the host page cannot
/// read overlay contents via document.querySelector or rewrite them via
/// insertAdjacentHTML. All caller-supplied strings (hostname, username)
/// are applied via textContent, never innerHTML.

import type { Request, Response } from '../shared/messages';
import type { DeviceSettings } from '../shared/types';
import { createShadowHost, type ShadowSurface } from './shadow';

// --- State ---

const hookedForms = new WeakSet<HTMLFormElement>();
const hookedButtons = new WeakSet<HTMLElement>();
let currentPrompt: ShadowSurface | null = null;

// --- Messaging ---

function sendMessage(request: Request): Promise<Response> {
  return new Promise((resolve) => {
    chrome.runtime.sendMessage(request, (response: Response) => {
      resolve(response);
    });
  });
}

// --- Username detection (same priority as detector.ts) ---

function findUsernameValue(pwField: HTMLInputElement): string {
  const form = pwField.closest('form');
  const scope = form ?? document;
  const inputs = scope.querySelectorAll<HTMLInputElement>('input');

  // 1. autocomplete="username"
  for (const input of inputs) {
    if (input === pwField) continue;
    if (input.autocomplete === 'username' && input.value) return input.value;
  }

  // 2. autocomplete="email"
  for (const input of inputs) {
    if (input === pwField) continue;
    if (input.autocomplete === 'email' && input.value) return input.value;
  }

  // 3. type="email"
  for (const input of inputs) {
    if (input === pwField) continue;
    if (input.type === 'email' && input.value) return input.value;
  }

  // 4. name/id matching common patterns
  const pattern = /user|email|login|account/i;
  for (const input of inputs) {
    if (input === pwField) continue;
    if (input.type === 'hidden' || input.type === 'password') continue;
    if ((pattern.test(input.name) || pattern.test(input.id)) && input.value) return input.value;
  }

  // 5. Nearest preceding visible text input
  const allInputs = Array.from(inputs);
  const pwIndex = allInputs.indexOf(pwField);
  for (let i = pwIndex - 1; i >= 0; i--) {
    const input = allInputs[i];
    if (input.type === 'hidden' || input.type === 'password' || input.type === 'submit') continue;
    if (input.offsetWidth > 0 && input.offsetHeight > 0 && input.value) return input.value;
  }

  return '';
}

// --- Form submission handler ---

async function onFormSubmit(pwField: HTMLInputElement): Promise<void> {
  const password = pwField.value;
  if (!password) return;

  const username = findUsernameValue(pwField);

  // Note: `url` is NOT sent — router derives origin from sender.tab.url.
  const resp = await sendMessage({
    type: 'check_credential',
    username,
    password,
  });

  if (!resp.ok) return;

  const data = resp.data as { action: string; entryId?: string; entryName?: string };
  if (data.action === 'skip') return;

  // Fetch settings for prompt style. Content scripts have direct
  // chrome.storage.local access (manifest grants "storage"), so we don't
  // need to round-trip through the SW for this — which also avoids the
  // router's content→popup-only rejection for 'get_settings'.
  const stored = await chrome.storage.local.get('relicarioSettings');
  const settings: DeviceSettings = (stored.relicarioSettings as DeviceSettings)
    ?? { captureEnabled: true, captureStyle: 'bar' };

  showPrompt(settings.captureStyle, data.action, username, password);
}

// --- Prompt UI ---

function removeExistingPrompt(): void {
  if (currentPrompt) {
    currentPrompt.destroy();
    currentPrompt = null;
  }
}

function showPrompt(
  style: 'bar' | 'toast',
  action: string,
  username: string,
  password: string,
): void {
  removeExistingPrompt();

  const hostname = (() => {
    try { return new URL(window.location.href).hostname; } catch { return window.location.href; }
  })();

  const surface = createShadowHost();
  currentPrompt = surface;
  const { host, root } = surface;

  // Position the host on the page; all further styling lives inside the
  // shadow root so the page's CSS can't reach us.
  const baseHostStyles = 'z-index: 2147483647; position: fixed;';
  if (style === 'bar') {
    host.style.cssText = `${baseHostStyles} top:0; left:0; right:0;`;
  } else {
    host.style.cssText = `${baseHostStyles} bottom:16px; right:16px;`;
  }

  // --- Build prompt DOM via createElement / textContent only ---

  const container = document.createElement('div');
  const containerBase = [
    'font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace',
    'font-size: 13px',
    'color: #c9d1d9',
    'background: #161b22',
    'box-sizing: border-box',
    'line-height: 1.4',
  ];
  if (style === 'bar') {
    container.style.cssText = [
      ...containerBase,
      'padding: 10px 16px',
      'display: flex',
      'align-items: center',
      'gap: 12px',
      'border-bottom: 1px solid #30363d',
      'box-shadow: 0 2px 8px rgba(0,0,0,0.4)',
      'transform: translateY(-100%)',
      'transition: transform 0.3s ease',
    ].join('; ');
  } else {
    container.style.cssText = [
      ...containerBase,
      'padding: 12px 16px',
      'border-radius: 4px',
      'border: 1px solid #30363d',
      'box-shadow: 0 4px 12px rgba(0,0,0,0.4)',
      'max-width: 360px',
      'opacity: 0',
      'transition: opacity 0.3s ease',
    ].join('; ');
  }

  const actionLabel = action === 'update' ? 'Update' : 'Save';

  // Message span: "<actionLabel> login for <hostname>(<username>)?"
  const msgSpan = document.createElement('span');
  msgSpan.style.cssText = 'flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;';
  msgSpan.appendChild(document.createTextNode(`${actionLabel} login for `));
  const hostStrong = document.createElement('strong');
  hostStrong.style.color = '#d2ab43';
  hostStrong.textContent = hostname;
  msgSpan.appendChild(hostStrong);
  if (username) {
    msgSpan.appendChild(document.createTextNode(` (${username})`));
  }
  msgSpan.appendChild(document.createTextNode('?'));

  const saveBtn = document.createElement('button');
  saveBtn.textContent = actionLabel;
  saveBtn.style.cssText = [
    'background:#7c5719', 'color:#fff', 'border:none', 'padding:5px 14px',
    'border-radius:3px', 'cursor:pointer', 'font-family:inherit', 'font-size:12px',
    'white-space:nowrap',
  ].join('; ');

  const neverBtn = document.createElement('button');
  neverBtn.textContent = 'Never';
  neverBtn.style.cssText = [
    'background:transparent', 'color:#8b949e', 'border:1px solid #30363d',
    'padding:5px 10px', 'border-radius:3px', 'cursor:pointer',
    'font-family:inherit', 'font-size:12px', 'white-space:nowrap',
  ].join('; ');

  const closeBtn = document.createElement('button');
  closeBtn.textContent = '✕';
  closeBtn.style.cssText = [
    'background:transparent', 'color:#8b949e', 'border:none',
    'cursor:pointer', 'font-size:16px', 'padding:2px 6px',
    'font-family:inherit', 'line-height:1',
  ].join('; ');

  container.append(msgSpan, saveBtn, neverBtn, closeBtn);
  root.appendChild(container);

  // Animate in
  requestAnimationFrame(() => {
    if (style === 'bar') {
      container.style.transform = 'translateY(0)';
    } else {
      container.style.opacity = '1';
    }
  });

  // Auto-dismiss for toast
  let autoDismissTimer: ReturnType<typeof setTimeout> | null = null;
  if (style === 'toast') {
    autoDismissTimer = setTimeout(() => removeExistingPrompt(), 15000);
  }

  const clearAutoDismiss = (): void => {
    if (autoDismissTimer) clearTimeout(autoDismissTimer);
  };

  // Save button — single content-callable message; the SW figures out
  // whether this is an add or an update (and enforces origin-binding).
  saveBtn.addEventListener('click', async () => {
    clearAutoDismiss();
    const resp = await sendMessage({ type: 'capture_save_login', username, password });
    if (!resp.ok) {
      msgSpan.textContent = `✗ ${resp.error}`;
      return;
    }
    msgSpan.textContent = '✓ Saved';
    saveBtn.style.display = 'none';
    neverBtn.style.display = 'none';
    setTimeout(() => removeExistingPrompt(), 1500);
  });

  // Never button: router derives hostname from sender.tab.url (no `hostname` field)
  neverBtn.addEventListener('click', async () => {
    clearAutoDismiss();
    await sendMessage({ type: 'blacklist_site' });
    removeExistingPrompt();
  });

  // Close button
  closeBtn.addEventListener('click', () => {
    clearAutoDismiss();
    removeExistingPrompt();
  });
}

// --- Form hooking ---

export function hookForms(): void {
  const passwordFields = document.querySelectorAll<HTMLInputElement>('input[type="password"]');

  for (const pwField of passwordFields) {
    if (pwField.offsetWidth < 20 || pwField.offsetHeight < 10) continue;

    const form = pwField.closest('form');

    if (form && !hookedForms.has(form)) {
      hookedForms.add(form);
      form.addEventListener('submit', () => {
        onFormSubmit(pwField);
      });
    }

    // Hook submit buttons (for forms that submit via JS click handlers)
    const scope = form ?? pwField.parentElement;
    if (!scope) continue;

    const buttons = scope.querySelectorAll<HTMLElement>(
      'button[type="submit"], input[type="submit"], button:not([type])',
    );
    for (const btn of buttons) {
      if (hookedButtons.has(btn)) continue;
      hookedButtons.add(btn);
      btn.addEventListener('click', () => {
        onFormSubmit(pwField);
      });
    }
  }
}
