/**
 * ClickUp node config — connection, browse, search, create task with list fields.
 */

import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
import type { NodeConfigRendererProps } from './types';
import {
  fetchConnections,
  fetchBrowse,
  fetchClickupList,
  fetchClickupTask,
  fetchClickupTeam,
  fetchClickupListFields,
  loadClickupListTasksForDropdown,
  type UserConnection,
  type BrowseEntry,
  type ApiRequestFunction,
} from '../../../../api/workflowApi';
import { SharepointBrowseTree } from '../../../FolderTree/SharepointBrowseTree';
import { HybridStaticRefField } from '../shared/HybridStaticRefField';
import {
  StatischKontextSelect,
  shouldShowStaticControl,
} from '../shared/RefSourceSelect';
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
import { isRef, isValue, createValue, resolvePreview } from '../shared/dataRef';
import {
  buildSyncFromClickUpList,
  findClosestUpstreamFormNode,
  type ClickUpFieldLike,
} from '../shared/clickupFormSync';
import styles from '../../editor/Automation2FlowEditor.module.css';

/** ClickUp browse: list folder paths end with /list/{id} */
const LIST_SEGMENT_RE = /\/list\/([^/]+)$/;

/** List id from stored path (e.g. /team/…/list/{id}/…) when listId param is empty. */
function parseListIdFromPath(path: unknown): string | null {
  if (typeof path !== 'string' || !path.trim()) return null;
  const m = path.match(/\/list\/([^/?#]+)/);
  return m && m[1] ? m[1].trim() : null;
}

function teamIdFromBrowseEntry(e: BrowseEntry): string | null {
  const m = e.path.match(/^\/team\/([^/]+)$/);
  if (m) return m[1];
  const raw = e.metadata?.id;
  if (typeof raw === 'string' && raw) return raw;
  if (typeof raw === 'number') return String(raw);
  return null;
}

/** Recursively collect list “tables” under /team/{teamId} (spaces, folders, folderless lists). */
async function collectListsUnderTeam(
  request: ApiRequestFunction,
  instanceId: string,
  connectionId: string,
  teamPath: string
): Promise<Array<{ id: string; name: string }>> {
  const acc: Array<{ id: string; name: string }> = [];
  const seen = new Set<string>();
  const visit = async (path: string) => {
    const { items } = await fetchBrowse(request, instanceId, connectionId, 'clickup', path);
    for (const e of items) {
      if (!e.isFolder) continue;
      if (LIST_SEGMENT_RE.test(e.path)) {
        const mid = e.path.match(LIST_SEGMENT_RE);
        if (mid && !seen.has(mid[1])) {
          seen.add(mid[1]);
          acc.push({ id: mid[1], name: e.name });
        }
      } else {
        await visit(e.path);
      }
    }
  };
  await visit(teamPath);
  acc.sort((a, b) => a.name.localeCompare(b.name, 'de'));
  return acc;
}

const browseDetailsStyle: React.CSSProperties = {
  marginTop: 12,
  border: '1px solid var(--border-color, #e0e0e0)',
  borderRadius: 6,
  background: 'var(--bg-secondary, #f8f9fa)',
  overflow: 'hidden',
};

const browseSummaryStyle: React.CSSProperties = {
  padding: '0.5rem 0.75rem',
  cursor: 'pointer',
  fontWeight: 500,
  fontSize: '0.875rem',
  display: 'flex',
  alignItems: 'center',
  gap: '0.5rem',
  userSelect: 'none',
};

const browseBodyStyle: React.CSSProperties = {
  padding: '0.5rem 0.75rem',
  borderTop: '1px solid var(--border-color, #e0e0e0)',
  maxHeight: 280,
  overflowY: 'auto',
};

function browseTitle(nodeType: string): string {
  switch (nodeType) {
    case 'clickup.listTasks':
      return 'Liste wählen (Ordner bis /team/…/list/…)';
    case 'clickup.getTask':
    case 'clickup.updateTask':
    case 'clickup.uploadAttachment':
      return 'Aufgabe wählen (Pfad …/task/…)';
    default:
      return 'ClickUp durchsuchen';
  }
}

function isListPicker(nodeType: string): boolean {
  return nodeType === 'clickup.listTasks';
}

function isTaskPicker(nodeType: string): boolean {
  return (
    nodeType === 'clickup.getTask' ||
    nodeType === 'clickup.updateTask' ||
    nodeType === 'clickup.uploadAttachment'
  );
}

const needsTeamList = (nt: string) =>
  nt === 'clickup.searchTasks' || nt === 'clickup.createTask' || nt === 'clickup.updateTask';

/** Status names from GET /list/{id} (same as ClickUp table columns). */
function parseListStatuses(data: Record<string, unknown>): Array<{ status: string; orderindex: number }> {
  if (data && typeof data === 'object' && 'error' in data && (data as { error?: unknown }).error) {
    return [];
  }
  const nested = data.list as Record<string, unknown> | undefined;
  const list = nested && typeof nested === 'object' ? nested : data;
  let raw = list?.statuses;
  if (!Array.isArray(raw) && Array.isArray(data.statuses)) {
    raw = data.statuses;
  }
  if (!Array.isArray(raw)) return [];
  const out: Array<{ status: string; orderindex: number }> = [];
  raw.forEach((s, idx) => {
    if (typeof s === 'string' && s.trim()) {
      out.push({ status: s.trim(), orderindex: idx });
      return;
    }
    if (!s || typeof s !== 'object') return;
    const ob = s as Record<string, unknown>;
    let st: string | undefined;
    if (typeof ob.status === 'string' && ob.status) {
      st = ob.status;
    } else if (ob.status && typeof ob.status === 'object' && ob.status !== null) {
      const inner = (ob.status as Record<string, unknown>).status;
      if (typeof inner === 'string' && inner) st = inner;
    }
    if (!st && typeof ob.name === 'string' && ob.name) st = ob.name;
    const oi = ob.orderindex;
    if (st) {
      const o =
        typeof oi === 'number'
          ? oi
          : typeof oi === 'string'
            ? parseInt(oi, 10)
            : 0;
      out.push({ status: st, orderindex: Number.isFinite(o) ? o : 0 });
    }
  });
  out.sort((a, b) => a.orderindex - b.orderindex);
  return out;
}

/** List id from GET /task/{id} when only task id is known (ref / static). */
function listIdFromClickUpTaskApi(data: Record<string, unknown>): string | null {
  const root =
    data.task && typeof data.task === 'object' && data.task !== null
      ? (data.task as Record<string, unknown>)
      : data;
  const list = root.list;
  if (list && typeof list === 'object' && list !== null) {
    const id = (list as Record<string, unknown>).id;
    if (id != null && String(id).trim()) return String(id);
  }
  if (root.list_id != null && String(root.list_id).trim()) return String(root.list_id);
  return null;
}

/** Reduces bogus GET /task calls (placeholders, garbage). */
function isPlausibleClickUpTaskId(tid: string): boolean {
  const t = tid.trim();
  if (t.length < 4 || t.length > 64) return false;
  if (t.includes('{{') || t.includes('}}')) return false;
  return /^[\w-]+$/.test(t);
}

/** Members from GET /team/{id} for assignee multi-select. */
function parseTeamMembers(data: Record<string, unknown>): Array<{ id: string; username: string }> {
  const team = (data.team as Record<string, unknown> | undefined) ?? data;
  const members = team?.members;
  if (!Array.isArray(members)) return [];
  const out: Array<{ id: string; username: string }> = [];
  for (const m of members) {
    if (!m || typeof m !== 'object') continue;
    const ob = m as Record<string, unknown>;
    const user = ob.user;
    const u =
      user && typeof user === 'object'
        ? (user as Record<string, unknown>)
        : ob;
    const id = u.id != null ? String(u.id) : '';
    if (!id) continue;
    const username = String(u.username ?? u.email ?? id);
    out.push({ id, username });
  }
  out.sort((a, b) => a.username.localeCompare(b.username, 'de'));
  return out;
}

type ClickUpField = Record<string, unknown>;

/** ClickUp API uses option UUID/string id as value, not orderindex (see custom fields docs). */
function normalizeFieldType(raw: unknown): string {
  return String(raw ?? 'short_text')
    .trim()
    .toLowerCase()
    .replace(/-/g, '_')
    .replace(/\s+/g, '_');
}

function dropdownOptions(field: ClickUpField): Array<{ label: string; value: string }> {
  const tc = field.type_config as Record<string, unknown> | undefined;
  if (!tc) return [];
  let options = tc.options as unknown[] | undefined;
  if (!options?.length && Array.isArray(tc.dropdowns)) {
    const d0 = (tc.dropdowns as unknown[])[0] as Record<string, unknown> | undefined;
    options = d0?.options as unknown[] | undefined;
  }
  if (!Array.isArray(options)) return [];
  const out: Array<{ label: string; value: string }> = [];
  for (const o of options) {
    if (!o || typeof o !== 'object') continue;
    const ob = o as Record<string, unknown>;
    const name = String(ob.name ?? ob.label ?? '');
    const idRaw = ob.id;
    const id = idRaw != null && idRaw !== '' ? String(idRaw) : '';
    const oi = ob.orderindex;
    const orderindex =
      typeof oi === 'number' ? oi : typeof oi === 'string' ? parseInt(oi, 10) : NaN;
    const fallback = Number.isFinite(orderindex) ? String(orderindex) : '';
    const value = id || fallback;
    if (!value) continue;
    out.push({ label: name, value });
  }
  return out;
}

function fieldUnsupported(ft: string): boolean {
  return ['tasks', 'user', 'users'].includes(ft);
}

/** Target list for a list_relationship field (ClickUp type_config varies by version). */
function linkedListIdFromRelationshipField(field: ClickUpField): string | null {
  const tc = (field.type_config ?? {}) as Record<string, unknown>;
  const asId = (v: unknown): string | null => {
    if (typeof v === 'string' && v.trim()) return v.trim();
    if (typeof v === 'number' && Number.isFinite(v)) return String(v);
    return null;
  };
  const directKeys = [
    'linked_list_id',
    'list_id',
    'related_list_id',
    'relationship_list_id',
    'relation_list_id',
    'target_list_id',
    'resource_id',
  ];
  for (const k of directKeys) {
    const raw = tc[k];
    const id = asId(raw);
    if (id) return id;
    if (raw && typeof raw === 'object' && raw !== null) {
      const nested = asId((raw as Record<string, unknown>).id);
      if (nested) return nested;
    }
  }
  if (String(tc.resource_type ?? '').toLowerCase() === 'list') {
    const id = asId(tc.resource_id);
    if (id) return id;
  }
  const rel = tc.relationship;
  if (rel && typeof rel === 'object' && rel !== null) {
    const r = rel as Record<string, unknown>;
    const fromRel = asId(
      r.list_id ?? r.id ?? r.target_id ?? r.linked_list_id ?? r.resource_id
    );
    if (fromRel) return fromRel;
  }
  const nestedKeys = ['list', 'linked_list', 'related_list', 'resource', 'related_resource'] as const;
  for (const k of nestedKeys) {
    const raw = tc[k];
    if (raw == null) continue;
    const id = asId(raw) ?? (typeof raw === 'object' ? asId((raw as Record<string, unknown>).id) : null);
    if (id) return id;
  }
  return null;
}

/** ClickUp relationship / tasks-style fields: { add: taskIds, rem: [] }. */
function relationshipPayloadFromValue(value: unknown): { add: string[]; rem: string[] } | null {
  if (value == null) return null;
  if (isRef(value)) return null;
  if (isValue(value)) {
    const v = value.value;
    if (v === '' || v === null || v === undefined) return null;
    if (typeof v === 'object' && v !== null && 'add' in v && Array.isArray((v as { add: unknown }).add)) {
      return v as { add: string[]; rem: string[] };
    }
    if (typeof v === 'string' && v) return { add: [v], rem: [] };
  }
  if (typeof value === 'object' && value !== null && 'add' in value) {
    return value as { add: string[]; rem: string[] };
  }
  if (typeof value === 'string' && value) return { add: [value], rem: [] };
  return null;
}

function selectedTaskIdFromRelationship(value: unknown): string {
  const p = relationshipPayloadFromValue(value);
  if (!p?.add?.length) return '';
  return String(p.add[0]);
}

function ClickUpListRelationshipFieldRow({
  fname,
  connectionId,
  request,
  linkedListId,
  value,
  onChange,
}: {
  fname: string;
  connectionId: string;
  request: ApiRequestFunction;
  linkedListId: string;
  value: unknown;
  onChange: (v: unknown) => void;
}) {
  const dataFlow = useAutomation2DataFlow();
  const [tasks, setTasks] = useState<Array<{ id: string; name: string }>>([]);
  const [loading, setLoading] = useState(false);
  const [loadError, setLoadError] = useState<string | null>(null);

  const hasSources =
    dataFlow &&
    dataFlow.getAvailableSourceIds().some((id) => {
      const n = dataFlow.nodes.find((x) => x.id === id);
      return n?.type !== 'trigger.manual';
    });

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setLoadError(null);
    loadClickupListTasksForDropdown(request, connectionId, linkedListId)
      .then((rows) => {
        if (!cancelled) setTasks(rows);
      })
      .catch(() => {
        if (!cancelled) {
          setTasks([]);
          setLoadError('Aufgaben konnten nicht geladen werden.');
        }
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [request, connectionId, linkedListId]);

  const sel = selectedTaskIdFromRelationship(value);

  return (
    <div className={styles.dynamicValueField}>
      <label>{fname}</label>
      {loadError ? (
        <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #c00)' }}>{loadError}</p>
      ) : null}
      {hasSources ? (
        <div style={{ marginBottom: 8 }}>
          <StatischKontextSelect
            value={value}
            onChange={onChange}
            placeholder="— Quelle wählen —"
            staticLabel="Statisch (Aufgabe wählen)"
          />
        </div>
      ) : null}
      {shouldShowStaticControl(value, Boolean(hasSources)) && (
        <>
          {loading ? (
            <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)' }}>
              Verknüpfte Aufgaben werden geladen…
            </p>
          ) : (
            <select
              value={sel}
              onChange={(e) => {
                const tid = e.target.value;
                if (!tid) onChange(createValue(''));
                else onChange(createValue({ add: [tid], rem: [] }));
              }}
            >
              <option value="">— Aufgabe wählen —</option>
              {tasks.map((t) => (
                <option key={t.id} value={t.id}>
                  {t.name}
                </option>
              ))}
            </select>
          )}
        </>
      )}
    </div>
  );
}

/** Selected dropdown option id (string); supports legacy numeric orderindex stored as number. */
function staticDropdownStringValue(value: unknown): string {
  if (value == null) return '';
  if (isRef(value)) return '';
  if (isValue(value)) {
    const v = value.value;
    if (v === '' || v === null || v === undefined) return '';
    return String(v);
  }
  return String(value ?? '');
}

function staticCheckbox(value: unknown): boolean {
  if (value == null) return false;
  if (isValue(value)) return Boolean(value.value);
  return Boolean(value);
}

/** One custom field row: static and/or context ref. */
function fieldConfigHintMessage(fname: string, ft: string): string {
  return `${fname} (${ft})`;
}

function ClickUpCustomFieldRow({
  field,
  value,
  onChange,
  connectionId,
  request,
  parentListId,
}: {
  field: ClickUpField;
  value: unknown;
  onChange: (v: unknown) => void;
  connectionId?: string;
  request?: ApiRequestFunction;
  /** List where the new task is created — fallback if relationship omits linked list (same-list). */
  parentListId?: string;
}) {
  const dataFlow = useAutomation2DataFlow();
  const fid = String(field.id ?? '');
  const fname = String(field.name ?? fid);
  const ft = normalizeFieldType(field.type);
  const hasSources =
    dataFlow &&
    dataFlow.getAvailableSourceIds().some((id) => {
      const n = dataFlow.nodes.find((x) => x.id === id);
      return n?.type !== 'trigger.manual';
    });

  if (fieldUnsupported(ft)) {
    return (
      <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)' }}>
        <strong>{fname}</strong> ({ft}): nur über „Zusätzliches JSON“ setzbar.
      </p>
    );
  }

  if (ft === 'list_relationship') {
    const linkedId =
      linkedListIdFromRelationshipField(field) ?? (parentListId?.trim() || null);
    if (!connectionId || !request) {
      return (
        <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)' }}>
          <strong>{fname}</strong> (List-Beziehung): Verbindung fehlt — Feld kann nicht geladen werden.
        </p>
      );
    }
    if (!linkedId) {
      return (
        <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)' }}>
          <strong>{fname}</strong> (List-Beziehung): Verknüpfte Liste nicht in den Felddaten — in ClickUp
          prüfen oder Support.
        </p>
      );
    }
    return (
      <ClickUpListRelationshipFieldRow
        fname={fname}
        connectionId={connectionId}
        request={request}
        linkedListId={linkedId}
        value={value}
        onChange={onChange}
      />
    );
  }

  if (ft === 'drop_down' || ft === 'dropdown') {
    const opts = dropdownOptions(field);
    if (!opts.length) {
      return (
        <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)' }}>
          <strong>{fieldConfigHintMessage(fname, ft)}</strong>: Keine Dropdown-Optionen in den
          Felddaten (type_config.options).
        </p>
      );
    }
    return (
      <div className={styles.dynamicValueField}>
        <label>{fname}</label>
        {hasSources ? (
          <div style={{ marginBottom: 8 }}>
            <StatischKontextSelect
              value={value}
              onChange={onChange}
              placeholder="— Quelle wählen —"
            />
          </div>
        ) : null}
        {shouldShowStaticControl(value, Boolean(hasSources)) && (
          <select
            value={staticDropdownStringValue(value)}
            onChange={(e) => {
              const v = e.target.value;
              if (v === '') onChange(createValue(''));
              else onChange(createValue(v));
            }}
          >
            <option value="">— Option wählen —</option>
            {opts.map((o) => (
              <option key={o.value} value={o.value}>
                {o.label}
              </option>
            ))}
          </select>
        )}
      </div>
    );
  }

  if (ft === 'checkbox') {
    const checked = staticCheckbox(value);
    return (
      <div className={styles.dynamicValueField}>
        <label>{fname}</label>
        {hasSources ? (
          <div style={{ marginBottom: 8 }}>
            <StatischKontextSelect
              value={value}
              onChange={onChange}
              placeholder="— Quelle wählen —"
            />
          </div>
        ) : null}
        {shouldShowStaticControl(value, Boolean(hasSources)) && (
          <label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <input
              type="checkbox"
              checked={checked}
              onChange={(e) => onChange(createValue(e.target.checked))}
            />
            <span>Ja</span>
          </label>
        )}
      </div>
    );
  }

  if (ft === 'date') {
    const ms = isValue(value)
      ? Number(value.value)
      : typeof value === 'number'
        ? value
        : 0;
    const day =
      ms > 0
        ? new Date(ms).toISOString().slice(0, 10)
        : '';
    return (
      <div className={styles.dynamicValueField}>
        <label>{fname}</label>
        {hasSources ? (
          <div style={{ marginBottom: 8 }}>
            <StatischKontextSelect
              value={value}
              onChange={onChange}
              placeholder="— Quelle wählen —"
            />
          </div>
        ) : null}
        {shouldShowStaticControl(value, Boolean(hasSources)) && (
          <input
            type="date"
            value={day}
            onChange={(e) => {
              const t = e.target.value ? new Date(e.target.value).getTime() : '';
              onChange(createValue(t === '' ? '' : t));
            }}
          />
        )}
      </div>
    );
  }

  if (ft === 'number' || ft === 'currency') {
    return (
      <HybridStaticRefField
        label={fname}
        value={value}
        onChange={onChange}
        inputType="number"
        placeholder={ft === 'currency' ? 'z. B. 1234.56' : undefined}
      />
    );
  }

  if (ft === 'text' || ft === 'long_text' || ft === 'short_text' || ft === 'email' || ft === 'phone' || ft === 'url') {
    const multiline = ft === 'text' || ft === 'long_text';
    return (
      <HybridStaticRefField
        label={fname}
        value={value}
        onChange={onChange}
        multiline={multiline}
        placeholder={fname}
      />
    );
  }

  return (
    <HybridStaticRefField
      label={`${fname} (${ft})`}
      value={value}
      onChange={onChange}
      multiline
    />
  );
}

