/**
 * CDP Client — Antigravity IDE와 Chrome DevTools Protocol로 통신
 * 
 * 채팅 DOM 스크래핑, 메시지 전송, 상태 모니터링
 * 
 * Antigravity DOM 구조 (v1.107.0 기준):
 *   .antigravity-agent-side-panel
 *     └ #conversation                        ← 대화 스크롤 영역
 *         ├ .overflow-y-auto                  ← 메시지 스크롤 컨테이너
 *         │   └ (메시지 블록들)
 *         └ .text-ide-message-block-bot-color ← 입력 영역 래퍼
 *             └ #antigravity.agentSidePanelInputBox
 *                 └ [contenteditable][role="textbox"] ← 입력창
 */

const CDP = require('chrome-remote-interface');

class CDPClient {
  constructor(host = 'localhost', port = 9000) {
    this.host = host;
    this.port = port;
    this.client = null;
    this.connected = false;
    this.pollInterval = null;
    this.onChatUpdate = null; // callback(messages[])
    this.onDisconnect = null; // callback()
    this.onScreencastFrame = null; // callback({data, metadata})
    this.screencastRunning = false;

    // 메시지 누적 시스템
    this.messageHistory = [];  // 전체 누적 메시지
    this.seenHashes = new Set(); // 중복 제거용 해시
    this.initialScrapeComplete = false;
  }

  /**
   * 메시지 해시 생성 (중복 제거용)
   */
  _msgHash(msg) {
    if (msg.type === 'task') return `task:${msg.title}:${msg.summary?.substring(0, 50) || ''}`;
    if (msg.type === 'user') return `user:${msg.content?.substring(0, 100) || ''}`;
    if (msg.type === 'text') return `text:${msg.content?.substring(0, 100) || ''}`;
    if (msg.type === 'thought') return `thought:${msg.label || ''}`;
    if (msg.type === 'status') return `status:${msg.content || ''}`;
    return `${msg.type}:${JSON.stringify(msg).substring(0, 100)}`;
  }

  /**
   * 새 메시지들을 히스토리에 누적 (중복 제거)
   * @returns {boolean} 새 메시지가 추가되었는지
   */
  _accumulateMessages(newMessages) {
    if (!newMessages || newMessages.length === 0) return false;

    let added = false;

    // status 타입은 항상 마지막 것만 유지 (변경될 수 있음)
    const statusMsgs = newMessages.filter(m => m.type === 'status');
    const nonStatusMsgs = newMessages.filter(m => m.type !== 'status');

    for (const msg of nonStatusMsgs) {
      const hash = this._msgHash(msg);
      if (!this.seenHashes.has(hash)) {
        this.seenHashes.add(hash);
        // task 카드의 actions/steps는 업데이트될 수 있음 → 기존 것 교체
        if (msg.type === 'task') {
          const existIdx = this.messageHistory.findIndex(m =>
            m.type === 'task' && m.title === msg.title
          );
          if (existIdx >= 0) {
            this.messageHistory[existIdx] = msg;
            added = true;
            continue;
          }
        }
        this.messageHistory.push(msg);
        added = true;
      }
    }

    // status는 마지막 것으로 교체
    if (statusMsgs.length > 0) {
      const lastStatus = statusMsgs[statusMsgs.length - 1];
      const existStatusIdx = this.messageHistory.findIndex(m => m.type === 'status');
      if (existStatusIdx >= 0) {
        if (this.messageHistory[existStatusIdx].content !== lastStatus.content) {
          this.messageHistory[existStatusIdx] = lastStatus;
          added = true;
        }
      } else {
        this.messageHistory.push(lastStatus);
        added = true;
      }
    }

    return added;
  }

  /**
   * CDP 연결 수립
   */
  async connect() {
    try {
      // CDP 타겟 목록에서 올바른 페이지 찾기
      const targets = await CDP.List({ host: this.host, port: this.port });

      // Antigravity 메인 워크벤치 타겟 (jetski-agent 런치패드 제외)
      const pageTarget = targets.find(t =>
        t.type === 'page' &&
        t.url.includes('workbench.html') &&
        !t.url.includes('jetski')
      ) || targets.find(t => t.type === 'page' && !t.url.includes('devtools'));

      if (!pageTarget) {
        throw new Error('Antigravity 페이지 타겟을 찾을 수 없습니다');
      }

      this.client = await CDP({
        host: this.host,
        port: this.port,
        target: pageTarget,
      });

      const { Runtime, DOM, Page, Input } = this.client;

      await Promise.all([
        Runtime.enable(),
        DOM.enable(),
        Page.enable(),
      ]);

      this.connected = true;

      // 연결 끊김 감지
      this.client.on('disconnect', () => {
        this.connected = false;
        this.stopPolling();
        if (this.onDisconnect) this.onDisconnect();
      });

      console.log(`[CDP] 연결 성공: ${this.host}:${this.port} → ${pageTarget.title}`);
      return { success: true, title: pageTarget.title };
    } catch (err) {
      this.connected = false;
      console.error(`[CDP] 연결 실패: ${this.host}:${this.port} — ${err.message}`);
      return { success: false, error: err.message };
    }
  }

