/**
 * useWorkspace Hook
 *
 * Central state management for the Unified AI Workspace.
 * Manages SSE streaming, messages, files, folders, data sources,
 * tool activity, voice, and file previews via the shared sseClient.
 */

import { useState, useCallback, useRef, useEffect } from 'react';
import api from '../../../api';
import { startSseStream, SseEvent } from '../../../utils/sseClient';
import type { Message, MessageDocument } from '../../../components/UiComponents/Messages/MessagesTypes';

export interface AgentProgress {
  round: number;
  maxRounds?: number;
  totalAiCalls: number;
  totalToolCalls: number;
  costCHF: number;
}

export interface ToolActivity {
  id: string;
  toolName: string;
  status: 'calling' | 'success' | 'error';
  args?: Record<string, any>;
  result?: string;
  error?: string;
  timestamp: number;
}

export interface WorkspaceFile {
  id: string;
  fileName: string;
  mimeType: string;
  fileSize: number;
  tags?: string[];
  folderId?: string;
  status?: string;
  description?: string;
  featureInstanceId?: string;
  featureInstanceLabel?: string;
}

export interface WorkspaceFolder {
  id: string;
  name: string;
  parentId?: string;
}

export interface DataSource {
  id: string;
  connectionId: string;
  sourceType: string;
  path: string;
  label: string;
}

export interface FeatureDataSource {
  id: string;
  featureInstanceId: string;
  featureCode: string;
  tableName: string;
  objectKey: string;
  label: string;
  mandateId: string;
  workspaceInstanceId: string;
}

export interface FileEditProposal {
  id: string;
  fileId: string;
  fileName: string;
  mimeType?: string;
  oldContent?: string;
  newContent?: string;
  oldSize?: number;
  newSize?: number;
  status: 'pending' | 'accepted' | 'rejected';
}

export interface DataSourceAccessEvent {
  sourceType: string;
  label: string;
  path: string;
  action: string;
}

interface UseWorkspaceReturn {
  messages: Message[];
  isProcessing: boolean;
  sendMessage: (prompt: string, fileIds?: string[], dataSourceIds?: string[], allowedProviders?: string[], featureDataSourceIds?: string[]) => void;
  stopProcessing: () => void;
  loadWorkflow: (workflowId: string) => void;
  resetToNew: () => void;
  files: WorkspaceFile[];
  folders: WorkspaceFolder[];
  dataSources: DataSource[];
  featureDataSources: FeatureDataSource[];
  refreshFeatureDataSources: () => void;
  agentProgress: AgentProgress | null;
  toolActivities: ToolActivity[];
  pendingEdits: FileEditProposal[];
  acceptEdit: (editId: string) => void;
  rejectEdit: (editId: string) => void;
  workflowId: string | null;
  workflowVersion: number;
  refreshFiles: () => void;
  refreshFolders: () => void;
  refreshDataSources: () => void;
  dataSourceAccesses: DataSourceAccessEvent[];
}