function ClickUpTaskDueDateRow({
  value,
  onChange,
}: {
  value: unknown;
  onChange: (v: unknown) => void;
}) {
  const dataFlow = useAutomation2DataFlow();
  const hasSources =
    dataFlow &&
    dataFlow.getAvailableSourceIds().some((id) => {
      const n = dataFlow.nodes.find((x) => x.id === id);
      return n?.type !== 'trigger.manual';
    });
  const ms = isValue(value)
    ? Number(value.value)
    : typeof value === 'number'
      ? value
      : 0;
  const day = ms > 0 ? new Date(ms).toISOString().slice(0, 10) : '';
  return (
    <div className={styles.dynamicValueField}>
      <label>Fälligkeit</label>
      {hasSources ? (
        <div style={{ marginBottom: 8 }}>
          <StatischKontextSelect
            value={value}
            onChange={onChange}
            placeholder="— Quelle wählen —"
          />
        </div>
      ) : null}
      {shouldShowStaticControl(value, Boolean(hasSources)) && (
        <input
          type="date"
          value={day}
          onChange={(e) => {
            const t = e.target.value ? new Date(e.target.value).getTime() : '';
            onChange(createValue(t === '' ? '' : t));
          }}
        />
      )}
    </div>
  );
}

function ClickUpTimeEstimateHoursRow({
  value,
  onChange,
}: {
  value: unknown;
  onChange: (v: unknown) => void;
}) {
  const dataFlow = useAutomation2DataFlow();
  const hasSources =
    dataFlow &&
    dataFlow.getAvailableSourceIds().some((id) => {
      const n = dataFlow.nodes.find((x) => x.id === id);
      return n?.type !== 'trigger.manual';
    });
  const h = isValue(value)
    ? Number(value.value)
    : typeof value === 'number'
      ? value
      : 0;
  const hours = h > 0 && Number.isFinite(h) ? h : '';
  return (
    <div className={styles.dynamicValueField}>
      <label>Zeitschätzung (Stunden)</label>
      {hasSources ? (
        <div style={{ marginBottom: 8 }}>
          <StatischKontextSelect
            value={value}
            onChange={onChange}
            placeholder="— Quelle wählen —"
          />
        </div>
      ) : null}
      {shouldShowStaticControl(value, Boolean(hasSources)) && (
        <input
          type="number"
          min={0}
          step={0.25}
          value={hours === '' ? '' : hours}
          onChange={(e) => {
            const raw = e.target.value;
            if (raw === '') onChange(createValue(''));
            else {
              const n = parseFloat(raw);
              onChange(createValue(Number.isFinite(n) ? n : ''));
            }
          }}
        />
      )}
    </div>
  );
}

