/**
 * Automation2FlowEditor
 *
 * n8n-style flow builder with backend-driven node list.
 * Workflow configuration (gear): primary start kind + invocations; canvas start node stays in sync.
 */

import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { FaSpinner } from 'react-icons/fa';
import { useApiRequest } from '../../../hooks/useApi';
import {
  fetchNodeTypes,
  executeGraph,
  fetchWorkflows,
  fetchWorkflow,
  createWorkflow,
  updateWorkflow,
  type NodeType,
  type NodeTypeCategory,
  type Automation2Graph,
  type Automation2Workflow,
  type ExecuteGraphResponse,
  type WorkflowEntryPoint,
} from '../../../api/automation2Api';
import { FlowCanvas, type CanvasNode, type CanvasConnection } from './FlowCanvas';
import { NodeConfigPanel } from './NodeConfigPanel';
import { NodeSidebar } from './NodeSidebar';
import { CanvasHeader } from './CanvasHeader';
import { WorkflowConfigurationModal } from './WorkflowConfigurationModal';
import { getCategoryIcon } from '../nodes/shared/utils';
import { fromApiGraph, toApiGraph } from '../nodes/shared/graphUtils';
import {
  syncCanvasStartNode,
  buildInvocationsForPrimaryKind,
} from '../nodes/runtime/workflowStartSync';
import { buildNodeOutputsPreview } from '../nodes/shared/outputPreviewRegistry';
import { Automation2DataFlowProvider } from '../context/Automation2DataFlowContext';
import { usePrompt } from '../../../hooks/usePrompt';
import styles from './Automation2FlowEditor.module.css';

const LOG = '[Automation2]';

const DEFAULT_INVOCATIONS = (): WorkflowEntryPoint[] =>
  buildInvocationsForPrimaryKind('manual', [], 'Jetzt ausführen');

interface Automation2FlowEditorProps {
  instanceId: string;
  language?: string;
  /** When set, load this workflow on mount (e.g. from workflows list edit) */
  initialWorkflowId?: string | null;
}

