const toElement = (value) => {
  if (value instanceof HTMLElement) {
    return value;
  }
  if (typeof value === 'string') {
    return document.querySelector(value);
  }
  return null;
};

const normalizeKeys = (value) => Array.from(new Set(
  (Array.isArray(value) ? value : [])
    .map((key) => String(key || '').trim())
    .filter(Boolean)
));

const readStoredKeys = (storageKey) => {
  if (!storageKey) {
    return [];
  }
  try {
    const raw = window.localStorage.getItem(storageKey);
    if (!raw) {
      return [];
    }
    const parsed = JSON.parse(raw);
    return normalizeKeys(parsed);
  } catch (error) {
    return [];
  }
};

const writeStoredKeys = (storageKey, keys) => {
  if (!storageKey) {
    return;
  }
  try {
    window.localStorage.setItem(storageKey, JSON.stringify(normalizeKeys(keys)));
  } catch (error) {
    // ignore storage errors
  }
};

const resolveEnsureKeys = (value) => {
  if (typeof value === 'function') {
    return normalizeKeys(value());
  }
  return normalizeKeys(value);
};

export function initPersistedDetailsGroup(options = {}) {
  const root = toElement(options.root || options.group || null);
  if (!root) {
    return null;
  }

  const storageKey = String(options.storageKey || '').trim();
  if (storageKey === '') {
    return null;
  }

  const detailsSelector = String(options.detailsSelector || 'details[data-details-key]').trim();
  const alwaysOpenSelector = String(options.alwaysOpenSelector || 'details[data-details-always-open]').trim();
  const ensureOpenKeysSource = options.ensureOpenKeys ?? [];
  const managedDetails = Array.from(root.querySelectorAll(detailsSelector))
    .filter((details) => details instanceof HTMLDetailsElement);
  const alwaysOpenDetails = Array.from(root.querySelectorAll(alwaysOpenSelector))
    .filter((details) => details instanceof HTMLDetailsElement);

  if (!managedDetails.length && !alwaysOpenDetails.length) {
    return null;
  }

  const detailsByKey = new Map();
  managedDetails.forEach((details) => {
    const key = String(details.dataset.detailsKey || '').trim();
    if (key !== '' && !detailsByKey.has(key)) {
      detailsByKey.set(key, details);
    }
  });

  let isInternalSync = false;

  const withInternalSync = (callback) => {
    isInternalSync = true;
    try {
      callback();
    } finally {
      isInternalSync = false;
    }
  };

  const applyAlwaysOpen = () => {
    withInternalSync(() => {
      alwaysOpenDetails.forEach((details) => {
        details.open = true;
      });
    });
  };

  const collectOpenKeys = () => managedDetails
    .filter((details) => details.open)
    .map((details) => String(details.dataset.detailsKey || '').trim())
    .filter(Boolean);

  const mergeOpenKeys = (keys) => {
    const merged = new Set(normalizeKeys(keys));
    resolveEnsureKeys(ensureOpenKeysSource).forEach((key) => merged.add(key));
    alwaysOpenDetails.forEach((details) => {
      const key = String(details.dataset.detailsKey || '').trim();
      if (key !== '') {
        merged.add(key);
      }
    });
    return Array.from(merged);
  };

  const writeOpenKeys = (keys) => {
    writeStoredKeys(storageKey, mergeOpenKeys(keys));
  };

  const persistCurrentState = () => {
    writeOpenKeys(collectOpenKeys());
  };

  const ensureOpen = (keys) => {
    const normalizedKeys = normalizeKeys(keys);
    if (!normalizedKeys.length) {
      return;
    }
    withInternalSync(() => {
      normalizedKeys.forEach((key) => {
        const details = detailsByKey.get(key);
        if (details) {
          details.open = true;
        }
      });
    });
  };

  const apply = () => {
    const storedKeys = readStoredKeys(storageKey);
    const ensureKeys = resolveEnsureKeys(ensureOpenKeysSource);

    if (storedKeys.length > 0) {
      const nextOpenSet = new Set(mergeOpenKeys(storedKeys));
      withInternalSync(() => {
        managedDetails.forEach((details) => {
          const key = String(details.dataset.detailsKey || '').trim();
          if (key !== '') {
            details.open = nextOpenSet.has(key);
          }
        });
      });
    } else if (ensureKeys.length > 0) {
      ensureOpen(ensureKeys);
    }

    applyAlwaysOpen();
    persistCurrentState();
  };

  const onManagedToggle = (event) => {
    if (isInternalSync) {
      return;
    }

    const target = event.currentTarget instanceof HTMLDetailsElement ? event.currentTarget : null;
    if (!target) {
      return;
    }

    const key = String(target.dataset.detailsKey || '').trim();
    if (key !== '' && resolveEnsureKeys(ensureOpenKeysSource).includes(key) && !target.open) {
      withInternalSync(() => {
        target.open = true;
      });
    }

    applyAlwaysOpen();
    persistCurrentState();
  };

  const onAlwaysOpenToggle = (event) => {
    if (isInternalSync) {
      return;
    }
    const target = event.currentTarget instanceof HTMLDetailsElement ? event.currentTarget : null;
    if (!target) {
      return;
    }
    if (!target.open) {
      withInternalSync(() => {
        target.open = true;
      });
    }
    persistCurrentState();
  };

  managedDetails.forEach((details) => {
    details.addEventListener('toggle', onManagedToggle);
  });

  alwaysOpenDetails
    .filter((details, index, list) => list.indexOf(details) === index)
    .forEach((details) => {
      details.addEventListener('toggle', onAlwaysOpenToggle);
    });

  apply();

  return {
    apply,
    destroy: () => {
      managedDetails.forEach((details) => {
        details.removeEventListener('toggle', onManagedToggle);
      });
      alwaysOpenDetails
        .filter((details, index, list) => list.indexOf(details) === index)
        .forEach((details) => {
          details.removeEventListener('toggle', onAlwaysOpenToggle);
        });
    },
    readOpenKeys: () => readStoredKeys(storageKey),
    writeOpenKeys,
  };
}