/** One row for clickup.updateTask — field + value (+ custom field id). */
export type ClickUpTaskUpdateEntryRow = {
  rowId: string;
  fieldKey: string;
  customFieldId: unknown;
  value: unknown;
};

function newClickUpTaskUpdateEntryRow(): ClickUpTaskUpdateEntryRow {
  return {
    rowId: `row_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
    fieldKey: 'name',
    customFieldId: createValue(''),
    value: createValue(''),
  };
}

const UPDATE_TASK_FIELD_OPTIONS: Array<{ value: string; label: string }> = [
  { value: 'name', label: 'Titel (name)' },
  { value: 'description', label: 'Beschreibung' },
  { value: 'status', label: 'Status' },
  { value: 'priority', label: 'Priorität (1–4)' },
  { value: 'due_date', label: 'Fälligkeit (Datum oder ms)' },
  { value: 'time_estimate_h', label: 'Zeitschätzung (Stunden)' },
  { value: 'time_estimate_ms', label: 'Zeitschätzung (ms)' },
  { value: 'assignees', label: 'Zugewiesene' },
  { value: 'custom_field', label: 'Benutzerdefiniertes Feld' },
];

function normalizeTaskUpdateEntries(raw: unknown): ClickUpTaskUpdateEntryRow[] {
  if (!Array.isArray(raw)) return [];
  return raw.map((r, i) => {
    if (r && typeof r === 'object' && !Array.isArray(r)) {
      const o = r as Record<string, unknown>;
      return {
        rowId: String(o.rowId ?? `row_${i}`),
        fieldKey: String(o.fieldKey ?? 'name'),
        customFieldId: o.customFieldId ?? createValue(''),
        value: o.value ?? createValue(''),
      };
    }
    return newClickUpTaskUpdateEntryRow();
  });
}

/** Static task id for dropdown (refs → empty). */
function taskIdStaticForDropdown(taskId: unknown): string {
  if (isRef(taskId)) return '';
  if (isValue(taskId)) return String(taskId.value ?? '').trim();
  if (typeof taskId === 'string' || typeof taskId === 'number') return String(taskId).trim();
  return '';
}

/** Pick an existing task from a list (same API as Form „ClickUp-Aufgabe“). */
function ClickUpTaskFromListDropdown({
  connectionId,
  listId,
  request,
  taskId,
  onSetTaskId,
}: {
  connectionId: string;
  listId: string;
  request: ApiRequestFunction;
  taskId: unknown;
  onSetTaskId: (v: unknown) => void;
}) {
  const [tasks, setTasks] = useState<Array<{ id: string; name: string }>>([]);
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState<string | null>(null);

  useEffect(() => {
    const cid = connectionId.trim();
    const lid = listId.trim();
    if (!cid || !lid) {
      setTasks([]);
      return;
    }
    let cancelled = false;
    setLoading(true);
    setErr(null);
    loadClickupListTasksForDropdown(request, cid, lid)
      .then((rows) => {
        if (!cancelled) setTasks(rows);
      })
      .catch(() => {
        if (!cancelled) {
          setTasks([]);
          setErr('Aufgaben konnten nicht geladen werden.');
        }
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [request, connectionId, listId]);

  const sel = taskIdStaticForDropdown(taskId);

  return (
    <div className={styles.dynamicValueField} style={{ marginTop: 10 }}>
      <label>Vorhandene Aufgabe in der gewählten Liste</label>
      {err ? (
        <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #c00)' }}>{err}</p>
      ) : null}
      {loading ? (
        <span style={{ fontSize: '0.85rem', color: '#666' }}>Aufgaben werden geladen…</span>
      ) : (
        <select
          value={sel}
          onChange={(e) => {
            const id = e.target.value;
            if (!id) onSetTaskId(createValue(''));
            else onSetTaskId(createValue(id));
          }}
        >
          <option value="">— Aufgabe aus ClickUp wählen —</option>
          {tasks.map((t) => (
            <option key={t.id} value={t.id}>
              {t.name || t.id}
            </option>
          ))}
        </select>
      )}
      <p style={{ fontSize: '0.75rem', color: 'var(--text-secondary, #666)', marginTop: 6, marginBottom: 0 }}>
        Workspace und Liste oben wählen. Oder Task-ID unten per Kontext-Referenz setzen (dann entfällt die
        Listenauswahl oben).
      </p>
    </div>
  );
}

function ClickUpUpdateTaskEntriesEditor({
  rows,
  onChangeRows,
  connectionId,
  listId,
  pathForListId,
  request,
  taskIdParam,
  teamMembers,
  teamMembersLoading,
  parentListStatuses,
  parentListStatusesLoading,
}: {
  rows: ClickUpTaskUpdateEntryRow[];
  onChangeRows: (next: ClickUpTaskUpdateEntryRow[]) => void;
  connectionId: string;
  /** Resolved list id (param or parsed from path). */
  listId: string;
  /** Raw path — fallback to parse /list/{id}/ when listId param empty. */
  pathForListId: string;
  request: ApiRequestFunction;
  taskIdParam: unknown;
  teamMembers: Array<{ id: string; username: string }>;
  teamMembersLoading: boolean;
  /** Same list fetch as parent — avoids empty child dropdown when listId is set but child effect lags. */
  parentListStatuses: Array<{ status: string; orderindex: number }>;
  parentListStatusesLoading: boolean;
}) {
  const dataFlow = useAutomation2DataFlow();
  const [resolvedStatuses, setResolvedStatuses] = useState<Array<{ status: string; orderindex: number }>>(
    []
  );
  const [resolvedStatusesLoading, setResolvedStatusesLoading] = useState(false);

  const previewFingerprint = useMemo(
    () => JSON.stringify(dataFlow?.nodeOutputsPreview ?? {}),
    [dataFlow?.nodeOutputsPreview]
  );

  /** GET /list/{id}; wenn keine List-ID: GET /task/{id} nur bei plausibler Task-ID (Vorschau/Statisch). */
  useEffect(() => {
    let cancelled = false;
    (async () => {
      const cid = (connectionId || '').trim();
      if (!cid || !request) {
        setResolvedStatuses([]);
        setResolvedStatusesLoading(false);
        return;
      }
      let lid = (listId || '').trim();
      if (!lid) {
        lid = parseListIdFromPath(pathForListId) ?? '';
      }
      const lidFromListOrPath = lid;
      if (!lid && taskIdParam != null && dataFlow) {
        let tid = '';
        if (isRef(taskIdParam)) {
          const r = resolvePreview(taskIdParam, dataFlow.nodeOutputsPreview);
          tid = r != null && r !== '' ? String(r) : '';
        } else if (isValue(taskIdParam)) {
          tid = String(taskIdParam.value ?? '').trim();
        } else if (typeof taskIdParam === 'string' || typeof taskIdParam === 'number') {
          tid = String(taskIdParam).trim();
        }
        if (tid && isPlausibleClickUpTaskId(tid)) {
          setResolvedStatusesLoading(true);
          try {
            const task = await fetchClickupTask(request, cid, tid);
            if (cancelled) return;
            if (
              task &&
              typeof task === 'object' &&
              !('error' in task && (task as { error?: unknown }).error)
            ) {
              const fromTask = listIdFromClickUpTaskApi(task as Record<string, unknown>) || '';
              if (fromTask) lid = fromTask;
            }
          } catch {
            lid = lidFromListOrPath;
          }
        }
      }
      if (!lid) {
        setResolvedStatuses([]);
        setResolvedStatusesLoading(false);
        return;
      }
      setResolvedStatusesLoading(true);
      try {
        const data = await fetchClickupList(request, cid, lid);
        if (cancelled) return;
        if (
          data &&
          typeof data === 'object' &&
          'error' in data &&
          (data as { error?: unknown }).error
        ) {
          setResolvedStatuses([]);
        } else {
          setResolvedStatuses(parseListStatuses(data as Record<string, unknown>));
        }
      } catch {
        if (!cancelled) setResolvedStatuses([]);
      } finally {
        if (!cancelled) setResolvedStatusesLoading(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [connectionId, listId, pathForListId, request, taskIdParam, previewFingerprint, dataFlow]);

  const statusOptions = useMemo(() => {
    if (resolvedStatuses.length > 0) return resolvedStatuses;
    return parentListStatuses ?? [];
  }, [resolvedStatuses, parentListStatuses]);

  const statusOptionsLoading =
    resolvedStatusesLoading ||
    (resolvedStatuses.length === 0 &&
      parentListStatusesLoading &&
      Boolean((listId || '').trim() || parseListIdFromPath(pathForListId)));

  const updateRow = (rowId: string, patch: Partial<ClickUpTaskUpdateEntryRow>) => {
    onChangeRows(rows.map((r) => (r.rowId === rowId ? { ...r, ...patch } : r)));
  };
  const removeRow = (rowId: string) => {
    onChangeRows(rows.filter((r) => r.rowId !== rowId));
  };

  const assigneeIdsFromValue = (v: unknown): string[] => {
    if (isRef(v)) return [];
    const raw = isValue(v) ? v.value : v;
    if (Array.isArray(raw)) return raw.map((x) => String(x)).filter(Boolean);
    if (typeof raw === 'string' && raw.trim()) {
      try {
        const p = JSON.parse(raw) as unknown;
        if (Array.isArray(p)) return p.map((x) => String(x)).filter(Boolean);
      } catch {
        return [];
      }
    }
    return [];
  };

  const renderValueEditor = (row: ClickUpTaskUpdateEntryRow) => {
    const fk = row.fieldKey;
    const commonHybrid = (multiline?: boolean, placeholder?: string, inputType?: 'text' | 'number') => (
      <HybridStaticRefField
        label="Oder Referenz (ohne Formular-Payload)"
        value={row.value}
        onChange={(v) => updateRow(row.rowId, { value: v })}
        multiline={multiline}
        inputType={inputType}
        placeholder={placeholder}
        pathPickMode="exclude_forms"
      />
    );

    if (fk === 'custom_field') {
      return (
        <>
          <HybridStaticRefField
            label="ClickUp-Feld-ID"
            value={row.customFieldId}
            onChange={(v) => updateRow(row.rowId, { customFieldId: v })}
            placeholder="Feld-ID"
            pathPickMode="exclude_forms"
          />
          <HybridStaticRefField
            label="Neuer Wert"
            value={row.value}
            onChange={(v) => updateRow(row.rowId, { value: v })}
            multiline
            placeholder="Wert"
            pathPickMode="exclude_forms"
          />
        </>
      );
    }

    if (fk === 'status') {
      if (isRef(row.value)) {
        return (
          <HybridStaticRefField
            label="Status (Referenz)"
            value={row.value}
            onChange={(v) => updateRow(row.rowId, { value: v })}
            placeholder="Kontext mit Status-String"
            pathPickMode="exclude_forms"
          />
        );
      }
      const staticVal = isValue(row.value)
        ? String(row.value.value ?? '')
        : String(row.value ?? '');
      if (statusOptionsLoading) {
        return (
          <div className={styles.dynamicValueField}>
            <label>Status (wie in ClickUp)</label>
            <select disabled value="">
              <option value="">Status werden geladen…</option>
            </select>
          </div>
        );
      }
      return (
        <div className={styles.dynamicValueField}>
          <label>Status (wie in ClickUp)</label>
          <select
            value={staticVal}
            onChange={(e) => updateRow(row.rowId, { value: createValue(e.target.value) })}
            disabled={statusOptions.length === 0}
          >
            <option value="">
              {statusOptions.length === 0
                ? '— Keine Status geladen —'
                : '— Status wählen —'}
            </option>
            {statusOptions.map((s) => (
              <option key={s.status} value={s.status}>
                {s.status}
              </option>
            ))}
          </select>
          {statusOptions.length === 0 && (
            <p
              style={{
                fontSize: '0.75rem',
                color: 'var(--text-secondary, #666)',
                marginTop: 6,
                marginBottom: 0,
              }}
            >
              <a href="#clickup-update-task-list-anchor" style={{ fontWeight: 600 }}>
                Liste wählen
              </a>{' '}
              (Kasten direkt über den Zeilen) oder gültige Task-ID mit Vorschau; alternativ Path mit{' '}
              <code>/list/…/</code>.
            </p>
          )}
        </div>
      );
    }

    if (fk === 'priority') {
      const pv = isRef(row.value) ? '' : isValue(row.value) ? String(row.value.value ?? '') : String(row.value ?? '');
      return (
        <div className={styles.dynamicValueField}>
          <label>Priorität</label>
          <select
            value={pv && ['1', '2', '3', '4'].includes(pv) ? pv : ''}
            onChange={(e) => updateRow(row.rowId, { value: createValue(e.target.value) })}
          >
            <option value="">— wählen —</option>
            <option value="1">1 — Dringend</option>
            <option value="2">2 — Hoch</option>
            <option value="3">3 — Normal</option>
            <option value="4">4 — Niedrig</option>
          </select>
        </div>
      );
    }

    if (fk === 'due_date') {
      return (
        <>
          <ClickUpTaskDueDateRow
            value={row.value}
            onChange={(v) => updateRow(row.rowId, { value: v })}
          />
          {commonHybrid(false, 'Alternativ nur Referenz')}
        </>
      );
    }

    if (fk === 'time_estimate_h') {
      return (
        <>
          <ClickUpTimeEstimateHoursRow
            value={row.value}
            onChange={(v) => updateRow(row.rowId, { value: v })}
          />
          {commonHybrid(false, 'Alternativ: Referenz (Stunden)')}
        </>
      );
    }

    if (fk === 'time_estimate_ms') {
      return commonHybrid(false, 'ms', 'number');
    }

    if (fk === 'assignees') {
      const selected = new Set(assigneeIdsFromValue(row.value));
      return (
        <>
          {teamMembersLoading ? (
            <span style={{ fontSize: '0.85rem', color: '#666' }}>Mitglieder werden geladen…</span>
          ) : teamMembers.length === 0 ? (
            <p style={{ fontSize: '0.8rem', color: '#666' }}>
              Keine Mitglieder — Workspace oben wählen oder JSON / Referenz unten.
            </p>
          ) : (
            <div
              className={styles.dynamicValueField}
              style={{
                maxHeight: 160,
                overflowY: 'auto',
                border: '1px solid var(--border-color, #e0e0e0)',
                borderRadius: 4,
                padding: 8,
              }}
            >
              <label>Zugewiesene</label>
              {teamMembers.map((m) => (
                <label
                  key={m.id}
                  style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}
                >
                  <input
                    type="checkbox"
                    checked={selected.has(m.id)}
                    onChange={() => {
                      const next = new Set(selected);
                      if (next.has(m.id)) next.delete(m.id);
                      else next.add(m.id);
                      updateRow(row.rowId, {
                        value: createValue([...next].map((id) => Number(id))),
                      });
                    }}
                  />
                  <span>{m.username}</span>
                </label>
              ))}
            </div>
          )}
          {commonHybrid(false, '[123,456] oder JSON-Array')}
        </>
      );
    }

    if (fk === 'description') {
      return (
        <HybridStaticRefField
          label="Wert"
          value={row.value}
          onChange={(v) => updateRow(row.rowId, { value: v })}
          multiline
          placeholder="Beschreibung"
          pathPickMode="exclude_forms"
        />
      );
    }

    if (fk === 'name') {
      return (
        <HybridStaticRefField
          label="Wert"
          value={row.value}
          onChange={(v) => updateRow(row.rowId, { value: v })}
          placeholder="Titel"
          pathPickMode="exclude_forms"
        />
      );
    }

    return commonHybrid(false);
  };

  return (
    <div style={{ marginTop: 12 }}>
      <label style={{ fontWeight: 600, display: 'block', marginBottom: 8 }}>
        Felder zum Aktualisieren
      </label>
      <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)', marginBottom: 10 }}>
        Pro Zeile ein Feld. Status und Priorität wie in ClickUp (Dropdown). Referenzen ohne
        Formular-Payload (nur Knoten wie „Aufgabe erstellen“).
      </p>
      {rows.map((row) => (
        <div
          key={row.rowId}
          style={{
            border: '1px solid var(--border-color, #e0e0e0)',
            borderRadius: 6,
            padding: 10,
            marginBottom: 10,
            background: 'var(--bg-secondary, #f8f9fa)',
          }}
        >
          <div
            style={{
              display: 'flex',
              alignItems: 'center',
              gap: 8,
              flexWrap: 'wrap',
              marginBottom: 8,
            }}
          >
            <label style={{ flex: '0 0 auto' }}>Feld</label>
            <select
              value={row.fieldKey}
              onChange={(e) =>
                updateRow(row.rowId, {
                  fieldKey: e.target.value,
                  customFieldId: createValue(''),
                  value: createValue(''),
                })
              }
            >
              {UPDATE_TASK_FIELD_OPTIONS.map((o) => (
                <option key={o.value} value={o.value}>
                  {o.label}
                </option>
              ))}
            </select>
            <button type="button" className={styles.retryButton} onClick={() => removeRow(row.rowId)}>
              Zeile entfernen
            </button>
          </div>
          {renderValueEditor(row)}
        </div>
      ))}
      <button
        type="button"
        className={styles.retryButton}
        onClick={() => onChangeRows([...rows, newClickUpTaskUpdateEntryRow()])}
      >
        Feld zum Aktualisieren hinzufügen
      </button>
    </div>
  );
}

export const ClickUpNodeConfig: React.FC<NodeConfigRendererProps> = ({
  params,
  updateParam,
  instanceId,
  request,
  mergeNodeParameters,
  nodeType = 'clickup.listTasks',
}) => {
  const dataFlow = useAutomation2DataFlow();
  const lastFormSyncSigRef = useRef<string>('');
  const [connections, setConnections] = useState<UserConnection[]>([]);
  const [browseOpen, setBrowseOpen] = useState(false);
  const [connectionsLoading, setConnectionsLoading] = useState(false);
  const [searchTeams, setSearchTeams] = useState<Array<{ id: string; name: string }>>([]);
  const [searchTeamsLoading, setSearchTeamsLoading] = useState(false);
  const [searchLists, setSearchLists] = useState<Array<{ id: string; name: string }>>([]);
  const [searchListsLoading, setSearchListsLoading] = useState(false);
  const [listFields, setListFields] = useState<ClickUpField[]>([]);
  const [listFieldsLoading, setListFieldsLoading] = useState(false);
  const [listStatuses, setListStatuses] = useState<Array<{ status: string; orderindex: number }>>([]);
  const [listStatusesLoading, setListStatusesLoading] = useState(false);
  const [teamMembers, setTeamMembers] = useState<Array<{ id: string; username: string }>>([]);
  const [teamMembersLoading, setTeamMembersLoading] = useState(false);

  const connectionId = (params.connectionId as string) ?? '';
  const path = (params.path as string) ?? '';
  const teamIdParam = (params.teamId as string) ?? '';
  const listIdParam = (params.listId as string) ?? '';
  /** listId param or list id parsed from path — loads statuses when Task-GET fails (ref / 404). */
  const effectiveListIdForStatuses = useMemo(() => {
    const lid = ((params.listId as string) ?? '').trim();
    if (lid) return lid;
    return parseListIdFromPath(path) ?? '';
  }, [params.listId, path]);
  const customFieldValues = useMemo(
    () => (params.customFieldValues as Record<string, unknown> | undefined) ?? {},
    [params.customFieldValues]
  );

  const taskAssigneeIds = useMemo(() => {
    const raw = params.taskAssigneeIds;
    if (Array.isArray(raw)) {
      return raw.map((x) => String(x)).filter(Boolean);
    }
    if (typeof raw === 'string' && raw.trim()) {
      try {
        const p = JSON.parse(raw) as unknown;
        if (Array.isArray(p)) return p.map((x) => String(x)).filter(Boolean);
      } catch {
        return [];
      }
    }
    return [];
  }, [params.taskAssigneeIds]);

  /** Stunden (API); Fallback: alte Speicherung in ms → Anzeige in Stunden */
  const timeEstimateHoursValue = useMemo(() => {
    const th = params.taskTimeEstimateHours;
    if (th != null && th !== '') return th;
    const tm = params.taskTimeEstimateMs;
    if (isValue(tm) && typeof tm.value === 'number' && tm.value > 0) {
      return createValue(tm.value / (3600 * 1000));
    }
    if (typeof tm === 'number' && tm > 0) return createValue(tm / (3600 * 1000));
    return createValue('');
  }, [params.taskTimeEstimateHours, params.taskTimeEstimateMs]);

  const runFormSyncFromList = useCallback(
    (force: boolean) => {
      if (!mergeNodeParameters || !connectionId || !teamIdParam || !listIdParam) return;
      if (!dataFlow) return;
      if (listFieldsLoading) return;
      if (listStatusesLoading) return;
      const formNode = findClosestUpstreamFormNode(
        dataFlow.currentNodeId,
        dataFlow.nodes,
        dataFlow.connections
      );
      if (!formNode) return;

      const sig = `${listIdParam}|${listFields.map((f) => String((f as ClickUpFieldLike).id ?? '')).sort().join(',')}|${listStatuses.map((s) => s.status).join('\x00')}`;
      if (!force && lastFormSyncSigRef.current === sig) return;
      lastFormSyncSigRef.current = sig;

      const built = buildSyncFromClickUpList({
        formNodeId: formNode.id,
        listFields: listFields as ClickUpFieldLike[],
        listStatuses,
        connectionId,
        teamId: teamIdParam,
        listId: listIdParam,
      });

      if (formNode.type === 'input.form') {
        mergeNodeParameters(formNode.id, { fields: built.inputFormFields });
      } else if (formNode.type === 'trigger.form') {
        mergeNodeParameters(formNode.id, { formFields: built.triggerFormFields });
      }

      for (const [k, v] of Object.entries(built.clickupPatch)) {
        updateParam(k, v);
      }
      updateParam('taskTimeEstimateMs', createValue(''));
    },
    [
      mergeNodeParameters,
      connectionId,
      teamIdParam,
      listIdParam,
      listFields,
      listFieldsLoading,
      listStatuses,
      listStatusesLoading,
      dataFlow,
      updateParam,
    ]
  );

  const clickupConnections = useMemo(
    () => connections.filter((c) => c.authority === 'clickup'),
    [connections]
  );

  useEffect(() => {
    if (instanceId && request) {
      setConnectionsLoading(true);
      fetchConnections(request, instanceId)
        .then(setConnections)
        .catch(() => setConnections([]))
        .finally(() => setConnectionsLoading(false));
    }
  }, [instanceId, request]);

  useEffect(() => {
    if (!needsTeamList(nodeType) || !instanceId || !request || !connectionId) {
      setSearchTeams([]);
      return;
    }
    let cancelled = false;
    setSearchTeamsLoading(true);
    fetchBrowse(request, instanceId, connectionId, 'clickup', '/')
      .then((r) => {
        if (cancelled) return;
        const rows: Array<{ id: string; name: string }> = [];
        for (const e of r.items ?? []) {
          const tid = teamIdFromBrowseEntry(e);
          if (tid) rows.push({ id: tid, name: e.name });
        }
        setSearchTeams(rows);
      })
      .catch(() => {
        if (!cancelled) setSearchTeams([]);
      })
      .finally(() => {
        if (!cancelled) setSearchTeamsLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [nodeType, instanceId, request, connectionId]);

  useEffect(() => {
    if (!needsTeamList(nodeType) || !instanceId || !request || !connectionId || !teamIdParam) {
      setSearchLists([]);
      return;
    }
    let cancelled = false;
    setSearchListsLoading(true);
    const teamPath = `/team/${teamIdParam}`;
    collectListsUnderTeam(request, instanceId, connectionId, teamPath)
      .then((rows) => {
        if (!cancelled) setSearchLists(rows);
      })
      .catch(() => {
        if (!cancelled) setSearchLists([]);
      })
      .finally(() => {
        if (!cancelled) setSearchListsLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [nodeType, instanceId, request, connectionId, teamIdParam]);

  useEffect(() => {
    if (nodeType !== 'clickup.createTask' || !connectionId || !listIdParam || !request) {
      setListFields([]);
      return;
    }
    let cancelled = false;
    setListFieldsLoading(true);
    fetchClickupListFields(request, connectionId, listIdParam)
      .then((data) => {
        if (cancelled) return;
        const raw = data?.fields;
        const list = Array.isArray(raw)
          ? raw
          : Array.isArray(data)
            ? (data as ClickUpField[])
            : [];
        setListFields(list as ClickUpField[]);
      })
      .catch(() => {
        if (!cancelled) setListFields([]);
      })
      .finally(() => {
        if (!cancelled) setListFieldsLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [nodeType, connectionId, listIdParam, request]);

  useEffect(() => {
    if (
      (nodeType !== 'clickup.createTask' && nodeType !== 'clickup.updateTask') ||
      !connectionId ||
      !effectiveListIdForStatuses ||
      !request
    ) {
      setListStatuses([]);
      return;
    }
    let cancelled = false;
    setListStatusesLoading(true);
    fetchClickupList(request, connectionId, effectiveListIdForStatuses)
      .then((data) => {
        if (cancelled) return;
        if (data && typeof data === 'object' && 'error' in data && (data as { error?: unknown }).error) {
          setListStatuses([]);
          return;
        }
        setListStatuses(parseListStatuses(data as Record<string, unknown>));
      })
      .catch(() => {
        if (!cancelled) setListStatuses([]);
      })
      .finally(() => {
        if (!cancelled) setListStatusesLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [nodeType, connectionId, effectiveListIdForStatuses, request]);

  useEffect(() => {
    if (
      (nodeType !== 'clickup.createTask' && nodeType !== 'clickup.updateTask') ||
      !connectionId ||
      !teamIdParam ||
      !request
    ) {
      setTeamMembers([]);
      return;
    }
    let cancelled = false;
    setTeamMembersLoading(true);
    fetchClickupTeam(request, connectionId, teamIdParam)
      .then((data) => {
        if (cancelled) return;
        if (data && typeof data === 'object' && 'error' in data && (data as { error?: unknown }).error) {
          setTeamMembers([]);
          return;
        }
        setTeamMembers(parseTeamMembers(data as Record<string, unknown>));
      })
      .catch(() => {
        if (!cancelled) setTeamMembers([]);
      })
      .finally(() => {
        if (!cancelled) setTeamMembersLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [nodeType, connectionId, teamIdParam, request]);

  useEffect(() => {
    if (nodeType !== 'clickup.createTask') return;
    if (params.autoSyncFormWithList === false) return;
    if (!mergeNodeParameters) return;
    runFormSyncFromList(false);
  }, [
    nodeType,
    params.autoSyncFormWithList,
    mergeNodeParameters,
    listIdParam,
    listFields,
    listFieldsLoading,
    connectionId,
    teamIdParam,
    runFormSyncFromList,
  ]);

  const loadChildren = useCallback(
    async (pathToLoad: string): Promise<BrowseEntry[]> => {
      if (!instanceId || !request || !connectionId) return [];
      const r = await fetchBrowse(request, instanceId, connectionId, 'clickup', pathToLoad);
      return r?.items ?? [];
    },
    [instanceId, request, connectionId]
  );

  const selectPath = useCallback(
    (p: string) => {
      updateParam('path', p);
      setBrowseOpen(false);
    },
    [updateParam]
  );

  const setCustomField = useCallback(
    (fieldId: string, v: unknown) => {
      updateParam('customFieldValues', { ...customFieldValues, [fieldId]: v });
    },
    [customFieldValues, updateParam]
  );

  const showBrowse = connectionId && (isListPicker(nodeType) || isTaskPicker(nodeType));

  return (
    <>
      <div>
        <label>Connection (ClickUp)</label>
        <select
          value={connectionId}
          onChange={(e) => updateParam('connectionId', e.target.value)}
          disabled={connectionsLoading}
        >
          <option value="">{connectionsLoading ? 'Loading...' : 'Select connection'}</option>
          {clickupConnections.map((c) => (
            <option key={c.id} value={c.id}>
              {c.externalUsername ?? c.id}
            </option>
          ))}
        </select>
      </div>

      {nodeType === 'clickup.searchTasks' && (
        <>
          <div>
            <label>Workspace</label>
            <select
              value={teamIdParam}
              onChange={(e) => {
                const v = e.target.value;
                updateParam('teamId', v);
                updateParam('listId', '');
              }}
              disabled={searchTeamsLoading || !connectionId}
            >
              <option value="">
                {searchTeamsLoading ? 'Workspaces werden geladen…' : 'Workspace wählen…'}
              </option>
              {searchTeams.map((t) => (
                <option key={t.id} value={t.id}>
                  {t.name}
                </option>
              ))}
            </select>
          </div>
          <div>
            <label>Liste (Tabelle)</label>
            <select
              value={listIdParam}
              onChange={(e) => updateParam('listId', e.target.value)}
              disabled={!teamIdParam || searchListsLoading}
            >
              <option value="">
                {searchListsLoading
                  ? 'Listen werden geladen…'
                  : teamIdParam
                    ? 'Alle Listen im Workspace'
                    : 'Zuerst Workspace wählen'}
              </option>
              {searchLists.map((L) => (
                <option key={L.id} value={L.id}>
                  {L.name}
                </option>
              ))}
            </select>
          </div>
          <div>
            <label>Suchbegriff</label>
            <input
              value={(params.query as string) ?? ''}
              onChange={(e) => updateParam('query', e.target.value)}
              placeholder="Stichwort für die Aufgabensuche"
            />
          </div>
          <details style={{ marginTop: 8 }}>
            <summary style={{ cursor: 'pointer', fontSize: '0.875rem', color: 'var(--text-secondary, #555)' }}>
              Erweitert
            </summary>
            <div style={{ marginTop: 8 }}>
              <label>Seite (Pagination)</label>
              <input
                type="number"
                min={0}
                value={(params.page as number) ?? 0}
                onChange={(e) => updateParam('page', parseInt(e.target.value, 10) || 0)}
              />
              <div style={{ marginTop: 10 }}>
                <label style={{ display: 'flex', alignItems: 'flex-start', gap: 8, cursor: 'pointer' }}>
                  <input
                    type="checkbox"
                    checked={params.matchNameOnly !== false}
                    onChange={(e) => updateParam('matchNameOnly', e.target.checked)}
                  />
                  <span>
                    Nur Aufgaben, deren Titel den Suchbegriff enthält (Standard: an — aus für
                    Relevanzsuche inkl. Beschreibung)
                  </span>
                </label>
              </div>
              {listIdParam ? (
                <div style={{ marginTop: 10 }}>
                  <label style={{ display: 'flex', alignItems: 'flex-start', gap: 8, cursor: 'pointer' }}>
                    <input
                      type="checkbox"
                      checked={Boolean(params.includeClosed)}
                      onChange={(e) => updateParam('includeClosed', e.target.checked)}
                    />
                    <span>Erledigte Aufgaben einbeziehen (Listen-Suche)</span>
                  </label>
                </div>
              ) : null}
              <div style={{ marginTop: 10 }}>
                <label style={{ display: 'flex', alignItems: 'flex-start', gap: 8, cursor: 'pointer' }}>
                  <input
                    type="checkbox"
                    checked={Boolean(params.fullTaskData)}
                    onChange={(e) => updateParam('fullTaskData', e.target.checked)}
                  />
                  <span>
                    Vollständige ClickUp-Rohdaten (alle Felder, sehr groß — nur für Debugging oder Spezialfälle)
                  </span>
                </label>
              </div>
            </div>
          </details>
        </>
      )}

      {nodeType === 'clickup.createTask' && (
        <>
          <div>
            <label>Workspace</label>
            <select
              value={teamIdParam}
              onChange={(e) => {
                const v = e.target.value;
                updateParam('teamId', v);
                updateParam('listId', '');
                updateParam('path', '');
                updateParam('customFieldValues', {});
                updateParam('taskStatus', '');
                updateParam('taskAssigneeIds', []);
              }}
              disabled={searchTeamsLoading || !connectionId}
            >
              <option value="">
                {searchTeamsLoading ? 'Workspaces werden geladen…' : 'Workspace wählen…'}
              </option>
              {searchTeams.map((t) => (
                <option key={t.id} value={t.id}>
                  {t.name}
                </option>
              ))}
            </select>
          </div>
          <div>
            <label>Liste</label>
            <select
              value={listIdParam}
              onChange={(e) => {
                const lid = e.target.value;
                updateParam('listId', lid);
                updateParam('customFieldValues', {});
                updateParam('taskStatus', '');
                if (teamIdParam && lid) {
                  updateParam('path', `/team/${teamIdParam}/list/${lid}`);
                } else {
                  updateParam('path', '');
                }
              }}
              disabled={!teamIdParam || searchListsLoading}
            >
              <option value="">{searchListsLoading ? 'Listen werden geladen…' : 'Liste wählen…'}</option>
              {searchLists.map((L) => (
                <option key={L.id} value={L.id}>
                  {L.name}
                </option>
              ))}
            </select>
          </div>


          {mergeNodeParameters && teamIdParam && listIdParam ? (
            <div
              style={{
                marginTop: 12,
                padding: 10,
                borderRadius: 6,
                border: '1px solid var(--border-color, #e0e0e0)',
                background: 'var(--bg-secondary, #f8f9fa)',
              }}
            >
              <label style={{ fontWeight: 600, display: 'block', marginBottom: 6 }}>
                Formular mit dieser Liste abgleichen
              </label>
              <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)', marginBottom: 8 }}>
                Sucht den nächsten <strong>Formular</strong>-Knoten (<code>input.form</code> oder{' '}
                <code>trigger.form</code>) stromaufwärts, legt dessen Felder wie die ClickUp-Liste an und
                setzt hier die Datenquellen (<code>payload.…</code>) auf dieses Formular. Anschließend
                können Sie Felder im Formular-Knoten anpassen.
              </p>
              <label style={{ display: 'flex', alignItems: 'flex-start', gap: 8, cursor: 'pointer', marginBottom: 8 }}>
                <input
                  type="checkbox"
                  checked={params.autoSyncFormWithList !== false}
                  onChange={(e) => updateParam('autoSyncFormWithList', e.target.checked)}
                />
                <span>Bei Listenwahl automatisch abgleichen</span>
              </label>
              <button
                type="button"
                className={styles.retryButton}
                style={{ marginTop: 4 }}
                onClick={() => runFormSyncFromList(true)}
                disabled={listFieldsLoading}
              >
                Jetzt Formular &amp; Referenzen setzen
              </button>
              {!dataFlow ||
              !findClosestUpstreamFormNode(
                dataFlow.currentNodeId,
                dataFlow.nodes,
                dataFlow.connections
              ) ? (
                <p style={{ fontSize: '0.8rem', color: '#a60', marginTop: 8, marginBottom: 0 }}>
                  Kein Formular-Knoten stromaufwärts verbunden — zuerst ein Formular vor diesen Node ziehen und
                  verbinden.
                </p>
              ) : null}
            </div>
          ) : null}

          {teamIdParam && listIdParam ? (
            <div style={{ marginTop: 14 }}>
              <label style={{ fontWeight: 600, display: 'block', marginBottom: 8 }}>
                Standardfelder (wie in ClickUp)
              </label>
              <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)', marginBottom: 10 }}>
                Status, Priorität, Fälligkeit, Zuweisungen und Zeitschätzung — dieselben Spalten wie in der
                Listenansicht (nicht die benutzerdefinierten Felder darunter).
              </p>

              <div className={styles.dynamicValueField}>
                <label>Status</label>
                {listStatusesLoading ? (
                  <span style={{ fontSize: '0.85rem', color: '#666' }}>Status wird geladen…</span>
                ) : (
                  <select
                    value={(params.taskStatus as string) ?? ''}
                    onChange={(e) => updateParam('taskStatus', e.target.value)}
                  >
                    <option value="">— Standard (ClickUp) —</option>
                    {listStatuses.map((s) => (
                      <option key={s.status} value={s.status}>
                        {s.status}
                      </option>
                    ))}
                  </select>
                )}
              </div>

              <div className={styles.dynamicValueField} style={{ marginTop: 8 }}>
                <label>Priorität</label>
                <select
                  value={(params.taskPriority as string) ?? ''}
                  onChange={(e) => updateParam('taskPriority', e.target.value)}
                >
                  <option value="">— keine —</option>
                  <option value="1">1 — Dringend</option>
                  <option value="2">2 — Hoch</option>
                  <option value="3">3 — Normal</option>
                  <option value="4">4 — Niedrig</option>
                </select>
              </div>

              <div style={{ marginTop: 8 }}>
                <ClickUpTaskDueDateRow
                  value={params.taskDueDateMs}
                  onChange={(v) => updateParam('taskDueDateMs', v)}
                />
              </div>

              <div className={styles.dynamicValueField} style={{ marginTop: 8 }}>
                <label>Zugewiesene</label>
                {teamMembersLoading ? (
                  <span style={{ fontSize: '0.85rem', color: '#666' }}>Mitglieder werden geladen…</span>
                ) : teamMembers.length === 0 ? (
                  <span style={{ fontSize: '0.85rem', color: '#666' }}>Keine Mitglieder geladen.</span>
                ) : (
                  <div
                    style={{
                      maxHeight: 160,
                      overflowY: 'auto',
                      border: '1px solid var(--border-color, #e0e0e0)',
                      borderRadius: 4,
                      padding: 8,
                    }}
                  >
                    {teamMembers.map((m) => (
                      <label
                        key={m.id}
                        style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}
                      >
                        <input
                          type="checkbox"
                          checked={taskAssigneeIds.includes(m.id)}
                          onChange={() => {
                            const set = new Set(taskAssigneeIds);
                            if (set.has(m.id)) set.delete(m.id);
                            else set.add(m.id);
                            updateParam('taskAssigneeIds', [...set]);
                          }}
                        />
                        <span>{m.username}</span>
                      </label>
                    ))}
                  </div>
                )}
              </div>

              <div style={{ marginTop: 8 }}>
                <ClickUpTimeEstimateHoursRow
                  value={timeEstimateHoursValue}
                  onChange={(v) => {
                    updateParam('taskTimeEstimateHours', v);
                    updateParam('taskTimeEstimateMs', createValue(''));
                  }}
                />
              </div>
            </div>
          ) : null}

          {listIdParam ? (
            <div style={{ marginTop: 12 }}>
              <label style={{ fontWeight: 600 }}>Felder der Liste</label>
              {listFieldsLoading ? (
                <p style={{ fontSize: '0.85rem', color: 'var(--text-secondary, #666)' }}>Felder werden geladen…</p>
              ) : listFields.length === 0 ? (
                <p style={{ fontSize: '0.85rem', color: 'var(--text-secondary, #666)' }}>
                  Keine benutzerdefinierten Felder oder keine Berechtigung.
                </p>
              ) : (
                listFields.map((f) => {
                  const id = String(f.id ?? '');
                  if (!id) return null;
                  return (
                    <ClickUpCustomFieldRow
                      key={id}
                      field={f}
                      value={customFieldValues[id]}
                      onChange={(v) => setCustomField(id, v)}
                      connectionId={connectionId}
                      request={request}
                      parentListId={listIdParam}
                    />
                  );
                })
              )}
            </div>
          ) : null}

          <details style={{ marginTop: 12 }}>
            <summary style={{ cursor: 'pointer', fontSize: '0.875rem', color: 'var(--text-secondary, #555)' }}>
              Erweitert (JSON)
            </summary>
            <div style={{ marginTop: 8 }}>
              <label>Zusätzliche Felder (JSON)</label>
              <textarea
                value={(params.taskFields as string) ?? ''}
                onChange={(e) => updateParam('taskFields', e.target.value)}
                rows={3}
                placeholder='{"priority": "3"}'
              />
            </div>
          </details>
        </>
      )}

      {nodeType === 'clickup.listTasks' && (
        <>
          <div>
            <label>List path</label>
            <input
              value={path}
              onChange={(e) => updateParam('path', e.target.value)}
              placeholder="/team/{teamId}/list/{listId}"
            />
          </div>
          <div>
            <label>
              <input
                type="checkbox"
                checked={Boolean(params.includeClosed)}
                onChange={(e) => updateParam('includeClosed', e.target.checked)}
              />{' '}
              Include closed tasks
            </label>
          </div>
        </>
      )}

      {(nodeType === 'clickup.getTask' ||
        nodeType === 'clickup.updateTask' ||
        nodeType === 'clickup.uploadAttachment') && (
        <>
          {nodeType === 'clickup.updateTask' &&
          !isRef(params.taskId) &&
          connectionId &&
          listIdParam &&
          request ? (
            <ClickUpTaskFromListDropdown
              connectionId={connectionId}
              listId={listIdParam}
              request={request}
              taskId={params.taskId}
              onSetTaskId={(v) => updateParam('taskId', v)}
            />
          ) : null}
          <HybridStaticRefField
            label="Task-ID"
            value={params.taskId}
            onChange={(v) => updateParam('taskId', v)}
            placeholder="Referenz: voriger Knoten „Aufgabe erstellen“ → taskId"
            pathPickMode={nodeType === 'clickup.updateTask' ? 'clickup_task_id' : 'default'}
          />
          <div>
            <label>Path (optional)</label>
            <input
              value={path}
              onChange={(e) => updateParam('path', e.target.value)}
              placeholder=".../task/{taskId}"
            />
          </div>
        </>
      )}

      {nodeType === 'clickup.updateTask' && (
        <div
          id="clickup-update-task-list-anchor"
          style={{
            marginTop: 16,
            padding: 12,
            borderRadius: 8,
            border: '1px solid var(--border-color, #e0e0e0)',
            background: 'var(--bg-secondary, #f8f9fa)',
          }}
        >
          <label style={{ fontWeight: 600, display: 'block', marginBottom: 6 }}>
            Liste für Status &amp; Felder
          </label>
          <p
            style={{
              fontSize: '0.8rem',
              color: 'var(--text-secondary, #666)',
              marginTop: 0,
              marginBottom: 10,
            }}
          >
            Ohne diese Auswahl bleiben die Status leer, es sei denn, die Task-ID ist gesetzt und die
            Vorschau liefert eine gültige ClickUp-Aufgaben-ID — dann wird die Liste aus der Aufgabe
            ermittelt.
          </p>
          <div>
            <label>Workspace</label>
            <select
              value={teamIdParam}
              onChange={(e) => {
                const v = e.target.value;
                updateParam('teamId', v);
                updateParam('listId', '');
                updateParam('path', '');
              }}
              disabled={searchTeamsLoading || !connectionId}
            >
              <option value="">
                {searchTeamsLoading ? 'Workspaces werden geladen…' : 'Workspace wählen…'}
              </option>
              {searchTeams.map((t) => (
                <option key={t.id} value={t.id}>
                  {t.name}
                </option>
              ))}
            </select>
          </div>
          <div style={{ marginTop: 8 }}>
            <label>Liste</label>
            <select
              value={listIdParam}
              onChange={(e) => {
                const lid = e.target.value;
                updateParam('listId', lid);
                if (teamIdParam && lid) {
                  updateParam('path', `/team/${teamIdParam}/list/${lid}`);
                } else {
                  updateParam('path', '');
                }
              }}
              disabled={!teamIdParam || searchListsLoading}
            >
              <option value="">{searchListsLoading ? 'Listen werden geladen…' : 'Liste wählen…'}</option>
              {searchLists.map((L) => (
                <option key={L.id} value={L.id}>
                  {L.name}
                </option>
              ))}
            </select>
          </div>
        </div>
      )}

      {nodeType === 'clickup.updateTask' && request && (
        <>
          <ClickUpUpdateTaskEntriesEditor
            rows={normalizeTaskUpdateEntries(params.taskUpdateEntries)}
            onChangeRows={(next) => updateParam('taskUpdateEntries', next)}
            connectionId={connectionId}
            listId={effectiveListIdForStatuses}
            pathForListId={path}
            request={request}
            taskIdParam={params.taskId}
            teamMembers={teamMembers}
            teamMembersLoading={teamMembersLoading}
            parentListStatuses={listStatuses}
            parentListStatusesLoading={listStatusesLoading}
          />
          <details style={{ marginTop: 12 }}>
            <summary style={{ cursor: 'pointer', fontSize: '0.875rem', color: 'var(--text-secondary, #555)' }}>
              Erweitert: JSON (optional, wird mit den Zeilen zusammengeführt)
            </summary>
            <div style={{ marginTop: 8 }}>
              <label>taskUpdate (JSON)</label>
              <textarea
                value={(params.taskUpdate as string) ?? ''}
                onChange={(e) => updateParam('taskUpdate', e.target.value)}
                rows={5}
                placeholder='{"name": "…"} — Basis; Zeilen darüber setzen/überschreiben Felder'
              />
            </div>
          </details>
        </>
      )}

      {nodeType === 'clickup.uploadAttachment' && (
        <div>
          <label>File name (optional)</label>
          <input
            value={(params.fileName as string) ?? ''}
            onChange={(e) => updateParam('fileName', e.target.value)}
            placeholder="report.pdf"
          />
        </div>
      )}

      {showBrowse && (
        <details
          open={browseOpen}
          onToggle={(e) => setBrowseOpen((e.target as HTMLDetailsElement).open)}
          style={browseDetailsStyle}
        >
          <summary style={browseSummaryStyle}>{browseTitle(nodeType)}</summary>
          <div style={browseBodyStyle}>
            <SharepointBrowseTree
              rootPath="/"
              onLoadChildren={loadChildren}
              onSelectFile={isTaskPicker(nodeType) ? selectPath : () => {}}
              onSelectFolder={isListPicker(nodeType) ? selectPath : undefined}
              foldersOnly={isListPicker(nodeType)}
              selectedPath={path || null}
            />
          </div>
        </details>
      )}
    </>
  );
};
