import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { Palette } from '../src/ui/Palette';
import { PropertyPanel } from '../src/ui/PropertyPanel';
import { SettingsDialog } from '../src/ui/SettingsDialog';
import { DEFAULT_CONFIG, type EditorController } from '../src/editor/paper';
import { defaultNodeProps } from '../src/model/nodeTypes';
import { defaultEdgeProps } from '../src/model/graph';

describe('Palette', () => {
  it('renders grouped node types with draggable items', () => {
    render(<Palette schemeId="classic" onQuickAdd={() => {}} />);
    expect(screen.getByText('Sources & supply')).toBeInTheDocument();
    expect(screen.getByText('Valves & fittings')).toBeInTheDocument();
    const pump = screen.getByTestId('palette-item-pump');
    expect(pump).toHaveAttribute('draggable', 'true');
  });

  it('filters items by search query', () => {
    render(<Palette schemeId="classic" onQuickAdd={() => {}} />);
    fireEvent.change(screen.getByPlaceholderText('Search nodes…'), { target: { value: 'boiler' } });
    expect(screen.getByTestId('palette-item-boiler')).toBeInTheDocument();
    expect(screen.queryByTestId('palette-item-pump')).not.toBeInTheDocument();
  });

  it('quick-adds a node on double click', () => {
    const onQuickAdd = vi.fn();
    render(<Palette schemeId="classic" onQuickAdd={onQuickAdd} />);
    fireEvent.doubleClick(screen.getByTestId('palette-item-valve'));
    expect(onQuickAdd).toHaveBeenCalledWith('valve');
  });
});

function stubController(kind: 'node' | 'edge', typeId: string | null, props: Record<string, unknown>) {
  const setCellProp = vi.fn();
  const controller = {
    cellKind: () => kind,
    cellTypeId: () => typeId,
    getCellProps: () => ({ ...props }),
    setCellProp,
  } as unknown as EditorController;
  return { controller, setCellProp };
}

describe('PropertyPanel', () => {
  it('shows a hint when nothing is selected', () => {
    render(<PropertyPanel controller={null} selection={[]} graphVersion={0} />);
    expect(screen.getByText(/Select a node or an edge/)).toBeInTheDocument();
  });

  it('renders grouped editors for a node', () => {
    const { controller } = stubController('node', 'pump', defaultNodeProps('pump'));
    render(<PropertyPanel controller={controller} selection={['n1']} graphVersion={0} />);
    expect(screen.getByTestId('prop-group-general')).toBeInTheDocument();
    expect(screen.getByTestId('prop-group-presentation')).toBeInTheDocument();
    expect(screen.getByTestId('prop-group-physics')).toBeInTheDocument();
    expect(screen.getByTestId('prop-title')).toHaveValue('Pump');
    expect(screen.getByTestId('prop-head')).toHaveValue(32);
    // catalogueItem picker renders catalogue options
    expect(screen.getByTestId('prop-medium')).toBeInTheDocument();
  });

  it('commits an edited numeric property through the controller', () => {
    const { controller, setCellProp } = stubController('node', 'pump', defaultNodeProps('pump'));
    render(<PropertyPanel controller={controller} selection={['n1']} graphVersion={0} />);
    const head = screen.getByTestId('prop-head');
    fireEvent.change(head, { target: { value: '48' } });
    fireEvent.blur(head);
    expect(setCellProp).toHaveBeenCalledWith('n1', 'head', 48);
  });

  it('rejects invalid values and shows the error instead of committing', () => {
    const { controller, setCellProp } = stubController('node', 'pump', defaultNodeProps('pump'));
    render(<PropertyPanel controller={controller} selection={['n1']} graphVersion={0} />);
    const head = screen.getByTestId('prop-head');
    fireEvent.change(head, { target: { value: '-5' } });
    fireEvent.blur(head);
    expect(setCellProp).not.toHaveBeenCalled();
    expect(screen.getByText(/must be ≥ 0/)).toBeInTheDocument();
  });

  it('renders edge properties with marker enums and read-only sim results', () => {
    const { controller } = stubController('edge', null, { ...defaultEdgeProps(), simFlow: 12.5 });
    render(<PropertyPanel controller={controller} selection={['e1']} graphVersion={0} />);
    expect(screen.getByText('Pipe / line')).toBeInTheDocument();
    expect(screen.getByTestId('prop-targetMarker')).toHaveValue('arrow');
    expect(screen.getByTestId('prop-simFlow')).toHaveTextContent('12.5');
    // multi catalogue items editor renders checkboxes
    expect(screen.getByTestId('prop-insulation')).toBeInTheDocument();
  });

  it('shows a count for multi-selection', () => {
    const { controller } = stubController('node', 'pump', {});
    render(<PropertyPanel controller={controller} selection={['a', 'b']} graphVersion={0} />);
    expect(screen.getByText('2 items selected.')).toBeInTheDocument();
  });
});

describe('SettingsDialog', () => {
  it('exposes the arc-on-intersection toggle and reports changes', () => {
    const onChange = vi.fn();
    render(
      <SettingsDialog open config={{ ...DEFAULT_CONFIG }} onChange={onChange} onClose={() => {}} />,
    );
    const arc = screen.getByTestId('setting-arc');
    expect(arc).toBeChecked();
    fireEvent.click(arc);
    expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ arcOnIntersections: false }));
  });

  it('renders nothing when closed', () => {
    const { container } = render(
      <SettingsDialog open={false} config={{ ...DEFAULT_CONFIG }} onChange={() => {}} onClose={() => {}} />,
    );
    expect(container).toBeEmptyDOMElement();
  });
});
