/**
 * Workflow API (GraphicalEditor)
 * Node types and graph execution for n8n-style flows.
 */

import type { ApiRequestOptions } from '../hooks/useApi';

const LOG = '[Workflow]';

// ============================================================================
// TYPES
// ============================================================================

export interface NodeTypeParameter {
  name: string;
  type: string;
  required?: boolean;
  description?: string;
  default?: unknown;
  frontendType?: string;
  frontendOptions?: Record<string, unknown>;
  options?: unknown[];
  validation?: Record<string, unknown>;
}

export interface PortField {
  name: string;
  type: string;
  description: Record<string, string>;
  required: boolean;
}

export interface PortSchema {
  name: string;
  fields: PortField[];
}

export interface InputPortDef {
  accepts: string[];
}

export interface OutputPortDef {
  schema: string;
  dynamic?: boolean;
  deriveFrom?: string;
}

export interface NodeType {
  id: string;
  category: string;
  label: string;
  description: string;
  parameters: NodeTypeParameter[];
  inputs: number;
  outputs: number;
  outputLabels?: string[];
  executor: string;
  inputPorts?: Record<number, InputPortDef>;
  outputPorts?: Record<number, OutputPortDef>;
  meta?: {
    icon?: string;
    color?: string;
    method?: string;
    action?: string;
  };
}

export interface NodeTypeCategory {
  id: string;
  label: Record<string, string> | string;
}

export interface SystemVariable {
  type: string;
  description: string;
}

export interface NodeTypesResponse {
  nodeTypes: NodeType[];
  categories: NodeTypeCategory[];
  portTypeCatalog?: Record<string, PortSchema>;
  systemVariables?: Record<string, SystemVariable>;
}

export interface Automation2GraphNode {
  id: string;
  type: string;
  parameters?: Record<string, unknown>;
  inputPorts?: Array<{ name: string; schema: string; accepts?: string[] }>;
  outputPorts?: Array<{ name: string; schema: string }>;
}

export interface Automation2Connection {
  source: string;
  target: string;
  sourceOutput?: number;
  targetInput?: number;
}

export interface Automation2Graph {
  nodes: Automation2GraphNode[];
  connections: Automation2Connection[];
}

export interface ExecuteGraphResponse {
  success: boolean;
  nodeOutputs?: Record<string, unknown>;
  error?: string;
  stopped?: boolean;
  failedNode?: string;
  paused?: boolean;
  taskId?: string;
  runId?: string;
  nodeId?: string;
}

/** Entry point / start configured outside the canvas (manual, form, schedule, …) */
export interface WorkflowEntryPoint {
  id: string;
  kind: string;
  category: 'on_demand' | 'always_on';
  enabled: boolean;
  title: Record<string, string> | string;
  description?: Record<string, string>;
  config: Record<string, unknown>;
}

export interface Automation2Workflow {
  id: string;
  label: string;
  graph: Automation2Graph;
  active?: boolean;
  /** Entry points (Starts) — how this workflow may be invoked */
  invocations?: WorkflowEntryPoint[];
  /** Enriched: run count */
  runCount?: number;
  /** Enriched: has active (running/paused) run */
  isRunning?: boolean;
  /** Enriched: status of active run */
  runStatus?: string;
  /** Enriched: nodeId where workflow is stuck (paused) */
  stuckAtNodeId?: string;
  /** Enriched: human-readable label for stuck node */
  stuckAtNodeLabel?: string;
  /** Enriched: created timestamp (seconds) */
  createdAt?: number;
  /** Enriched: last run started timestamp (seconds) */
  lastStartedAt?: number;
}

// ============================================================================
// AUTO-PREFIX TYPES (Greenfield)
// ============================================================================

export type AutoWorkflowStatus = 'draft' | 'published' | 'archived';
export type AutoRunStatus = 'running' | 'paused' | 'completed' | 'failed' | 'cancelled';
export type AutoStepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
export type AutoTaskStatus = 'pending' | 'completed' | 'cancelled' | 'expired';
export type AutoTemplateScope = 'user' | 'instance' | 'mandate' | 'system';