export function useWorkspace(instanceId: string): UseWorkspaceReturn {
  const [messages, setMessages] = useState<Message[]>([]);
  const [isProcessing, setIsProcessing] = useState(false);
  const [files, setFiles] = useState<WorkspaceFile[]>([]);
  const [folders, setFolders] = useState<WorkspaceFolder[]>([]);
  const [dataSources, setDataSources] = useState<DataSource[]>([]);
  const [featureDataSources, setFeatureDataSources] = useState<FeatureDataSource[]>([]);
  const [agentProgress, setAgentProgress] = useState<AgentProgress | null>(null);
  const [toolActivities, setToolActivities] = useState<ToolActivity[]>([]);
  const [pendingEdits, setPendingEdits] = useState<FileEditProposal[]>([]);
  const [workflowId, setWorkflowId] = useState<string | null>(null);
  const [workflowVersion, setWorkflowVersion] = useState(0);
  const [dataSourceAccesses, setDataSourceAccesses] = useState<DataSourceAccessEvent[]>([]);
  const cleanupRef = useRef<(() => void) | null>(null);

  const refreshFiles = useCallback(() => {
    if (!instanceId) return;
    api.get(`/api/workspace/${instanceId}/files`)
      .then(res => setFiles(res.data.files || []))
      .catch(err => console.error('Failed to load workspace files:', err));
  }, [instanceId]);

  const refreshFolders = useCallback(() => {
    if (!instanceId) return;
    api.get(`/api/workspace/${instanceId}/folders`)
      .then(res => setFolders(res.data.folders || []))
      .catch(err => console.error('Failed to load workspace folders:', err));
  }, [instanceId]);

  const refreshDataSources = useCallback(() => {
    if (!instanceId) return;
    api.get(`/api/workspace/${instanceId}/datasources`)
      .then(res => setDataSources(res.data.dataSources || []))
      .catch(() => {});
  }, [instanceId]);

  const refreshFeatureDataSources = useCallback(() => {
    if (!instanceId) return;
    api.get(`/api/workspace/${instanceId}/feature-datasources`)
      .then(res => setFeatureDataSources(res.data.featureDataSources || []))
      .catch(() => {});
  }, [instanceId]);

  useEffect(() => {
    if (!instanceId) return;
    refreshFiles();
    refreshFolders();
    refreshDataSources();
    refreshFeatureDataSources();
  }, [instanceId, refreshFiles, refreshFolders, refreshDataSources, refreshFeatureDataSources]);

  const loadWorkflow = useCallback((wfId: string) => {
    if (!instanceId || !wfId) return;
    setWorkflowId(wfId);
    setMessages([]);
    setToolActivities([]);
    setPendingEdits([]);
    setAgentProgress(null);
    setDataSourceAccesses([]);

    api.get(`/api/workspace/${instanceId}/workflows/${wfId}/messages`)
      .then(res => {
        const msgs = (res.data.messages || [])
          .map((m: any) => _mapLoadedWorkspaceMessage(m, wfId))
          .sort(_compareWorkspaceMessages);
        setMessages(msgs);
      })
      .catch(() => {});
  }, [instanceId]);

  const resetToNew = useCallback(() => {
    setWorkflowId(null);
    setMessages([]);
    setToolActivities([]);
    setPendingEdits([]);
    setAgentProgress(null);
    setDataSourceAccesses([]);
  }, []);

  const sendMessage = useCallback(
    (prompt: string, fileIds: string[] = [], dataSourceIds: string[] = [], allowedProviders: string[] = [], featureDataSourceIds: string[] = []) => {
      if (!instanceId || isProcessing) return;

      setIsProcessing(true);
      setAgentProgress(null);
      setToolActivities([]);
      setDataSourceAccesses([]);
      setMessages(prev => [
        ...prev,
        {
          id: `user-${Date.now()}`,
          workflowId: workflowId || '',
          role: 'user',
          message: prompt,
          publishedAt: Date.now() / 1000,
          documents: _documentsFromFileIds(files, fileIds),
          documentsLabel: _attachmentLabelFromContext(
            dataSourceIds,
            featureDataSourceIds,
            dataSources,
            featureDataSources,
          ),
        },
      ]);

      if (cleanupRef.current) {
        cleanupRef.current();
      }

      const baseURL = api.defaults.baseURL || '';
      const url = `${baseURL}/api/workspace/${instanceId}/start/stream`;

      const body: Record<string, any> = {
        prompt,
        fileIds,
        dataSourceIds,
        featureDataSourceIds,
        userLanguage: navigator.language?.slice(0, 2) || 'en',
      };
      if (workflowId) {
        body.workflowId = workflowId;
      }
      if (allowedProviders.length > 0) {
        body.allowedProviders = allowedProviders;
      }

      cleanupRef.current = startSseStream({
        url,
        body,
        handlers: {
          onMessage: (event) => _handleMessage(event, setMessages),
          onChunk: (event) => _handleChunk(event, setMessages),
          onStatus: (event) => _handleStatus(event, setMessages),
          onToolCall: (event) => _handleToolCall(event, setToolActivities),
          onToolResult: (event) => _handleToolResult(event, setToolActivities),
          onAgentProgress: (event) => setAgentProgress(event.item || event.data || null),
          onAgentSummary: (event) => {
            const s = event.item || event.data || {};
            setMessages(prev => [
              ...prev,
              {
                id: `summary-${Date.now()}`,
                workflowId: '',
                role: 'system',
                message: `Agent completed: ${s.rounds || '?'} rounds, ${s.totalToolCalls || 0} tool calls, ${s.costCHF || '?'} CHF`,
                publishedAt: Date.now() / 1000,
              },
            ]);
            setAgentProgress(null);
          },
          onFileEditProposal: (event) => {
            const data = event.item || event.data || {};
            if (data.id) {
              setPendingEdits(prev => [...prev, {
                id: data.id,
                fileId: data.fileId || '',
                fileName: data.fileName || '',
                mimeType: data.mimeType || '',
                oldSize: data.oldSize || 0,
                newSize: data.newSize || 0,
                status: 'pending' as const,
              }]);
            }
          },
          onFileVersion: (event) => {
            const data = event.item || event.data || {};
            if (data.fileId) {
              setPendingEdits(prev =>
                prev.map(e =>
                  e.fileId === data.fileId
                    ? { ...e, status: 'accepted' as const }
                    : e,
                ),
              );
              refreshFiles();
            }
          },
          onFileCreated: (event) => {
            refreshFiles();
            const data = event.item || event.data || {};
            if (data.fileId && data.fileName) {
              setMessages(prev => [
                ...prev,
                {
                  id: `file-${data.fileId}-${Date.now()}`,
                  workflowId: '',
                  role: 'assistant',
                  message: '',
                  publishedAt: Date.now() / 1000,
                  documents: [{
                    id: data.fileId,
                    messageId: '',
                    fileId: data.fileId,
                    fileName: data.fileName,
                    mimeType: data.mimeType || 'application/octet-stream',
                    fileSize: data.fileSize || 0,
                    roundNumber: 0,
                    taskNumber: 0,
                    actionNumber: 0,
                    actionId: '',
                  }],
                },
              ]);
            }
          },
          onDataSourceAccess: (event) => {
            const data = event.item || event.data || {};
            setDataSourceAccesses(prev => [...prev, {
              sourceType: data.sourceType || '',
              label: data.label || '',
              path: data.path || '',
              action: data.action || 'access',
            }]);
          },
          onVoiceResponse: (event) => {
            const audioUrl = _buildAudioUrl(event);
            if (audioUrl) {
              const lang = event.item?.language || event.data?.language || '';
              const charCount = event.item?.charCount || event.data?.charCount || 0;
              setMessages(prev => [
                ...prev,
                {
                  id: `voice-${Date.now()}`,
                  role: 'assistant',
                  message: '',
                  _audioUrl: audioUrl,
                  _audioLang: lang,
                  _audioCharCount: charCount,
                } as any,
              ]);
            }
          },
          onWorkflowUpdated: (event) => {
            if (event.workflowId) setWorkflowId(event.workflowId);
            setWorkflowVersion(v => v + 1);
          },
          onComplete: (event) => {
            setIsProcessing(false);
            if (event.workflowId) setWorkflowId(event.workflowId);
          },
          onStopped: () => setIsProcessing(false),
          onError: (event) => {
            setIsProcessing(false);
            const item = event.item as Record<string, unknown> | undefined;
            let msg = event.content || 'Unknown error';
            if (item && item.error === 'INSUFFICIENT_BALANCE') {
              const preferDe =
                typeof navigator !== 'undefined' && navigator.language?.toLowerCase().startsWith('de');
              const de = typeof item.messageDe === 'string' ? item.messageDe : '';
              const en = typeof item.messageEn === 'string' ? item.messageEn : '';
              msg = preferDe ? de || en || msg : en || de || msg;
              if (item.userAction === 'TOP_UP_SELF' && typeof item.billingUiPath === 'string') {
                msg += `\n\n→ ${item.billingUiPath}`;
              }
            } else {
              msg = `Error: ${msg}`;
            }
            setMessages(prev => [
              ...prev,
              {
                id: `error-${Date.now()}`,
                workflowId: '',
                role: 'system',
                message: msg,
                publishedAt: Date.now() / 1000,
              },
            ]);
          },
        },
        onConnectionError: (err) => {
          setIsProcessing(false);
          setMessages(prev => [
            ...prev,
            {
              id: `error-${Date.now()}`,
              workflowId: '',
              role: 'system',
              message: `Connection error: ${err.message}`,
              publishedAt: Date.now() / 1000,
            },
          ]);
        },
        onStreamEnd: () => setIsProcessing(false),
      });
    },
    [
      instanceId,
      isProcessing,
      workflowId,
      refreshFiles,
      files,
      dataSources,
      featureDataSources,
    ],
  );

  const stopProcessing = useCallback(() => {
    if (cleanupRef.current) {
      cleanupRef.current();
      cleanupRef.current = null;
    }
    if (instanceId && workflowId) {
      api.post(`/api/workspace/${instanceId}/${workflowId}/stop`).catch(console.error);
    }
    setIsProcessing(false);
  }, [instanceId, workflowId]);

  const acceptEdit = useCallback(
    (editId: string) => {
      if (!instanceId) return;
      setPendingEdits(prev =>
        prev.map(e => (e.id === editId ? { ...e, status: 'accepted' as const } : e)),
      );
      api.post(`/api/workspace/${instanceId}/edit/${editId}/accept`)
        .then(() => refreshFiles())
        .catch(err => {
          console.error('Failed to accept edit:', err);
          setPendingEdits(prev =>
            prev.map(e => (e.id === editId ? { ...e, status: 'pending' as const } : e)),
          );
        });
    },
    [instanceId, refreshFiles],
  );

  const rejectEdit = useCallback(
    (editId: string) => {
      if (!instanceId) return;
      setPendingEdits(prev =>
        prev.map(e => (e.id === editId ? { ...e, status: 'rejected' as const } : e)),
      );
      api.post(`/api/workspace/${instanceId}/edit/${editId}/reject`)
        .catch(err => {
          console.error('Failed to reject edit:', err);
          setPendingEdits(prev =>
            prev.map(e => (e.id === editId ? { ...e, status: 'pending' as const } : e)),
          );
        });
    },
    [instanceId],
  );

  return {
    messages,
    isProcessing,
    sendMessage,
    stopProcessing,
    loadWorkflow,
    resetToNew,
    files,
    folders,
    dataSources,
    featureDataSources,
    refreshFeatureDataSources,
    agentProgress,
    toolActivities,
    pendingEdits,
    acceptEdit,
    rejectEdit,
    workflowId,
    workflowVersion,
    refreshFiles,
    refreshFolders,
    refreshDataSources,
    dataSourceAccesses,
  };
}

