/**
 * NodeConfigPanel - Generic parameter renderer for all node types.
 * Renders each parameter using FRONTEND_TYPE_RENDERERS based on frontendType.
 */

import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import type { CanvasNode } from './FlowCanvas';
import type { GraphDefinedSchemaRef, NodeType, NodeTypeParameter, PortSchema } from '../../../api/workflowApi';
import type { ApiRequestFunction } from '../../../api/workflowApi';
import { getLabel } from '../nodes/shared/utils';
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker';
import { findRequiredErrors } from '../nodes/shared/paramValidation';
import { useAutomation2DataFlow } from '../context/Automation2DataFlowContext';
import styles from './Automation2FlowEditor.module.css';

import { useLanguage } from '../../../providers/language/LanguageContext';

interface NodeConfigPanelProps {
  node: CanvasNode | null;
  nodeType: NodeType | undefined;
  language: string;
  onParametersChange: (nodeId: string, parameters: Record<string, unknown>) => void;
  onMergeNodeParameters?: (nodeId: string, patch: Record<string, unknown>) => void;
  onNodeUpdate?: (nodeId: string, updates: Partial<Pick<CanvasNode, 'title' | 'comment'>>) => void;
  instanceId?: string;
  request?: ApiRequestFunction;
  /** When true, render developer-oriented sections (Schema-Typ-Referenz,
   *  parameter type-badges). Toggle in CanvasHeader, sysadmin-only. */
  verboseSchema?: boolean;
}