export interface AutoVersion {
  id: string;
  workflowId: string;
  versionNumber: number;
  status: AutoWorkflowStatus;
  graph: Automation2Graph;
  invocations?: WorkflowEntryPoint[];
  publishedAt?: number;
  publishedBy?: string;
}

export interface AutoRun {
  id: string;
  workflowId: string;
  versionId?: string;
  status: AutoRunStatus;
  trigger?: Record<string, unknown>;
  startedAt?: number;
  completedAt?: number;
  nodeOutputs?: Record<string, unknown>;
  currentNodeId?: string;
  resumeContext?: Record<string, unknown>;
  error?: string;
  costTokens?: number;
  costCredits?: number;
}

export interface AutoWorkflow {
  id: string;
  mandateId: string;
  featureInstanceId: string;
  label: string;
  description?: string;
  tags?: string[];
  isTemplate: boolean;
  templateSourceId?: string;
  templateScope?: AutoTemplateScope;
  sharedReadOnly?: boolean;
  currentVersionId?: string;
  active: boolean;
  eventId?: string;
  notifyOnFailure?: boolean;
  graph: Automation2Graph;
  invocations?: WorkflowEntryPoint[];
  sysCreatedBy?: string;
  sysCreatedAt?: number;
  sysModifiedBy?: string;
  sysModifiedAt?: number;
}

export interface AutoTask {
  id: string;
  runId: string;
  workflowId: string;
  nodeId: string;
  nodeType: string;
  config: Record<string, unknown>;
  assigneeId?: string;
  status: AutoTaskStatus;
  result?: Record<string, unknown>;
  expiresAt?: number;
  sysCreatedAt?: number;
}

export interface AutoStepLog {
  id: string;
  runId: string;
  nodeId: string;
  nodeType: string;
  status: AutoStepStatus;
  inputSnapshot?: Record<string, unknown>;
  output?: Record<string, unknown>;
  error?: string;
  startedAt?: number;
  completedAt?: number;
  durationMs?: number;
  tokensUsed?: number;
  retryCount?: number;
}

// ============================================================================
// API FUNCTIONS
// ============================================================================

export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<any>;

/**
 * Fetch node types for the flow builder (backend-driven).
 * GET /api/workflows/{instanceId}/node-types?language=de
 */
export async function fetchNodeTypes(
  request: ApiRequestFunction,
  instanceId: string,
  language = 'de'
): Promise<NodeTypesResponse> {
  console.log(`${LOG} fetchNodeTypes: instanceId=${instanceId} language=${language}`);
  const data = await request({
    url: `/api/workflows/${instanceId}/node-types`,
    method: 'get',
    params: { language },
  });
  const nodeTypes = data?.nodeTypes ?? [];
  const categories = data?.categories ?? [];
  console.log(`${LOG} fetchNodeTypes response: ${nodeTypes.length} nodeTypes, ${categories.length} categories`);
  return { nodeTypes, categories };
}

/**
 * Execute an automation2 graph.
 * POST /api/workflows/{instanceId}/execute
 */
export interface ExecuteGraphOptions {
  /** Use a configured start on the saved workflow */
  entryPointId?: string;
  /** Full run envelope (overrides entry point mapping) */
  runEnvelope?: Record<string, unknown>;
  /** Merged into envelope.payload */
  payload?: Record<string, unknown>;
}

