function newIssueOperationId() {
  if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
  if (globalThis.crypto?.getRandomValues) {
    const bytes = new Uint8Array(16);
    globalThis.crypto.getRandomValues(bytes);
    return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
  }
  return String(Date.now()) + '-' + Math.random().toString(16).slice(2);
}

function createIssueModalLifecycle({ root, background = [], document, requestClose }) {
  let launcher = null;
  let active = false;
  let backgroundState = [];
  const focusableSelector = [
    'a[href]', 'button:not([disabled])', 'input:not([disabled])',
    'select:not([disabled])', 'textarea:not([disabled])',
    '[tabindex]:not([tabindex="-1"])',
  ].join(',');
  const focusable = () => Array.from(root.querySelectorAll(focusableSelector)).filter(element =>
    !element.hidden && !element.disabled && element.getClientRects().length > 0
  );
  function keydown(event) {
    if (!active) return;
    if (event.key === 'Escape') {
      event.preventDefault();
      requestClose();
      return;
    }
    if (event.key !== 'Tab') return;
    const controls = focusable();
    if (!controls.length) return;
    const first = controls[0];
    const last = controls[controls.length - 1];
    if (event.shiftKey && document.activeElement === first) {
      event.preventDefault();
      last.focus();
    } else if (!event.shiftKey && document.activeElement === last) {
      event.preventDefault();
      first.focus();
    }
  }
  root.addEventListener('keydown', keydown);
  return {
    open(trigger = null) {
      launcher = trigger?.isConnected ? trigger : document.activeElement;
      backgroundState = background.map(element => [element, element.inert]);
      backgroundState.forEach(([element]) => { element.inert = true; });
      active = true;
      (root.querySelector('#cancel-new-issue') || focusable()[0])?.focus();
    },
    close({ restore = true } = {}) {
      active = false;
      backgroundState.forEach(([element, inert]) => { element.inert = inert; });
      backgroundState = [];
      const panel = root.querySelector('.create-issue-panel');
      if (panel) panel.scrollTop = 0;
      if (restore && launcher?.isConnected) launcher.focus();
      launcher = null;
    },
  };
}

function normalizeSharedContent(value = {}) {
  const clean = input => String(input || '').replace(/\s+/g, ' ').trim();
  const text = String(value.text || '').trim().slice(0, 9500);
  const textSummary = clean(text);
  const sentence = (textSummary.match(/^.{1,80}?[.!?](?:\s|$)/)?.[0] || textSummary.slice(0, 80)).trim();
  const title = (clean(value.title) || sentence).slice(0, 240);
  const url = clean(value.url).slice(0, 2000);
  const body = text && url && text.includes(url) ? text : [text, url].filter(Boolean).join('\n\n');
  return { title, body };
}

function buildRelatedDraft(value = {}, template = null) {
  const repository = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(String(value.repository || ''))
    ? String(value.repository) : '';
  const labelIds = Array.from(new Set((Array.isArray(value.labelIds) ? value.labelIds : [])
    .filter(id => Number.isInteger(id) && id > 0))).slice(0, 20);
  const draft = {repository, title:'', body:'', labelIds};
  const milestoneId = Number(value.milestoneId);
  if (Number.isInteger(milestoneId) && milestoneId > 0) draft.milestoneId = milestoneId;
  if (/^\d{4}-\d{2}-\d{2}$/.test(String(value.dueDate || ''))) draft.dueDate = String(value.dueDate);
  if (value.unassigned === true) draft.unassigned = true;
  else if (/^[A-Za-z0-9_.-]+$/.test(String(value.assignee || ''))) {
    draft.assignee = String(value.assignee);
    draft.assigneeName = String(value.assigneeName || value.assignee).replace(/\s+/g, ' ').trim().slice(0, 255);
  }
  const templateId = String(value.templateId || '').slice(0, 80);
  if (templateId && template && String(template.id || '') === templateId) {
    draft.templateId = templateId;
    draft.templateName = String(value.templateName || template.name || 'Issue template').trim().slice(0, 80);
    draft.capturedBody = '';
    draft.body = String(template.body || '').trim().slice(0, 9000);
  }
  return draft;
}

function relatedChecklistDraft(item, label) {
  const title = String(label || '').trim().replace(/\s+/g, ' ').slice(0, 240);
  if (!title) throw new Error('Choose a checklist step to file.');
  const repository = String(item?.repository || '');
  const number = Number(item?.number);
  const url = String(item?.url || '');
  if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) ||
      !Number.isInteger(number) || number < 1 || !/^https:\/\//.test(url)) {
    throw new Error('The parent issue link is unavailable.');
  }
  return {repository, title,
    body:'Related to [' + repository + '#' + number + '](' + url + ').', labelIds:[]};
}