  /**
   * 연결 해제
   */
  async disconnect() {
    this.stopPolling();
    if (this.client) {
      try {
        await this.client.close();
      } catch (e) { /* ignore */ }
      this.client = null;
    }
    this.connected = false;
  }

  /**
   * 채팅 영역의 DOM을 구조화된 JSON으로 추출
   * 
   * Antigravity DOM을 순회하여 메시지 블록을 타입별로 분류:
   * - task: 작업 카드 (접기/펼치기 가능한 그룹)
   * - text: 마크다운 텍스트
   * - thought: Thought Process 블록
   * - tool: 도구 호출/결과
   * - code: 코드 블록
   * - image: 이미지
   * - user: 사용자 메시지
   * - status: 상태 표시 (진행 중 등)
   */
  async scrapeChatDOM() {
    if (!this.connected || !this.client) return null;

    try {
      const { result } = await this.client.Runtime.evaluate({
        expression: `
          (function() {
            const conv = document.querySelector('#conversation');
            if (!conv) return JSON.stringify([]);

            const scrollEl = conv.querySelector('.overflow-y-auto');
            if (!scrollEl) return JSON.stringify([]);

            const messages = [];
            const topContainer = scrollEl.querySelector('.mx-auto.w-full > div > div');
            if (!topContainer) return JSON.stringify([]);

            const actionKeywords = ['Proceed','Cancel','Open','View','Review','Approve','Reject','Yes','No','Accept','Deny','Allow','Skip'];

            // 유틸: 액션 버튼 추출
            function extractActions(container) {
              return Array.from(container.querySelectorAll('button')).map(b => {
                const label = b.textContent.trim();
                const rect = b.getBoundingClientRect();
                return {
                  label,
                  x: Math.round(rect.left + rect.width / 2),
                  y: Math.round(rect.top + rect.height / 2),
                  w: Math.round(rect.width),
                  h: Math.round(rect.height),
                };
              }).filter(b => b.label && b.w > 0 && actionKeywords.some(k => b.label.includes(k)));
            }

            // 유틸: 마크다운 영역에서 콘텐츠를 추출
            function extractContentBlocks(container) {
              // select-text 또는 leading-relaxed 마크다운 렌더링 영역 찾기
              const mkEls = container.querySelectorAll('.select-text .leading-relaxed, .leading-relaxed.select-text');
              
              for (const mkEl of mkEls) {
                // style 태그 제거
                const clone = mkEl.cloneNode(true);
                clone.querySelectorAll('style').forEach(s => s.remove());
                
                const html = clone.innerHTML;
                const text = clone.textContent.trim();
                
                if (!text || text.length < 2) continue;
                // CSS 필터
                if (text.startsWith('/*') || text.includes('prefers-color-scheme')) continue;
                
                messages.push({
                  type: 'text',
                  content: text.substring(0, 5000),
                  html: html.substring(0, 10000),
                });
              }
            }

            // 각 turn을 순회
            const turns = topContainer.children;
            for (let i = 0; i < turns.length; i++) {
              const turn = turns[i];
              
              // placeholder 건너뛰기
              if (turn.children.length > 0 && 
                Array.from(turn.children).every(c => 
                  c.classList.contains('rounded-lg') && 
                  c.classList.contains('bg-gray-500/10') &&
                  c.textContent.trim() === ''
                )) continue;

              // style 태그 미리 제거
              turn.querySelectorAll('style').forEach(s => s.remove());

              // --- 사용자 메시지 감지 (bg-gray-500/15 + select-text) ---
              const userMsgEl = turn.querySelector('.bg-gray-500\\\\/15.select-text, .bg-gray-500\\\\/15 .select-text');
              if (userMsgEl) {
                const text = userMsgEl.textContent.trim();
                if (text) {
                  messages.push({ type: 'user', content: text.substring(0, 2000) });
                }
              }

              // --- isolate 카드들 (task boundary) ---
              const isolates = turn.querySelectorAll('.isolate');
              for (const card of isolates) {
                const titleEl = card.querySelector('.font-semibold');
                const summaryEl = card.querySelector('.text-sm .leading-relaxed');
                const expanded = card.querySelector('[aria-expanded]');
                
                const steps = [];
                card.querySelectorAll('.flex.items-center.gap-2, .flex.w-full.items-center.gap-2').forEach(step => {
                  const txt = step.textContent.trim();
                  if (txt && txt.length > 2) {
                    const svg = step.querySelector('svg');
                    let icon = '';
                    if (svg) {
                      const cls = svg.getAttribute('class') || '';
                      if (cls.includes('check')) icon = '✓';
                      else if (cls.includes('loader') || cls.includes('spin')) icon = '⟳';
                      else if (cls.includes('x-circle') || cls.includes('alert')) icon = '⚠';
                    }
                    steps.push({ icon, text: txt.substring(0, 200) });
                  }
                });

                const cardBtns = extractActions(card);

                messages.push({
                  type: 'task',
                  title: titleEl ? titleEl.textContent.trim() : '',
                  summary: summaryEl ? summaryEl.textContent.trim().substring(0, 500) : '',
                  collapsed: expanded ? expanded.getAttribute('aria-expanded') === 'false' : true,
                  steps: steps.slice(0, 20),
                  actions: cardBtns.slice(0, 5),
                });
              }

              // --- Thought Process ---
              const thoughtBtns = turn.querySelectorAll('button');
              for (const btn of thoughtBtns) {
                if (btn.textContent.includes('Thought for')) {
                  messages.push({ type: 'thought', label: btn.textContent.trim(), collapsed: true });
                }
              }

              // --- isolate 바깥의 마크다운 콘텐츠 ---
              // (isolate 내부가 아닌 마크다운 블록)
              const allMkEls = turn.querySelectorAll('.leading-relaxed.select-text, .select-text .leading-relaxed');
              for (const mkEl of allMkEls) {
                // isolate 내부면 건너뛰기 (이미 task로 처리)
                if (mkEl.closest('.isolate')) continue;

                const clone = mkEl.cloneNode(true);
                clone.querySelectorAll('style').forEach(s => s.remove());
                
                const html = clone.innerHTML;
                const text = clone.textContent.trim();
                
                if (!text || text.length < 2) continue;
                if (text.startsWith('/*') || text.includes('prefers-color-scheme')) continue;
                
                messages.push({
                  type: 'text',
                  content: text.substring(0, 5000),
                  html: html.substring(0, 10000),
                });
              }

              // --- isolate 바깥 독립 코드/이미지/상태 ---
              const turnBlocks = turn.querySelectorAll(':scope > *');
              for (const block of turnBlocks) {
                if (block.querySelector('.isolate') || block.classList.contains('isolate')) continue;
                
                // 상태 텍스트 (Running, Generating 등)
                if (block.classList.contains('whitespace-nowrap')) {
                  const st = block.textContent.trim();
                  if (st) messages.push({ type: 'status', content: st });
                  continue;
                }
              }
            }
            
            return JSON.stringify(messages);
          })()
        `,
        returnByValue: true,
      });

      try {
        return JSON.parse(result.value) || [];
      } catch {
        return [];
      }
    } catch (err) {
      console.error('[CDP] 채팅 스크래핑 오류:', err.message);
      return [];
    }
  }

