/**
 * LivePreview：用 sucrase 将用户输入的 JSX 字符串实时编译并渲染。
 *
 * 工作原理：
 * 1. 将 code 字符串包裹成完整的函数组件
 * 2. 用 sucrase 将 TSX → JS
 * 3. 用 new Function 执行，注入 React + 所有组件
 * 4. 渲染返回的组件，捕获错误展示友好提示
 */
import React, { useMemo, useState, useEffect } from 'react';
import { transform } from 'sucrase';
import { Alert } from 'antd';

// 引入所有 canvas 需要的组件
import {
  NumCard,
  NumLineCard,
  Filter,
  BarChart,
  LineChart,
  PieChart,
  RadarChart,
  TreemapChart,
  Table,
  WorldMap,
  MultiChart,
  Section,
} from '@gadmin2n/charts';
import * as echarts from 'echarts';
import {
  Form,
  Select,
  Input,
  DatePicker,
  Tag,
  Button,
  Space,
  Spin,
} from 'antd';
import {
  CaretUpOutlined,
  CaretDownOutlined,
  UserOutlined,
  InfoCircleFilled,
  RightOutlined,
} from '@ant-design/icons';

// 注入 scope：code 里可以直接用这些名称
const SCOPE: Record<string, unknown> = {
  React,
  NumCard,
  NumLineCard,
  Filter,
  BarChart,
  LineChart,
  PieChart,
  RadarChart,
  TreemapChart,
  Table,
  WorldMap,
  MultiChart,
  Section,
  echarts,
  Form,
  Select,
  Input,
  DatePicker,
  Tag,
  Button,
  Space,
  Spin,
  CaretUpOutlined,
  CaretDownOutlined,
  UserOutlined,
  InfoCircleFilled,
  RightOutlined,
};

function compileAndRun(code: string): React.ComponentType | null {
  // 如果 code 是箭头函数或具名函数，直接把它作为组件使用
  const trimmed = code.trim();
  const isComponentFunction =
    trimmed.startsWith('()') ||
    trimmed.startsWith('(props)') ||
    trimmed.startsWith('function ');

  const wrapped = isComponentFunction
    ? `
    const __CanvasComponent = ${trimmed};
    return __CanvasComponent;
  `
    : `
    function __CanvasComponent() {
      return (
        <React.Fragment>
          ${code}
        </React.Fragment>
      );
    }
    return __CanvasComponent;
  `;

  // sucrase 编译
  const { code: compiled } = transform(wrapped, {
    transforms: ['jsx', 'typescript'],
    jsxRuntime: 'classic',
    production: false,
  });

  // 执行：将 scope 里的变量作为参数注入
  const scopeKeys = Object.keys(SCOPE);
  const scopeValues = Object.values(SCOPE);
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
  const fn = new Function(...scopeKeys, compiled);
  return fn(...scopeValues) as React.ComponentType;
}

interface LivePreviewProps {
  code: string;
}

const LivePreview: React.FC<LivePreviewProps> = ({ code }) => {
  const [renderKey, setRenderKey] = useState(0);

  const { Component, error } = useMemo(() => {
    try {
      const comp = compileAndRun(code);
      return { Component: comp, error: null };
    } catch (e: unknown) {
      return {
        Component: null,
        error: e instanceof Error ? e.message : String(e),
      };
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [code]);

  // After each new Component mounts, force all ECharts instances to resize.
  // ECharts initializes in useLayoutEffect (before paint), at which point the container
  // may not yet have its final layout size. We watch for the [_echarts_instance_] attribute
  // appearing on DOM nodes and immediately resize those charts.
  useEffect(() => {
    if (!Component) return;

    const resizeAll = () => {
      document
        .querySelectorAll<HTMLDivElement>('[_echarts_instance_]')
        .forEach((el) => {
          const chart = echarts.getInstanceByDom(el);
          chart?.resize();
        });
    };

    // MutationObserver: fires as soon as ECharts stamps [_echarts_instance_] onto a div
    const mo = new MutationObserver(() => {
      requestAnimationFrame(resizeAll);
    });
    mo.observe(document.body, {
      subtree: true,
      attributes: true,
      attributeFilter: ['_echarts_instance_'],
    });

    // Fallback timers for any charts already present or that stamp the attribute before MO fires
    const id1 = window.setTimeout(resizeAll, 80);
    const id2 = window.setTimeout(resizeAll, 400);

    return () => {
      mo.disconnect();
      window.clearTimeout(id1);
      window.clearTimeout(id2);
    };
  }, [Component]);

  if (error) {
    return (
      <Alert
        type="error"
        message="编译 / 运行错误"
        description={
          <pre style={{ margin: 0, fontSize: 12, whiteSpace: 'pre-wrap' }}>
            {error}
          </pre>
        }
        style={{ margin: 16 }}
      />
    );
  }

  if (!Component) return null;

  return (
    <ErrorBoundary key={renderKey} onReset={() => setRenderKey((k) => k + 1)}>
      <div style={{ width: '100%', height: '100%' }}>
        <Component />
      </div>
    </ErrorBoundary>
  );
};

// ─── ErrorBoundary ─────────────────────────────────────────────────────────────

interface ErrorBoundaryProps {
  children: React.ReactNode;
  onReset: () => void;
}

interface ErrorBoundaryState {
  error: string | null;
}

class ErrorBoundary extends React.Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { error: null };
  }

  static getDerivedStateFromError(e: Error): ErrorBoundaryState {
    return { error: e.message };
  }

  override componentDidCatch(error: Error) {
    console.error('[LivePreview error]', error);
  }

  override render() {
    if (this.state.error) {
      return (
        <Alert
          type="error"
          message="运行时错误"
          description={
            <pre style={{ margin: 0, fontSize: 12, whiteSpace: 'pre-wrap' }}>
              {this.state.error}
            </pre>
          }
          style={{ margin: 16 }}
        />
      );
    }
    return this.props.children;
  }
}

export default LivePreview;