export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
  nodeType,
  language,
  onParametersChange,
  onMergeNodeParameters: _onMergeNodeParameters,
  onNodeUpdate,
  instanceId,
  request,
  verboseSchema = false,
}) => {
  const { t } = useLanguage();
  const [params, setParams] = useState<Record<string, unknown>>({});
  const nodeIdRef = useRef<string | undefined>(undefined);
  nodeIdRef.current = node?.id;
  const notifyParentTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    setParams(node?.parameters ?? {});
  }, [node?.id, node?.parameters]);

  useEffect(() => {
    return () => {
      if (notifyParentTimeoutRef.current != null) {
        clearTimeout(notifyParentTimeoutRef.current);
        notifyParentTimeoutRef.current = null;
      }
    };
  }, [node?.id]);

  const updateParam = useCallback(
    (key: string, value: unknown) => {
      setParams((prev) => {
        const next = { ...prev, [key]: value };
        const id = nodeIdRef.current;
        if (id) {
          if (notifyParentTimeoutRef.current != null) {
            clearTimeout(notifyParentTimeoutRef.current);
          }
          notifyParentTimeoutRef.current = setTimeout(() => {
            notifyParentTimeoutRef.current = null;
            onParametersChange(id, next);
          }, 0);
        }
        return next;
      });
    },
    [onParametersChange]
  );

  const dataFlow = useAutomation2DataFlow();
  const portTypeCatalog: Record<string, PortSchema> = (dataFlow?.portTypeCatalog as Record<string, PortSchema> | undefined) ?? {};

  // Phase-4 Schicht-4 — Pflicht-Params zuerst sortieren, damit der User
  // nicht nach unten scrollen muss, um zu sehen was fehlt.
  const sortedParameters: NodeTypeParameter[] = useMemo(() => {
    const all = nodeType?.parameters ?? [];
    const required = all.filter((p) => p.required);
    const optional = all.filter((p) => !p.required);
    return [...required, ...optional];
  }, [nodeType?.parameters]);

  // Pre-compute which required params are unbound on this node so we can
  // surface a panel-level summary banner. The hidden-param safety net lives
  // inside `findRequiredErrors` so banner, canvas badges and Run-button stay
  // in lockstep.
  // Banner labels are kept short (`param.name`); the full description is
  // attached as the tooltip below.
  const requiredErrors = useMemo(() => {
    if (!node || !nodeType) return [];
    return findRequiredErrors(node, nodeType, (p) => p.name);
  }, [node, nodeType]);

  // Resolve full descriptions per missing param (for the banner tooltip).
  const requiredErrorTooltip = useMemo(() => {
    if (!requiredErrors.length || !nodeType) return '';
    const byName = new Map((nodeType.parameters ?? []).map((p) => [p.name, p]));
    return requiredErrors
      .map((e) => {
        const p = byName.get(e.paramName);
        const desc = p ? (getLabel(p.description, language) || '') : '';
        return desc ? `${e.paramName}: ${desc}` : e.paramName;
      })
      .join('\n');
  }, [requiredErrors, nodeType, language]);

  if (!node || !nodeType) return null;

  const isTrigger = node.type.startsWith('trigger.');
  const showNameField = onNodeUpdate && !isTrigger;
  const parameters = sortedParameters;

  const inputPortDefs = nodeType.inputPorts ?? {};
  const outputPortDefs = nodeType.outputPorts ?? {};
  const inputPortEntries = Object.entries(inputPortDefs);
  const outputPortEntries = Object.entries(outputPortDefs);
  const hasPortInfo = inputPortEntries.length > 0 || outputPortEntries.length > 0;

  return (
    <div className={styles.nodeConfigPanel}>
      {showNameField && (
        <div className={styles.nodeConfigNameRow}>
          <label htmlFor="node-config-name">{t('Bezeichnung')}</label>
          <input
            id="node-config-name"
            type="text"
            value={node.title ?? ''}
            onChange={(e) => onNodeUpdate(node.id, { title: e.target.value })}
            placeholder={t('z.B. Kundenformular prüfen, Land')}
          />
          <p className={styles.nodeConfigNameHint}>
            {t('Wird im Data Picker angezeigt, um diesen Node zu identifizieren.')}
          </p>
        </div>
      )}
      <h4>{getLabel(nodeType?.label, language) || node.type}</h4>
      {nodeType?.description && (
        <p className={styles.nodeConfigDescription}>
          {getLabel(nodeType.description, language)}
        </p>
      )}
      {hasPortInfo && verboseSchema && (
        <details className={styles.nodeConfigPorts ?? ''} style={{ margin: '0 0 0.75rem', fontSize: '0.7rem' }}>
          <summary
            style={{
              cursor: 'pointer',
              color: 'var(--text-secondary)',
              fontWeight: 500,
              padding: '0.15rem 0',
              fontStyle: 'italic',
            }}
            title={t('Statische Schema-Referenz f\u00fcr diesen Node-Typ \u2014 keine Live-Daten')}
          >
            {t('Schema (Typ-Referenz, Sysadmin-Ansicht)')}
          </summary>
          {inputPortEntries.length > 0 && (
            <div style={{ marginTop: '0.4rem' }}>
              <div style={{ color: 'var(--text-secondary)', fontWeight: 600, marginBottom: 2 }}>
                {'\u2B07'} {t('Eingabe')}
              </div>
              {inputPortEntries.map(([idx, def]) => (
                <_PortFieldList
                  key={`in-${idx}`}
                  portIndex={Number(idx)}
                  schemaNames={def?.accepts ?? []}
                  catalog={portTypeCatalog}
                  emptyLabel={t('keine Felder')}
                  language={language}
                />
              ))}
            </div>
          )}
          {outputPortEntries.length > 0 && (
            <div style={{ marginTop: '0.4rem' }}>
              <div style={{ color: 'var(--text-secondary)', fontWeight: 600, marginBottom: 2 }}>
                {'\u2B06'} {t('Ausgabe')}
              </div>
              {outputPortEntries.map(([idx, def]) => (
                <_PortFieldList
                  key={`out-${idx}`}
                  portIndex={Number(idx)}
                  schemaNames={_schemaNamesFromOutputPort(def)}
                  catalog={portTypeCatalog}
                  emptyLabel={t('keine Felder')}
                  language={language}
                />
              ))}
            </div>
          )}
        </details>
      )}
      {requiredErrors.length > 0 && (
        <div
          style={{
            marginBottom: 8,
            padding: '6px 10px',
            background: 'rgba(220,53,69,0.10)',
            borderLeft: '3px solid var(--danger-color, #dc3545)',
            borderRadius: 4,
            fontSize: 12,
            color: 'var(--danger-color, #dc3545)',
            overflowWrap: 'anywhere',
            wordBreak: 'break-word',
          }}
          title={requiredErrorTooltip || undefined}
        >
          {t('Pflicht-Felder ohne Quelle:')}{' '}
          <strong>{requiredErrors.map((e) => e.paramLabel).join(', ')}</strong>
        </div>
      )}
      {parameters.map((param: NodeTypeParameter) => {
        // Safety net: hidden params have no UI footprint at all — no row,
        // no required-mark, no type-badge. Their value is system-set.
        if (param.frontendType === 'hidden') return null;
        const useRequiredPicker = _shouldUseRequiredPicker(param);
        if (useRequiredPicker) {
          return (
            <div key={param.name} style={{ marginBottom: 8 }}>
              <RequiredAttributePicker
                label={getLabel(param.description, language) || param.name}
                expectedType={param.type}
                value={params[param.name] ?? param.default}
                onChange={(val) => updateParam(param.name, val)}
              />
            </div>
          );
        }
        const frontendType = param.frontendType || 'text';
        const Renderer = FRONTEND_TYPE_RENDERERS[frontendType] ?? FRONTEND_TYPE_RENDERERS.text;
        return (
          <div key={param.name} style={{ marginBottom: 4, minWidth: 0, maxWidth: '100%' }}>
            <div
              style={{
                display: 'flex',
                alignItems: 'center',
                gap: 6,
                marginBottom: 2,
                flexWrap: 'wrap',
                minWidth: 0,
              }}
            >
              {param.required && (
                <span
                  title={t('Pflichtfeld')}
                  style={{ color: 'var(--danger-color, #dc3545)', fontWeight: 700, flexShrink: 0 }}
                >
                  *
                </span>
              )}
              {verboseSchema && param.type && (
                <span
                  title={t('Parameter-Typ')}
                  style={{
                    fontSize: 10,
                    fontWeight: 600,
                    color: 'var(--text-secondary)',
                    background: 'var(--bg-secondary)',
                    border: '1px solid var(--border-color)',
                    borderRadius: 4,
                    padding: '1px 6px',
                    maxWidth: '100%',
                    overflow: 'hidden',
                    textOverflow: 'ellipsis',
                    whiteSpace: 'nowrap',
                  }}
                >
                  {param.type}
                </span>
              )}
            </div>
            <Renderer
              param={param}
              value={params[param.name] ?? param.default}
              onChange={(val: unknown) => updateParam(param.name, val)}
              allParams={params}
              instanceId={instanceId}
              request={request}
              nodeType={node.type}
            />
          </div>
        );
      })}
    </div>
  );
};