  /**
   * Antigravity 채팅 입력창에 메시지를 전송
   * 
   * 단일 Runtime.evaluate로 요소 찾기 + 텍스트 입력 수행
   * (2단계 분리 시 셀렉터 이스케이프 손실 버그 방지)
   */
  async sendMessage(text) {
    if (!this.connected || !this.client) {
      return { success: false, error: 'CDP 연결 없음' };
    }

    try {
      const safeText = JSON.stringify(text);
      const { result } = await this.client.Runtime.evaluate({
        expression: `
          (function() {
            const selectors = [
              '#antigravity\\\\.agentSidePanelInputBox [contenteditable="true"][role="textbox"]',
              '.antigravity-agent-side-panel [contenteditable="true"][role="textbox"]',
              '#conversation [contenteditable="true"]',
              '[contenteditable="true"][role="textbox"]',
            ];
            
            let el = null;
            for (const sel of selectors) {
              el = document.querySelector(sel);
              if (el) break;
            }
            if (!el) return { success: false, error: 'input not found' };

            el.focus();

            const selection = window.getSelection();
            const range = document.createRange();
            range.selectNodeContents(el);
            selection.removeAllRanges();
            selection.addRange(range);

            document.execCommand('insertText', false, ${safeText});

            return { success: true, text: el.textContent.substring(0, 50) };
          })()
        `,
        returnByValue: true,
      });

      if (!result.value?.success) {
        return { success: false, error: result.value?.error || '입력 실패' };
      }

      // Enter 키 전송
      await this.client.Input.dispatchKeyEvent({
        type: 'keyDown',
        key: 'Enter',
        code: 'Enter',
        nativeVirtualKeyCode: 13,
        windowsVirtualKeyCode: 13,
      });
      await this.client.Input.dispatchKeyEvent({
        type: 'keyUp',
        key: 'Enter',
        code: 'Enter',
        nativeVirtualKeyCode: 13,
        windowsVirtualKeyCode: 13,
      });

      console.log(`[CDP] 메시지 전송: "${text.substring(0, 50)}"`);
      return { success: true };
    } catch (err) {
      console.error('[CDP] 메시지 전송 오류:', err.message);
      return { success: false, error: err.message };
    }
  }