export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({
  instanceId,
  language = 'de',
  initialWorkflowId,
}) => {
  const { request } = useApiRequest();
  const { prompt: promptInput, PromptDialog } = usePrompt();
  const [nodeTypes, setNodeTypes] = useState<NodeType[]>([]);
  const [categories, setCategories] = useState<NodeTypeCategory[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [filter, setFilter] = useState('');
  const [expandedCategories, setExpandedCategories] = useState<Set<string>>(
    new Set(['trigger', 'input', 'flow', 'data', 'ai', 'email', 'sharepoint', 'clickup'])
  );
  const [canvasNodes, setCanvasNodes] = useState<CanvasNode[]>([]);
  const [canvasConnections, setCanvasConnections] = useState<CanvasConnection[]>([]);
  const [executing, setExecuting] = useState(false);
  const [executeResult, setExecuteResult] = useState<ExecuteGraphResponse | null>(null);
  const [workflows, setWorkflows] = useState<Automation2Workflow[]>([]);
  const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null);
  const [selectedNode, setSelectedNode] = useState<CanvasNode | null>(null);
  const [saving, setSaving] = useState(false);
  const [invocations, setInvocations] = useState<WorkflowEntryPoint[]>(DEFAULT_INVOCATIONS);
  const [workflowSettingsOpen, setWorkflowSettingsOpen] = useState(false);

  const sidebarExcludedCategories = useMemo(() => new Set(['trigger']), []);

  const nodeOutputsPreview = useMemo(
    () =>
      buildNodeOutputsPreview(canvasNodes, executeResult?.nodeOutputs as Record<string, unknown> | undefined),
    [canvasNodes, executeResult?.nodeOutputs]
  );

  const applyGraphWithSync = useCallback(
    (graph: Automation2Graph | null | undefined, wfInvocations: WorkflowEntryPoint[] | undefined) => {
      const inv = wfInvocations?.length ? wfInvocations : DEFAULT_INVOCATIONS();
      setInvocations(inv);
      if (!graph?.nodes?.length) {
        const synced = syncCanvasStartNode([], [], inv, nodeTypes, language);
        setCanvasNodes(synced.nodes);
        setCanvasConnections(synced.connections);
        return;
      }
      const { nodes, connections } = fromApiGraph(graph, nodeTypes);
      const synced = syncCanvasStartNode(nodes, connections, inv, nodeTypes, language);
      setCanvasNodes(synced.nodes);
      setCanvasConnections(synced.connections);
    },
    [nodeTypes, language]
  );

  const handleFromApiGraph = useCallback(
    (graph: Automation2Graph, wfInvocations?: WorkflowEntryPoint[]) => {
      applyGraphWithSync(graph, wfInvocations);
    },
    [applyGraphWithSync]
  );

  const handleExecute = useCallback(async () => {
    const graph = toApiGraph(canvasNodes, canvasConnections);
    if (graph.nodes.length === 0) {
      setExecuteResult({ success: false, error: 'Keine Nodes im Workflow.' });
      return;
    }
    setExecuting(true);
    setExecuteResult(null);
    try {
      const ep = currentWorkflowId ? invocations[0]?.id : undefined;
      const result = await executeGraph(request, instanceId, graph, currentWorkflowId ?? undefined, {
        ...(ep ? { entryPointId: ep } : {}),
      });
      setExecuteResult(result);
    } catch (err: unknown) {
      setExecuteResult({ success: false, error: err instanceof Error ? err.message : String(err) });
    } finally {
      setExecuting(false);
    }
  }, [request, instanceId, canvasNodes, canvasConnections, currentWorkflowId, invocations]);

  const handleSave = useCallback(async () => {
    const graph = toApiGraph(canvasNodes, canvasConnections);
    if (graph.nodes.length === 0) {
      setExecuteResult({ success: false, error: 'Keine Nodes zum Speichern.' });
      return;
    }
    setSaving(true);
    try {
      if (currentWorkflowId) {
        await updateWorkflow(request, instanceId, currentWorkflowId, { graph, invocations });
        setExecuteResult({ success: true } as ExecuteGraphResponse);
      } else {
        const label = await promptInput('Workflow-Name:', {
          title: 'Workflow speichern',
          defaultValue: 'Neuer Workflow',
          placeholder: 'Name des Workflows',
        });
        if (!label) {
          setSaving(false);
          return;
        }
        const created = await createWorkflow(request, instanceId, {
          label: label.trim() || 'Neuer Workflow',
          graph,
          invocations,
        });
        setCurrentWorkflowId(created.id);
        if (created.invocations?.length) setInvocations(created.invocations);
        setWorkflows((prev) => [...prev, created]);
        setExecuteResult({ success: true } as ExecuteGraphResponse);
      }
    } catch (err: unknown) {
      setExecuteResult({ success: false, error: err instanceof Error ? err.message : String(err) });
    } finally {
      setSaving(false);
    }
  }, [request, instanceId, canvasNodes, canvasConnections, currentWorkflowId, promptInput, invocations]);

  const handleLoad = useCallback(
    async (workflowId: string) => {
      try {
        const wf = await fetchWorkflow(request, instanceId, workflowId);
        if (wf.graph) {
          handleFromApiGraph(wf.graph, wf.invocations);
        } else {
          applyGraphWithSync({ nodes: [], connections: [] }, wf.invocations);
        }
      } catch (err: unknown) {
        setExecuteResult({
          success: false,
          error: err instanceof Error ? err.message : String(err),
        });
      }
    },
    [request, instanceId, handleFromApiGraph, applyGraphWithSync]
  );

  const handleWorkflowSelect = useCallback(
    (workflowId: string | null) => {
      setCurrentWorkflowId(workflowId);
      if (workflowId) handleLoad(workflowId);
      else {
        setExecuteResult(null);
        applyGraphWithSync({ nodes: [], connections: [] }, DEFAULT_INVOCATIONS());
      }
    },
    [handleLoad, applyGraphWithSync]
  );

  const handleNew = useCallback(() => {
    setCurrentWorkflowId(null);
    setExecuteResult(null);
    applyGraphWithSync({ nodes: [], connections: [] }, DEFAULT_INVOCATIONS());
  }, [applyGraphWithSync]);

  const handleNodeParametersChange = useCallback((nodeId: string, parameters: Record<string, unknown>) => {
    setCanvasNodes((prev) =>
      prev.map((n) => {
        if (n.id !== nodeId) return n;
        const next = { ...n, parameters };
        if (n.type === 'flow.switch' && 'cases' in parameters) {
          const cases = (parameters.cases as unknown[]) ?? [];
          next.outputs = Math.max(1, cases.length);
        }
        return next;
      })
    );
  }, []);

  const handleMergeNodeParameters = useCallback((nodeId: string, patch: Record<string, unknown>) => {
    setCanvasNodes((prev) =>
      prev.map((n) => {
        if (n.id !== nodeId) return n;
        const merged = { ...(n.parameters ?? {}), ...patch };
        const next = { ...n, parameters: merged };
        if (n.type === 'flow.switch' && 'cases' in merged) {
          const cases = (merged.cases as unknown[]) ?? [];
          next.outputs = Math.max(1, cases.length);
        }
        return next;
      })
    );
  }, []);

  const handleNodeUpdate = useCallback(
    (nodeId: string, updates: Partial<Pick<CanvasNode, 'title' | 'comment'>>) => {
      setCanvasNodes((prev) =>
        prev.map((n) => (n.id === nodeId ? { ...n, ...updates } : n))
      );
    },
    []
  );

  const handleApplyWorkflowConfiguration = useCallback(
    (next: WorkflowEntryPoint[]) => {
      setInvocations(next);
      setCanvasNodes((nodes) => {
        const r = syncCanvasStartNode(nodes, canvasConnections, next, nodeTypes, language);
        setCanvasConnections(r.connections);
        return r.nodes;
      });
    },
    [canvasConnections, nodeTypes, language]
  );

  const loadNodeTypes = useCallback(async () => {
    if (!instanceId) return;
    setLoading(true);
    setError(null);
    try {
      const data = await fetchNodeTypes(request, instanceId, language);
      setNodeTypes(data.nodeTypes);
      setCategories(data.categories);
    } catch (err: unknown) {
      setError(err instanceof Error ? err.message : String(err));
      setNodeTypes([]);
      setCategories([]);
    } finally {
      setLoading(false);
    }
  }, [instanceId, language, request]);

  const loadWorkflows = useCallback(async () => {
    if (!instanceId) return;
    try {
      const items = await fetchWorkflows(request, instanceId);
      setWorkflows(items);
    } catch (e) {
      console.error(`${LOG} loadWorkflows failed`, e);
    }
  }, [instanceId, request]);

  useEffect(() => {
    loadNodeTypes();
  }, [loadNodeTypes]);

  useEffect(() => {
    loadWorkflows();
  }, [loadWorkflows]);

  useEffect(() => {
    if (initialWorkflowId && workflows.length > 0 && !currentWorkflowId && nodeTypes.length > 0) {
      handleWorkflowSelect(initialWorkflowId);
    }
  }, [initialWorkflowId, workflows, currentWorkflowId, handleWorkflowSelect, nodeTypes.length]);

  useEffect(() => {
    if (loading || nodeTypes.length === 0) return;
    if (currentWorkflowId || initialWorkflowId) return;
    if (canvasNodes.length > 0) return;
    applyGraphWithSync({ nodes: [], connections: [] }, DEFAULT_INVOCATIONS());
  }, [
    loading,
    nodeTypes.length,
    currentWorkflowId,
    initialWorkflowId,
    canvasNodes.length,
    applyGraphWithSync,
  ]);

  const toggleCategory = useCallback((id: string) => {
    setExpandedCategories((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }, []);

  const handleDropNodeType = useCallback(
    (nodeTypeId: string, x: number, y: number) => {
      if (nodeTypeId.startsWith('trigger.')) return;
      const nt = nodeTypes.find((n) => n.id === nodeTypeId);
      if (!nt) return;
      const id = `n_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
      const label =
        typeof nt.label === 'string' ? nt.label : (nt.label as Record<string, string>)?.[language] ?? nt.id;
      setCanvasNodes((prev) => [
        ...prev,
        {
          id,
          type: nodeTypeId,
          x,
          y,
          label,
          title: label,
          color: nt.meta?.color,
          inputs: nt.inputs ?? 1,
          outputs: nt.outputs ?? 1,
          parameters: {},
        },
      ]);
    },
    [nodeTypes, language]
  );

  const renderSidebar = () => {
    if (loading) {
      return (
        <div className={styles.sidebar}>
          <div className={styles.sidebarHeader}>
            <h3 className={styles.sidebarTitle}>Nodes</h3>
          </div>
          <div className={styles.loading}>
            <FaSpinner className={styles.spinner} style={{ marginBottom: '0.5rem' }} />
            <p>Lade Node-Typen...</p>
          </div>
        </div>
      );
    }
    if (error) {
      return (
        <div className={styles.sidebar}>
          <div className={styles.sidebarHeader}>
            <h3 className={styles.sidebarTitle}>Nodes</h3>
          </div>
          <div className={styles.error}>
            <p>{error}</p>
            <button className={styles.retryButton} onClick={loadNodeTypes}>
              Erneut versuchen
            </button>
          </div>
        </div>
      );
    }
    return (
      <NodeSidebar
        nodeTypes={nodeTypes}
        categories={categories}
        filter={filter}
        onFilterChange={setFilter}
        language={language}
        expandedCategories={expandedCategories}
        onToggleCategory={toggleCategory}
        excludedCategories={sidebarExcludedCategories}
      />
    );
  };

  const configurableSelected =
    selectedNode &&
    ['input.', 'ai.', 'email.', 'sharepoint.', 'clickup.', 'trigger.', 'flow.', 'file.'].some((p) =>
      selectedNode.type.startsWith(p)
    );

  return (
    <div className={styles.container}>
      {renderSidebar()}

      <div className={styles.canvas}>
        <CanvasHeader
          workflows={workflows}
          currentWorkflowId={currentWorkflowId}
          onWorkflowSelect={handleWorkflowSelect}
          onNew={handleNew}
          onSave={handleSave}
          onExecute={handleExecute}
          onWorkflowSettings={() => setWorkflowSettingsOpen(true)}
          saving={saving}
          executing={executing}
          hasNodes={canvasNodes.length > 0}
          executeResult={executeResult}
        />
        <div className={styles.canvasArea} style={{ display: 'flex', flex: 1, minWidth: 0 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <FlowCanvas
              nodes={canvasNodes}
              connections={canvasConnections}
              nodeTypes={nodeTypes}
              onNodesChange={setCanvasNodes}
              onConnectionsChange={setCanvasConnections}
              onDropNodeType={handleDropNodeType}
              getLabel={(node) => node.title ?? node.label ?? node.type}
              getCategoryIcon={getCategoryIcon}
              onSelectionChange={setSelectedNode}
            />
          </div>
          {configurableSelected && selectedNode && (
            <Automation2DataFlowProvider
              node={selectedNode}
              nodes={canvasNodes}
              connections={canvasConnections}
              nodeOutputsPreview={nodeOutputsPreview}
              nodeTypes={nodeTypes}
              language={language}
            >
              <NodeConfigPanel
                node={selectedNode}
                nodeType={nodeTypes.find((nt) => nt.id === selectedNode.type)}
                language={language}
                onParametersChange={handleNodeParametersChange}
                onMergeNodeParameters={handleMergeNodeParameters}
                onNodeUpdate={handleNodeUpdate}
                instanceId={instanceId}
                request={request}
              />
            </Automation2DataFlowProvider>
          )}
        </div>
      </div>
      <PromptDialog />
      <WorkflowConfigurationModal
        open={workflowSettingsOpen}
        onClose={() => setWorkflowSettingsOpen(false)}
        invocations={invocations}
        onApply={handleApplyWorkflowConfiguration}
      />
    </div>
  );
};

export default Automation2FlowEditor;