/** Heuristic: required params with a Schicht-1 catalog type (non-primitive
 *  ref/record/list) get the typed RequiredAttributePicker; primitive scalars
 *  fall through to the legacy frontend-type renderer (text/number/select etc.)
 *  unless they have no frontendType at all and a non-trivial type. */
function _shouldUseRequiredPicker(param: NodeTypeParameter): boolean {
  if (!param.required) return false;
  if (!param.type) return false;
  // Hidden params never get a picker — they are system-set or rendered to
  // nothing on purpose. The render loop above also skips hidden rows entirely.
  if (param.frontendType === 'hidden') return false;
  // Always defer to specialized FE renderers when explicitly chosen.
  if (param.frontendType && _LEGACY_RENDERERS_THAT_HANDLE_BINDINGS.has(param.frontendType)) {
    return false;
  }
  // Catalog ref/record/list types are best handled by RequiredAttributePicker.
  if (/^(List\[|Dict\[)/.test(param.type)) return true;
  if (/^[A-Z]/.test(param.type)) return true;
  return false;
}

const _LEGACY_RENDERERS_THAT_HANDLE_BINDINGS = new Set([
  'userConnection',
  'featureInstance',
  'sharepointFolder',
  'sharepointFile',
  'clickupList',
  'clickupTask',
  'dataRef',
  'caseList',
  'fieldBuilder',
  'keyValueRows',
  'cron',
  'condition',
  'mappingTable',
  'filterExpression',
  'attachmentBuilder',
  'json',
  'modelMultiSelect',
]);

function _schemaNamesFromOutputPort(def: { schema?: string | GraphDefinedSchemaRef } | undefined): string[] {
  if (!def?.schema) return [];
  if (typeof def.schema === 'string') return [def.schema];
  if (typeof def.schema === 'object' && def.schema.kind === 'fromGraph') return ['FormPayload', 'FormPayload_dynamic'];
  return [];
}

interface _PortFieldListProps {
  portIndex: number;
  schemaNames: string[];
  catalog: Record<string, PortSchema>;
  emptyLabel: string;
  language: string;
}

const _PortFieldList: React.FC<_PortFieldListProps> = ({ portIndex, schemaNames, catalog, emptyLabel, language }) => {
  if (!schemaNames.length) return null;
  return (
    <div style={{ marginLeft: 4, marginBottom: 4 }}>
      <div style={{ color: 'var(--text-secondary)', fontSize: '0.7rem' }}>
        {`#${portIndex} `}{schemaNames.join(' | ')}
      </div>
      {schemaNames.map((name) => {
        const schema = catalog[name];
        const fields = schema?.fields ?? [];
        if (name === 'Transit') {
          return (
            <div key={name} style={{ marginLeft: 8, color: 'var(--text-tertiary)', fontStyle: 'italic', fontSize: '0.7rem' }}>
              {'\u00B7 Transit (durchgereichte Daten)'}
            </div>
          );
        }
        if (!fields.length) {
          return (
            <div key={name} style={{ marginLeft: 8, color: 'var(--text-tertiary)', fontSize: '0.7rem' }}>
              {`\u00B7 ${emptyLabel}`}
            </div>
          );
        }
        return (
          <ul key={name} style={{ margin: '2px 0 4px 16px', padding: 0, listStyle: 'none' }}>
            {fields.map((f) => (
              <li key={f.name} style={{ fontSize: '0.7rem', lineHeight: 1.4, color: 'var(--text-secondary)' }}>
                <span style={{ fontFamily: 'monospace', color: 'var(--text-primary)' }}>{f.name}</span>
                <span style={{ color: 'var(--text-tertiary)' }}>{`: ${f.type}`}</span>
                {!f.required && <span style={{ color: 'var(--text-tertiary)' }}>{' (optional)'}</span>}
                {f.description && (
                  <div style={{ color: 'var(--text-secondary)', marginLeft: 4 }}>
                    {getLabel(f.description, language)}
                  </div>
                )}
              </li>
            ))}
          </ul>
        );
      })}
    </div>
  );
};