// ---------------------------------------------------------------------------
// Internal: loaded message mapping & attachment display
// ---------------------------------------------------------------------------

function _mapLoadedWorkspaceMessage(m: Record<string, unknown>, wfId: string): Message {
  const publishedAt =
    (typeof m.publishedAt === 'number' ? m.publishedAt : undefined) ??
    (typeof m.createdAt === 'number' ? m.createdAt : undefined) ??
    Date.now() / 1000;
  const docsRaw = Array.isArray(m.documents) ? m.documents : [];
  const documents: MessageDocument[] = docsRaw.map((d: any) => ({
    id: String(d.id || `doc-${d.fileId}`),
    messageId: String(d.messageId || ''),
    fileId: String(d.fileId || ''),
    fileName: String(d.fileName || ''),
    mimeType: String(d.mimeType || 'application/octet-stream'),
    fileSize: Number(d.fileSize || 0),
    roundNumber: Number(d.roundNumber ?? 0),
    taskNumber: Number(d.taskNumber ?? 0),
    actionNumber: Number(d.actionNumber ?? 0),
    actionId: String(d.actionId || ''),
  }));
  return {
    id: String(m.id || `loaded-${Math.random()}`),
    workflowId: wfId,
    role: String(m.role || 'assistant'),
    message: String(m.content ?? m.message ?? ''),
    publishedAt,
    sequenceNr: typeof m.sequenceNr === 'number' ? m.sequenceNr : undefined,
    documents: documents.length ? documents : undefined,
    documentsLabel: typeof m.documentsLabel === 'string' ? m.documentsLabel : undefined,
  };
}

