const DEFAULT_SUGGESTIONS = [
  'hope you had a great weekend',
  'hope your week is going well',
  'thanks for the quick response',
  'appreciate your quick reply',
  'thank you for the update',
  'thanks for confirming',
  'I appreciate your feedback',
  'grateful for your support',
  'thanks for understanding',
  'I wanted to follow up',
  'just checking in',
  'wanted to touch base',
  'quick reminder',
  'friendly reminder',
  'just a heads up',
  'wanted to circle back',
  'as per your request',
  'in response to',
  'further to our discussion',
  'with reference to',
  'I would like to',
  'I am writing to',
  'I am reaching out to',
  'the purpose of this email',
  'I hope this helps',
  'please feel free to',
  'if you have any questions',
  'should you have any questions',
  'if you need anything else',
  'anything else I can help with',
  'is there anything else',
  'let me know your thoughts',
  'what do you think',
  'your thoughts on this',
  'I would love to hear',
  'would appreciate your input',
  'value your opinion',
  'looking forward to your reply',
  'awaiting your response',
  'hope to hear from you soon',
  'eager to hear back',
  'at your convenience',
  'whenever you have time',
  'no rush',
  'take your time',
  'when you have a moment',
  'sorry for any confusion',
  'apologies for any inconvenience',
  'sorry for the late reply',
  'apologies for not getting back sooner',
  'thank you for bringing this to my attention',
  'thanks for pointing that out',
  'good catch',
  'you are absolutely right',
  'completely agree',
  'could not agree more',
  'well said',
  'great idea',
  'love this idea',
  'brilliant',
  'impressive',
  'outstanding',
  'nicely done',
  'well done',
  'congratulations',
  'congrats on',
  'excited to',
  'thrilled to',
  'pleased to',
  'delighted to',
  'honored to',
  'happy to announce',
  'proud to share',
  'quick update',
  'just a quick note',
  'keeping you in the loop',
  'wanted to keep you posted',
  'thought I would share',
  'hope everything is going smoothly',
  'trust all is progressing well',
  'hope the project is going well',
  'per your request',
  'as requested',
  'as you suggested',
  'taking your advice',
  'thanks to your help',
  'with your assistance',
  'I have reviewed',
  'after reviewing',
  'upon review',
  'having considered',
  'after careful consideration',
  'I have given this some thought',
  'been thinking about',
  'to answer your question',
  'in answer to',
  'regarding your question',
  'in regards to your inquiry',
  'to address your concern',
  'rest assured',
  'please be assured',
  'I can assure you',
  'I guarantee',
  'I will make sure',
  'I will ensure',
  'I will take care of',
  'leave it with me',
  'I have got this',
  'on it',
  'consider it done',
  'will do',
  'absolutely will',
  'more than happy to',
  'would be glad to',
  'would be delighted to',
  'it would be my pleasure',
  'I look forward to working with you',
  'excited to collaborate',
  'looking forward to our meeting',
  'see you at the meeting',
  'until then',
  'until we meet',
  'in the coming days',
  'in the near future',
  'over the next few days',
  'by end of week',
  'by end of day',
  'before close of business',
  'within the next',
  'shortly',
  'momentarily',
  'right away',
  'immediately',
  'as quickly as possible',
  'at the earliest',
  'once I have more information',
  'as soon as I know more',
  'will keep you updated',
  'will keep you informed',
  'will let you know',
  'you will be the first to know',
  'I will circle back',
  'touching base on this',
  'wanted to reconnect',
  'been a while',
  'long time no talk',
  'been too long',
  'great to reconnect',
  'pleasure speaking with you',
  'enjoyed our conversation',
  'thanks for taking the time',
  'appreciate you making time',
  'know you are busy',
  'thanks for squeezing this in',
  'I understand you have a lot on your plate',
  'whenever works best for you',
  'whatever works for you',
  'flexible on my end',
  'happy to accommodate',
  'can work around your schedule',
  'let me know what suits you',
  'does this timeline work',
  'is this feasible',
  'is this doable',
  'how does this sound',
  'thoughts on this approach',
  'makes perfect sense',
  'crystal clear',
  'understood',
  'got it',
  'noted',
  'duly noted',
  'will make a note',
  'have made a note of this',
  'adding this to my calendar',
  'marked in my calendar',
  'all set',
  'we are all set',
  'everything is in order',
  'looks good to me',
  'looks great',
  'looks perfect',
  'spot on',
  'right on track',
  'going according to plan',
  'as expected',
  'no surprises',
  'smooth sailing',
  'coming along nicely',
  'making good progress',
  'making headway',
  'almost there',
  'nearly complete',
  'wrapping up',
  'finishing up',
  'final stages',
  'home stretch',
  'crossing the finish line',
  'hope this message finds you well',
  'trust this email finds you in good health',
  'hope you are having a good day',
  'wishing you a great week ahead',
  'happy Monday',
  'happy Friday',
  'hope you had a productive week',
  'thanks for your prompt response',
  'appreciate you getting back to me so quickly',
  'thank you for the clarification',
  'thanks for the detailed explanation',
  'grateful for your assistance',
  'thank you for your continued support',
  'appreciate your collaboration',
  'thanks for being so flexible',
  'I am grateful for',
  'many thanks for',
  'wanted to say thank you',
  'cannot thank you enough',
  'I owe you one',
  'really appreciate it',
  'means a lot',
  'very helpful',
  'extremely helpful',
  'super helpful',
  'incredibly useful',
  'exactly what I needed',
  'perfect timing',
  'just what I was looking for',
  'this is perfect',
  'could not have asked for more',
  'above and beyond',
  'went the extra mile',
  'I wanted to reach out',
  'reaching out regarding',
  'getting in touch about',
  'dropping you a line',
  'sending this your way',
  'looping you in',
  'bringing you into the conversation',
  'wanted to include you',
  'thought you should know',
  'keeping everyone informed',
  'for visibility',
  'for awareness',
  'heads up that',
  'just so you know',
  'wanted to make you aware',
  'thought you might like to know',
  'in case you missed it',
  'circling back on this',
  'following up on our previous conversation',
  'per our last discussion',
  'continuing our conversation',
  'picking up where we left off',
  'back to what we were discussing',
  'revisiting this topic',
  'wanted to check in on',
  'checking the status of',
  'any updates on',
  'wondering if there are any updates',
  'have you had a chance to',
  'did you get a chance to',
  'were you able to',
  'any luck with',
  'how did it go',
  'how is it going',
  'how are things progressing',
  'what is the latest',
  'any news on',
  'keeping tabs on',
  'just wanted to confirm that',
  'can you confirm',
  'please confirm receipt',
  'confirming we are aligned',
  'making sure we are on the same page',
  'want to make sure',
  'double checking that',
  'verifying that',
  'validating that',
  'I believe we discussed',
  'if I recall correctly',
  'if memory serves',
  'unless I am mistaken',
  'correct me if I am wrong',
  'my understanding is',
  'from what I understand',
  'as far as I know',
  'to the best of my knowledge',
  'I could be wrong but',
  'not entirely sure but',
  'correct me if needed',
  'hope I am not overstepping',
  'hope this is not too forward',
  'pardon the intrusion',
  'sorry to bother you',
  'hate to be a pest',
  'do not mean to pry',
  'if you do not mind me asking',
  'may I ask',
  'quick favor to ask',
  'need your help with something',
  'could use your expertise',
  'value your insight on',
  'trust your judgment on',
  'defer to your expertise',
  'you would know better than I',
  'what would you recommend',
  'what would you suggest',
  'how would you approach this',
  'what is your take on',
  'your perspective would be valuable',
  'keen to get your views',
  'interested in your feedback',
  'open to suggestions',
  'welcome any feedback',
  'all feedback is appreciated',
  'constructive criticism welcome',
  'tell me what you think',
  'be honest with me',
  'shoot me straight',
  'give it to me straight',
  'no sugarcoating needed',
  'want your honest opinion',
  'unfiltered thoughts',
  'raw feedback',
  'this is just a draft',
  'still a work in progress',
  'preliminary thoughts',
  'initial ideas',
  'rough outline',
  'brain dump',
  'thinking out loud',
  'off the top of my head',
  'spit balling here',
  'just throwing this out there',
  'food for thought',
  'something to consider',
  'worth considering',
  'might be worth exploring',
  'could be interesting to',
  'have you thought about',
  'what if we',
  'how about we',
  'another option would be',
  'alternatively',
  'on the flip side',
  'another way to look at it',
  'from another angle',
  'different perspective',
  'playing devil advocate',
  'to be fair',
  'in all fairness',
  'giving credit where due',
  'to give you context',
  'some background on this',
  'for context',
  'long story short',
  'to cut a long story short',
  'bottom line is',
  'the key takeaway is',
  'in a nutshell',
  'to sum up',
  'to summarize',
  'in summary',
  'wrapping this up',
  'to conclude',
  'final thoughts',
  'parting thoughts',
  'one last thing',
  'before I forget',
  'almost forgot to mention',
  'nearly slipped my mind',
  'while I remember',
  'while I have you',
  'since I have your attention',
  'changing gears for a moment',
  'on a different note',
  'shifting topics',
  'moving on to',
  'next item on the agenda',
  'which brings me to',
  'speaking of which',
  'that reminds me',
  'funny you should mention',
  'coincidentally',
  'as luck would have it',
  'interestingly enough',
  'worth noting that',
  'important to mention',
  'should also mention',
  'might also add',
  'would also like to say',
  'one more point',
  'also worth mentioning',
  'equally important',
  'just as important',
  'cannot forget to mention',
  'must mention',
  'have to say',
  'need to point out',
  'want to emphasize',
  'cannot stress enough',
  'bears repeating',
  'worth repeating',
  'hope you are well',
  'hope this finds you well',
  'hope you are doing well',
  'hope all is well',
  'trust you are well',
  'how are you',
  'good morning',
  'good afternoon',
  'good evening',
  'it was great to',
  'nice to hear from you',
  'thanks for reaching out',
  'thanks for getting back to me',
  'hello',
  'hey there',
  'it was great talking to you',
  'it was great meeting you',
  'hope you’re doing well',
  'i hope this email finds you well',
  'i hope you are having a great day',
  'i hope all is well',
  'i hope you’re having a good week',
  'i hope you are doing great',
  'thank you',
  'thanks so much',
  'thank you very much',
  'thank you for your time',
  'thank you for your help',
  'thank you for your patience',
  'I appreciate it',
  'I really appreciate',
  'much appreciated',
  'thanks in advance',
  'grateful for your',
  'thank you for reaching out',
  'thank you for getting back to me',
  'thank you for your message',
  'thank you for your email',
  'thank you for your support',
  'thank you for your understanding',
  'thanks again',
  'many thanks',
  'i really appreciate it',
  'i truly appreciate it',
  'i sincerely appreciate it',
  'absolutely',
  'definitely',
  'certainly',
  'of course',
  'sure thing',
  'sounds good',
  'sounds great',
  'sounds perfect',
  'that works for me',
  'works perfectly',
  'no problem',
  'not a problem',
  'my pleasure',
  'happy to help',
  'glad to hear',
  'that sounds like a plan',
  'that makes sense',
  'that makes perfect sense',
  'that works',
  'works for me',
  'all good',
  'all set',
  'no worries',
  'no worries at all',
  'for sure',
  'and more',
  'as well as',
  'in addition',
  'on the other hand',
  'however',
  'furthermore',
  'moreover',
  'that being said',
  'with that in mind',
  'in other words',
  'for example',
  'for instance',
  'such as',
  'in addition to that',
  'on a related note',
  'having said that',
  'in particular',
  'in general',
  'to clarify',
  'to be more specific',
  'to answer your question',
  'as a next step',
  'the next step is',
  'please let me know',
  'let me know if',
  'let me know when',
  'could you please',
  'would you mind',
  'if you could',
  'when you get a chance',
  'at your earliest convenience',
  'would it be possible',
  'I was wondering if',
  'do you have time to',
  'can you help me',
  'would you be able to',
  'could you clarify',
  'please let me know if you have any questions',
  'please let me know if you need anything else',
  'please let me know if this works for you',
  'please let me know your thoughts',
  'please let me know what you think',
  'please let me know how you would like to proceed',
  'please let me know if that helps',
  'please feel free to reach out',
  'please feel free to contact me',
  'please feel free to ask any questions',
  'whenever you have a moment',
  'when you have a chance',
  'does that work for you',
  'does that make sense',
  'do you have any questions',
  'do you have any concerns',
  'what are your thoughts',
  'how does that sound',
  'how does that look',
  'would that be okay',
  'would that work for you',
  'is that correct',
  'is that okay with you',
  'are you available',
  'are you available tomorrow',
  'are you available next week',
  'can we schedule a call',
  'can we set up a meeting',
  'can we jump on a quick call',
  'looking forward to it',
  'looking forward to',
  'look forward to hearing',
  'looking forward to hearing from you',
  'talk to you soon',
  'speak to you soon',
  'catch up soon',
  'see you soon',
  'see you then',
  'what time works',
  'does that work',
  'would that work',
  'let me check my calendar',
  'I will get back to you',
  'I will follow up',
  'following up on',
  'looking forward to your response',
  'looking forward to your reply',
  'looking forward to working with you',
  'i look forward to hearing from you',
  'i look forward to your response',
  'i look forward to your reply',
  'i look forward to working with you',
  'i look forward to our call',
  'i look forward to our meeting',
  'i look forward to speaking with you',
  'as soon as possible',
  'as soon as i can',
  'as soon as you can',
  'by the end of the day',
  'by the end of the week',
  'later today',
  'early next week',
  'sometime next week',
  'whenever is best for you',
  'whenever works best for you',
  'whichever works best for you',
  'I apologize',
  'sorry for the delay',
  'apologies for the delay',
  'sorry about that',
  'my apologies',
  'I understand',
  'I appreciate your patience',
  'sorry for the inconvenience',
  'i apologize for the delay',
  'i apologize for the inconvenience',
  'thank you for your patience with this',
  'thanks for your patience on this',
  'thanks for bearing with me',
  'thank you for bearing with us',
  'just to confirm',
  'to confirm',
  'as discussed',
  'as mentioned',
  'as promised',
  'per our conversation',
  'according to',
  'based on',
  'i can confirm',
  'i can definitely do that',
  'i can help with that',
  'happy to assist',
  'i will take care of it',
  'i will handle it',
  'i will look into it',
  'i will get back to you shortly',
  'i will get back to you as soon as i can',
  'i will get back to you as soon as possible',
  'i will keep you posted',
  'i will keep you updated',
  'i will let you know',
  'i will let you know as soon as possible',
  'as we discussed',
  'as we agreed',
  'best regards',
  'kind regards',
  'warm regards',
  'best wishes',
  'all the best',
  'have a great day',
  'have a wonderful day',
  'take care',
  'talk soon',
  'best',
  'cheers',
  'sincerely',
  'yours sincerely',
  'yours truly',
  'speak soon',
  'have a nice day',
  'have a great weekend',
  'have a nice weekend',
  'I will',
  'I can',
  'I would be happy to',
  'I am happy to',
  'feel free to',
  'please do not hesitate',
  'do not hesitate to',
  'reach out if',
  'let me know if you need',
  'let me know',
  'reach out',
  'get back to you',
  'follow up',
  'check in',
  'keep you posted',
  'keep you updated',
  'touch base',
  'set up a call',
  'schedule a meeting',
  'share the details',
  'send you the details',
  'send you an update',
  'review this',
  'take a look',
  'have a look',
  'look into this',
  'discuss this',
  'go over this',
  'work on this',
  'move forward with this',
  'proceed with this',
  'I agree',
  'I see what you mean',
  'good point',
  'you are right',
  'exactly',
  'I understand your concern',
  'makes sense',
  'i completely agree',
  'i totally agree',
  'i agree with you',
  'you’re right',
  'great point',
  'makes sense to me',
  'I wanted to',
  'just wanted to',
  'wanted to let you know',
  'wanted to share',
  'thought you might',
  'you might be interested',
  'for your information',
  'FYI',
  'attached is',
  'please find attached',
  'I have attached',
  'just to let you know',
  'for your reference',
  'for reference',
  'for your convenience',
  'as an update',
  'a quick update on this',
  'here is an update',
  'here is the latest',
  'here are the details',
  'here is the information you requested',
  'here is what i found',
  'we are currently working on it',
  'we are looking into it',
  'we are reviewing this',
  'everything is on track',
  'we are on track',
  'we are making progress',
  'we have completed this',
  'this has been completed',
  'this is now resolved',
  'this issue has been resolved',
  'moving forward',
  'going forward',
  'in the meantime',
  'at this time',
  'currently',
  'regarding',
  'with respect to',
  'in regards to',
  'concerning',
  'ASAP',
  'as we move forward',
  'as we continue',
  'based on our conversation',
  'based on our call',
  'based on our meeting',
  'as per our discussion',
  'as per our call',
  'as a reminder',
  'for the time being',
  'for now',
  'for the moment',
  'going to',
  'planning to',
  'happy to',
  'I think',
  'I believe',
  'in my opinion',
  'it seems',
  'perhaps',
  'maybe',
  'possibly',
  'potentially',
  'might be',
  'could be',
  'as you know',
  'as you may know',
  'I hope',
  'I trust',
  'just to',
  'in case',
  'by the way',
  'on a side note',
  'quick question',
  'one more thing',
  'amazing',
  'awesome',
  'excellent',
  'fantastic',
  'great',
  'perfect',
  'wonderful',
  'appreciate',
  'understand',
  'following',
  'attached',
  'available',
  'convenient',
  'important',
  'necessary',
  'possible',
  'ready',
  'received',
  'response',
  'update',
  'additionally',
  'therefore',
  'nonetheless',
  'nevertheless',
  'meanwhile',
  'overall',
  'basically',
  'actually',
  'essentially',
  'ideally',
  'typically',
  'generally',
  'usually',
  'sometimes',
  'occasionally',
  'probably',
  'hopefully',
  'obviously',
  'eventually',
  'soon',
  'got it',
  'got it, thanks',
  'understood',
  'understood, thank you',
  'noted',
  'noted, thanks',
  'thanks for the heads up',
  'thanks for letting me know about this',
  'i appreciate the update',
  'i appreciate the information',
  'thanks for the quick response',
  'thanks for your quick reply',
];

