#!/usr/bin/env node
/**
 * 布局诊断 v4 — 针对 ChatInput + IoTStatusBar 不可见问题
 */
import { writeFileSync, mkdirSync } from 'fs';
import WebSocket from 'ws';

const BASE = 'http://localhost:5199';
const API = 'http://localhost:8080/api/v1';
const CDP_PORT = parseInt(process.env.CDP_PORT || '9225');
const OUT = '/home/aska/Code/Cyrene/debug/logs/chromium';

mkdirSync(OUT, { recursive: true });

function makeCDPHelper(ws) {
  let msgId = 1;
  const pending = new Map();
  ws.on('message', (raw) => {
    try {
      const msg = JSON.parse(raw.toString());
      if (msg.id && pending.has(msg.id)) {
        pending.get(msg.id)(msg.result || msg);
        pending.delete(msg.id);
      }
    } catch {}
  });
  return (method, params = {}) => new Promise((resolve, reject) => {
    const id = msgId++;
    const timer = setTimeout(() => {
      pending.delete(id);
      reject(new Error(`CDP timeout: ${method}`));
    }, 15000);
    ws.send(JSON.stringify({ id, method, params }));
    pending.set(id, (r) => { clearTimeout(timer); resolve(r); });
  });
}

async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

async function evaluate(cdp, expression, label) {
  try {
    const r = await cdp('Runtime.evaluate', {
      returnByValue: true,
      expression,
    });
    const val = r?.result?.value ?? r?.value;
    if (label) console.log(`   [${label}]:`, typeof val === 'string' ? val.slice(0, 500) : JSON.stringify(val).slice(0, 500));
    return val;
  } catch (e) {
    console.error(`   [${label}] ERROR:`, e.message);
    return null;
  }
}