  /**
   * 채팅 폴링 시작 (1초 간격)
   */
  startPolling(intervalMs = 1000) {
    this.stopPolling();

    // 초기 전체 스캔 (한 번만)
    if (!this.initialScrapeComplete) {
      this._initialFullScrape().then(() => {
        this.initialScrapeComplete = true;
        console.log(`[CDP] 초기 전체 스캔 완료: ${this.messageHistory.length}개 메시지`);
      });
    }

    this.pollInterval = setInterval(async () => {
      if (!this.connected) {
        this.stopPolling();
        return;
      }

      const messages = await this.scrapeChatDOM();
      if (this._accumulateMessages(messages)) {
        if (this.onChatUpdate) {
          this.onChatUpdate([...this.messageHistory]);
        }
      }
    }, intervalMs);

    console.log(`[CDP] 폴링 시작 (${intervalMs}ms 간격)`);
  }

  /**
   * 초기 전체 스크롤 스캔 — Antigravity 대화를 top→bottom으로 순회하며 전체 수집
   */
  async _initialFullScrape() {
    if (!this.connected || !this.client) return;

    try {
      // 스크롤 정보 가져오기
      const { result: scrollInfo } = await this.client.Runtime.evaluate({
        expression: `
          (function() {
            const conv = document.querySelector('#conversation');
            if (!conv) return JSON.stringify(null);
            const sc = conv.querySelector('.overflow-y-auto');
            if (!sc) return JSON.stringify(null);
            return JSON.stringify({
              scrollTop: sc.scrollTop,
              scrollHeight: sc.scrollHeight,
              clientHeight: sc.clientHeight,
            });
          })()
        `,
        returnByValue: true,
      });

      const info = JSON.parse(scrollInfo.value);
      if (!info) return;

      const savedScrollTop = info.scrollTop;
      const totalHeight = info.scrollHeight;
      const viewHeight = info.clientHeight;
      const steps = Math.ceil(totalHeight / (viewHeight * 0.8)); // 80% 겹침두며 스크롤

      console.log(`[CDP] 전체 스캔 시작: ${totalHeight}px, ${steps}단계`);

      // top으로 이동
      await this.client.Runtime.evaluate({
        expression: `document.querySelector('#conversation .overflow-y-auto').scrollTop = 0;`,
      });
      await new Promise(r => setTimeout(r, 300));

      // 상단부터 하단까지 스크래핑
      for (let step = 0; step < steps; step++) {
        const messages = await this.scrapeChatDOM();
        this._accumulateMessages(messages);

        // 80% 단위로 스크롤
        await this.client.Runtime.evaluate({
          expression: `
            const sc = document.querySelector('#conversation .overflow-y-auto');
            sc.scrollTop += Math.round(sc.clientHeight * 0.8);
          `,
        });
        await new Promise(r => setTimeout(r, 200));
      }

      // 원래 스크롤 위치 복원
      await this.client.Runtime.evaluate({
        expression: `document.querySelector('#conversation .overflow-y-auto').scrollTop = ${savedScrollTop};`,
      });

      // 초기 수집 결과 전송
      if (this.messageHistory.length > 0 && this.onChatUpdate) {
        this.onChatUpdate([...this.messageHistory]);
      }

      console.log(`[CDP] 전체 스캔 결과: ${this.messageHistory.length}개 메시지 수집`);
    } catch (err) {
      console.error('[CDP] 전체 스캔 오류:', err.message);
    }
  }

