import type { ObjectDirective } from 'vue';

interface PasswordInputState {
  activate: () => void;
  input: HTMLInputElement;
  unlock: () => void;
}

interface DirectiveState {
  inputStates: Map<HTMLInputElement, PasswordInputState>;
  observer: MutationObserver;
}

const directiveStates = new WeakMap<HTMLElement, DirectiveState>();

function disableFormAutocomplete(root: HTMLElement) {
  if (root instanceof HTMLFormElement) root.autocomplete = 'off';
  root.querySelectorAll('form').forEach((form) => (form.autocomplete = 'off'));
}

function protectInput(input: HTMLInputElement, state: DirectiveState) {
  if (state.inputStates.has(input)) return;

  input.autocomplete = 'off';
  input.name = `credential-${Date.now()}-${Math.random().toString(36).slice(2)}`;
  input.readOnly = true;
  input.type = 'text';
  input.style.setProperty('-webkit-text-security', 'disc');
  Object.assign(input.dataset, { '1pIgnore': 'true', lpignore: 'true' });

  const activate = () => {
    input.type = 'password';
    input.readOnly = false;
    input.style.removeProperty('-webkit-text-security');
    input.removeEventListener('pointerdown', unlock);
    input.removeEventListener('keydown', activate);
    input.removeEventListener('beforeinput', activate);
    input.removeEventListener('paste', activate);
  };

  // Keep the field disguised as text while it receives focus; password managers commonly fill on focus.
  const unlock = () => {
    input.readOnly = false;
    input.removeEventListener('pointerdown', unlock);
  };

  input.addEventListener('pointerdown', unlock);
  input.addEventListener('keydown', activate);
  input.addEventListener('beforeinput', activate);
  input.addEventListener('paste', activate);
  state.inputStates.set(input, { activate, input, unlock });
}

function protectPasswordInputs(root: HTMLElement, state: DirectiveState) {
  disableFormAutocomplete(root);
  if (root instanceof HTMLInputElement && root.type === 'password') protectInput(root, state);
  root.querySelectorAll<HTMLInputElement>('input[type="password"]').forEach((input) => protectInput(input, state));
}

/** Keep password managers from identifying and filling password fields before the user interacts with them. */
export const vDisablePasswordAutofill: ObjectDirective<HTMLElement, boolean | undefined> = {
  mounted(el, binding) {
    if (binding.value === false) return;

    const state: DirectiveState = {
      inputStates: new Map(),
      observer: new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          mutation.addedNodes.forEach((node) => {
            if (node instanceof HTMLElement) protectPasswordInputs(node, state);
          });
        });
      }),
    };
    protectPasswordInputs(el, state);
    state.observer.observe(el, { childList: true, subtree: true });
    directiveStates.set(el, state);
  },
  unmounted(el) {
    const state = directiveStates.get(el);
    if (!state) return;

    state.observer.disconnect();
    state.inputStates.forEach(({ activate, input, unlock }) => {
      input.removeEventListener('pointerdown', unlock);
      input.removeEventListener('keydown', activate);
      input.removeEventListener('beforeinput', activate);
      input.removeEventListener('paste', activate);
    });
    directiveStates.delete(el);
  },
};
