import { useState, useCallback, ReactNode } from 'react';
import {
  ReactFlow,
  applyNodeChanges,
  applyEdgeChanges,
  addEdge,
  Background,
  Controls,
  ReactFlowProvider,
  NodeTypes,
  NodeChange,
  EdgeChange,
  MarkerType,
  NodeMouseHandler,
  OnConnect,
  MiniMap,
  IsValidConnection,
  EdgeMouseHandler
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { AppNode, AppEdge, NodeData, EdgeData, NodeDataUpdate, EdgeDataUpdate } from './types/graph';
import {Settings, SettingsContext} from './types/settings'
import CustomNode from './components/CustomNode';
import NodeEditor from './components/NodeEditor';
import EdgeEditor from './components/EdgeEditor'
import SettingsEditor from './components/SettingsEditor'
import Toggle from "./components/Toggle"
import { generateWireGuardPrivateKey } from './utils/wireguardConfig';
import CryptoJS from 'crypto-js';
import './App.css';
import toast, { Toaster } from 'react-hot-toast';
import SaveConfig from './types/saveConfig'
import { plainToInstance, instanceToPlain } from 'class-transformer';
import SaveLoadPanel from './components/SaveLoadPanel';


const initialNodes: AppNode[] = [];
const initialEdges: AppEdge[] = [];

const nodeTypes : NodeTypes = {
  custom: CustomNode,
};

function generateNodeData(count: number) : NodeData | null {
  const privateKey = generateWireGuardPrivateKey();
  const node : NodeData = {
    id: `n-${crypto.randomUUID()}`,
    label: `Node-${count + 1}`,
    privateKey: privateKey
  }
  return node
}

function FlowContent(): ReactNode {
  const [nodes, setNodes] = useState<AppNode[]>(initialNodes);
  const [edges, setEdges] = useState<AppEdge[]>(initialEdges);
  const [settings, setSettings] = useState<Settings>(new Settings());

  const [editingNode, setEditingNode] = useState<NodeData | undefined>(undefined);
  const [editingEdge, setEditingEdge] = useState<EdgeData | undefined>(undefined);
  const [enableTwoWay, setEnableTwoWay] = useState(false);
  const [editSettings, setEditSettings] = useState<boolean>(false);

  const onNodesChange = useCallback(
    (changes: NodeChange<AppNode>[]) => setNodes((nds) => applyNodeChanges<AppNode>(changes, nds)),
    []);

  const onEdgesChange = useCallback(
    (changes : EdgeChange<AppEdge>[]) => setEdges((eds) => applyEdgeChanges<AppEdge>(changes, eds)),
    []);

  const onConnect = useCallback<OnConnect>(
    (params) => {
      const id = `e-${crypto.randomUUID()}`
      const newEdge : AppEdge = {
        ...params,
        id: id,
        animated: !enableTwoWay,
        markerEnd: enableTwoWay ? undefined : { type: MarkerType.ArrowClosed },
        data : {
          id: id,
          isTwoWayEdge: enableTwoWay
        }
      }
      return setEdges((eds) => addEdge<AppEdge>(newEdge, eds));
    },
    [enableTwoWay]);

  const onNodeClick = useCallback<NodeMouseHandler<AppNode>>(
    (_event, node) => setEditingNode(node.data), 
    []);

  const onEdgeClick = useCallback<EdgeMouseHandler<AppEdge>>(
    (_event, edge) => setEditingEdge(edge.data),
    []
  )

  const validateConnection = useCallback<IsValidConnection>(
    (connection) => {
      if (connection.source === connection.target) {
        return false;
      }
      const isDuplicate = edges.some(
        (edge) => (
          (edge.source === connection.source && edge.target === connection.target) ||
          (edge.source === connection.target && edge.target === connection.source)
        )
      );
      return !isDuplicate;
    },
    [edges]);

  const handleAddNode = (): void => {
    const result = generateNodeData(nodes.length);
    if(!result) return;
    const newNode: AppNode = {
      id: result.id,
      position: { x: 0, y: 0 },
      data: result,
      type: 'custom',
      selected: true
    };
    setNodes((prev) => [...prev.map(node => ({ ...node, selected: false })), newNode]);
  };

  const handleUpdateNode = (updatedData: NodeDataUpdate): void => {
    setNodes((prev) =>
      prev.map((node) => {
        if (node.data.id === editingNode?.id) {
          return {
            ...node,
            data: {...node.data, ...updatedData}
          }
        }
        return node;
      })
    );
  };

  const handleUpdateEdge = (updatedData: EdgeDataUpdate): void => {
    setEdges((prev) =>
      prev.map((edge) => {
        if (edge.data && edge.data.id === editingEdge?.id) {
          edge.data = {...edge.data, ...updatedData}
        }
        return edge;
      })
    );
  };

  const handleSaveConfig = async (pass?: string): Promise<void> => {
    try {
      const saveConfig: SaveConfig = {settings: settings, nodes: [], edges: [], encrypted: false}

      nodes.forEach(node => {
        const nodeData = node.data;
        let privateKey: string = nodeData.privateKey;
        if(pass) {
          privateKey = CryptoJS.AES.encrypt(privateKey, pass).toString();
          saveConfig.encrypted = true;
        }
        saveConfig.nodes.push({...node, data: {...nodeData, privateKey: privateKey}});
      });

      edges.forEach(edge => {
        saveConfig.edges.push(edge);
      });

      const json = instanceToPlain(saveConfig);
      const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' });
      const lastNameKey = 'wg-last-filename';
      const suggestedName = (localStorage.getItem(lastNameKey) || 'wg-config.json');

      // Use File System Access API when available to show save file picker
      if ((window as any).showSaveFilePicker) {
        try {
          const handle = await (window as any).showSaveFilePicker({
            suggestedName: suggestedName,
            types: [{
              description: 'WireGuard Config',
              accept: { 'application/json': ['.json'] }
            }]
          });
          const writable = await handle.createWritable();
          await writable.write(blob);
          await writable.close();
          // remember chosen name if available
          try {
            const name = handle.name || suggestedName;
            localStorage.setItem(lastNameKey, name);
          } catch (e) {
            localStorage.setItem(lastNameKey, suggestedName);
          }
        } catch (e) {
          // If user cancels or error, fallback to anchor download
          const url = URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = suggestedName;
          a.click();
          URL.revokeObjectURL(url);
        }
      } else {
        // Fallback: create an anchor and trigger download, using last used filename
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = suggestedName;
        a.click();
        URL.revokeObjectURL(url);
        localStorage.setItem(lastNameKey, suggestedName);
      }
    } catch (e) {
      toast.error('保存失败: ' + e);
    }
  };

  const handleLoadConfig = async (fileText: string, pass?: string): Promise<void> => {
    try {
      const plainObject = JSON.parse(fileText);
      const saveConfig: SaveConfig = plainToInstance(SaveConfig, plainObject);

      if (saveConfig.encrypted) {
        if (!pass) { toast.error('需要密码以解密私钥'); return; }
        for(let node of saveConfig.nodes) {
          const privateKey = CryptoJS.AES.decrypt(node.data.privateKey, pass).toString(CryptoJS.enc.Utf8);
          if(!privateKey) {
            toast.error('密码错误');
            return;
          }
          node.data.privateKey = privateKey;
        }
      }
      setSettings(saveConfig.settings);
      setNodes(saveConfig.nodes);
      setEdges(saveConfig.edges);
    } catch (e) {
      toast.error('加载失败: ' + e);
    }
  };

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Toaster/>
      <SettingsContext value={settings}>
        <ReactFlow
          nodes={nodes}
          edges={edges}
          onNodesChange={onNodesChange}
          onEdgesChange={onEdgesChange}
          onConnect={onConnect}
          onNodeDoubleClick={onNodeClick}
          onEdgeDoubleClick={onEdgeClick}
          nodeTypes={nodeTypes}
          deleteKeyCode={["Delete"]}
          fitView
          isValidConnection={validateConnection}
        >
          <Background />
          <Controls />
          <MiniMap 
          nodeColor={(n) => {
            if (n.type === 'input') return 'blue';
            return '#eee';
          }} 
          maskColor="rgba(0, 0, 0, 0.1)"
          position="bottom-right" // 也可以是 top-right 等
        />
        </ReactFlow>
      </SettingsContext>
      
      <div className="toolbar">
        <div className="toolbar-group">
          <div className="action-section">
            <div className="section-title">图表操作</div>
            <Toggle 
              className="toolbar-item" 
              checked = {enableTwoWay}
              onChange={checked => setEnableTwoWay(checked)} >双向连接</Toggle>

            <button className="toolbar-btn" onClick={handleAddNode} title="添加新节点">
              ➕ 添加节点
            </button>

            <button className="toolbar-btn" onClick={() => {setEditSettings(true);}} title="设置">
              📋 设置
            </button>
          </div>

          <div className="action-section">
            <div className="section-title">文件操作</div>
            <SaveLoadPanel onSave={handleSaveConfig} onLoad={handleLoadConfig} />
          </div>
        </div>
      </div>

      {editingNode && (
        <NodeEditor
          node={editingNode}
          onUpdate={handleUpdateNode}
          onClose={() => setEditingNode(undefined)}
          settings={settings}
        />
      )}

      {editingEdge && (
        <EdgeEditor
          edge={editingEdge}
          onUpdate={handleUpdateEdge}
          onClose={() => setEditingEdge(undefined)}
        />
      )}

      {editSettings && (
        <SettingsEditor
          settings={settings}
          onUpdate={settingsUpdate => {setSettings(settingsUpdate)}}
          onClose={() => setEditSettings(false)}
        />
      )}
      
    </div>
  );
}

export default function App(): ReactNode {
  return (
    <ReactFlowProvider>
      <FlowContent />
    </ReactFlowProvider>
  );
}