export async function executeGraph(
  request: ApiRequestFunction,
  instanceId: string,
  graph: Automation2Graph,
  workflowId?: string,
  options?: ExecuteGraphOptions
): Promise<ExecuteGraphResponse> {
  console.log(
    `${LOG} executeGraph request: instanceId=${instanceId} workflowId=${workflowId} nodes=${graph.nodes.length} connections=${graph.connections.length}`,
    { nodes: graph.nodes, connections: graph.connections, options }
  );
  const start = performance.now();
  try {
    const data: Record<string, unknown> = { graph, workflowId };
    if (options?.entryPointId) data.entryPointId = options.entryPointId;
    if (options?.runEnvelope) data.runEnvelope = options.runEnvelope;
    if (options?.payload && Object.keys(options.payload).length > 0) data.payload = options.payload;
    const result = await request({
      url: `/api/workflows/${instanceId}/execute`,
      method: 'post',
      data,
    });
    const ms = Math.round(performance.now() - start);
    console.log(
      `${LOG} executeGraph response (${ms}ms): success=${result?.success} error=${result?.error ?? 'none'} nodeOutputs_keys=${Object.keys(result?.nodeOutputs ?? {}).join(',')} failedNode=${result?.failedNode ?? '-'}`,
      result
    );
    return result;
  } catch (err) {
    const ms = Math.round(performance.now() - start);
    console.error(
      `${LOG} executeGraph FAILED (${ms}ms): instanceId=${instanceId}`,
      err
    );
    throw err;
  }
}

// -------------------------------------------------------------------------
// Workflows CRUD
// -------------------------------------------------------------------------

export async function fetchWorkflows(
  request: ApiRequestFunction,
  instanceId: string,
  params?: { active?: boolean; pagination?: any }
): Promise<Automation2Workflow[] | { items: Automation2Workflow[]; pagination: any }> {
  const queryParams: Record<string, any> = {};
  if (params?.active !== undefined) queryParams.active = params.active;
  if (params?.pagination) queryParams.pagination = JSON.stringify(params.pagination);
  const data = await request({
    url: `/api/workflows/${instanceId}/workflows`,
    method: 'get',
    params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
  });
  if (data?.items && data?.pagination) return data;
  return data?.workflows ?? [];
}

export async function fetchWorkflow(
  request: ApiRequestFunction,
  instanceId: string,
  workflowId: string
): Promise<Automation2Workflow> {
  return await request({
    url: `/api/workflows/${instanceId}/workflows/${workflowId}`,
    method: 'get',
  });
}

export async function createWorkflow(
  request: ApiRequestFunction,
  instanceId: string,
  body: { label: string; graph: Automation2Graph; invocations?: WorkflowEntryPoint[] }
): Promise<Automation2Workflow> {
  return await request({
    url: `/api/workflows/${instanceId}/workflows`,
    method: 'post',
    data: body,
  });
}

export async function updateWorkflow(
  request: ApiRequestFunction,
  instanceId: string,
  workflowId: string,
  body: {
    label?: string;
    graph?: Automation2Graph;
    invocations?: WorkflowEntryPoint[];
    active?: boolean;
    notifyOnFailure?: boolean;
  }
): Promise<Automation2Workflow> {
  return await request({
    url: `/api/workflows/${instanceId}/workflows/${workflowId}`,
    method: 'put',
    data: body,
  });
}

export async function deleteWorkflow(
  request: ApiRequestFunction,
  instanceId: string,
  workflowId: string
): Promise<void> {
  await request({
    url: `/api/workflows/${instanceId}/workflows/${workflowId}`,
    method: 'delete',
  });
}

/** Delete by workflow ID only (Automations dashboard / orphan rows without featureInstanceId). */
export async function deleteSystemWorkflow(
  request: ApiRequestFunction,
  workflowId: string,
): Promise<void> {
  await request({
    url: `/api/system/workflow-runs/workflows/${workflowId}`,
    method: 'delete',
  });
}

export interface Automation2Run {
  id: string;
  workflowId: string;
  status: string;
  nodeOutputs?: Record<string, unknown>;
  currentNodeId?: string;
}

export async function fetchWorkflowRuns(
  request: ApiRequestFunction,
  instanceId: string,
  workflowId: string
): Promise<Automation2Run[]> {
  const data = await request({
    url: `/api/workflows/${instanceId}/workflows/${workflowId}/runs`,
    method: 'get',
  });
  return data?.runs ?? [];
}

