import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NodeInspector } from '../NodeInspector';
import type { WfNode, NodeTypeDef, NodeExecView } from '../types';

const node: WfNode = {
  id: 'n1',
  name: 'Send email',
  type: 'action.email',
  parameters: { to: 'a@b.com', retries: 3, urgent: true },
};

const nodeType: NodeTypeDef & { parameterSchema?: Record<string, unknown> } = {
  slug: 'action.email',
  name: 'Send email',
  category: 'action',
  parameterSchema: {
    type: 'object',
    properties: {
      to: { type: 'string', title: 'Recipient' },
      retries: { type: 'number', title: 'Retries' },
      urgent: { type: 'boolean', title: 'Urgent' },
    },
  },
};

describe('NodeInspector', () => {
  it('renders a field per schema property with current values', () => {
    render(<NodeInspector node={node} nodeType={nodeType} />);
    expect(screen.getByLabelText('Recipient')).toHaveValue('a@b.com');
    expect(screen.getByLabelText('Retries')).toHaveValue(3);
    expect(screen.getByLabelText('Urgent')).toBeChecked();
  });

  it('emits parameter changes via onParametersChange', () => {
    const onParametersChange = jest.fn();
    render(
      <NodeInspector
        node={node}
        nodeType={nodeType}
        onParametersChange={onParametersChange}
      />
    );
    fireEvent.change(screen.getByLabelText('Recipient'), {
      target: { value: 'c@d.com' },
    });
    expect(onParametersChange).toHaveBeenCalledWith(
      'n1',
      expect.objectContaining({ to: 'c@d.com' })
    );
  });

  it('renders an empty state when no node is selected', () => {
    render(<NodeInspector node={null} />);
    expect(screen.getByText(/select a node/i)).toBeInTheDocument();
  });

  it('shows read-only input/output tabs when given an execution view', async () => {
    const exec: NodeExecView = {
      nodeId: 'n1',
      status: 'success',
      inputData: { foo: 'in' },
      outputData: { foo: 'out' },
    };
    const user = userEvent.setup();
    render(<NodeInspector node={node} nodeType={nodeType} execution={exec} />);
    // the exec tabs exist
    expect(screen.getByRole('tab', { name: /input/i })).toBeInTheDocument();
    expect(screen.getByRole('tab', { name: /output/i })).toBeInTheDocument();
    // switch to the Output tab
    await user.click(screen.getByRole('tab', { name: /output/i }));
    expect(screen.getByText(/"foo": "out"/)).toBeInTheDocument();
  });

  it('marks required fields (schema.required[]) with an indicator', () => {
    const requiredType: typeof nodeType = {
      ...nodeType,
      parameterSchema: {
        type: 'object',
        required: ['to'],
        properties: {
          to: { type: 'string', title: 'Recipient' },
          retries: { type: 'number', title: 'Retries' },
        },
      },
    };
    render(<NodeInspector node={node} nodeType={requiredType} />);
    // required field exposes aria-required + a visible "*" marker
    expect(screen.getByLabelText(/Recipient/)).toHaveAttribute(
      'aria-required',
      'true'
    );
    // non-required field is not marked
    expect(screen.getByLabelText('Retries')).not.toHaveAttribute(
      'aria-required',
      'true'
    );
    expect(screen.getByText('*')).toBeInTheDocument();
  });
});