async function main() {
  // Step 1: Login
  console.log('[1] Logging in...');
  const loginRes = await fetch(`${API}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'admin', password: 'admin123' }),
  });
  const loginData = await loginRes.json();
  const token = loginData?.token;
  const userId = loginData?.user_id || 'admin';
  console.log('   userId:', userId, 'token:', token?.slice(0, 20) + '...');
  if (!token) throw new Error('Login failed: ' + JSON.stringify(loginData));

  // Step 2: Connect to browser CDP, create target
  console.log('[2] Getting browser WS URL...');
  const ver = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json/version`)).json();
  const browserWsUrl = ver.webSocketDebuggerUrl;

  const bw = new WebSocket(browserWsUrl);
  await new Promise((resolve, reject) => {
    bw.once('open', resolve);
    bw.once('error', reject);
    setTimeout(() => reject(new Error('browser WS timeout')), 5000);
  });
  const bcdp = makeCDPHelper(bw);

  // Create a new page target
  console.log('[3] Creating page target...');
  const targetResult = await bcdp('Target.createTarget', { url: 'about:blank', width: 1440, height: 900 });
  const targetId = targetResult?.targetId;

  const pages = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json`)).json();
  const ourPage = pages.find(p => p.id === targetId || p.url === 'about:blank');
  const pageWsUrl = ourPage?.webSocketDebuggerUrl;
  if (!pageWsUrl) throw new Error('Could not find page WS URL');

  bw.close();

  // Step 4: Connect to page CDP
  console.log('[4] Connecting to page target...');
  const pw = new WebSocket(pageWsUrl);
  await new Promise((resolve, reject) => {
    pw.once('open', resolve);
    pw.once('error', reject);
    setTimeout(() => reject(new Error('page WS timeout')), 5000);
  });
  const cdp = makeCDPHelper(pw);

  await cdp('Page.enable');
  await cdp('Runtime.enable');
  await cdp('DOM.enable');

  // Step 5: Navigate, inject auth, reload
  console.log('[5] Navigating and setting auth...');
  await cdp('Page.navigate', { url: BASE });
  await sleep(3000);

  await cdp('Runtime.evaluate', {
    expression: `localStorage.setItem('token', ${JSON.stringify(token)}); localStorage.setItem('user_id', '${userId}'); 'injected';`
  });

  console.log('[6] Reloading with auth...');
  await cdp('Page.navigate', { url: BASE });
  await sleep(5000);

  // Check page state
  const pageState = await evaluate(cdp,
    `JSON.stringify({ title: document.title, hasTextarea: !!document.querySelector('textarea'), bodyClasses: document.body.className, rootExists: !!document.getElementById('root') })`,
    'PageState'
  );
  console.log('   Page state:', pageState);

  // Step 7: Screenshot
  console.log('[7] Taking screenshot...');
  const ss = await cdp('Page.captureScreenshot', { format: 'png', clip: { x: 0, y: 0, width: 1440, height: 900, scale: 1 } });
  if (ss?.data) {
    writeFileSync(`${OUT}/screenshot_layout.png`, Buffer.from(ss.data, 'base64'));
    console.log('   ✅ Screenshot saved');
  }

  // ============ DETAILED DIAGNOSTICS ============
  console.log('\n[8] === HEIGHT CHAIN DIAGNOSIS ===');

  // 8a: Viewport
  const vp = await evaluate(cdp,
    `JSON.stringify({ w: innerWidth, h: innerHeight, scrollY: scrollY, scrollX: scrollX })`,
    'Viewport'
  );

  // 8b: Height chain: #root → .h-screen → main → App wrapper → ChatContainer
  await evaluate(cdp, `
    (() => {
      const results = [];
      const root = document.getElementById('root');
      results.push({ id: '#root', exists: !!root, clientH: root?.clientHeight, scrollH: root?.scrollHeight, 
        cs: root ? getComputedStyle(root).height : 'N/A' });
      
      const hScreen = document.querySelector('.h-screen');
      results.push({ id: '.h-screen', exists: !!hScreen, clientH: hScreen?.clientHeight, scrollH: hScreen?.scrollHeight,
        cs: hScreen ? getComputedStyle(hScreen).height : 'N/A', csDisplay: hScreen ? getComputedStyle(hScreen).display : 'N/A' });
      
      const main = document.querySelector('main');
      results.push({ id: 'main', exists: !!main, clientH: main?.clientHeight, scrollH: main?.scrollHeight,
        cs: main ? getComputedStyle(main).height : 'N/A', csOverflow: main ? getComputedStyle(main).overflow : 'N/A' });
      
      const flexColFull = document.querySelector('.flex.flex-col.h-full.overflow-hidden') || 
                           document.querySelector('main > div');
      results.push({ id: 'App flex-col', exists: !!flexColFull, clientH: flexColFull?.clientHeight, scrollH: flexColFull?.scrollHeight,
        className: flexColFull?.className?.slice(0, 200) || 'N/A' });
      
      const chatBg = document.querySelector('.chat-background');
      results.push({ id: '.chat-background', exists: !!chatBg, clientH: chatBg?.clientHeight, scrollH: chatBg?.scrollHeight,
        rect: chatBg ? (()=>{const r=chatBg.getBoundingClientRect(); return {t:r.top,b:r.bottom,h:r.height};})() : null });
      
      const textarea = document.querySelector('textarea');
      results.push({ id: 'textarea', exists: !!textarea, 
        rect: textarea ? (()=>{const r=textarea.getBoundingClientRect(); return {t:Math.round(r.top),b:Math.round(r.bottom),l:r.left,r:r.right,w:Math.round(r.width),h:Math.round(r.height)};})() : null,
        display: textarea ? getComputedStyle(textarea).display : 'N/A',
        visibility: textarea ? getComputedStyle(textarea).visibility : 'N/A',
        opacity: textarea ? getComputedStyle(textarea).opacity : 'N/A' });
      
      return JSON.stringify(results, null, 2);
    })()
  `, 'HeightChain');

  // 8c: ChatInput wrapper and IoTStatusBar wrapper
  await evaluate(cdp, `
    (() => {
      const results = [];
      
      // Find ChatInput wrapper (flex-shrink-0 after flex-1)
      const flexShrinkDivs = Array.from(document.querySelectorAll('.flex-shrink-0'));
      for (const div of flexShrinkDivs) {
        const r = div.getBoundingClientRect();
        const hasTextarea = div.querySelector('textarea');
        const hasIoT = div.querySelector('button[title*="IoT"]') || div.textContent?.includes('IoT');
        results.push({
          element: hasTextarea ? 'ChatInput wrapper' : (div.textContent?.includes('IoT') ? 'IoT wrapper' : 'other flex-shrink-0'),
          className: div.className?.slice(0, 200) || '',
          rect: { t: Math.round(r.top), b: Math.round(r.bottom), l: Math.round(r.left), h: Math.round(r.height) },
          clientH: div.clientHeight,
          display: getComputedStyle(div).display,
          visibility: getComputedStyle(div).visibility,
          hasTextarea: !!hasTextarea,
          textContent: (div.textContent || '').slice(0, 80),
        });
      }
      
      // Find border-t elements (ChatInput has border-t)
      const borderT = Array.from(document.querySelectorAll('.border-t'));
      for (const el of borderT) {
        const r = el.getBoundingClientRect();
        results.push({
          element: 'border-t element',
          className: (el.className?.slice(0, 200) || ''),
          rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) },
          clientH: el.clientHeight,
          display: getComputedStyle(el).display,
          visible: r.top < innerHeight && r.bottom > 0,
          textContent: (el.textContent || '').slice(0, 100),
        });
      }
      
      return JSON.stringify(results, null, 2);
    })()
  `, 'FlexShrinkAndBorder');

  // 8d: Direct query for ChatInput and IoTStatusBar components
  await evaluate(cdp, `
    (() => {
      const results = {};
      
      // All textareas
      const textareas = Array.from(document.querySelectorAll('textarea'));
      results.textareas = textareas.map((ta, i) => {
        const r = ta.getBoundingClientRect();
        let parent = ta.parentElement;
        const parentChain = [];
        for (let j = 0; j < 6 && parent; j++) {
          parentChain.push({
            tag: parent.tagName,
            cls: (parent.className?.slice(0, 150) || ''),
            clientH: parent.clientHeight,
            scrollH: parent.scrollHeight,
            display: getComputedStyle(parent).display,
            overflow: getComputedStyle(parent).overflow,
            rect: (() => { const pr = parent.getBoundingClientRect(); return { t: Math.round(pr.top), b: Math.round(pr.bottom), h: Math.round(pr.height) }; })(),
          });
          parent = parent.parentElement;
        }
        return {
          idx: i,
          rect: { t: Math.round(r.top), b: Math.round(r.bottom), l: Math.round(r.left), r: Math.round(r.right), w: Math.round(r.width), h: Math.round(r.height) },
          clientH: ta.clientHeight,
          display: getComputedStyle(ta).display,
          visibility: getComputedStyle(ta).visibility,
          opacity: getComputedStyle(ta).opacity,
          isVisible: r.top < innerHeight && r.bottom > 0 && r.width > 0 && r.height > 0,
          parentChain,
        };
      });
      
      // All divs with IoT-related text
      const allDivs = Array.from(document.querySelectorAll('div'));
      results.iotDivs = allDivs
        .filter(d => (d.textContent || '').includes('IoT'))
        .map(d => {
          const r = d.getBoundingClientRect();
          return {
            text: (d.textContent || '').slice(0, 100),
            cls: (d.className?.slice(0, 200) || ''),
            rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) },
            clientH: d.clientHeight,
            display: getComputedStyle(d).display,
            visibility: getComputedStyle(d).visibility,
            isVisible: r.top < innerHeight && r.bottom > 0 && r.width > 0,
          };
        });
      
      // Check if #root children are rendered
      const root = document.getElementById('root');
      if (root) {
        results.rootChildren = Array.from(root.children).map((c, i) => ({
          idx: i,
          tag: c.tagName,
          cls: (c.className?.slice(0, 200) || ''),
          childCount: c.children.length,
          clientH: c.clientHeight,
          rect: (() => { const r = c.getBoundingClientRect(); return { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }; })(),
        }));
      }
      
      return JSON.stringify(results, null, 2);
    })()
  `, 'DetailedElements');

  // 8e: Check what's inside main element
  await evaluate(cdp, `
    (() => {
      const main = document.querySelector('main');
      if (!main) return 'NO MAIN ELEMENT FOUND!';
      
      const results = {
        mainClientH: main.clientHeight,
        mainChildCount: main.children.length,
        children: Array.from(main.children).map((c, i) => ({
          idx: i,
          tag: c.tagName,
          cls: (c.className?.slice(0, 200) || ''),
          clientH: c.clientHeight,
          scrollH: c.scrollHeight,
          rect: (() => { const r = c.getBoundingClientRect(); return { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }; })(),
          display: getComputedStyle(c).display,
          overflow: getComputedStyle(c).overflow,
          childCount: c.children.length,
          grandchildren: Array.from(c.children).map((gc, j) => ({
            idx: j,
            tag: gc.tagName,
            cls: (gc.className?.slice(0, 200) || ''),
            clientH: gc.clientHeight,
            rect: (() => { const r = gc.getBoundingClientRect(); return { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }; })(),
          })),
        })),
      };
      return JSON.stringify(results, null, 2);
    })()
  `, 'MainChildren');

  // Write diagnostics
  console.log('\n[9] Collecting final diagnostics...');
  const finalDiag = await evaluate(cdp, `
    (() => {
      const D = {};
      D.viewport = { w: innerWidth, h: innerHeight };
      
      D.chatInputVisible = (() => {
        const ta = document.querySelector('textarea');
        if (!ta) return { reason: 'NO_TEXTAREA_FOUND' };
        const r = ta.getBoundingClientRect();
        const cs = getComputedStyle(ta);
        return {
          rect: { t: Math.round(r.top), b: Math.round(r.bottom), l: Math.round(r.left), r: Math.round(r.right), w: Math.round(r.width), h: Math.round(r.height) },
          display: cs.display,
          visibility: cs.visibility,
          opacity: cs.opacity,
          isInViewport: r.top < innerHeight && r.bottom > 0,
          isBelowFold: r.top >= innerHeight,
          isAboveFold: r.bottom <= 0,
          hasSize: r.width > 0 && r.height > 0,
        };
      })();
      
      D.iotStatusBarVisible = (() => {
        const iotDivs = Array.from(document.querySelectorAll('div')).filter(d => (d.textContent || '').includes('IoT 设备'));
        if (iotDivs.length === 0) return { reason: 'NO_IOT_DIV_FOUND' };
        const el = iotDivs[0];
        const r = el.getBoundingClientRect();
        const cs = getComputedStyle(el);
        return {
          rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) },
          display: cs.display,
          visibility: cs.visibility,
          opacity: cs.opacity,
          isInViewport: r.top < innerHeight && r.bottom > 0,
          isBelowFold: r.top >= innerHeight,
        };
      })();
      
      // Check all parent heights
      D.parentHeights = [];
      const root = document.getElementById('root');
      if (root) {
        let el = root;
        let depth = 0;
        while (el && depth < 10) {
          D.parentHeights.push({
            depth,
            tag: el.tagName,
            cls: (el.className?.slice(0, 150) || ''),
            clientH: el.clientHeight,
            scrollH: el.scrollHeight,
            csHeight: getComputedStyle(el).height,
            csOverflow: getComputedStyle(el).overflow,
            csDisplay: getComputedStyle(el).display,
          });
          el = el.children[0];
          depth++;
        }
      }
      
      // Check what's in the main content area
      const main = document.querySelector('main');
      if (main) {
        D.mainInfo = {
          clientH: main.clientHeight,
          scrollH: main.scrollHeight,
          csHeight: getComputedStyle(main).height,
          csOverflow: getComputedStyle(main).overflow,
          childCount: main.children.length,
          childTags: Array.from(main.children).map(c => ({ tag: c.tagName, cls: (c.className?.slice(0, 100) || ''), clientH: c.clientHeight })),
        };
      }
      
      return JSON.stringify(D, null, 2);
    })()
  `, 'FinalDiagnosis');

  writeFileSync(`${OUT}/diagnostics.json`, JSON.stringify(JSON.parse(finalDiag), null, 2));
  console.log('\n✅ Diagnostics saved to diagnostics.json');

  pw.close();
  console.log('Done.');
}

main().catch(err => {
  console.error('Fatal:', err);
  process.exit(1);
});