export interface CompletedRun extends Automation2Run {
  workflowLabel?: string;
  sysModifiedAt?: number;
  sysCreatedAt?: number;
}

export async function fetchCompletedRuns(
  request: ApiRequestFunction,
  instanceId: string,
  limit = 20
): Promise<CompletedRun[]> {
  const data = await request({
    url: `/api/workflows/${instanceId}/runs/completed`,
    method: 'get',
    params: { limit },
  });
  return data?.runs ?? [];
}

// -------------------------------------------------------------------------
// Tasks
// -------------------------------------------------------------------------

export interface Automation2Task {
  id: string;
  runId: string;
  workflowId: string;
  nodeId: string;
  nodeType: string;
  config: Record<string, unknown>;
  status: string;
  result?: Record<string, unknown>;
  /** Workflow label (enriched by API) */
  workflowLabel?: string;
  /** Unix timestamp ms (from sysCreatedAt) */
  createdAt?: number;
  /** Optional due date - configurable in future */
  dueAt?: number;
}

export async function fetchTasks(
  request: ApiRequestFunction,
  instanceId: string,
  params?: { workflowId?: string; status?: string }
): Promise<Automation2Task[]> {
  const data = await request({
    url: `/api/workflows/${instanceId}/tasks`,
    method: 'get',
    params,
  });
  return data?.tasks ?? [];
}

export async function completeTask(
  request: ApiRequestFunction,
  instanceId: string,
  taskId: string,
  result: Record<string, unknown>
): Promise<ExecuteGraphResponse> {
  return await request({
    url: `/api/workflows/${instanceId}/tasks/${taskId}/complete`,
    method: 'post',
    data: { result },
  });
}

// -------------------------------------------------------------------------
// Versions (AutoVersion Lifecycle)
// -------------------------------------------------------------------------

export async function fetchVersions(
  request: ApiRequestFunction,
  instanceId: string,
  workflowId: string
): Promise<AutoVersion[]> {
  const data = await request({
    url: `/api/workflows/${instanceId}/workflows/${workflowId}/versions`,
    method: 'get',
  });
  return data?.versions ?? [];
}

export async function createDraftVersion(
  request: ApiRequestFunction,
  instanceId: string,
  workflowId: string
): Promise<AutoVersion> {
  return await request({
    url: `/api/workflows/${instanceId}/workflows/${workflowId}/versions/draft`,
    method: 'post',
  });
}

export async function publishVersion(
  request: ApiRequestFunction,
  instanceId: string,
  versionId: string
): Promise<AutoVersion> {
  return await request({
    url: `/api/workflows/${instanceId}/versions/${versionId}/publish`,
    method: 'post',
  });
}

export async function unpublishVersion(
  request: ApiRequestFunction,
  instanceId: string,
  versionId: string
): Promise<AutoVersion> {
  return await request({
    url: `/api/workflows/${instanceId}/versions/${versionId}/unpublish`,
    method: 'post',
  });
}

export async function archiveVersion(
  request: ApiRequestFunction,
  instanceId: string,
  versionId: string
): Promise<AutoVersion> {
  return await request({
    url: `/api/workflows/${instanceId}/versions/${versionId}/archive`,
    method: 'post',
  });
}

// -------------------------------------------------------------------------
// Templates
// -------------------------------------------------------------------------

export interface AutoWorkflowTemplate extends Automation2Workflow {
  isTemplate: boolean;
  templateScope?: AutoTemplateScope;
  templateSourceId?: string;
  sharedReadOnly?: boolean;
}

