(function (root, factory) {
  const createSavedSearches = factory();
  if (typeof module === 'object' && module.exports) module.exports = createSavedSearches;
  if (root) root.createSavedSearches = createSavedSearches;
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
  function createSavedSearches(options) {
    const fetchJson = options.fetchJson;
    const createId = options.createId || (() => crypto.randomUUID().replace(/-/g, ''));
    const onOpen = options.onOpen;
    const onState = options.onState || (() => {});
    let current = { revision:0, views:[] };

    function snapshot() {
      return { revision:current.revision, views:current.views.map(view => ({ ...view })) };
    }

    function publish(status, message) {
      onState({ ...snapshot(), status, ...(message ? { message } : {}) });
    }

    function normalizeSearch(name, search, id) {
      const cleanName = String(name || '').trim();
      const query = String(search?.query || '').trim();
      if (!cleanName || cleanName.length > 60) throw new Error('Name must be between 1 and 60 characters.');
      if (query.length < 2 || query.length > 200) throw new Error('Search query must be between 2 and 200 characters.');
      return {
        id, name:cleanName, query,
        kind:['all', 'issue', 'pull'].includes(search?.kind) ? search.kind : 'all',
        state:['all', 'open', 'closed'].includes(search?.state) ? search.state : 'all',
        repository:String(search?.repository || '').trim(),
      };
    }

    async function replace(views) {
      publish('saving');
      try {
        current = await fetchJson('api/v1/saved-searches', {
          method:'PUT', headers:{ 'Content-Type':'application/json' },
          body:JSON.stringify({ revision:current.revision, views }),
        });
        publish('ready');
        return snapshot();
      } catch (error) {
        const conflict = error?.status === 409 && error?.payload?.detail?.snapshot;
        if (conflict) {
          current = conflict;
          publish('conflict', 'Saved searches changed on another device.');
        } else {
          publish('error', 'Saved searches could not sync. Search still works.');
        }
        throw error;
      }
    }

    return {
      async load() {
        publish('loading');
        try {
          current = await fetchJson('api/v1/saved-searches');
          publish('ready');
        } catch (error) {
          publish('error', 'Saved searches could not load. Search still works.');
        }
        return snapshot();
      },
      save(name, search) {
        if (current.views.length >= 20) return Promise.reject(new Error('Saved searches are limited to 20.'));
        const created = normalizeSearch(name, search, createId());
        return replace(current.views.concat(created));
      },
      rename(id, name) {
        const existing = current.views.find(view => view.id === id);
        if (!existing) return Promise.reject(new Error('Saved search no longer exists.'));
        const changed = normalizeSearch(name, existing, existing.id);
        return replace(current.views.map(view => view.id === id ? changed : view));
      },
      remove(id) {
        if (!current.views.some(view => view.id === id)) return Promise.resolve(snapshot());
        return replace(current.views.filter(view => view.id !== id));
      },
      open(id) {
        const selected = current.views.find(view => view.id === id);
        if (selected) onOpen({ ...selected });
      },
      snapshot,
    };
  }

  createSavedSearches.mount = function mountSavedSearches(document, fetch, search, applyScope, history) {
    const query = selector => document.querySelector(selector);
    const input = query('#cmd-input');
    const request = async (url, init) => {
      const response = await fetch(url, init);
      const payload = await response.json().catch(() => ({}));
      if (!response.ok) {
        const error = new Error(payload.detail?.message || payload.detail || 'Saved Search request failed.');
        error.status = response.status;
        error.payload = payload;
        throw error;
      }
      return payload;
    };
    let controller;
    function render(state) {
      const list = query('#saved-search-list');
      const status = query('#saved-search-status');
      list.replaceChildren();
      status.textContent = ({loading:'Loading…', saving:'Syncing…', ready:'',
        conflict:state.message, error:state.message})[state.status] || '';
      state.views.forEach(view => {
        const row = document.createElement('div');
        row.className = 'saved-search-row';
        const open = document.createElement('button');
        open.type = 'button'; open.className = 'saved-search-open saved-search-action';
        open.textContent = view.name; open.title = view.query;
        open.addEventListener('click', () => controller.open(view.id));
        const actions = document.createElement('div');
        actions.className = 'saved-search-row-actions';
        for (const action of ['Rename', 'Delete']) {
          const button = document.createElement('button');
          button.type = 'button'; button.className = 'saved-search-action'; button.textContent = action;
          button.setAttribute('aria-label', action + ' ' + view.name);
          button.addEventListener('click', async () => {
            if (action === 'Rename') {
              const name = globalThis.prompt('Rename saved search', view.name);
              if (name !== null && name.trim()) await controller.rename(view.id, name).catch(() => {});
            } else if (globalThis.confirm('Delete saved search “' + view.name + '”?')) {
              await controller.remove(view.id).catch(() => {});
            }
          });
          actions.append(button);
        }
        row.append(open, actions); list.append(row);
      });
    }
    controller = createSavedSearches({ fetchJson:request, onState:render, onOpen:view => {
      search.setQuery('');
      input.value = view.query;
      applyScope(view);
      search.setQuery(view.query);
      history.update({query:view.query, scope:view});
      input.focus();
    }});
    query('#save-current-search').addEventListener('click', async () => {
      const name = query('#saved-search-name').value.trim();
      const current = {query:input.value.trim(), kind:query('#cmd-search-kind').value,
        state:query('#cmd-search-state').value, repository:query('#cmd-search-repository').value.trim()};
      if (!name || current.query.length < 2) {
        query('#saved-search-status').textContent = 'Enter a name and at least 2 search characters.';
        return;
      }
      await controller.save(name, current).then(() => { query('#saved-search-name').value = ''; })
        .catch(error => { query('#saved-search-status').textContent = error.message; });
    });
    return controller;
  };

  return createSavedSearches;
});