  /**
   * 채팅 폴링 중지
   */
  stopPolling() {
    if (this.pollInterval) {
      clearInterval(this.pollInterval);
      this.pollInterval = null;
    }
  }

  /**
   * 현재 페이지 타이틀 가져오기
   */
  async getTitle() {
    if (!this.connected || !this.client) return null;
    try {
      const { result } = await this.client.Runtime.evaluate({
        expression: 'document.title',
        returnByValue: true,
      });
      return result.value;
    } catch {
      return null;
    }
  }

  /**
   * 스크린샷 (미리보기용)
   */
  async captureScreenshot() {
    if (!this.connected || !this.client) return null;
    try {
      const { data } = await this.client.Page.captureScreenshot({ format: 'jpeg', quality: 60 });
      return data; // base64
    } catch (err) {
      console.error('[CDP] 스크린샷 오류:', err.message);
      return null;
    }
  }

  // ─── Phase 2: Screencast & Remote Input ────────────────

  /**
   * 실시간 화면 스트리밍 시작
   * Page.startScreencast로 프레임을 수신하고 onScreencastFrame 콜백으로 전달
   */
  async startScreencast(options = {}) {
    if (!this.connected || !this.client) return false;
    if (this.screencastRunning) return true;

    const {
      format = 'jpeg',
      quality = 60,
      maxWidth = 1280,
      maxHeight = 900,
      everyNthFrame = 2,
    } = options;

    try {
      // 프레임 이벤트 리스너 등록
      this.client.Page.screencastFrame(async (params) => {
        // ACK — 이걸 보내야 다음 프레임이 옴
        try {
          await this.client.Page.screencastFrameAck({ sessionId: params.sessionId });
        } catch { /* ignore */ }

        if (this.onScreencastFrame) {
          this.onScreencastFrame({
            data: params.data,       // base64 JPEG
            metadata: params.metadata, // {offsetTop, pageScaleFactor, deviceWidth, deviceHeight, ...}
            sessionId: params.sessionId,
          });
        }
      });

      await this.client.Page.startScreencast({
        format,
        quality,
        maxWidth,
        maxHeight,
        everyNthFrame,
      });

      this.screencastRunning = true;
      console.log(`[CDP] Screencast 시작 (${maxWidth}x${maxHeight}, q${quality}, every ${everyNthFrame}th frame)`);
      return true;
    } catch (err) {
      console.error('[CDP] Screencast 시작 오류:', err.message);
      return false;
    }
  }

  /**
   * 실시간 화면 스트리밍 중지
   */
  async stopScreencast() {
    if (!this.connected || !this.client || !this.screencastRunning) return;
    try {
      await this.client.Page.stopScreencast();
    } catch { /* ignore */ }
    this.screencastRunning = false;
    this.onScreencastFrame = null;
    console.log('[CDP] Screencast 중지');
  }

  /**
   * 원격 입력 이벤트 디스패치
   * @param {Object} evt — { type: 'mouse'|'key'|'scroll', ... }
   */
  async dispatchInput(evt) {
    if (!this.connected || !this.client) return;

    try {
      switch (evt.type) {
        case 'mouse':
          await this.client.Input.dispatchMouseEvent({
            type: evt.action,       // mousePressed, mouseReleased, mouseMoved
            x: evt.x,
            y: evt.y,
            button: evt.button || 'left',
            clickCount: evt.clickCount || 1,
            modifiers: evt.modifiers || 0,
          });
          break;

        case 'scroll':
          await this.client.Input.dispatchMouseEvent({
            type: 'mouseWheel',
            x: evt.x,
            y: evt.y,
            deltaX: evt.deltaX || 0,
            deltaY: evt.deltaY || 0,
          });
          break;

        case 'key':
          await this.client.Input.dispatchKeyEvent({
            type: evt.action,        // keyDown, keyUp, char
            key: evt.key,
            code: evt.code || '',
            text: evt.text || '',
            nativeVirtualKeyCode: evt.keyCode || 0,
            windowsVirtualKeyCode: evt.keyCode || 0,
            modifiers: evt.modifiers || 0,
          });
          break;
      }
    } catch (err) {
      // 입력 이벤트 오류는 빈번할 수 있으므로 조용히 처리
    }
  }
}

module.exports = CDPClient;