export async function fetchTemplates(
  request: ApiRequestFunction,
  instanceId: string,
  scope?: AutoTemplateScope,
  pagination?: any
): Promise<AutoWorkflowTemplate[] | { items: AutoWorkflowTemplate[]; pagination: any }> {
  const queryParams: Record<string, any> = {};
  if (scope) queryParams.scope = scope;
  if (pagination) queryParams.pagination = JSON.stringify(pagination);
  const data = await request({
    url: `/api/workflows/${instanceId}/templates`,
    method: 'get',
    params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
  });
  if (data?.items && data?.pagination) return data;
  return data?.templates ?? [];
}

export async function createTemplateFromWorkflow(
  request: ApiRequestFunction,
  instanceId: string,
  workflowId: string,
  scope: AutoTemplateScope = 'user'
): Promise<AutoWorkflowTemplate> {
  return await request({
    url: `/api/workflows/${instanceId}/templates/from-workflow`,
    method: 'post',
    data: { workflowId, scope },
  });
}

export async function copyTemplate(
  request: ApiRequestFunction,
  instanceId: string,
  templateId: string
): Promise<Automation2Workflow> {
  return await request({
    url: `/api/workflows/${instanceId}/templates/${templateId}/copy`,
    method: 'post',
  });
}

export async function shareTemplate(
  request: ApiRequestFunction,
  instanceId: string,
  templateId: string,
  scope: AutoTemplateScope
): Promise<AutoWorkflowTemplate> {
  return await request({
    url: `/api/workflows/${instanceId}/templates/${templateId}/share`,
    method: 'post',
    data: { scope },
  });
}

// -------------------------------------------------------------------------
// Connections and Browse (for Email/SharePoint node config)
// -------------------------------------------------------------------------

export interface UserConnection {
  id: string;
  authority: string;
  externalUsername?: string;
  externalEmail?: string;
  status: string;
}

export async function fetchConnections(
  request: ApiRequestFunction,
  instanceId: string
): Promise<UserConnection[]> {
  const data = await request({
    url: `/api/workflows/${instanceId}/connections`,
    method: 'get',
  });
  return data?.connections ?? [];
}

export interface ConnectionService {
  service: string;
  label: string;
  icon: string;
}

export async function fetchConnectionServices(
  request: ApiRequestFunction,
  instanceId: string,
  connectionId: string
): Promise<ConnectionService[]> {
  const data = await request({
    url: `/api/workflows/${instanceId}/connections/${connectionId}/services`,
    method: 'get',
  });
  return data?.services ?? [];
}

export interface BrowseEntry {
  name: string;
  path: string;
  isFolder: boolean;
  size?: number;
  mimeType?: string;
  metadata?: Record<string, unknown>;
}

export async function fetchBrowse(
  request: ApiRequestFunction,
  instanceId: string,
  connectionId: string,
  service: string,
  path = '/'
): Promise<{ items: BrowseEntry[]; path: string; service: string }> {
  const data = await request({
    url: `/api/workflows/${instanceId}/connections/${connectionId}/browse`,
    method: 'get',
    params: { service, path },
  });
  return { items: data?.items ?? [], path: data?.path ?? path, service: data?.service ?? service };
}

/** ClickUp GET /task/{taskId} — list.id for resolving list-scoped fields when only task id is known. */
export async function fetchClickupTask(
  request: ApiRequestFunction,
  connectionId: string,
  taskId: string
): Promise<Record<string, unknown>> {
  const data = await request({
    url: `/api/clickup/${connectionId}/tasks/${encodeURIComponent(taskId)}`,
    method: 'get',
  });
  return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
}

/** ClickUp list metadata (statuses, etc.) — GET /api/clickup/{connectionId}/lists/{listId}. */
export async function fetchClickupList(
  request: ApiRequestFunction,
  connectionId: string,
  listId: string
): Promise<Record<string, unknown>> {
  const data = await request({
    url: `/api/clickup/${connectionId}/lists/${listId}`,
    method: 'get',
  });
  return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
}

