import { useCallback, useEffect, useMemo, useState } from 'react';
import ReactFlow, {
  addEdge,
  applyEdgeChanges,
  applyNodeChanges,
  Background,
  Controls,
  MiniMap,
  MarkerType,
  Panel,
} from 'reactflow';
import 'reactflow/dist/style.css';

const API = '/api';

const NODE_COLORS = {
  start: '#d1fae5',
  end: '#fee2e2',
  menu: '#ede9fe',
  decision: '#fef3c7',
  process: '#dbeafe',
  input: '#cffafe',
  error: '#fee2e2',
};

const getNodeStyle = (nodeType) => ({
  background: NODE_COLORS[nodeType] || '#f3f4f6',
  border: '1px solid #374151',
  borderRadius: 8,
  minWidth: 220,
  fontSize: 12,
  padding: 8,
});

function toReactFlowNodes(flow) {
  return (flow.nodes || []).map((node, index) => {
    const position = flow.positions?.[node.id] || { x: 80 + index * 40, y: 80 + index * 20 };
    return {
      id: node.id,
      type: 'default',
      position,
      data: {
        label: node.label,
        nodeType: node.nodeType,
        description: node.description || '',
        pageRef: node.pageRef || '',
      },
      style: getNodeStyle(node.nodeType),
    };
  });
}

function toReactFlowEdges(flow) {
  return (flow.edges || []).map((edge, index) => ({
    id: edge.id || `e-${index + 1}`,
    source: edge.source,
    target: edge.target,
    label: edge.label || '',
    markerEnd: { type: MarkerType.ArrowClosed },
    animated: false,
    style: { strokeWidth: 1.5 },
  }));
}

function fromReactFlow(nodes, edges, currentFlowMeta) {
  const positions = {};
  const normalizedNodes = nodes.map((node) => {
    positions[node.id] = node.position;
    return {
      id: node.id,
      label: node.data?.label || node.id,
      nodeType: node.data?.nodeType || 'process',
      pageRef: node.data?.pageRef || '',
      description: node.data?.description || '',
    };
  });

  const normalizedEdges = edges.map((edge) => ({
    id: edge.id,
    source: edge.source,
    target: edge.target,
    label: edge.label || '',
  }));

  return {
    ...currentFlowMeta,
    nodes: normalizedNodes,
    edges: normalizedEdges,
    positions,
  };
}