function _compareWorkspaceMessages(a: Message, b: Message): number {
  const ta = (a.publishedAt || 0) - (b.publishedAt || 0);
  if (ta !== 0) return ta;
  const sa = (a.sequenceNr ?? 0) - (b.sequenceNr ?? 0);
  if (sa !== 0) return sa;
  return String(a.id).localeCompare(String(b.id));
}

function _documentsFromFileIds(files: WorkspaceFile[], fileIds: string[]): MessageDocument[] | undefined {
  const out: MessageDocument[] = [];
  for (const fid of fileIds) {
    const f = files.find(x => x.id === fid);
    if (f) {
      out.push({
        id: `local-${fid}-${Date.now()}`,
        messageId: '',
        fileId: f.id,
        fileName: f.fileName,
        mimeType: f.mimeType,
        fileSize: f.fileSize,
        roundNumber: 0,
        taskNumber: 0,
        actionNumber: 0,
        actionId: '',
      });
    }
  }
  return out.length ? out : undefined;
}

function _attachmentLabelFromContext(
  dataSourceIds: string[],
  featureDataSourceIds: string[],
  dataSources: DataSource[],
  featureDataSources: FeatureDataSource[],
): string | undefined {
  const parts: string[] = [];
  const dsLabels = dataSourceIds
    .map(id => {
      const ds = dataSources.find(d => d.id === id);
      return ds?.label || ds?.path;
    })
    .filter((x): x is string => Boolean(x));
  if (dsLabels.length) parts.push(`Datenquellen: ${dsLabels.join(', ')}`);
  const fdsLabels = featureDataSourceIds
    .map(id => {
      const fds = featureDataSources.find(x => x.id === id);
      return fds ? `${fds.tableName} (${fds.label})` : '';
    })
    .filter(Boolean);
  if (fdsLabels.length) parts.push(`Feature-Daten: ${fdsLabels.join(', ')}`);
  return parts.length ? parts.join(' | ') : undefined;
}