/** ClickUp workspace/team (members for assignees) — GET /api/clickup/{connectionId}/teams/{teamId}. */
export async function fetchClickupTeam(
  request: ApiRequestFunction,
  connectionId: string,
  teamId: string
): Promise<Record<string, unknown>> {
  const data = await request({
    url: `/api/clickup/${connectionId}/teams/${teamId}`,
    method: 'get',
  });
  return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
}

/** ClickUp list custom fields (GET /api/clickup/{connectionId}/lists/{listId}/fields). */
export async function fetchClickupListFields(
  request: ApiRequestFunction,
  connectionId: string,
  listId: string
): Promise<{ fields?: unknown[] } & Record<string, unknown>> {
  const data = await request({
    url: `/api/clickup/${connectionId}/lists/${listId}/fields`,
    method: 'get',
  });
  return (data && typeof data === 'object' ? data : {}) as { fields?: unknown[] } & Record<string, unknown>;
}

/** ClickUp GET /list/{id}/task page (tasks in a list for relationship dropdowns). */
export interface ClickupListTaskItem {
  id?: string;
  name?: string;
}

export async function fetchClickupListTasks(
  request: ApiRequestFunction,
  connectionId: string,
  listId: string,
  options?: { page?: number; includeClosed?: boolean }
): Promise<
  { tasks?: ClickupListTaskItem[]; last_page?: boolean } & Record<string, unknown>
> {
  const data = await request({
    url: `/api/clickup/${connectionId}/lists/${listId}/tasks`,
    method: 'get',
    params: {
      page: options?.page ?? 0,
      include_closed: options?.includeClosed ?? false,
    },
  });
  return (data && typeof data === 'object' ? data : {}) as {
    tasks?: ClickupListTaskItem[];
    last_page?: boolean;
  } & Record<string, unknown>;
}

// -------------------------------------------------------------------------
// Monitoring / Metrics
// -------------------------------------------------------------------------

export interface WorkflowMetrics {
  workflowCount: number;
  activeWorkflows: number;
  totalRuns: number;
  runsByStatus: Record<string, number>;
  totalTasks: number;
  tasksByStatus: Record<string, number>;
  totalTokens: number;
  totalCredits: number;
}

export async function fetchMetrics(
  request: ApiRequestFunction,
  instanceId: string
): Promise<WorkflowMetrics> {
  return await request({
    url: `/api/workflows/${instanceId}/metrics`,
    method: 'get',
  });
}

/** Paginated tasks in a list — for ClickUp relationship dropdowns and input.form „ClickUp-Aufgabe“. */
export async function loadClickupListTasksForDropdown(
  request: ApiRequestFunction,
  connectionId: string,
  listId: string
): Promise<Array<{ id: string; name: string }>> {
  const acc: Array<{ id: string; name: string }> = [];
  const seen = new Set<string>();
  const maxPages = 12;
  const pageSizeHint = 100;
  for (let page = 0; page < maxPages; page++) {
    const data = await fetchClickupListTasks(request, connectionId, listId, {
      page,
      includeClosed: false,
    });
    if (data && typeof data === 'object' && 'error' in data && (data as { error?: unknown }).error) {
      const err = (data as { error?: unknown }).error;
      const body = (data as { body?: string }).body;
      throw new Error(
        typeof err === 'string' ? err + (body ? `: ${body.slice(0, 200)}` : '') : 'ClickUp API error'
      );
    }
    const tasks = Array.isArray(data.tasks) ? data.tasks : [];
    for (const t of tasks) {
      const id = t?.id != null ? String(t.id) : '';
      if (!id || seen.has(id)) continue;
      seen.add(id);
      acc.push({ id, name: String(t.name ?? id) });
    }
    const rawLast = (data as Record<string, unknown>).last_page;
    const last =
      rawLast === true ||
      rawLast === 'true' ||
      tasks.length === 0 ||
      tasks.length < pageSizeHint;
    if (last) break;
  }
  acc.sort((a, b) => a.name.localeCompare(b.name, 'de'));
  return acc;
}