function linkChecklistTask(raw, targetIndex, child) {
  const url = String(child?.url || '');
  if (!/^https:\/\//.test(url)) throw new Error('The related issue link is unavailable.');
  const parts = String(raw || '').split(/(\r\n|\n|\r)/);
  let fenced = false;
  let taskIndex = 0;
  for (let index = 0; index < parts.length; index += 2) {
    const line = parts[index];
    if (/^\s*```/.test(line)) { fenced = !fenced; continue; }
    if (fenced) continue;
    const task = line.match(/^(\s*[-*+]\s+\[[ xX]\]\s+)(.*)$/);
    if (!task) continue;
    if (taskIndex === Number(targetIndex)) {
      if (/^\s*\[[^\]]+\]\([^)]+\)\s*$/.test(task[2])) throw new Error('That checklist step is already linked.');
      const label = task[2].trim().replace(/\s+/g, ' ');
      parts[index] = task[1] + '[' + label + '](' + url + ')';
      return parts.join('');
    }
    taskIndex += 1;
  }
  throw new Error('The checklist step is no longer available.');
}

function createChecklistPromotion({ issueController, issueCapture, clearAttachments, onLinked, onStatus }) {
  let pending = null;
  const persistedPromotion = value => ({
    item:{repository:value.repository, number:value.number, url:value.url},
    detail:{title:value.title, body:value.body, updated_at:value.updatedAt}, taskIndex:value.taskIndex,
  });
  return {
    pending:() => Boolean(pending),
    start(context) {
      pending = {item:{...context.item}, detail:{...context.detail}, taskIndex:Number(context.taskIndex)};
      const draft = issueController.relatedTaskDraft(context.item, context.detail, context.taskIndex, context.label);
      issueCapture.saveDraft(draft);
      clearAttachments();
      return draft;
    },
    cancel() { if (!pending) return false; pending = null; issueCapture.clearDraft(); return true; },
    deliveryContext() {
      if (!pending) return null;
      return {repository:pending.item.repository, number:pending.item.number, url:pending.item.url,
        title:pending.detail.title, body:pending.detail.body, updatedAt:pending.detail.updated_at,
        taskIndex:pending.taskIndex};
    },
    async finish(child, persisted = null) {
      const promotion = pending || (persisted ? persistedPromotion(persisted) : null);
      if (!promotion || !child) return false;
      pending = null;
      try {
        const confirmed = await issueController.linkRelatedTask(
          promotion.item, promotion.detail, promotion.taskIndex, child
        );
        onLinked(promotion, confirmed);
        onStatus('Related issue created and linked from its parent checklist.');
        return true;
      } catch (error) {
        onStatus('Related issue created, but the parent link needs attention.' +
          (child.url ? ' ' + child.url : '') + ' ' + error.message);
        return false;
      }
    },
  };
}

function createIssueOwnerPicker(issueCapture, documentRef, onChange) {
  const NO_OWNER = '__unassigned__';
  const select = documentRef.querySelector('#create-issue-assignee');
  const status = documentRef.querySelector('#create-issue-assignee-status');
  const getRepository = () => documentRef.querySelector('#create-issue-repository').value;
  const loadOwners = repository => issueCapture.loadOwners(repository);
  let ownerRequest = 0;
  function reset(repository, selected = {}) {
    select.replaceChildren();
    const me = documentRef.createElement('option');
    me.value = '';
    me.textContent = 'Me';
    select.appendChild(me);
    const noOwner = documentRef.createElement('option');
    noOwner.value = NO_OWNER;
    noOwner.textContent = 'No owner';
    select.appendChild(noOwner);
    if (selected.unassigned === true) select.value = NO_OWNER;
    if (selected.assignee) {
      const option = documentRef.createElement('option');
      option.value = selected.assignee;
      option.textContent = (selected.assigneeName || selected.assignee) + ' (@' + selected.assignee + ')';
      option.dataset.name = selected.assigneeName || selected.assignee;
      select.appendChild(option);
      select.value = selected.assignee;
    }
    select.dataset.repository = '';
    status.textContent = repository ? 'Open the owner picker to load eligible teammates.' : 'Choose a repository first.';
  }

  async function load(repository) {
    if (!repository || select.dataset.repository === repository) return;
    const request = ++ownerRequest;
    const selected = select.value;
    const selectedName = select.selectedOptions?.[0]?.dataset.name || '';
    status.textContent = 'Loading eligible teammates…';
    try {
      const owners = await loadOwners(repository);
      if (request !== ownerRequest || getRepository() !== repository) return;
      const selectedStillEligible = owners.some(owner => owner?.login === selected);
      reset(repository, selected === NO_OWNER ? {unassigned:true} :
        (selectedStillEligible ? {assignee:selected, assigneeName:selectedName} : {}));
      owners.forEach(owner => {
        if (!owner?.login || owner.login === selected) return;
        const option = documentRef.createElement('option');
        option.value = owner.login;
        option.textContent = (owner.name || owner.login) + ' (@' + owner.login + ')';
        option.dataset.name = owner.name || owner.login;
        select.appendChild(option);
      });
      select.dataset.repository = repository;
      status.textContent = owners.length ? 'Choose yourself, no owner, or an eligible teammate.' :
        'Choose yourself or no owner; no eligible teammates are available.';
    } catch (_error) {
      if (request !== ownerRequest || getRepository() !== repository) return;
      status.textContent = 'Teammates could not be loaded. The issue will stay assigned to you.';
    }
  }
  function updateActions(hasRepository, hasBlockers, canStart) {
    const submit = documentRef.querySelector('#submit-new-issue');
    const start = documentRef.querySelector('#create-and-start-issue');
    const hasNoOwner = select.value === NO_OWNER;
    const hasTeammateOwner = Boolean(select.value) && !hasNoOwner;
    submit.disabled = !hasRepository;
    submit.textContent = hasNoOwner ? 'Create unassigned' :
      (hasTeammateOwner ? 'Create & assign' : 'Create & assign to me');
    start.disabled = !hasRepository || hasBlockers || hasTeammateOwner || hasNoOwner || !canStart;
    start.title = hasBlockers ? 'Blocked work cannot start until its blockers are complete.' :
      (hasNoOwner ? 'No owner work cannot be added to your Today queue.' :
        (hasTeammateOwner ? 'Work assigned to a teammate cannot be added to your Today queue.' : ''));
  }
  function fields() {
    return {
      assignee: select.value === NO_OWNER ? '' : select.value,
      assigneeName: select.value === NO_OWNER ? '' : (select.selectedOptions?.[0]?.dataset.name || ''),
      unassigned: select.value === NO_OWNER,
    };
  }
  function draft(labelIds, blockers, trim = false) {
    const value = id => documentRef.querySelector(id).value;
    const clean = input => trim ? input.trim() : input;
    return {
      repository:value('#create-issue-repository'),
      title:clean(value('#create-issue-title')), body:clean(value('#create-issue-body')),
      labelIds, milestoneId:Number(value('#create-issue-milestone')) || null,
      dueDate:value('#create-issue-due-date'), ...fields(), blockers,
    };
  }
  select.addEventListener('focus', () => {
    const repository = getRepository();
    if (repository) load(repository);
  });
  select.addEventListener('change', onChange);
  return { reset, load, updateActions, fields, draft };
}

function createIssueCapture({ fetchJson, storage, createOperationId = newIssueOperationId }) {
  const storageKey = 'stackchain.issue-capture.v1';
  const sharedStorageKey = 'stackchain.issue-share.v1';
  const followUpStorageKey = 'stackchain.issue-follow-up.v1';
  let pending = null;
  let issueTemplates = [];
  let activeTemplate = null;
  let templateRequest = 0;
  let duplicateRequest = 0;
  let repositorySearchRequest = 0;
  let filingMetadataRequest = 0;
  let blockerSearchRequest = 0;
  let duplicateState = {status: 'idle', key: '', candidates: []};
  let acknowledgedDuplicateKey = '';
  const repositoryPageRequests = new Map();
  const safeLabelIds = value => Array.from(new Set(
    (Array.isArray(value) ? value : []).filter(id => Number.isInteger(id) && id > 0)
  )).slice(0, 20);
  const safeBlockers = value => {
    const seen = new Set();
    return (Array.isArray(value) ? value : []).reduce((items, blocker) => {
      const repository = String(blocker?.repository || '').trim();
      const number = Number(blocker?.number);
      const key = repository + '#' + number;
      if (items.length >= 5 || seen.has(key) ||
          !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) ||
          !Number.isInteger(number) || number < 1) return items;
      seen.add(key);
      items.push({repository, number,
        title:String(blocker?.title || '').replace(/\s+/g, ' ').trim().slice(0, 255)});
      return items;
    }, []);
  };
  const emptyDraft = () => ({ repository: '', title: '', body: '', labelIds: [] });
  const safeMilestoneId = value => Number.isInteger(Number(value)) && Number(value) > 0 ? Number(value) : null;
  const safeDueDate = value => /^\d{4}-\d{2}-\d{2}$/.test(String(value || '')) ? String(value) : '';
  const safeAssignee = value => /^[A-Za-z0-9_.-]+$/.test(String(value || '')) ? String(value) : '';
  const safeEstimate = value => Number.isInteger(Number(value)) && Number(value) >= 5 && Number(value) <= 1440 ?
    Number(value) : null;

  function loadStored() {
    try {
      const parsed = JSON.parse(storage.getItem(storageKey) || 'null');
      if (!parsed || typeof parsed !== 'object') return {...emptyDraft(), operationId: ''};
      const draft = {
        repository: String(parsed.repository || ''),
        title: String(parsed.title || ''),
        body: String(parsed.body || ''),
        labelIds: safeLabelIds(parsed.labelIds),
        operationId: String(parsed.operationId || '').slice(0, 128),
      };
      if (parsed.unassigned === true) draft.unassigned = true;
      if (typeof parsed.templateName === 'string' && parsed.templateName.trim()) {
        draft.templateName = parsed.templateName.trim().slice(0, 80);
        draft.templateId = String(parsed.templateId || '').slice(0, 80);
        draft.capturedBody = String(parsed.capturedBody || '').slice(0, 10000);
      }
      const assignee = safeAssignee(parsed.assignee);
      if (assignee) {
        draft.assignee = assignee;
        draft.assigneeName = String(parsed.assigneeName || assignee).replace(/\s+/g, ' ').trim().slice(0, 255);
      }
      const milestoneId = safeMilestoneId(parsed.milestoneId);
      const dueDate = safeDueDate(parsed.dueDate);
      if (milestoneId !== null) draft.milestoneId = milestoneId;
      if (dueDate) draft.dueDate = dueDate;
      const estimateMinutes = safeEstimate(parsed.estimateMinutes);
      if (estimateMinutes !== null) draft.estimateMinutes = estimateMinutes;
      if (['create', 'create-and-start'].includes(parsed.completionIntent)) {
        draft.completionIntent = parsed.completionIntent;
      }
      const blockers = safeBlockers(parsed.blockers);
      if (blockers.length) draft.blockers = blockers;
      return draft;
    } catch (_error) {
      return {...emptyDraft(), operationId: ''};
    }
  }

  function writeStored(record) {
    try { storage.setItem(storageKey, JSON.stringify(record)); }
    catch (_error) { /* Keep the form as the in-memory fallback. */ }
  }

  function saveDraft(draft) {
    const previous = loadStored();
    const safe = {
      repository: String(draft?.repository || ''),
      title: String(draft?.title || ''),
      body: String(draft?.body || ''),
      labelIds: safeLabelIds(draft?.labelIds),
    };
    if (draft?.unassigned === true) safe.unassigned = true;
    if (typeof draft?.templateName === 'string' && draft.templateName.trim()) {
      safe.templateName = draft.templateName.trim().slice(0, 80);
      safe.templateId = String(draft.templateId || '').slice(0, 80);
      safe.capturedBody = String(draft.capturedBody || '').slice(0, 10000);
    }
    const assignee = safe.unassigned ? '' : safeAssignee(draft?.assignee);
    if (assignee) {
      safe.assignee = assignee;
      safe.assigneeName = String(draft?.assigneeName || assignee).replace(/\s+/g, ' ').trim().slice(0, 255);
    }
    const milestoneId = safeMilestoneId(draft?.milestoneId);
    const dueDate = safeDueDate(draft?.dueDate);
    if (milestoneId !== null) safe.milestoneId = milestoneId;
    if (dueDate) safe.dueDate = dueDate;
    const estimateMinutes = safeEstimate(draft?.estimateMinutes);
    if (estimateMinutes !== null) safe.estimateMinutes = estimateMinutes;
    if (['create', 'create-and-start'].includes(draft?.completionIntent)) {
      safe.completionIntent = draft.completionIntent;
    }
    const blockers = safeBlockers(draft?.blockers);
    if (blockers.length) safe.blockers = blockers;
    const unchanged = ['repository', 'title', 'body', 'milestoneId', 'dueDate', 'assignee', 'assigneeName', 'unassigned',
      'templateName', 'templateId', 'capturedBody', 'estimateMinutes', 'completionIntent']
      .every(key => (previous[key] || '') === (safe[key] || '')) &&
      JSON.stringify(previous.labelIds) === JSON.stringify(safe.labelIds) &&
      JSON.stringify(previous.blockers || []) === JSON.stringify(safe.blockers || []);
    writeStored({...safe, operationId: unchanged ? previous.operationId : ''});
    return safe;
  }

  function loadDraft() {
    const {operationId: _operationId, ...draft} = loadStored();
    return draft;
  }

  function clearDraft() {
    try { storage.removeItem(storageKey); }
    catch (_error) { /* Confirmed creation remains authoritative. */ }
  }

  function pendingSharedContent() {
    try {
      const parsed = JSON.parse(storage.getItem(sharedStorageKey) || 'null');
      if (!parsed || typeof parsed !== 'object') return null;
      const shared = normalizeSharedContent({title: parsed.title, text: parsed.body});
      return shared.title || shared.body ? shared : null;
    } catch (_error) {
      return null;
    }
  }

  function acceptSharedContent() {
    const shared = pendingSharedContent();
    if (!shared) return loadDraft();
    const accepted = saveDraft({...loadDraft(), ...shared});
    try { storage.removeItem(sharedStorageKey); }
    catch (_error) { /* Accepted content is already persisted as the issue draft. */ }
    return accepted;
  }

  function discardSharedContent() {
    try { storage.removeItem(sharedStorageKey); }
    catch (_error) { /* The existing issue draft remains authoritative. */ }
  }

  function stageSharedContent(value) {
    const shared = normalizeSharedContent(value);
    if (!shared.title && !shared.body) return {status: 'empty'};
    try { storage.setItem(sharedStorageKey, JSON.stringify(shared)); }
    catch (_error) { /* The caller can still use the in-page share payload. */ }
    const existing = loadDraft();
    if (existing.title || existing.body) return {status: 'conflict'};
    acceptSharedContent();
    return {status: 'ready'};
  }

  function pendingFollowUp() {
    try {
      const parsed = JSON.parse(storage.getItem(followUpStorageKey) || 'null');
      if (!parsed || typeof parsed !== 'object') return null;
      const repository = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(String(parsed.repository || '')) ? String(parsed.repository) : '';
      const title = String(parsed.title || '').trim().slice(0, 240);
      const body = String(parsed.body || '').trim().slice(0, 9500);
      return title || body ? {repository, title, body, labelIds: []} : null;
    } catch (_error) { return null; }
  }

  function acceptFollowUp() {
    const followUp = pendingFollowUp();
    if (!followUp) return loadDraft();
    const accepted = saveDraft(followUp);
    try { storage.removeItem(followUpStorageKey); }
    catch (_error) { /* Accepted content is already persisted as the issue draft. */ }
    return accepted;
  }

  function discardFollowUp() {
    try { storage.removeItem(followUpStorageKey); }
    catch (_error) { /* The existing capture remains authoritative. */ }
  }

  function stageFollowUp(value) {
    const followUp = {repository: String(value?.repository || ''), title: String(value?.title || ''), body: String(value?.body || '')};
    if (!followUp.title && !followUp.body) return {status: 'empty'};
    try { storage.setItem(followUpStorageKey, JSON.stringify(followUp)); }
    catch (_error) { /* The in-page flow can still continue. */ }
    const existing = loadDraft();
    if (existing.title || existing.body) return {status: 'conflict'};
    acceptFollowUp();
    return {status: 'ready'};
  }

  function loadLabels(repository) {
    const encoded = String(repository || '').split('/').map(encodeURIComponent).join('/');
    const priorities = new Set(['p0', 'priority-high', 'critical']);
    return fetchJson('api/v1/repos/' + encoded + '/labels').then(labels =>
      (Array.isArray(labels) ? labels : []).slice().sort((left, right) => {
        const leftPriority = priorities.has(String(left?.name || '').toLowerCase());
        const rightPriority = priorities.has(String(right?.name || '').toLowerCase());
        return Number(rightPriority) - Number(leftPriority);
      })
    );
  }

  function loadMilestones(repository) {
    const encoded = String(repository || '').split('/').map(encodeURIComponent).join('/');
    return fetchJson('api/v1/repos/' + encoded + '/milestones').then(milestones =>
      Array.isArray(milestones) ? milestones : []
    );
  }

  function loadOwners(repository) {
    const encoded = String(repository || '').split('/').map(encodeURIComponent).join('/');
    return fetchJson('api/v1/repos/' + encoded + '/issue-assignees').then(owners =>
      Array.isArray(owners) ? owners : []
    );
  }

  function loadTemplates(repository) {
    const encoded = String(repository || '').split('/').map(encodeURIComponent).join('/');
    return fetchJson('api/v1/repos/' + encoded + '/issue-templates').then(templates =>
      Array.isArray(templates) ? templates.slice(0, 20) : []
    );
  }

  async function loadFilingMetadata(repository) {
    const selectedRepository = String(repository || '');
    const encoded = selectedRepository.split('/').map(encodeURIComponent).join('/');
    const request = ++filingMetadataRequest;
    const payload = await fetchJson(
      'api/v1/repos/' + encoded + '/issue-filing-metadata'
    );
    if (request !== filingMetadataRequest) {
      return {status:'stale', repository:selectedRepository};
    }
    const section = name => {
      const value = payload?.[name];
      const available = value?.available === true;
      return {
        available,
        items:available && Array.isArray(value?.items) ? value.items : [],
        ...(!available && typeof value?.error === 'string' ? {error:value.error} : {}),
      };
    };
    return {
      status:'ready', repository:selectedRepository,
      labels:section('labels'), milestones:section('milestones'),
      templates:section('templates'),
    };
  }

  function applyTemplate(draft, template, availableLabels = []) {
    const source = {...(draft || {})};
    const capturedBody = source.templateName ? String(source.capturedBody || '') : String(source.body || '');
    if (!template) {
      delete source.templateName;
      delete source.templateId;
      delete source.capturedBody;
      return {...source, body: capturedBody};
    }
    const scaffold = String(template.body || '').trim().slice(0, 9000);
    const body = [capturedBody.trim(), scaffold].filter(Boolean).join('\n\n---\n\n').slice(0, 10000);
    const validByName = new Map((Array.isArray(availableLabels) ? availableLabels : [])
      .filter(label => Number.isInteger(label?.id) && typeof label?.name === 'string')
      .map(label => [label.name.toLowerCase(), label.id]));
    const templateLabelIds = (Array.isArray(template.labels) ? template.labels : [])
      .map(name => validByName.get(String(name).toLowerCase())).filter(Boolean);
    return {
      ...source,
      title: source.title || String(template.title || '').trim().slice(0, 255),
      body,
      labelIds: safeLabelIds([...(source.labelIds || []), ...templateLabelIds]),
      templateId: String(template.id || '').slice(0, 80),
      templateName: String(template.name || 'Issue template').trim().slice(0, 80),
      capturedBody,
    };
  }

  function setTemplateState(draft) {
    activeTemplate = draft?.templateName ? {
      templateName:draft.templateName, templateId:draft.templateId,
      capturedBody:draft.capturedBody || '',
    } : null;
  }

  function templateFields(draft) {
    return activeTemplate ? {...draft, ...activeTemplate} : draft;
  }

  function restoreTemplate(draft, availableLabels) {
    const restored = applyTemplate(templateFields(draft), null, availableLabels);
    activeTemplate = null;
    return restored;
  }

  async function loadTemplateOptions(repository, elements, selectedId = '') {
    const request = ++templateRequest;
    issueTemplates = [];
    elements.select.innerHTML = '<option value="">Blank issue</option>';
    elements.field.hidden = true;
    if (!repository) return;
    elements.status.textContent = 'Loading issue types…';
    try {
      const templates = await loadTemplates(repository);
      if (request !== templateRequest || elements.getRepository() !== repository) return;
      issueTemplates = templates;
      templates.forEach(template => {
        const option = elements.document.createElement('option');
        option.value = template.id;
        option.textContent = template.name;
        elements.select.appendChild(option);
      });
      elements.field.hidden = templates.length === 0;
      elements.select.value = templates.some(template => template.id === selectedId) ? selectedId : '';
      elements.status.textContent = templates.length ?
        'Choose a repository guide or keep a blank issue.' : 'Blank issue selected.';
    } catch (_error) {
      if (request !== templateRequest || elements.getRepository() !== repository) return;
      elements.status.textContent = 'Issue types could not be loaded. Blank issue filing is still available.';
    }
  }

  function selectTemplate(identifier, draft, availableLabels) {
    const template = issueTemplates.find(item => item.id === identifier) || null;
    const applied = applyTemplate(templateFields(draft), template, availableLabels);
    activeTemplate = template ? {
      templateId:applied.templateId, templateName:applied.templateName,
      capturedBody:applied.capturedBody,
    } : null;
    return {draft: applied, template};
  }

  function relatedDraft(draft) {
    const template = issueTemplates.find(item => String(item?.id || '') === String(draft?.templateId || '')) || null;
    return buildRelatedDraft(draft, template);
  }

  function bindTemplatePicker(elements, callbacks) {
    let labels = [];
    elements.select.addEventListener('change', event => {
      const {draft, template} = selectTemplate(event.target.value, callbacks.getDraft(), labels);
      callbacks.setDraft(draft);
      const selected = new Set(draft.labelIds || []);
      elements.labelInputs().forEach(input => { input.checked = selected.has(Number(input.value)); });
      elements.status.textContent = template ?
        template.name + (template.about ? ' — ' + template.about : '') : 'Blank issue selected.';
      callbacks.changed();
    });
    return {
      fields:templateFields,
      reset:setTemplateState,
      setLabels(value) { labels = Array.isArray(value) ? value : []; },
      load(repository, selectedId) {
        return loadTemplateOptions(repository, elements, selectedId);
      },
      render(repository, section, selectedId = '') {
        ++templateRequest;
        issueTemplates = [];
        elements.select.innerHTML = '<option value="">Blank issue</option>';
        elements.field.hidden = true;
        if (!repository || elements.getRepository() !== repository) return;
        if (section?.available !== true) {
          elements.status.textContent = 'Issue types could not be loaded. Blank issue filing is still available.';
          return;
        }
        issueTemplates = Array.isArray(section.items) ? section.items.slice(0, 20) : [];
        issueTemplates.forEach(template => {
          const option = elements.document.createElement('option');
          option.value = template.id;
          option.textContent = template.name;
          elements.select.appendChild(option);
        });
        elements.field.hidden = issueTemplates.length === 0;
        elements.select.value = issueTemplates.some(template => template.id === selectedId) ? selectedId : '';
        elements.status.textContent = issueTemplates.length ?
          'Choose a repository guide or keep a blank issue.' : 'Blank issue selected.';
      },
      changeRepository(draft) {
        const restored = restoreTemplate(draft, labels);
        callbacks.setDraft(restored);
        return restored;
      },
    };
  }

  function bindFilingMetadata(elements, templatePicker) {
    function renderLabels(repository, section, selectedIds = []) {
      elements.labelList.replaceChildren();
      if (!repository) {
        elements.labelStatus.textContent = 'Choose a repository to load labels.';
        return;
      }
      if (section?.available !== true) {
        templatePicker.setLabels([]);
        elements.labelStatus.textContent = 'Labels could not be loaded. You can still create the issue without labels.';
        return;
      }
      const priorities = new Set(['p0', 'priority-high', 'critical']);
      const labels = (Array.isArray(section.items) ? section.items : []).slice().sort((left, right) =>
        Number(priorities.has(String(right?.name || '').toLowerCase())) -
        Number(priorities.has(String(left?.name || '').toLowerCase()))
      );
      templatePicker.setLabels(labels);
      const selected = new Set(selectedIds.map(Number));
      labels.forEach(label => {
        const option = elements.document.createElement('label');
        option.className = 'create-issue-label-option';
        const input = elements.document.createElement('input');
        input.type = 'checkbox';
        input.name = 'create-issue-label';
        input.value = String(Number(label.id));
        input.checked = selected.has(Number(label.id));
        const name = elements.document.createElement('span');
        name.textContent = label.name;
        option.append(input, name);
        elements.labelList.appendChild(option);
      });
      elements.labelStatus.textContent = labels.length ?
        'Select labels to triage this issue.' : 'This repository has no labels.';
    }

    function renderMilestones(repository, section, selectedId = null) {
      elements.milestoneSelect.replaceChildren();
      const blank = elements.document.createElement('option');
      blank.value = '';
      blank.textContent = 'No milestone';
      elements.milestoneSelect.appendChild(blank);
      if (!repository) {
        elements.milestoneStatus.textContent = 'Choose a repository to load milestones.';
        return;
      }
      if (section?.available !== true) {
        elements.milestoneStatus.textContent = 'Milestones could not be loaded. You can still create an unplanned issue.';
        return;
      }
      const milestones = Array.isArray(section.items) ? section.items : [];
      milestones.forEach(milestone => {
        const option = elements.document.createElement('option');
        option.value = String(Number(milestone.id));
        option.textContent = milestone.title;
        elements.milestoneSelect.appendChild(option);
      });
      if (selectedId) elements.milestoneSelect.value = String(selectedId);
      elements.milestoneStatus.textContent = milestones.length ?
        'Choose the release lane for this issue.' : 'This repository has no open milestones.';
    }

    async function load(repository, selected = {}) {
      renderLabels(repository, null, selected.labelIds || []);
      renderMilestones(repository, null, selected.milestoneId);
      templatePicker.render(repository, null, selected.templateId);
      if (!repository) return;
      elements.labelStatus.textContent = 'Loading labels…';
      elements.milestoneStatus.textContent = 'Loading milestones…';
      elements.templateStatus.textContent = 'Loading issue types…';
      try {
        const metadata = await loadFilingMetadata(repository);
        if (metadata.status !== 'ready' || elements.getRepository() !== repository) return;
        renderLabels(repository, metadata.labels, selected.labelIds || []);
        renderMilestones(repository, metadata.milestones, selected.milestoneId);
        templatePicker.render(repository, metadata.templates, selected.templateId);
      } catch (_error) {
        if (elements.getRepository() !== repository) return;
        renderLabels(repository, {available:false}, selected.labelIds || []);
        renderMilestones(repository, {available:false}, selected.milestoneId);
        templatePicker.render(repository, {available:false}, selected.templateId);
      }
    }
    return {load};
  }

  function loadRepositoryPage(page) {
    const safePage = Math.max(1, Math.floor(Number(page) || 1));
    if (repositoryPageRequests.has(safePage)) return repositoryPageRequests.get(safePage);
    const request = fetchJson('api/v1/repositories?page=' + safePage + '&limit=50')
      .then(payload => ({
        items: Array.isArray(payload?.items) ? payload.items : [],
        page: Number(payload?.page) || safePage,
        total: Math.max(0, Number(payload?.total) || 0),
        has_more: payload?.has_more === true,
      }))
      .finally(() => repositoryPageRequests.delete(safePage));
    repositoryPageRequests.set(safePage, request);
    return request;
  }

  async function searchRepositories(value) {
    const query = String(value || '').trim().slice(0, 80);
    const request = ++repositorySearchRequest;
    if (query.length < 2) return {status: 'idle', items: []};
    try {
      const payload = await fetchJson(
        'api/v1/repositories/search?q=' + encodeURIComponent(query) + '&limit=20'
      );
      if (request !== repositorySearchRequest) return {status: 'stale', items: []};
      return {
        status: 'ready',
        items: Array.isArray(payload?.items) ? payload.items : [],
      };
    } catch (error) {
      if (request !== repositorySearchRequest) return {status: 'stale', items: []};
      return {status: 'failed', items: [], error};
    }
  }

  async function searchBlockers(value) {
    const query = String(value || '').trim().slice(0, 80);
    const request = ++blockerSearchRequest;
    if (query.length < 2) return {status:'idle', items:[]};
    try {
      const payload = await fetchJson('api/v1/search?q=' + encodeURIComponent(query) + '&limit=20');
      if (request !== blockerSearchRequest) return {status:'stale', items:[]};
      return {status:'ready', items:(Array.isArray(payload?.items) ? payload.items : [])
        .filter(item => item?.kind === 'issue' && item?.state === 'open').slice(0, 20)};
    } catch (error) {
      if (request !== blockerSearchRequest) return {status:'stale', items:[]};
      return {status:'failed', items:[], error};
    }
  }

  function duplicateKey(draft) {
    const repository = String(draft?.repository || '').trim();
    const title = String(draft?.title || '').replace(/\s+/g, ' ').trim();
    return { repository, title, key: repository + '\n' + title.toLowerCase() };
  }

  async function findDuplicates(draft) {
    const input = duplicateKey(draft);
    const request = ++duplicateRequest;
    if (!input.repository || input.title.length < 4) {
      duplicateState = {status: 'idle', key: input.key, candidates: []};
      return duplicateState;
    }
    try {
      const payload = await fetchJson('api/v1/search?q=' + encodeURIComponent(input.title) + '&limit=10');
      if (request !== duplicateRequest) return {status: 'stale', key: input.key, candidates: []};
      duplicateState = {
        status: 'ready',
        key: input.key,
        partial: payload?.partial === true,
        candidates: (Array.isArray(payload?.items) ? payload.items : []).filter(item =>
          item?.kind === 'issue' && item?.state === 'open' && item?.repository === input.repository
        ).slice(0, 3),
      };
      return duplicateState;
    } catch (error) {
      if (request !== duplicateRequest) return {status: 'stale', key: input.key, candidates: []};
      duplicateState = {status: 'failed', key: input.key, candidates: [], error};
      return duplicateState;
    }
  }

  function needsDuplicateAcknowledgement(draft) {
    const {key} = duplicateKey(draft);
    return duplicateState.status === 'ready' && duplicateState.partial !== true && duplicateState.key === key &&
      duplicateState.candidates.length > 0 && acknowledgedDuplicateKey !== key;
  }

  function acknowledgeDuplicates(draft) {
    const {key} = duplicateKey(draft);
    if (duplicateState.status === 'ready' && duplicateState.key === key && duplicateState.candidates.length) {
      acknowledgedDuplicateKey = key;
    }
  }

  function submit(draft) {
    if (pending) return pending;
    const saved = saveDraft(draft);
    const stored = loadStored();
    const operationId = stored.operationId || String(createOperationId()).slice(0, 128);
    writeStored({...saved, operationId});
    const repository = saved.repository.split('/').map(encodeURIComponent).join('/');
    pending = fetchJson('api/v1/repos/' + repository + '/issues', {
      method: 'POST',
      headers: {
        Accept: 'application/json', 'Content-Type': 'application/json',
        'Idempotency-Key': operationId,
      },
      body: JSON.stringify({
        title: saved.title, body: saved.body, label_ids: saved.labelIds,
        ...(saved.unassigned ? {unassigned:true} : {}),
        ...(saved.assignee ? {assignee: saved.assignee} : {}),
        ...(saved.milestoneId ? {milestone_id: saved.milestoneId} : {}),
        ...(saved.dueDate ? {due_date: saved.dueDate + 'T23:59:59Z'} : {}),
      }),
    }).then(issue => {
      clearDraft();
      return issue;
    }).finally(() => { pending = null; });
    return pending;
  }

  return {
    saveDraft, loadDraft, clearDraft, loadLabels, loadMilestones, loadOwners, loadTemplates,
    loadFilingMetadata, applyTemplate, setTemplateState, templateFields, restoreTemplate, loadTemplateOptions,
    selectTemplate, buildRelatedDraft:relatedDraft, bindTemplatePicker, bindFilingMetadata, loadRepositoryPage,
    searchRepositories, searchBlockers, findDuplicates,
    needsDuplicateAcknowledgement, acknowledgeDuplicates, submit,
    stageSharedContent, pendingSharedContent, acceptSharedContent, discardSharedContent,
    stageFollowUp, pendingFollowUp, acceptFollowUp, discardFollowUp,
  };
}

createIssueCapture.normalizeSharedContent = normalizeSharedContent;
createIssueCapture.createOwnerPicker = createIssueOwnerPicker;
createIssueCapture.buildRelatedDraft = buildRelatedDraft;
createIssueCapture.relatedChecklistDraft = relatedChecklistDraft;
createIssueCapture.linkChecklistTask = linkChecklistTask;
createIssueCapture.createChecklistPromotion = createChecklistPromotion;
createIssueCapture.createModalLifecycle = createIssueModalLifecycle;

if (typeof module !== 'undefined' && module.exports) module.exports = createIssueCapture;
