// PL-039 Phase 1: Claude API hook (= 3 版 capture path parity for release Step 1)。
// MAIN world で document_start 時に install され、 claude.ai 自身の
// /api/organizations/{org}/chat_conversations/{cid} fetch を passive sniff で
// capture、 chat_messages array を含む完全 JSON を cache 化。 save click 時
// content.js は cache から messages を parse する経路に切替、 auto-scroll は
// 一切走らせない (= ChatGPT/Gemini と同 invariant)。
//
// Claude 構造的特異点 (= Phase 0 probe で確定、 charter §試行 Phase 0 記録):
//  - Cookie session のみで認証通る (= Authorization header 不使用)
//    → ChatGPT の Bearer 抽出 / token fallback path 不要、 saved Request 保存不要
//  - tree=True で全 chat_messages 一括取得 = lazy-load 不在 (scroll-up で追加 fetch なし)
//  - POST /completion は SSE だが capture path 対象外 (= GET 単発 JSON のみ sniff)
//  - URL に v2 接尾辞付き endpoint (= sidebar 一覧用) は別物、 filter で除外

(function () {
  if (window.__CLAUDE_ASA_PAGE_HOOK_INSTALLED__) {
    return;
  }
  window.__CLAUDE_ASA_PAGE_HOOK_INSTALLED__ = true;

  const MESSAGE_SOURCE = 'asa-claude-page-hook';
  const MESSAGE_TYPE_CACHE_NOTIFY = 'cache-notify';
  const MESSAGE_TYPE_READ_REQUEST = 'cache-read-request';
  const MESSAGE_TYPE_READ_RESPONSE = 'cache-read-response';
  const MESSAGE_TYPE_REPLAY_REQUEST = 'replay-request';
  const MESSAGE_TYPE_REPLAY_RESPONSE = 'replay-response';
  const MESSAGE_TYPE_SNAPSHOT_REQUEST = 'snapshot-request';
  const MESSAGE_TYPE_SNAPSHOT_RESPONSE = 'snapshot-response';
  const MESSAGE_TYPE_CACHE_CLEAR = 'cache-clear-request';

  const conversationCache = Object.create(null);

  // PL-039: org_uuid + conv_uuid (= 36-char hyphenated UUID) 2 階層、 v2 接尾辞
  // (= chat_conversations_v2 = sidebar 一覧) は本命 endpoint ではないため除外。
  // capture group 1 = org_uuid / group 2 = conv_uuid (= ChatGPT cid 相当の cache key)。
  const CONVERSATION_URL_RE = /\/api\/organizations\/([0-9a-f-]{36})\/chat_conversations\/([0-9a-f-]{36})(?:[?#]|$)/i;

  // active replay 時の URL 再構築用 (= 1 site session 内 org は固定前提、 multi-org
  // 切替時は次 passive sniff で更新)。 cache miss + replay 直叩き path で必要。
  // 新規 conv で chat_conversations GET が走らない case でも replay 可能にする
  // ため、 chat_conversations 以外の /api/organizations/{org}/... 系 URL (=
  // settings / model_configs / chat_conversations_v2 等) からも org_uuid を抽出。
  let lastSniffOrgUuid = null;
  const ORG_UUID_ANY_RE = /\/api\/organizations\/([0-9a-f-]{36})\//i;
  function recordOrgFromAnyUrl(url) {
    try {
      const m = String(url || '').match(ORG_UUID_ANY_RE);
      if (m) lastSniffOrgUuid = m[1];
    } catch (_e) {}
  }

  function matchConv(url) {
    try {
      const m = String(url || '').match(CONVERSATION_URL_RE);
      return m ? { orgUuid: m[1], cid: m[2] } : null;
    } catch (_e) {
      return null;
    }
  }

  function shouldCapture(url, method) {
    if (String(method || 'GET').toUpperCase() !== 'GET') return false;
    return CONVERSATION_URL_RE.test(String(url || ''));
  }

  function postCacheNotify(cid, source) {
    try {
      window.postMessage({
        source: MESSAGE_SOURCE,
        type: MESSAGE_TYPE_CACHE_NOTIFY,
        cid,
        source_kind: source,
        fetched_at: Date.now(),
      }, location.origin);
    } catch (_e) {}
  }

  function storeResponse(cid, text, status, kind) {
    conversationCache[cid] = {
      resp_text: String(text || ''),
      status,
      fetched_at: Date.now(),
      source_kind: kind,
    };
    postCacheNotify(cid, kind);
  }

  const originalFetch = window.fetch;
  window.fetch = async function (...args) {
    const input = args[0];
    const init = args[1] || {};
    const url = typeof input === 'string' ? input : (input && input.url) || '';
    const method = (init.method || (input && input.method) || 'GET').toUpperCase();
    recordOrgFromAnyUrl(url);
    const response = await originalFetch.apply(this, args);
    if (shouldCapture(url, method)) {
      try {
        const m = matchConv(url);
        if (m && response.status === 200) {
          const cloned = response.clone();
          cloned.text().then((text) => {
            storeResponse(m.cid, text, response.status, 'passive_sniff');
          }).catch(() => {});
        }
      } catch (_e) {}
    }
    return response;
  };

  const originalOpen = XMLHttpRequest.prototype.open;
  const originalSend = XMLHttpRequest.prototype.send;
  XMLHttpRequest.prototype.open = function (method, url, ...rest) {
    this.__asaClaudeHookUrl = url;
    this.__asaClaudeHookMethod = method;
    return originalOpen.call(this, method, url, ...rest);
  };
  XMLHttpRequest.prototype.send = function (...args) {
    const url = this.__asaClaudeHookUrl || '';
    const method = String(this.__asaClaudeHookMethod || 'GET').toUpperCase();
    recordOrgFromAnyUrl(url);
    if (shouldCapture(url, method)) {
      this.addEventListener('load', function () {
        try {
          const m = matchConv(url);
          if (m && this.status === 200) {
            storeResponse(m.cid, this.responseText, this.status, 'passive_sniff_xhr');
          }
        } catch (_e) {}
      });
    }
    return originalSend.apply(this, args);
  };

  // PL-039 active replay: Claude は Cookie session のみで認証通るため (= Phase 0
  // probe で req_auth_present:false 確認)、 saved Request clone / Bearer 抽出は
  // 不要、 credentials:'include' + cache:'no-store' で site URL 直叩きで OK。
  // ChatGPT で必要だった site cache stale 回避 workaround も Claude では不要
  // (= header 由来 stale 現象未観測)。 URL 構築には passive sniff で記録した
  // org_uuid を使う。
  async function replayConversationFetch(cid) {
    if (!lastSniffOrgUuid) {
      return { ok: false, reason: 'no_org_uuid', status: null };
    }
    const url = `${location.origin}/api/organizations/${lastSniffOrgUuid}/chat_conversations/${cid}?tree=True&rendering_mode=messages&render_all_tools=true`;
    try {
      const response = await originalFetch.call(window, url, {
        method: 'GET',
        credentials: 'include',
        cache: 'no-store',
      });
      if (response.status === 200) {
        const text = await response.text();
        storeResponse(cid, text, response.status, 'active_replay');
        return { ok: true, status: response.status };
      }
      return { ok: false, reason: 'http_error', status: response.status };
    } catch (e) {
      return { ok: false, reason: 'fetch_threw', status: null, error: String(e) };
    }
  }

  // PL-120: 共有ページ (/share/<id>) の org スコープ snapshot を取得。 公開 snapshot は閲覧者の
  // org 文脈で render_all_tools=true 付き endpoint から widget_code 込みの完全構造で返る (= live
  // ページが図を描くのと同 endpoint、 オーナー不問)。 org_uuid は passive sniff を流用 (share ページ
  // でも org 系 GET が走り lastSniffOrgUuid が立つ)。 cid cache とは別に text を直接返す。
  async function replaySnapshotFetch(snapshotId) {
    if (!lastSniffOrgUuid) {
      return { ok: false, reason: 'no_org_uuid', status: null };
    }
    const url = `${location.origin}/api/organizations/${lastSniffOrgUuid}/chat_snapshots/${snapshotId}?rendering_mode=messages&render_all_tools=true`;
    try {
      const response = await originalFetch.call(window, url, {
        method: 'GET',
        credentials: 'include',
        cache: 'no-store',
      });
      if (response.status === 200) {
        const text = await response.text();
        return { ok: true, status: response.status, text };
      }
      return { ok: false, reason: 'http_error', status: response.status };
    } catch (e) {
      return { ok: false, reason: 'fetch_threw', status: null, error: String(e) };
    }
  }

  window.addEventListener('message', (event) => {
    if (event.source !== window) return;
    const data = event.data;
    if (!data || data.source !== MESSAGE_SOURCE) return;
    if (data.type === MESSAGE_TYPE_READ_REQUEST) {
      const cid = data.cid;
      const reqId = data.reqId;
      const entry = (cid && conversationCache[cid]) || null;
      try {
        window.postMessage({
          source: MESSAGE_SOURCE,
          type: MESSAGE_TYPE_READ_RESPONSE,
          reqId,
          cid,
          entry,
          has_org_uuid: !!lastSniffOrgUuid,
        }, location.origin);
      } catch (_e) {}
    } else if (data.type === MESSAGE_TYPE_REPLAY_REQUEST) {
      const cid = data.cid;
      const reqId = data.reqId;
      replayConversationFetch(cid).then((result) => {
        try {
          window.postMessage({
            source: MESSAGE_SOURCE,
            type: MESSAGE_TYPE_REPLAY_RESPONSE,
            reqId,
            cid,
            result,
          }, location.origin);
        } catch (_e) {}
      });
    } else if (data.type === MESSAGE_TYPE_SNAPSHOT_REQUEST) {
      const snapshotId = data.snapshotId;
      const reqId = data.reqId;
      replaySnapshotFetch(snapshotId).then((result) => {
        try {
          window.postMessage({
            source: MESSAGE_SOURCE,
            type: MESSAGE_TYPE_SNAPSHOT_RESPONSE,
            reqId,
            snapshotId,
            result,
          }, location.origin);
        } catch (_e) {}
      });
    } else if (data.type === MESSAGE_TYPE_CACHE_CLEAR) {
      // PL-039 Phase 1 では送信側 (content.js) 未実装、 ChatGPT 版 page_hook の
      // PL-045 codex review #3 由来 stale clear 機能と structural mirror で受信側
      // のみ先行整備。 realtime 周期 API 統合の別 Phase で送信側追加予定。
      const cid = data.cid;
      if (cid) {
        delete conversationCache[cid];
      }
    }
  });
})();