// ---------------------------------------------------------------------------
// Internal event handlers
// ---------------------------------------------------------------------------

function _handleMessage(
  event: SseEvent,
  setMessages: React.Dispatch<React.SetStateAction<Message[]>>,
) {
  const item = event.item || {};
  setMessages(prev => [
    ...prev,
    {
      id: item.id || `msg-${Date.now()}-${Math.random()}`,
      workflowId: item.workflowId || '',
      role: item.role || 'assistant',
      message: item.content || event.content || '',
      publishedAt: item.createdAt || Date.now() / 1000,
      documents: item.documents,
    },
  ]);
}

function _handleChunk(
  event: SseEvent,
  setMessages: React.Dispatch<React.SetStateAction<Message[]>>,
) {
  const chunkText = event.content || '';
  if (!chunkText) return;
  setMessages(prev => {
    const lastMsg = prev[prev.length - 1];
    if (lastMsg && lastMsg.role === 'assistant' && lastMsg.id?.startsWith('stream-')) {
      return [
        ...prev.slice(0, -1),
        { ...lastMsg, message: lastMsg.message + chunkText },
      ];
    }
    return [
      ...prev,
      {
        id: `stream-${Date.now()}`,
        workflowId: '',
        role: 'assistant',
        message: chunkText,
        publishedAt: Date.now() / 1000,
      },
    ];
  });
}

function _handleStatus(
  event: SseEvent,
  setMessages: React.Dispatch<React.SetStateAction<Message[]>>,
) {
  setMessages(prev => {
    const lastIsStatus = prev.length > 0 && prev[prev.length - 1].role === 'status';
    const statusMsg: Message = {
      id: `status-${Date.now()}`,
      workflowId: '',
      role: 'status',
      message: event.label || event.content || '',
      publishedAt: Date.now() / 1000,
    };
    return lastIsStatus ? [...prev.slice(0, -1), statusMsg] : [...prev, statusMsg];
  });
}

function _handleToolCall(
  event: SseEvent,
  setToolActivities: React.Dispatch<React.SetStateAction<ToolActivity[]>>,
) {
  const data = event.item || event.data || {};
  setToolActivities(prev => [
    ...prev,
    {
      id: `tc-${Date.now()}-${Math.random()}`,
      toolName: data.toolName || 'unknown',
      status: 'calling',
      args: data.args,
      timestamp: Date.now(),
    },
  ]);
}

function _handleToolResult(
  event: SseEvent,
  setToolActivities: React.Dispatch<React.SetStateAction<ToolActivity[]>>,
) {
  const data = event.item || event.data || {};
  setToolActivities(prev => {
    const idx = [...prev].reverse().findIndex(t => t.toolName === data.toolName && t.status === 'calling');
    if (idx >= 0) {
      const realIdx = prev.length - 1 - idx;
      const updated = [...prev];
      updated[realIdx] = {
        ...updated[realIdx],
        status: data.success ? 'success' : 'error',
        result: data.data,
        error: data.error,
      };
      return updated;
    }
    return [
      ...prev,
      {
        id: `tr-${Date.now()}-${Math.random()}`,
        toolName: data.toolName || 'unknown',
        status: data.success ? 'success' : 'error',
        result: data.data,
        error: data.error,
        timestamp: Date.now(),
      },
    ];
  });
}

function _buildAudioUrl(event: SseEvent): string | null {
  const audioData = event.item?.audio || event.data?.audio;
  if (!audioData) return null;

  try {
    const byteChars = atob(audioData);
    const byteArray = new Uint8Array(byteChars.length);
    for (let i = 0; i < byteChars.length; i++) {
      byteArray[i] = byteChars.charCodeAt(i);
    }
    const blob = new Blob([byteArray], { type: 'audio/mp3' });
    return URL.createObjectURL(blob);
  } catch (err) {
    console.error('Failed to decode voice response:', err);
    return null;
  }
}