export default function App() {
  const [flowMeta, setFlowMeta] = useState({
    id: 'hbb-ne-template-v1',
    name: 'HBB Flow',
    source: 'AA_NE_HBB_Call flow.pdf',
    description: '',
  });
  const [nodes, setNodes] = useState([]);
  const [edges, setEdges] = useState([]);
  const [selectedNodeId, setSelectedNodeId] = useState(null);
  const [message, setMessage] = useState('Loading flow...');
  const [runningWorkflowId, setRunningWorkflowId] = useState('');
  const [workflowStatus, setWorkflowStatus] = useState(null);
  const [conductorHealth, setConductorHealth] = useState('unknown');
  const [mockMappings, setMockMappings] = useState({});
  const [mockApiUrl, setMockApiUrl] = useState('');

  const selectedNode = useMemo(
    () => nodes.find((node) => node.id === selectedNodeId) || null,
    [nodes, selectedNodeId],
  );
  const selectedNodeMockOperation = selectedNodeId ? mockMappings[selectedNodeId] || null : null;

  const loadFlow = useCallback(async () => {
    try {
      const response = await fetch(`${API}/flow`);
      const payload = await response.json();
      if (!payload.success) throw new Error(payload.error || 'Failed to load flow');

      const flow = payload.flow;
      setFlowMeta({
        id: flow.id,
        name: flow.name,
        source: flow.source,
        description: flow.description,
      });
      setNodes(toReactFlowNodes(flow));
      setEdges(toReactFlowEdges(flow));
      setMessage(`Loaded flow: ${flow.name}`);
    } catch (error) {
      setMessage(`Load flow failed: ${error.message}`);
    }
  }, []);

  const checkConductor = useCallback(async () => {
    try {
      const response = await fetch(`${API}/conductor/health`);
      const payload = await response.json();
      setConductorHealth(payload.success ? 'healthy' : 'down');
    } catch {
      setConductorHealth('down');
    }
  }, []);

  const loadMockMappings = useCallback(async () => {
    try {
      const response = await fetch(`${API}/mock-api/mappings`);
      const payload = await response.json();
      if (!payload.success) return;
      setMockMappings(payload.mappings || {});
      setMockApiUrl(payload.mockApiUrl || '');
    } catch {
      setMockMappings({});
      setMockApiUrl('');
    }
  }, []);

  useEffect(() => {
    loadFlow();
    checkConductor();
    loadMockMappings();
  }, [loadFlow, checkConductor, loadMockMappings]);

  const onNodesChange = useCallback((changes) => {
    setNodes((current) => {
      const next = applyNodeChanges(changes, current);
      return next.map((node) => ({
        ...node,
        style: getNodeStyle(node.data?.nodeType),
      }));
    });
  }, []);

  const onEdgesChange = useCallback((changes) => {
    setEdges((current) => applyEdgeChanges(changes, current));
  }, []);

  const onConnect = useCallback((params) => {
    setEdges((current) =>
      addEdge(
        {
          ...params,
          id: `e-${Date.now()}`,
          markerEnd: { type: MarkerType.ArrowClosed },
        },
        current,
      ),
    );
  }, []);

  const saveFlow = useCallback(async () => {
    const flow = fromReactFlow(nodes, edges, flowMeta);
    try {
      const response = await fetch(`${API}/flow`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ flow }),
      });
      const payload = await response.json();
      if (!payload.success) throw new Error(payload.error || 'Failed to save flow');
      setMessage('Flow saved.');
    } catch (error) {
      setMessage(`Save failed: ${error.message}`);
    }
  }, [nodes, edges, flowMeta]);

  const exportFlowJson = useCallback(async () => {
    const flow = fromReactFlow(nodes, edges, flowMeta);
    try {
      const response = await fetch(`${API}/flow/export`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ flow }),
      });
      const payload = await response.json();
      if (!payload.success) throw new Error(payload.error || 'Export failed');

      const fileName = `${flow.name || 'flow'}-export-${Date.now()}.json`
        .replace(/\s+/g, '-')
        .toLowerCase();
      const blob = new Blob([JSON.stringify(payload.export, null, 2)], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = fileName;
      document.body.appendChild(link);
      link.click();
      link.remove();
      URL.revokeObjectURL(url);

      setMessage(`Exported JSON with ${payload.export.mockApi.apiToolCalls.length} API tool call definitions.`);
    } catch (error) {
      setMessage(`Export failed: ${error.message}`);
    }
  }, [nodes, edges, flowMeta]);

  const resetTemplate = useCallback(async () => {
    try {
      const response = await fetch(`${API}/flow/reset-template`, { method: 'POST' });
      const payload = await response.json();
      if (!payload.success) throw new Error(payload.error || 'Failed to reset template');
      await loadFlow();
      setMessage('Template reloaded from PDF model.');
    } catch (error) {
      setMessage(`Reset failed: ${error.message}`);
    }
  }, [loadFlow]);

  const deploy = useCallback(async () => {
    try {
      const flow = fromReactFlow(nodes, edges, flowMeta);
      const response = await fetch(`${API}/conductor/deploy`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ flow }),
      });
      const payload = await response.json();
      if (!payload.success) throw new Error(payload.error || 'Deploy failed');
      setMessage(`Deployed ${payload.workflowName} with ${payload.steps} steps.`);
    } catch (error) {
      setMessage(`Deploy failed: ${error.message}`);
    }
  }, [nodes, edges, flowMeta]);

  const start = useCallback(async () => {
    try {
      const response = await fetch(`${API}/conductor/start`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ callerNumber: '22700000000' }),
      });
      const payload = await response.json();
      if (!payload.success) throw new Error(payload.error || 'Start failed');
      setRunningWorkflowId(payload.workflowId);
      setMessage(`Started workflow: ${payload.workflowId}`);
    } catch (error) {
      setMessage(`Start failed: ${error.message}`);
    }
  }, []);

  const refreshStatus = useCallback(async () => {
    if (!runningWorkflowId) {
      setMessage('No running workflow id. Start one first.');
      return;
    }

    try {
      const response = await fetch(`${API}/conductor/status/${runningWorkflowId}`);
      const payload = await response.json();
      if (!payload.success) throw new Error(payload.error || 'Status fetch failed');
      setWorkflowStatus(payload);
      setMessage(`Workflow status: ${payload.status}`);
    } catch (error) {
      setMessage(`Status failed: ${error.message}`);
    }
  }, [runningWorkflowId]);

  const addNode = useCallback(() => {
    const id = `custom-${Date.now()}`;
    const x = 100 + nodes.length * 30;
    const y = 120 + nodes.length * 20;
    setNodes((current) => [
      ...current,
      {
        id,
        type: 'default',
        position: { x, y },
        data: {
          label: 'New Node',
          nodeType: 'process',
          description: '',
          pageRef: '',
        },
        style: getNodeStyle('process'),
      },
    ]);
    setSelectedNodeId(id);
  }, [nodes.length]);

  const updateSelectedNode = useCallback(
    (field, value) => {
      if (!selectedNodeId) return;
      setNodes((current) =>
        current.map((node) => {
          if (node.id !== selectedNodeId) return node;
          const next = {
            ...node,
            data: {
              ...node.data,
              [field]: value,
            },
          };
          if (field === 'nodeType') {
            next.style = getNodeStyle(value);
          }
          return next;
        }),
      );
    },
    [selectedNodeId],
  );

  return (
    <div className="app-shell">
      <header className="topbar">
        <div>
          <h1>HBB Flow Builder</h1>
          <p>No login. Fresh UI + dedicated Conductor backend.</p>
        </div>
        <div className="status-group">
          <span className={`pill ${conductorHealth}`}>Conductor: {conductorHealth}</span>
          <button onClick={checkConductor}>Check Conductor</button>
        </div>
      </header>

      <div className="workspace">
        <aside className="sidebar">
          <h2>Actions</h2>
          <button onClick={loadFlow}>Reload Flow</button>
          <button onClick={resetTemplate}>Reset From PDF Model</button>
          <button onClick={addNode}>Add Node</button>
          <button onClick={saveFlow}>Save Flow</button>
          <button onClick={exportFlowJson}>Export JSON</button>
          <button onClick={deploy}>Deploy to Conductor</button>
          <button onClick={start}>Start Workflow</button>
          <button onClick={refreshStatus}>Refresh Status</button>

          <div className="meta-block">
            <h3>Template</h3>
            <div><strong>Name:</strong> {flowMeta.name}</div>
            <div><strong>Source:</strong> {flowMeta.source}</div>
            <div><strong>Nodes:</strong> {nodes.length}</div>
            <div><strong>Edges:</strong> {edges.length}</div>
          </div>

          <div className="meta-block">
            <h3>Runtime</h3>
            <div><strong>Workflow ID:</strong> {runningWorkflowId || '-'}</div>
            <div><strong>Status:</strong> {workflowStatus?.status || '-'}</div>
            <div><strong>Steps:</strong> {workflowStatus?.tasks?.length || 0}</div>
            <div><strong>Mock API:</strong> {mockApiUrl || '-'}</div>
          </div>

          <div className="meta-block">
            <h3>Message</h3>
            <div>{message}</div>
          </div>
        </aside>

        <main className="canvas-wrap">
          <ReactFlow
            nodes={nodes}
            edges={edges}
            onNodesChange={onNodesChange}
            onEdgesChange={onEdgesChange}
            onConnect={onConnect}
            onNodeClick={(_, node) => setSelectedNodeId(node.id)}
            minZoom={0.1}
            maxZoom={1.8}
            fitView
            fitViewOptions={{ padding: 0.15, minZoom: 0.1 }}
          >
            <Background gap={24} size={1} />
            <Controls />
            <MiniMap />
            <Panel position="top-right" className="panel-note">
              Select a node to edit metadata in the right pane.
            </Panel>
          </ReactFlow>
        </main>

        <aside className="sidebar right">
          <h2>Node Editor</h2>
          {!selectedNode && <p>Select a node from the graph.</p>}
          {selectedNode && (
            <div className="form">
              <label>
                Node ID
                <input value={selectedNode.id} disabled />
              </label>
              <label>
                Label
                <input
                  value={selectedNode.data?.label || ''}
                  onChange={(e) => updateSelectedNode('label', e.target.value)}
                />
              </label>
              <label>
                Type
                <select
                  value={selectedNode.data?.nodeType || 'process'}
                  onChange={(e) => updateSelectedNode('nodeType', e.target.value)}
                >
                  <option value="start">start</option>
                  <option value="menu">menu</option>
                  <option value="decision">decision</option>
                  <option value="input">input</option>
                  <option value="process">process</option>
                  <option value="error">error</option>
                  <option value="end">end</option>
                </select>
              </label>
              <label>
                PDF Page Ref
                <input
                  value={selectedNode.data?.pageRef || ''}
                  onChange={(e) => updateSelectedNode('pageRef', e.target.value)}
                />
              </label>
              <label>
                Description
                <textarea
                  rows={8}
                  value={selectedNode.data?.description || ''}
                  onChange={(e) => updateSelectedNode('description', e.target.value)}
                />
              </label>
              <label>
                Mock API Operation
                <input value={selectedNodeMockOperation || 'No mock API mapped'} disabled />
              </label>
            </div>
          )}

          {workflowStatus?.tasks?.length > 0 && (
            <div className="task-list">
              <h3>Latest Task Status</h3>
              {workflowStatus.tasks.map((task) => (
                <div key={task.taskId} className="task-item">
                  <div><strong>{task.nodeLabel || task.refName}</strong></div>
                  <div>Status: {task.status}</div>
                  <div>Mock Op: {task.output?.mockApiResult?.operation || '-'}</div>
                  {task.output?.mockApiError && <div>Mock Error: {task.output.mockApiError}</div>}
                </div>
              ))}
            </div>
          )}
        </aside>
      </div>
    </div>
  );
}
