// Copyright (c) 2025 Patrick Motsch
// All rights reserved.
//
// Plan #2 — Track A1.1: Component-level tests for RequiredAttributePicker.
// Validates the 0/1/N rendering logic that orchestrates DataPicker selection
// + the iterierens-suggestion (T5, T6).
//
// We mock the two consumed contexts (LanguageContext + Automation2DataFlow)
// and the DataPicker child so we can assert on the picker UI in isolation.

import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { CanvasConnection, CanvasNode } from '../../editor/FlowCanvas';
import type { NodeType, PortField, PortSchema } from '../../../../api/workflowApi';
import type { DataRef, SystemVarRef } from './dataRef';

// ---------------------------------------------------------------------------
// Module mocks — must be registered before importing the SUT
// ---------------------------------------------------------------------------

vi.mock('../../../../providers/language/LanguageContext', () => ({
  useLanguage: () => ({ t: (s: string) => s }),
}));

let _ctxValue: unknown = null;
vi.mock('../../context/Automation2DataFlowContext', () => ({
  useAutomation2DataFlow: () => _ctxValue,
}));

vi.mock('./DataPicker', () => ({
  DataPicker: (props: {
    open: boolean;
    onClose: () => void;
    onPick: (ref: DataRef | SystemVarRef) => void;
  }) => {
    if (!props.open) return null;
    return (
      <div data-testid="mock-data-picker">
        <button
          type="button"
          onClick={() => {
            props.onPick({ type: 'ref', nodeId: 'picked', path: [], expectedType: 'DocumentList' });
            props.onClose();
          }}
        >
          mock-pick
        </button>
        <button type="button" onClick={props.onClose}>
          mock-close
        </button>
      </div>
    );
  },
}));

// SUT imported AFTER mocks (so mocks are applied)
import { RequiredAttributePicker } from './RequiredAttributePicker';

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------

function _field(name: string, type: string): PortField {
  return { name, type, description: '', required: false };
}

const _docListSchema: PortSchema = {
  name: 'DocumentList',
  fields: [_field('documents', 'List[UdmDocument]'), _field('count', 'int')],
};
const _udmDocumentSchema: PortSchema = {
  name: 'UdmDocument',
  fields: [_field('name', 'str'), _field('mimeType', 'str')],
};
const _portCatalog: Record<string, PortSchema> = {
  DocumentList: _docListSchema,
  UdmDocument: _udmDocumentSchema,
};

function _setContext(opts: {
  consumerNodeId: string;
  nodes: CanvasNode[];
  connections: CanvasConnection[];
  nodeTypes: NodeType[];
}) {
  _ctxValue = {
    currentNodeId: opts.consumerNodeId,
    nodes: opts.nodes,
    connections: opts.connections,
    nodeTypes: opts.nodeTypes,
    portTypeCatalog: _portCatalog,
    nodeOutputsPreview: {},
    systemVariables: {},
    language: 'de',
    getNodeLabel: (n: { id: string; title?: string }) => n.title ?? n.id,
    getAvailableSourceIds: () => opts.nodes.map((n) => n.id).filter((id) => id !== opts.consumerNodeId),
    parseGraphDefinedSchema: () => null,
  };
}

function _node(id: string, type: string): CanvasNode {
  return { id, type, title: id, x: 0, y: 0, inputs: 1, outputs: 1, parameters: {} };
}

function _conn(id: string, src: string, tgt: string): CanvasConnection {
  return { id, sourceId: src, sourceHandle: 0, targetId: tgt, targetHandle: 0 };
}

