(() => {
  if (window.__geminiAsaPageHookInstalled) {
    return;
  }
  window.__geminiAsaPageHookInstalled = true;

  const EVENT_NAME = 'gemini-asa-network';
  const REPLAY_EVENT_NAME = 'gemini-asa-replay-history';
  const MESSAGE_SOURCE = 'gemini-asa-page-hook';
  const MESSAGE_TYPE_NETWORK = 'network';
  const MESSAGE_TYPE_REPLAY = 'replay-history';
  const MESSAGE_TYPE_TEMPLATE_STATUS = 'template-status';
  const MESSAGE_TYPE_HOOK_STATUS = 'hook-status';
  const MESSAGE_TYPE_FULL_HISTORY_READY = 'full-history-ready';
  const PAGINATE_BATCH_SIZE = 10;
  const PAGINATE_MAX_BATCHES = 100;
  const historyRequestTemplates = Object.create(null);
  const inFlightReplayByConversationId = Object.create(null);
  const hookDebug = {
    installed: true,
    candidateRequestCount: 0,
    candidateResponseCount: 0,
    templateCount: 0,
    lastCandidateUrl: '',
    lastTemplateUrl: '',
    lastTemplateReason: 'none',
  };

  function postBridgeMessage(payload) {
    try {
      window.postMessage({
        source: MESSAGE_SOURCE,
        ...(payload && typeof payload === 'object' ? payload : {}),
      }, '*');
    } catch (_e) {}
  }

  function postHookStatus(extra) {
    postBridgeMessage({
      type: MESSAGE_TYPE_HOOK_STATUS,
      ...(hookDebug || {}),
      ...(extra && typeof extra === 'object' ? extra : {}),
    });
  }

  function getNestedString(value, path) {
    let current = value;
    for (const key of path) {
      if (!Array.isArray(current) || current.length <= key) {
        return '';
      }
      current = current[key];
    }
    return typeof current === 'string' ? current : '';
  }

  function findFirstJsonValue(text) {
    const source = String(text || '');
    const start = source.search(/[\[{]/);
    if (start < 0) return '';
    let depth = 0;
    let inString = false;
    let escaped = false;
    for (let i = start; i < source.length; i++) {
      const ch = source[i];
      if (inString) {
        if (escaped) {
          escaped = false;
        } else if (ch === '\\') {
          escaped = true;
        } else if (ch === '"') {
          inString = false;
        }
        continue;
      }
      if (ch === '"') {
        inString = true;
        continue;
      }
      if (ch === '[' || ch === '{') {
        depth++;
        continue;
      }
      if (ch === ']' || ch === '}') {
        depth--;
        if (depth === 0) {
          return source.slice(start, i + 1);
        }
      }
    }
    return '';
  }

  function extractOuterJsonFromBatchexecute(text) {
    try {
      const source = String(text || '').replace(/^\)\]\}'\s*/, '');
      return findFirstJsonValue(source);
    } catch (_e) {}
    return '';
  }

  function findResponseEntry(value) {
    try {
      if (!Array.isArray(value)) {
        return null;
      }
      for (const item of value) {
        if (Array.isArray(item) && item[1] === 'hNvQHb' && typeof item[2] === 'string') {
          return item;
        }
      }
      for (const item of value) {
        const found = findResponseEntry(item);
        if (found) {
          return found;
        }
      }
    } catch (_e) {}
    return null;
  }

  function findRequestEntry(value) {
    try {
      if (!Array.isArray(value)) {
        return null;
      }
      if (value[0] === 'hNvQHb' && typeof value[1] === 'string') {
        return value;
      }
      for (const item of value) {
        const found = findRequestEntry(item);
        if (found) {
          return found;
        }
      }
    } catch (_e) {}
    return null;
  }

  function extractConversationIdFromResponseBody(rawBody) {
    try {
      const responseText = String(rawBody || '');
      if (!responseText.includes('hNvQHb')) {
        return '';
      }
      const outerJson = extractOuterJsonFromBatchexecute(responseText);
      if (!outerJson) {
        return '';
      }
      const outer = JSON.parse(outerJson);
      const entry = findResponseEntry(outer);
      if (!Array.isArray(entry) || typeof entry[2] !== 'string') {
        return '';
      }
      const inner = JSON.parse(entry[2]);
      const turns = Array.isArray(inner) && Array.isArray(inner[0]) ? inner[0] : null;
      if (!Array.isArray(turns) || !turns.length) {
        return '';
      }
      for (const turn of turns) {
        if (!Array.isArray(turn)) continue;
        const conversationId = getNestedString(turn, [0, 0]) || getNestedString(turn, [1, 0]);
        if (conversationId) {
          return conversationId;
        }
      }
    } catch (_e) {}
    return '';
  }

  function shouldCapture(url) {
    return String(url || '').includes('/_/BardChatUi/data/batchexecute');
  }

  function normalizeConversationId(value) {
    const raw = String(value || '').trim();
    if (!raw) {
      return '';
    }
    return raw.replace(/^c_/, '');
  }

  function getConversationIdVariants(value) {
    const raw = String(value || '').trim();
    const normalized = normalizeConversationId(raw);
    const variants = [];
    if (normalized) {
      variants.push(normalized, `c_${normalized}`);
    }
    if (raw && !variants.includes(raw)) {
      variants.push(raw);
    }
    return variants;
  }

  function storeTemplateVariants(template) {
    for (const key of getConversationIdVariants(template && template.conversationId)) {
      historyRequestTemplates[key] = template;
    }
    hookDebug.templateCount = Object.keys(historyRequestTemplates).length;
  }

  function getStoredTemplate(conversationId) {
    for (const key of getConversationIdVariants(conversationId)) {
      if (historyRequestTemplates[key]) {
        return historyRequestTemplates[key];
      }
    }
    return null;
  }

  function maybeStoreTemplate(conversationId, url, body) {
    try {
      const normalizedConversationId = String(conversationId || '').trim();
      const normalizedBody = String(body || '');
      if (!normalizedConversationId || !normalizedBody) {
        hookDebug.lastTemplateReason = 'missing_conversation_or_body';
        return false;
      }
      if (!normalizedBody.includes('f.req=')) {
        hookDebug.lastTemplateReason = 'missing_f_req';
        return false;
      }
      const template = {
        conversationId: normalizedConversationId,
        url: String(url || ''),
        body: normalizedBody,
      };
      storeTemplateVariants(template);
      hookDebug.lastTemplateUrl = String(url || '');
      hookDebug.lastTemplateReason = 'stored';
      postBridgeMessage({
        type: MESSAGE_TYPE_TEMPLATE_STATUS,
        conversationId: normalizedConversationId,
        hasTemplate: true,
      });
      postHookStatus();
      return true;
    } catch (_e) {}
    hookDebug.lastTemplateReason = 'store_exception';
    return false;
  }

  function getRequestTemplate(body, url) {
    try {
      const params = new URLSearchParams(String(body || ''));
      const wrappedText = params.get('f.req');
      if (!wrappedText) {
        hookDebug.lastTemplateReason = 'f_req_absent';
        return null;
      }
      const wrapped = JSON.parse(wrappedText);
      const requestEntry = findRequestEntry(wrapped);
      const innerText = Array.isArray(requestEntry) ? requestEntry[1] : null;
      if (typeof innerText !== 'string') {
        hookDebug.lastTemplateReason = 'request_entry_not_found';
        return null;
      }
      const inner = JSON.parse(innerText);
      const conversationId = Array.isArray(inner) && typeof inner[0] === 'string' ? inner[0] : '';
      if (!conversationId) {
        hookDebug.lastTemplateReason = 'conversation_id_missing';
        return null;
      }
      hookDebug.lastTemplateReason = 'parsed';
      return {
        conversationId,
        url: String(url || ''),
        body: String(body || ''),
      };
    } catch (_e) {
      hookDebug.lastTemplateReason = 'parse_exception';
      return null;
    }
  }

  async function replayHistoryRequest(conversationId) {
    const cidKey = String(conversationId || '');
    if (inFlightReplayByConversationId[cidKey]) {
      return;
    }
    inFlightReplayByConversationId[cidKey] = true;
    try {
      const template = getStoredTemplate(conversationId);
      if (!template) {
        return;
      }
      const accumulated = [];
      let cursor = null;
      let baseUrl = '';
      for (let i = 0; i < PAGINATE_MAX_BATCHES; i++) {
        const params = new URLSearchParams(template.body || '');
        const wrapped = JSON.parse(params.get('f.req') || 'null');
        const requestEntry = findRequestEntry(wrapped);
        if (!Array.isArray(requestEntry) || typeof requestEntry[1] !== 'string') {
          return;
        }
        const inner = JSON.parse(requestEntry[1]);
        inner[1] = PAGINATE_BATCH_SIZE;
        inner[2] = cursor;
        requestEntry[1] = JSON.stringify(inner);
        params.set('f.req', JSON.stringify(wrapped));
        const urlObj = new URL(template.url, location.origin);
        urlObj.searchParams.set('_reqid', String((Date.now() + i * 7) % 1000000));
        if (!baseUrl) baseUrl = urlObj.toString();
        // PL-036 Phase 2 regression fix: 自身の fetch hook を bypass。wrapped
        // fetch (line 402-) は response を emit 経路に流す = 各 batch 生 response
        // が content.js bucket を replace → synth emit 後 last batch async emit
        // が late macrotask で bucket 上書きする race を発生させる (失敗 conv で
        // bucket=61 turn → 1 turn 上書き観測済)。originalFetch を直接呼んで
        // hook を回避、active replay は accumulate 後 1 度の synth emit のみで
        // bucket 更新する設計を担保。
        const response = await originalFetch.call(window, urlObj.toString(), {
          method: 'POST',
          credentials: 'include',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
          body: params.toString(),
        });
        if (!response.ok) return;
        const text = await response.text();
        const outerJson = extractOuterJsonFromBatchexecute(text);
        if (!outerJson) break;
        const outer = JSON.parse(outerJson);
        const entry = findResponseEntry(outer);
        if (!Array.isArray(entry) || typeof entry[2] !== 'string') break;
        const respInner = JSON.parse(entry[2]);
        const batchTurns = Array.isArray(respInner) && Array.isArray(respInner[0]) ? respInner[0] : [];
        for (const t of batchTurns) {
          accumulated.push(t);
        }
        const nextCursor = (Array.isArray(respInner) && typeof respInner[1] === 'string' && respInner[1]) ? respInner[1] : null;
        if (!nextCursor || batchTurns.length < PAGINATE_BATCH_SIZE) break;
        cursor = nextCursor;
        const delay = 100 + Math.floor(Math.random() * 200);
        await new Promise(r => setTimeout(r, delay));
      }
      if (!accumulated.length) return;
      const synthInner = JSON.stringify([accumulated, null, null, null]);
      const synthEntry = ['wrb.fr', 'hNvQHb', synthInner, null, null, null, 'generic'];
      const synthOuter = ")]}'\n" + JSON.stringify([[synthEntry]]);
      const synthUrl = baseUrl + (baseUrl.includes('?') ? '&' : '?') + 'asa-synth=1';
      emit(synthUrl, synthOuter);
      postBridgeMessage({
        type: MESSAGE_TYPE_FULL_HISTORY_READY,
        conversationId: String(conversationId || ''),
      });
    } catch (_e) {
    } finally {
      delete inFlightReplayByConversationId[cidKey];
    }
  }

  function emit(url, body) {
    try {
      if (!shouldCapture(url)) {
        return;
      }
      hookDebug.candidateResponseCount += 1;
      hookDebug.lastCandidateUrl = String(url || '');
      postBridgeMessage({
        type: MESSAGE_TYPE_NETWORK,
        url: String(url || ''),
        body: String(body || ''),
      });
      postHookStatus();
      window.dispatchEvent(new CustomEvent(EVENT_NAME, {
        detail: {
          url: String(url || ''),
          body: String(body || ''),
        },
      }));
    } catch (_e) {}
  }

  async function extractRequestBody(input, init) {
    try {
      const body = init && Object.prototype.hasOwnProperty.call(init, 'body') ? init.body : null;
      if (typeof body === 'string') {
        return body;
      }
      if (body instanceof URLSearchParams) {
        return body.toString();
      }
      if (typeof FormData !== 'undefined' && body instanceof FormData) {
        const params = new URLSearchParams();
        for (const [key, value] of body.entries()) {
          params.append(String(key || ''), typeof value === 'string' ? value : String(value || ''));
        }
        return params.toString();
      }
      if (typeof Blob !== 'undefined' && body instanceof Blob) {
        return await body.text();
      }
      if (body instanceof ArrayBuffer) {
        return new TextDecoder().decode(body);
      }
      if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView && ArrayBuffer.isView(body)) {
        return new TextDecoder().decode(body);
      }
      if (typeof Request !== 'undefined' && input instanceof Request) {
        const cloned = input.clone();
        return await cloned.text();
      }
    } catch (_e) {}
    return '';
  }

  const originalFetch = window.fetch;
  window.fetch = async function (...args) {
    const input = args[0];
    const init = args[1] || {};
    const requestUrl = typeof input === 'string' ? input : (input && input.url) || '';
    let requestBody = '';
    try {
      requestBody = await extractRequestBody(input, init);
      if (shouldCapture(requestUrl)) {
        hookDebug.candidateRequestCount += 1;
        hookDebug.lastCandidateUrl = String(requestUrl || '');
      }
      const template = getRequestTemplate(requestBody, requestUrl);
      if (template) {
        storeTemplateVariants(template);
        hookDebug.lastTemplateUrl = String(requestUrl || '');
        postBridgeMessage({
          type: MESSAGE_TYPE_TEMPLATE_STATUS,
          conversationId: template.conversationId,
          hasTemplate: true,
        });
        postHookStatus();
      }
    } catch (_e) {}

    const response = await originalFetch.apply(this, args);
    try {
      const cloned = response.clone();
      cloned.text().then(text => {
        const responseConversationId = extractConversationIdFromResponseBody(text);
        if (responseConversationId) {
          maybeStoreTemplate(responseConversationId, requestUrl, requestBody);
        }
        emit(cloned.url || requestUrl || '', text);
      }).catch(() => {});
    } catch (_e) {}
    return response;
  };

  const originalOpen = XMLHttpRequest.prototype.open;
  const originalSend = XMLHttpRequest.prototype.send;

  XMLHttpRequest.prototype.open = function (method, url, ...rest) {
    this.__geminiAsaUrl = url;
    return originalOpen.call(this, method, url, ...rest);
  };

  XMLHttpRequest.prototype.send = function (...args) {
    let requestBody = '';
    try {
      const rawBody = args[0];
      if (typeof rawBody === 'string') {
        requestBody = rawBody;
      } else if (rawBody instanceof URLSearchParams) {
        requestBody = rawBody.toString();
      } else if (typeof FormData !== 'undefined' && rawBody instanceof FormData) {
        const params = new URLSearchParams();
        for (const [key, value] of rawBody.entries()) {
          params.append(String(key || ''), typeof value === 'string' ? value : String(value || ''));
        }
        requestBody = params.toString();
      } else {
        requestBody = String(rawBody || '');
      }
      if (shouldCapture(this.__geminiAsaUrl || '')) {
        hookDebug.candidateRequestCount += 1;
        hookDebug.lastCandidateUrl = String(this.__geminiAsaUrl || '');
      }
      const template = getRequestTemplate(requestBody, this.__geminiAsaUrl || '');
      if (template) {
        storeTemplateVariants(template);
        hookDebug.lastTemplateUrl = String(this.__geminiAsaUrl || '');
        postBridgeMessage({
          type: MESSAGE_TYPE_TEMPLATE_STATUS,
          conversationId: template.conversationId,
          hasTemplate: true,
        });
        postHookStatus();
      }
    } catch (_e) {}

    this.addEventListener('load', function () {
      try {
        const responseBody = this.responseText || '';
        const responseConversationId = extractConversationIdFromResponseBody(responseBody);
        if (responseConversationId) {
          maybeStoreTemplate(responseConversationId, this.responseURL || this.__geminiAsaUrl || '', requestBody);
        }
        emit(this.responseURL || this.__geminiAsaUrl || '', responseBody);
      } catch (_e) {}
    });
    return originalSend.apply(this, args);
  };

  function handleReplayDetail(detail) {
    try {
      if (!detail) {
        return;
      }
      void replayHistoryRequest(detail.conversationId || '');
    } catch (_e) {}
  }

  window.addEventListener(REPLAY_EVENT_NAME, (event) => {
    const detail = event && event.detail && typeof event.detail === 'object' ? event.detail : null;
    handleReplayDetail(detail);
  });

  window.addEventListener('message', (event) => {
    try {
      if (event.source !== window) {
        return;
      }
      const data = event && event.data && typeof event.data === 'object' ? event.data : null;
      if (!data || data.source !== MESSAGE_SOURCE || data.type !== MESSAGE_TYPE_REPLAY) {
        return;
      }
      handleReplayDetail(data);
    } catch (_e) {}
  });

  postHookStatus();
})();