/**
 * @typedef {Object} SmartComposeOptions
 * @property {string[]=} suggestions Override static suggestion list.
 * @property {(fragment: string, allText: string) => string|undefined=} getSuggestion Custom suggestion resolver.
 * @property {number=} swipeThreshold Pixels to trigger accept on mobile.
 */

let globalState = {
  observers: [],
  instances: new Map(), // textarea -> instance data
  options: null,
};

// Find a suggestion whose prefix matches a suffix of the text up to caret.
// Returns { suggestion, matchLen } or undefined.
function findSuggestionMatch(upToCaret, suggestions) {
  if (!upToCaret) return undefined;
  const lowerUp = upToCaret.toLowerCase();
  /** @type {{ suggestion: string, matchLen: number }[]} */
  const candidates = [];
  for (const s of suggestions) {
    const sLower = s.toLowerCase();
    // We only suggest if at least 2 non-space chars match the beginning of s
    let best = 0;
    const maxL = Math.min(lowerUp.length, sLower.length - 1); // must be < full length
    for (let L = 2; L <= maxL; L++) {
      const prefix = sLower.slice(0, L);
      if (lowerUp.endsWith(prefix) && prefix.replace(/\s+/g, '').length >= 2) {
        // Ensure the start of this matched suffix is at a word boundary in the typed text
        const startIdx = lowerUp.length - L;
        const prevChar = startIdx > 0 ? lowerUp[startIdx - 1] : '';
        const atBoundary = startIdx === 0 || /[\s\-.,;:!\?\(\)\[\]\{\}"'’]/.test(prevChar);
        if (atBoundary) {
          best = L; // keep longest valid boundary match
        }
      }
    }
    if (best > 0) candidates.push({ suggestion: s, matchLen: best });
  }
  if (candidates.length === 0) return undefined;
  // Prefer those with the longest match; if multiple, pick one randomly among them
  const maxLen = Math.max(...candidates.map((c) => c.matchLen));
  const top = candidates.filter((c) => c.matchLen === maxLen);
  return top[Math.floor(Math.random() * top.length)];
}

function createGhostLayer(textarea) {
  // Capture layout metrics BEFORE DOM manipulation so we can preserve natural sizing.
  const preRect = textarea.getBoundingClientRect();
  const parent = textarea.parentNode;
  const cs = getComputedStyle(textarea);

  const wrapper = document.createElement('div');
  wrapper.style.position = 'relative';
  wrapper.style.width = '100%';
  // Make wrapper participate in layout similarly to the original textarea but without controlling width.
  // Use flex/inline-flex to avoid block formatting context differences while keeping relative stacking.
  const display = cs.display.startsWith('inline') ? 'inline-flex' : 'flex';
  wrapper.style.display = display;
  // // Preserve flex/grid growth when width is not explicitly set by letting wrapper flex like textarea.
  // ['flex', 'flexGrow', 'flexShrink', 'flexBasis'].forEach((prop) => {
  //   if (cs[prop] && cs[prop] !== 'auto') wrapper.style[prop] = cs[prop];
  // });

  parent.insertBefore(wrapper, textarea);
  wrapper.appendChild(textarea);

  // Hidden mirror used to measure caret coordinates precisely; kept for potential future precise positioning logic.
  const mirror = document.createElement('div');
  mirror.style.position = 'absolute';
  mirror.style.top = '0';
  mirror.style.left = '0';
  mirror.style.visibility = 'hidden';
  mirror.style.whiteSpace = 'pre-wrap';
  mirror.style.overflowWrap = 'break-word';
  mirror.style.wordBreak = 'break-word';
  mirror.style.boxSizing = cs.boxSizing;
  mirror.style.fontFamily = cs.fontFamily;
  mirror.style.fontSize = cs.fontSize;
  mirror.style.fontWeight = cs.fontWeight;
  mirror.style.fontStyle = cs.fontStyle;
  mirror.style.letterSpacing = cs.letterSpacing;
  mirror.style.lineHeight = cs.lineHeight;
  mirror.style.padding = cs.padding;
  mirror.style.border = cs.border;
  mirror.style.width = '100%';
  mirror.style.pointerEvents = 'none';
  wrapper.appendChild(mirror);

  const ghost = document.createElement('div');
  ghost.className = 'generatify-smart-compose-ghost';
  ghost.style.position = 'absolute';
  // Offset ghost content area by textarea border so text lines align precisely.
  const bt = parseFloat(cs.borderTopWidth) || 0;
  const bl = parseFloat(cs.borderLeftWidth) || 0;
  ghost.style.top = bt + 'px';
  ghost.style.left = bl + 'px';
  ghost.style.zIndex = '10';
  ghost.style.pointerEvents = 'none';
  ghost.style.color = 'rgba(0,0,0,0.35)';
  ghost.style.whiteSpace = 'pre-wrap';
  ghost.style.fontFamily = cs.fontFamily;
  ghost.style.fontSize = cs.fontSize;
  ghost.style.fontWeight = cs.fontWeight;
  ghost.style.fontStyle = cs.fontStyle;
  ghost.style.letterSpacing = cs.letterSpacing;
  ghost.style.lineHeight = cs.lineHeight;
  ghost.style.userSelect = 'none';
  ghost.style.boxSizing = 'border-box';
  ghost.style.padding = cs.padding;
  ghost.style.width = '100%';
  ghost.style.pointerEvents = 'none';
  wrapper.appendChild(ghost);

  // Track size changes to keep wrapper & mirror in sync (e.g., responsive layout / user resize).
  const ro = new ResizeObserver(() => {
    // Keep font metrics in sync; width stays at 100% as requested.
    const csNow = getComputedStyle(textarea);
    ghost.style.fontFamily = csNow.fontFamily;
    ghost.style.fontSize = csNow.fontSize;
    ghost.style.lineHeight = csNow.lineHeight;
    mirror.style.fontFamily = csNow.fontFamily;
    mirror.style.fontSize = csNow.fontSize;
    mirror.style.lineHeight = csNow.lineHeight;
  });
  ro.observe(textarea);

  return { wrapper, ghost, mirror, ro };
}

function updateGhost(textarea, ghostEl, suggestion, caretPos, mirror, matchLen = 0) {
  if (!suggestion || matchLen <= 0) {
    ghostEl.innerHTML = '';
    return;
  }
  const remainder = suggestion.slice(matchLen);
  const beforeCaret = textarea.value.slice(0, caretPos);
  // Escape HTML to avoid injection from user text
  const escape = (s) => s.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
  // Hidden span holds typed text space for layout; visible span shows remainder in ghost color.
  ghostEl.innerHTML = `<span style="visibility:hidden;white-space:pre-wrap">${escape(beforeCaret)}</span><span>${escape(remainder)}</span>`;
  // Sync scroll positioning
  ghostEl.style.transform = `translate(0, -${textarea.scrollTop}px)`; // counter scroll so overlay aligns
}

function acceptSuggestion(textarea, suggestion, caretPos, matchLen = 0) {
  if (!suggestion || matchLen <= 0) return;
  const remainder = suggestion.slice(matchLen);
  const before = textarea.value.slice(0, caretPos) + remainder;
  const after = textarea.value.slice(caretPos);
  textarea.value = before + after;
  const newCaret = before.length; // place after accepted text
  textarea.setSelectionRange(newCaret, newCaret);
  // Trigger input event for listeners (e.g., frameworks)
  textarea.dispatchEvent(new Event('input', { bubbles: true }));
}

function attachInstance(textarea, options) {
  const { wrapper, ghost, mirror, ro } = createGhostLayer(textarea);
  let currentSuggestion = undefined;
  let matchLen = 0;
  let focused = document.activeElement === textarea;
  // Manage post-accept behavior:
  // - If extension candidates exist (longer suggestions with accepted suggestion as prefix), allow immediate extension on space only.
  // - Else require two non-space characters after the space before suggesting.
  let lastAccepted = null;
  let extensionCandidates = [];
  let extensionMode = false; // waiting for space to trigger extension remainder
  let requireTwoCharsAfterSpace = false; // gating suggestions until 2 chars typed
  let touchStartX = null;
  let touchStartY = null;
  const swipeThreshold = options.swipeThreshold || 50;

  function recompute() {
    if (!focused) {
      currentSuggestion = undefined;
      matchLen = 0;
      updateGhost(textarea, ghost, null, textarea.selectionStart, mirror, 0);
      return;
    }
    const caretPos = textarea.selectionStart;
    const upToCaret = textarea.value.slice(0, caretPos);
    // If content diverged before the last acceptance anchor, drop any gating
    if ((extensionMode || requireTwoCharsAfterSpace) && lastAccepted != null) {
      if (upToCaret.length < lastAccepted.length || upToCaret.slice(0, lastAccepted.length) !== lastAccepted) {
        extensionMode = false;
        requireTwoCharsAfterSpace = false;
        lastAccepted = null;
        extensionCandidates = [];
      }
    }
    // Extension logic
    if (extensionMode) {
      const expectedPrefix = lastAccepted + ' ';
      if (upToCaret === expectedPrefix) {
        // Show extension remainder (choose randomly among candidates)
        const pick = extensionCandidates[Math.floor(Math.random() * extensionCandidates.length)];
        currentSuggestion = pick;
        matchLen = expectedPrefix.length; // accepted suggestion + space length
        updateGhost(textarea, ghost, currentSuggestion, caretPos, mirror, matchLen);
        return;
      } else if (upToCaret.startsWith(expectedPrefix)) {
        // User started typing next word: disable extension mode and fall through
        extensionMode = false;
      } else {
        // User modified text differently; cancel extension
        extensionMode = false;
      }
    }

    if (requireTwoCharsAfterSpace && lastAccepted != null) {
      const tail = upToCaret.slice(lastAccepted.length);
      if (tail.length === 0) {
        currentSuggestion = undefined;
        matchLen = 0;
        updateGhost(textarea, ghost, null, caretPos, mirror, 0);
        return;
      }
      // must begin with whitespace after acceptance
      if (!/^\s/.test(tail)) {
        currentSuggestion = undefined;
        matchLen = 0;
        updateGhost(textarea, ghost, null, caretPos, mirror, 0);
        return;
      }
      const afterSpace = tail.replace(/^\s+/, '');
      const cleaned = afterSpace.replace(/\s+/g, '');
      // Only require ONE non-space char now (was two) to make UX less confusing.
      if (cleaned.length < 1) {
        currentSuggestion = undefined;
        matchLen = 0;
        updateGhost(textarea, ghost, null, caretPos, mirror, 0);
        return;
      } else {
        requireTwoCharsAfterSpace = false; // allow normal suggestions now
      }
    }

    const match = findSuggestionMatch(upToCaret, options.suggestions);
    currentSuggestion = match ? match.suggestion : undefined;
    matchLen = match ? match.matchLen : 0;
    updateGhost(textarea, ghost, currentSuggestion, caretPos, mirror, matchLen);
  }

  function handleKey(e) {
    if (!currentSuggestion) return;
    if (e.key === 'Tab' || e.key === 'ArrowRight') {
      e.preventDefault();
      acceptSuggestion(textarea, currentSuggestion, textarea.selectionStart, matchLen);
      currentSuggestion = undefined;
      matchLen = 0;
      // Setup post-accept gating
      lastAccepted = textarea.value.slice(0, textarea.selectionStart); // full text up to caret after acceptance
      // Determine extension candidates
      const prefixLower = lastAccepted.toLowerCase() + ' ';
      extensionCandidates = options.suggestions.filter((s) => s.toLowerCase().startsWith(prefixLower) && s.length > prefixLower.length);
      if (extensionCandidates.length > 0) {
        extensionMode = true; // wait for space to show extension
        requireTwoCharsAfterSpace = false;
      } else {
        extensionMode = false;
        requireTwoCharsAfterSpace = true; // need two chars after space
      }
      updateGhost(textarea, ghost, null, textarea.selectionStart, mirror, 0);
    } else if (['Escape', 'ArrowLeft', 'Enter'].includes(e.key)) {
      // Cancel suggestion on certain navigation keys
      currentSuggestion = undefined;
      matchLen = 0;
      extensionMode = false;
      updateGhost(textarea, ghost, null, textarea.selectionStart, mirror, 0);
    }
  }

  function syncSize() {
    const cs = getComputedStyle(textarea);
    ghost.style.fontFamily = cs.fontFamily;
    ghost.style.fontSize = cs.fontSize;
    ghost.style.lineHeight = cs.lineHeight;
    // Mirror style sync to maintain caret positioning accuracy on resize
    if (mirror) {
      mirror.style.fontFamily = cs.fontFamily;
      mirror.style.fontSize = cs.fontSize;
      mirror.style.lineHeight = cs.lineHeight;
      mirror.style.padding = cs.padding;
      mirror.style.border = cs.border;
      mirror.style.boxSizing = cs.boxSizing;
      mirror.style.width = textarea.clientWidth + 'px';
    }
  }

  textarea.addEventListener('input', recompute);
  textarea.addEventListener('keyup', recompute);
  textarea.addEventListener('click', recompute);
  textarea.addEventListener('focus', () => {
    focused = true;
    recompute();
  });
  textarea.addEventListener('blur', () => {
    focused = false;
    currentSuggestion = undefined;
    matchLen = 0;
    extensionMode = false;
    requireTwoCharsAfterSpace = false;
    lastAccepted = null;
    updateGhost(textarea, ghost, null, textarea.selectionStart, mirror, 0);
  });
  textarea.addEventListener('keydown', handleKey);
  textarea.addEventListener('scroll', () => updateGhost(textarea, ghost, currentSuggestion, textarea.selectionStart, mirror, matchLen));
  window.addEventListener('resize', syncSize);
  // Recompute on selection changes initiated outside key events (e.g., mouse drag).
  const selListener = () => {
    if (document.activeElement === textarea) {
      recompute();
    }
  };
  document.addEventListener('selectionchange', selListener);

  // Mobile swipe accept
  textarea.addEventListener('touchstart', (e) => {
    touchStartX = e.touches[0].clientX;
    touchStartY = e.touches[0].clientY;
  });
  textarea.addEventListener('touchmove', (e) => {
    if (!currentSuggestion || touchStartX == null) return;
    const dx = e.touches[0].clientX - touchStartX;
    const dy = Math.abs(e.touches[0].clientY - touchStartY);
    if (dx > swipeThreshold && dy < 30) {
      acceptSuggestion(textarea, currentSuggestion, textarea.selectionStart, matchLen);
      currentSuggestion = undefined;
      matchLen = 0;
      lastAccepted = textarea.value.slice(0, textarea.selectionStart);
      const prefixLower = lastAccepted.toLowerCase() + ' ';
      extensionCandidates = options.suggestions.filter((s) => s.toLowerCase().startsWith(prefixLower) && s.length > prefixLower.length);
      if (extensionCandidates.length > 0) {
        extensionMode = true;
        requireTwoCharsAfterSpace = false;
      } else {
        extensionMode = false;
        requireTwoCharsAfterSpace = true;
      }
      updateGhost(textarea, ghost, null, textarea.selectionStart, mirror, 0);
      touchStartX = null;
      touchStartY = null;
    }
  });
  textarea.addEventListener('touchend', () => {
    touchStartX = null;
    touchStartY = null;
  });

  recompute();

  return {
    destroy() {
      textarea.removeEventListener('input', recompute);
      textarea.removeEventListener('keyup', recompute);
      textarea.removeEventListener('click', recompute);
      // focus/blur listeners are anonymous above; removing by cloning node is heavy. Use capture reference instead:
      // Fallback: rely on wrapper removal during page teardown. No-op here.
      textarea.removeEventListener('keydown', handleKey);
      window.removeEventListener('resize', syncSize);
      ghost.remove();
      document.removeEventListener('selectionchange', selListener);
      ro.disconnect();
    },
    recompute,
  };
}

function initAll(options) {
  const roots = [document];
  // Include shadow roots from custom elements if accessible
  document.querySelectorAll('*').forEach((el) => {
    if (el.shadowRoot) roots.push(el.shadowRoot);
  });
  for (const root of roots) {
    root.querySelectorAll &&
      root.querySelectorAll('textarea').forEach((t) => {
        if (!globalState.instances.has(t)) {
          const inst = attachInstance(t, options);
          globalState.instances.set(t, inst);
        }
      });
  }
}

function observeNew(options) {
  const observer = new MutationObserver((mutations) => {
    for (const m of mutations) {
      if (m.type === 'childList') {
        m.addedNodes.forEach((node) => {
          if (node.nodeType === 1) {
            if (node.tagName === 'TEXTAREA') {
              if (!globalState.instances.has(node)) {
                const inst = attachInstance(node, options);
                globalState.instances.set(node, inst);
              }
            }
            // Also search descendants if container inserted
            node.querySelectorAll &&
              node.querySelectorAll('textarea').forEach((t) => {
                if (!globalState.instances.has(t)) {
                  const inst = attachInstance(t, options);
                  globalState.instances.set(t, inst);
                }
              });
            // If this node hosts a shadow root, scan inside it
            if (node.shadowRoot) {
              node.shadowRoot.querySelectorAll &&
                node.shadowRoot.querySelectorAll('textarea').forEach((t) => {
                  if (!globalState.instances.has(t)) {
                    const inst = attachInstance(t, options);
                    globalState.instances.set(t, inst);
                  }
                });
            }
          }
        });
      }
    }
  });
  observer.observe(document.body, { childList: true, subtree: true });
  globalState.observers.push(observer);
}

function initSmartCompose(options = /** @type {SmartComposeOptions} */ ({})) {
  if (globalState.options) return; // already initialized
  const merged = {
    suggestions: options.suggestions || DEFAULT_SUGGESTIONS,
    getSuggestion: options.getSuggestion,
    swipeThreshold: options.swipeThreshold || 50,
  };
  globalState.options = merged;
  initAll(merged);
  observeNew(merged);
}

document.addEventListener('DOMContentLoaded', () => {
  initSmartCompose();
});
