import { initDetailActionPolicy } from './app-details-action-policy.js';
import { confirmDialog } from './app-confirm-dialog.js';

const DEFAULT_UNSAVED_MESSAGE = 'You have unsaved changes. Leave without saving?';

const toFormElement = (value) => {
  if (!value) {return null;}
  if (typeof value === 'string') {
    return document.querySelector(value);
  }
  if (value instanceof HTMLFormElement) {
    return value;
  }
  return null;
};

const escapeAttr = (value) => {
  const raw = String(value || '');
  if (typeof CSS !== 'undefined' && CSS && typeof CSS.escape === 'function') {
    return CSS.escape(raw);
  }
  return raw.replace(/["\\]/g, '\\$&');
};

const normalizeFieldKey = (key) => String(key || '').replace(/\[\]$/, '');

const normalizeFieldValue = (value) => {
  if (value instanceof File) {
    return `[file:${value.name || ''}:${Number(value.size || 0)}]`;
  }
  return String(value ?? '');
};

const createFormStateMap = (form) => {
  const data = new FormData(form);
  const state = new Map();
  data.forEach((value, key) => {
    const normalizedKey = normalizeFieldKey(key);
    const normalizedValue = normalizeFieldValue(value);
    if (!state.has(normalizedKey)) {
      state.set(normalizedKey, []);
    }
    state.get(normalizedKey).push(normalizedValue);
  });
  state.forEach((values) => values.sort());
  return state;
};

const countChangedFields = (initialState, currentState) => {
  const keys = new Set([...initialState.keys(), ...currentState.keys()]);
  let changed = 0;
  keys.forEach((key) => {
    const initialValues = initialState.get(key) || [];
    const currentValues = currentState.get(key) || [];
    if (initialValues.length !== currentValues.length) {
      changed += 1;
      return;
    }
    for (let index = 0; index < initialValues.length; index += 1) {
      if (initialValues[index] !== currentValues[index]) {
        changed += 1;
        return;
      }
    }
  });
  return changed;
};

const formatCountLabel = (template, count) => {
  const normalizedTemplate = String(template || '').trim();
  if (normalizedTemplate === '') {
    return String(count);
  }
  if (normalizedTemplate.includes('{count}')) {
    return normalizedTemplate.replace('{count}', String(count));
  }
  if (normalizedTemplate.includes('%d')) {
    return normalizedTemplate.replace('%d', String(count));
  }
  return `${normalizedTemplate} (${count})`;
};

const belongsToForm = (el, form) => {
  if (!(el instanceof HTMLElement)) {
    return false;
  }
  const formAttr = (el.getAttribute('form') || '').trim();
  if (formAttr !== '') {
    const formId = (form.id || '').trim();
    return formId !== '' && formAttr === formId;
  }
  return form.contains(el);
};

const findPrimarySubmitter = (form, titlebar, savePrimarySelector = '[data-detail-save-primary="1"]') => {
  const preferred = [];
  if (titlebar) {
    preferred.push(...Array.from(titlebar.querySelectorAll(savePrimarySelector)));
  }
  preferred.push(...Array.from(form.querySelectorAll(savePrimarySelector)));

  const formId = (form.id || '').trim();
  if (formId !== '') {
    const escapedId = escapeAttr(formId);
    preferred.push(...Array.from(document.querySelectorAll(`${savePrimarySelector}[form="${escapedId}"]`)));
  }

  const unique = Array.from(new Set(preferred));
  const firstPreferred = unique.find((el) => belongsToForm(el, form) && !el.disabled);
  if (firstPreferred) {
    return firstPreferred;
  }

  const fallback = [];
  fallback.push(...Array.from(form.querySelectorAll('button[type="submit"], input[type="submit"]')));
  if (formId !== '') {
    const escapedId = escapeAttr(formId);
    fallback.push(...Array.from(document.querySelectorAll(`button[type="submit"][form="${escapedId}"], input[type="submit"][form="${escapedId}"]`)));
  }

  return fallback.find((el) => belongsToForm(el, form) && !el.disabled) || null;
};

const resolveNamedControl = (form, fieldName) => {
  const normalizedName = normalizeFieldKey(fieldName).trim();
  if (normalizedName === '') {
    return null;
  }
  const escapedName = escapeAttr(normalizedName);
  const controls = [
    ...Array.from(form.querySelectorAll(`[name="${escapedName}"]`))
  ];
  const formId = (form.id || '').trim();
  if (formId !== '') {
    const escapedId = escapeAttr(formId);
    controls.push(...Array.from(document.querySelectorAll(`[name="${escapedName}"][form="${escapedId}"]`)));
  }
  return controls.find((el) => belongsToForm(el, form) && !el.disabled) || controls[0] || null;
};

const resolveNamedControls = (form, fieldName) => {
  const normalizedName = normalizeFieldKey(fieldName).trim();
  if (normalizedName === '') {
    return [];
  }
  const escapedName = escapeAttr(normalizedName);
  const controls = [
    ...Array.from(form.querySelectorAll(`[name="${escapedName}"]`))
  ];
  const formId = (form.id || '').trim();
  if (formId !== '') {
    const escapedId = escapeAttr(formId);
    controls.push(...Array.from(document.querySelectorAll(`[name="${escapedName}"][form="${escapedId}"]`)));
  }
  return Array.from(new Set(controls))
    .filter((el) => el instanceof HTMLElement)
    .filter((el) => belongsToForm(el, form));
};

const revealInDetailsTree = (target) => {
  if (!(target instanceof HTMLElement)) {
    return;
  }
  let node = target.parentElement;
  while (node) {
    if (node instanceof HTMLDetailsElement) {
      node.open = true;
    }
    node = node.parentElement;
  }
};

const resolveValidationTarget = (form, target = {}) => {
  const targetId = String(target.fieldId || '').trim();
  if (targetId !== '') {
    const element = document.getElementById(targetId);
    if (element && belongsToForm(element, form)) {
      return element;
    }
  }

  const targetName = String(target.fieldName || '').trim();
  if (targetName !== '') {
    const byName = resolveNamedControl(form, targetName);
    if (byName) {
      return byName;
    }
  }

  const fallbackSelectors = [
    '[aria-invalid="true"]',
    '.is-invalid',
    'input:invalid',
    'select:invalid',
    'textarea:invalid'
  ];
  for (const selector of fallbackSelectors) {
    const candidate = form.querySelector(selector);
    if (candidate && !candidate.disabled) {
      return candidate;
    }
  }
  return null;
};

const focusValidationTarget = (target, summary = null) => {
  if (!(target instanceof HTMLElement)) {
    if (summary instanceof HTMLElement) {
      summary.focus();
    }
    return false;
  }
  revealInDetailsTree(target);
  const focusTarget = target.matches('input, select, textarea, button, [tabindex], a[href]')
    ? target
    : target.querySelector('input, select, textarea, button, [tabindex], a[href]');
  if (!(focusTarget instanceof HTMLElement)) {
    if (summary instanceof HTMLElement) {
      summary.focus();
    }
    return false;
  }
  try {
    focusTarget.focus({ preventScroll: true });
  } catch (error) {
    focusTarget.focus();
  }
  if (typeof focusTarget.scrollIntoView === 'function') {
    focusTarget.scrollIntoView({ block: 'center', inline: 'nearest' });
  }
  return true;
};

const initValidationSummaryExperience = (form, container, options = {}) => {
  const summary = options.summary
    || container?.querySelector('[data-validation-summary]')
    || null;
  if (!(summary instanceof HTMLElement)) {
    return { summary: null, destroy: () => {} };
  }

  const autoFocusEnabled = options.autoFocus !== false && summary.dataset.validationSummaryAutofocus !== '0';
  if (summary.id === '') {
    const randomSuffix = Math.random().toString(36).slice(2, 10);
    summary.id = `detail-validation-summary-${randomSuffix}`;
  }

  const applyValidationMarker = (target) => {
    if (!(target instanceof HTMLElement)) {
      return;
    }
    target.setAttribute('aria-invalid', 'true');
    const summaryId = summary.id;
    if (summaryId === '') {
      return;
    }
    const describedBy = new Set(
      String(target.getAttribute('aria-describedby') || '')
        .split(/\s+/)
        .map((token) => token.trim())
        .filter(Boolean)
    );
    describedBy.add(summaryId);
    target.setAttribute('aria-describedby', Array.from(describedBy).join(' '));
  };

  const resolveTargetsFromLink = (link) => {
    const targetId = String(link?.dataset?.validationTargetId || '').trim();
    if (targetId !== '') {
      const target = resolveValidationTarget(form, { fieldId: targetId });
      return target ? [target] : [];
    }
    const targetName = String(link?.dataset?.validationTargetName || '').trim();
    if (targetName !== '') {
      return resolveNamedControls(form, targetName);
    }
    return [];
  };

  const resolveTargetFromLink = (link) => resolveValidationTarget(form, {
    fieldId: link?.dataset?.validationTargetId || '',
    fieldName: link?.dataset?.validationTargetName || ''
  });

  const onSummaryClick = (event) => {
    const trigger = event.target instanceof HTMLElement
      ? event.target.closest('[data-validation-error-link]')
      : null;
    if (!(trigger instanceof HTMLElement) || !summary.contains(trigger)) {
      return;
    }
    event.preventDefault();
    const target = resolveTargetFromLink(trigger);
    if (!target) {
      summary.focus();
      return;
    }
    focusValidationTarget(target, summary);
  };

  summary.addEventListener('click', onSummaryClick);

  const links = Array.from(summary.querySelectorAll('[data-validation-error-link]'));
  links.forEach((link) => {
    resolveTargetsFromLink(link).forEach(applyValidationMarker);
  });

  if (autoFocusEnabled) {
    window.requestAnimationFrame(() => {
      const firstLink = summary.querySelector('[data-validation-error-link]');
      const target = firstLink
        ? resolveTargetFromLink(firstLink)
        : resolveValidationTarget(form);
      if (!focusValidationTarget(target, summary)) {
        if (typeof summary.scrollIntoView === 'function') {
          summary.scrollIntoView({ block: 'start', inline: 'nearest' });
        }
      }
    });
  }

  return {
    summary,
    destroy: () => {
      summary.removeEventListener('click', onSummaryClick);
    }
  };
};

export function initStandardDetailPage(options = {}) {
  const form = toFormElement(options.form || options.formSelector || '[data-standard-detail-form]');
  if (!form) {
    return null;
  }
  if (form.dataset.standardDetailBound === '1' && form._standardDetailApi) {
    return form._standardDetailApi;
  }

  const container = options.container
    || form.closest('.app-details-container')
    || document;
  const titlebar = options.titlebar
    || container.querySelector('.app-details-titlebar');
  const backLinkSelector = options.backLinkSelector || '.app-details-titlebar h1 a[href]';
  const backLink = options.backLink
    || (titlebar ? titlebar.querySelector('h1 a[href]') : document.querySelector(backLinkSelector));
  const savePrimarySelector = options.savePrimarySelector || '[data-detail-save-primary="1"]';
  const actionPolicyEnabled = options.actionPolicy === false
    ? false
    : (options.actionPolicy === true
      ? true
      : titlebar?.getAttribute('data-detail-action-policy') === '1');
  const actionPolicy = initDetailActionPolicy({
    container,
    enabled: actionPolicyEnabled
  });
  const unsavedMessage = String(
    options.unsavedMessage
    || titlebar?.getAttribute('data-detail-unsaved-message')
    || DEFAULT_UNSAVED_MESSAGE
  );
  const unsavedIndicatorEnabled = options.unsavedIndicator !== false;
  const unsavedIndicatorLabel = String(
    options.unsavedIndicatorLabel
    || titlebar?.getAttribute('data-detail-unsaved-indicator-label')
    || 'Unsaved changes'
  );
  const unsavedIndicatorLabelOne = String(
    options.unsavedIndicatorLabelOne
    || titlebar?.getAttribute('data-detail-unsaved-indicator-label-one')
    || ''
  );
  const unsavedIndicatorLabelMany = String(
    options.unsavedIndicatorLabelMany
    || titlebar?.getAttribute('data-detail-unsaved-indicator-label-many')
    || ''
  );
  const titlebarActions = titlebar?.querySelector('.app-details-titlebar-actions') || null;
  const primarySubmitter = findPrimarySubmitter(form, titlebar, savePrimarySelector);
  const validationSummaryExperience = initValidationSummaryExperience(form, container, {
    summary: options.validationSummary || null,
    autoFocus: options.validationSummaryAutoFocus
  });
  let unsavedIndicator = titlebar?.querySelector('[data-detail-unsaved-indicator]') || null;
  if (unsavedIndicatorEnabled && titlebarActions) {
    if (!unsavedIndicator) {
      unsavedIndicator = document.createElement('small');
      unsavedIndicator.className = 'muted app-detail-unsaved-indicator';
      unsavedIndicator.setAttribute('data-detail-unsaved-indicator', '1');
    }
    if (primarySubmitter && titlebarActions.contains(primarySubmitter)) {
      titlebarActions.insertBefore(unsavedIndicator, primarySubmitter);
    } else {
      titlebarActions.prepend(unsavedIndicator);
    }
  }

  let initialState = createFormStateMap(form);
  let allowNextUnload = false;

  const changedFieldCount = () => countChangedFields(initialState, createFormStateMap(form));
  const isDirty = () => changedFieldCount() > 0;
  const syncUnsavedIndicator = () => {
    if (!unsavedIndicatorEnabled || !unsavedIndicator) {
      return;
    }
    const count = changedFieldCount();
    if (count <= 0) {
      unsavedIndicator.hidden = true;
      unsavedIndicator.textContent = '';
      return;
    }
    const labelTemplate = count === 1
      ? (unsavedIndicatorLabelOne || unsavedIndicatorLabel)
      : (unsavedIndicatorLabelMany || unsavedIndicatorLabel);
    unsavedIndicator.hidden = false;
    unsavedIndicator.textContent = formatCountLabel(labelTemplate, count);
  };
  const markClean = () => {
    initialState = createFormStateMap(form);
    syncUnsavedIndicator();
  };
  const shouldWarn = () => !actionPolicy.isSubmitting() && isDirty();

  const onBeforeUnload = (event) => {
    if (allowNextUnload) {
      allowNextUnload = false;
      return undefined;
    }
    if (!shouldWarn()) {
      return undefined;
    }
    event.preventDefault();
    event.returnValue = '';
    return '';
  };

  const onBackLinkClick = async (event) => {
    if (!shouldWarn()) {
      return;
    }
    event.preventDefault();
    const leaveWithoutSaving = await confirmDialog.confirm({
      title: unsavedIndicatorLabel,
      message: unsavedMessage,
      variant: 'warning',
      focus: 'cancel'
    });
    if (!leaveWithoutSaving) {
      return;
    }
    // Avoid a second native beforeunload prompt after explicit confirmation.
    allowNextUnload = true;
    if (backLink instanceof HTMLAnchorElement) {
      window.location.href = backLink.href;
    }
  };

  const onFormStateChange = () => {
    if (actionPolicy.isSubmitting()) {
      return;
    }
    syncUnsavedIndicator();
  };

  const onFormReset = () => {
    window.setTimeout(() => {
      if (!actionPolicy.isSubmitting()) {
        syncUnsavedIndicator();
      }
    }, 0);
  };

  let formObserver = null;
  if (unsavedIndicatorEnabled && typeof MutationObserver !== 'undefined') {
    // Observe dynamic form controls (for example MultiSelect hidden inputs).
    formObserver = new MutationObserver(() => {
      if (!actionPolicy.isSubmitting()) {
        syncUnsavedIndicator();
      }
    });
    formObserver.observe(form, {
      subtree: true,
      childList: true,
      attributes: true,
      attributeFilter: ['name', 'value', 'checked', 'selected', 'disabled']
    });
  }

  const onKeyDown = (event) => {
    if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) {
      return;
    }
    if (String(event.key || '').toLowerCase() !== 's') {
      return;
    }
    event.preventDefault();

    const submitter = findPrimarySubmitter(form, titlebar, savePrimarySelector);
    if (submitter && typeof form.requestSubmit === 'function') {
      form.requestSubmit(submitter);
      return;
    }
    if (submitter) {
      submitter.click();
      return;
    }
    if (typeof form.requestSubmit === 'function') {
      form.requestSubmit();
      return;
    }
    form.submit();
  };

  window.addEventListener('beforeunload', onBeforeUnload);
  form.addEventListener('input', onFormStateChange);
  form.addEventListener('change', onFormStateChange);
  form.addEventListener('reset', onFormReset);
  document.addEventListener('keydown', onKeyDown);
  if (backLink) {
    backLink.addEventListener('click', onBackLinkClick);
  }
  syncUnsavedIndicator();

  const destroy = () => {
    window.removeEventListener('beforeunload', onBeforeUnload);
    form.removeEventListener('input', onFormStateChange);
    form.removeEventListener('change', onFormStateChange);
    form.removeEventListener('reset', onFormReset);
    document.removeEventListener('keydown', onKeyDown);
    if (backLink) {
      backLink.removeEventListener('click', onBackLinkClick);
    }
    if (formObserver) {
      formObserver.disconnect();
    }
    actionPolicy.destroy();
    validationSummaryExperience.destroy();
    delete form._standardDetailApi;
    delete form.dataset.standardDetailBound;
  };

  const api = {
    form,
    validationSummary: validationSummaryExperience.summary,
    isDirty,
    markClean,
    destroy
  };

  form.dataset.standardDetailBound = '1';
  form._standardDetailApi = api;
  return api;
}

export function autoInitStandardDetailPages(root = document) {
  if (!root || typeof root.querySelectorAll !== 'function') {
    return [];
  }
  const forms = Array.from(root.querySelectorAll('[data-standard-detail-form]'));
  return forms
    .map((form) => initStandardDetailPage({ form }))
    .filter(Boolean);
}