function _nodeType(id: string, outputSchema: string): NodeType {
  return {
    id,
    label: id,
    description: id,
    category: 'test',
    parameters: [],
    inputs: 1,
    outputs: 1,
    outputPorts: [{ schema: outputSchema }],
  } as unknown as NodeType;
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('RequiredAttributePicker — 0/1/N rendering (T5/T6)', () => {
  it('shows red "no source" pill when no upstream candidate matches (0-case)', () => {
    _setContext({
      consumerNodeId: 'cons',
      nodes: [_node('cons', 'ai.summarizeDocument')],
      connections: [],
      nodeTypes: [_nodeType('ai.summarizeDocument', 'AiResult')],
    });
    render(
      <RequiredAttributePicker
        label="Document List"
        expectedType="DocumentList"
        value={undefined}
        onChange={() => {}}
      />,
    );
    expect(
      screen.getByText(/Keine typkompatible Quelle vorhanden/i),
    ).toBeInTheDocument();
  });

  it('shows auto-bind suggestion when exactly one candidate matches (1-case)', () => {
    _setContext({
      consumerNodeId: 'cons',
      nodes: [_node('up', 'sharepoint.readDocs'), _node('cons', 'ai.summarizeDocument')],
      connections: [_conn('c1', 'up', 'cons')],
      nodeTypes: [
        _nodeType('sharepoint.readDocs', 'DocumentList'),
        _nodeType('ai.summarizeDocument', 'AiResult'),
      ],
    });
    render(
      <RequiredAttributePicker
        label="Document List"
        expectedType="DocumentList"
        value={undefined}
        onChange={() => {}}
      />,
    );
    expect(screen.getByText(/Vorschlag übernehmen/i)).toBeInTheDocument();
  });

  it('shows iterieren-suggestion when upstream is List[X] and required is X (T6)', () => {
    _setContext({
      consumerNodeId: 'cons',
      nodes: [_node('up', 'sharepoint.readDocs'), _node('cons', 'ai.summarizeDocument')],
      connections: [_conn('c1', 'up', 'cons')],
      nodeTypes: [
        _nodeType('sharepoint.readDocs', 'DocumentList'),
        _nodeType('ai.summarizeDocument', 'AiResult'),
      ],
    });
    render(
      <RequiredAttributePicker
        label="Single document"
        expectedType="UdmDocument"
        value={undefined}
        onChange={() => {}}
      />,
    );
    expect(screen.getByText(/iterieren/i)).toBeInTheDocument();
  });

  it('renders bound chip + "Andere wählen" when value is already a DataRef', async () => {
    _setContext({
      consumerNodeId: 'cons',
      nodes: [_node('up', 'sharepoint.readDocs'), _node('cons', 'ai.summarizeDocument')],
      connections: [_conn('c1', 'up', 'cons')],
      nodeTypes: [
        _nodeType('sharepoint.readDocs', 'DocumentList'),
        _nodeType('ai.summarizeDocument', 'AiResult'),
      ],
    });
    const onChange = vi.fn();
    render(
      <RequiredAttributePicker
        label="Document List"
        expectedType="DocumentList"
        value={{ type: 'ref', nodeId: 'up', path: [], expectedType: 'DocumentList' }}
        onChange={onChange}
      />,
    );
    expect(screen.getByText('up')).toBeInTheDocument();
    const clearButton = screen.getByTitle(/Bindung entfernen/i);
    await userEvent.click(clearButton);
    expect(onChange).toHaveBeenCalledWith(null);
  });

  it('opens DataPicker via "Andere wählen" and forwards the picked ref to onChange', async () => {
    _setContext({
      consumerNodeId: 'cons',
      nodes: [_node('up', 'sharepoint.readDocs'), _node('cons', 'ai.summarizeDocument')],
      connections: [_conn('c1', 'up', 'cons')],
      nodeTypes: [
        _nodeType('sharepoint.readDocs', 'DocumentList'),
        _nodeType('ai.summarizeDocument', 'AiResult'),
      ],
    });
    const onChange = vi.fn();
    render(
      <RequiredAttributePicker
        label="Document List"
        expectedType="DocumentList"
        value={{ type: 'ref', nodeId: 'up', path: [], expectedType: 'DocumentList' }}
        onChange={onChange}
      />,
    );
    const otherButton = screen.getByText(/Andere wählen…/i);
    await userEvent.click(otherButton);
    expect(screen.getByTestId('mock-data-picker')).toBeInTheDocument();
    await userEvent.click(screen.getByText('mock-pick'));
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'ref', nodeId: 'picked', expectedType: 'DocumentList' }),
    );
  });
});
