import React from 'react';
import type { CanvasComponent, DropdownOption, TableColumn, TableRow } from '../../types/project';
import type { ComponentRuntimeState } from './usePreviewRuntime';
import { labelDisplayValue } from './bindingUtils';
import styles from './PreviewComponent.module.css';

// ── Per-type renderers ────────────────────────────────────────────────────────

type LabelRendererProps = {
  /** Configured/design-time label text from component properties. */
  configuredLabel: string;
  /** Runtime state — a defined value overrides configuredLabel. */
  runtimeState?: ComponentRuntimeState;
};

function LabelRenderer({ configuredLabel, runtimeState }: LabelRendererProps): React.ReactElement {
  // A defined runtime value overrides the configured label.
  // Falsy runtime values (false, 0, "", null) must still override — only
  // undefined means "no runtime value set".
  const display =
    runtimeState?.value !== undefined
      ? labelDisplayValue(runtimeState.value)
      : configuredLabel;

  return <span className={styles.label}>{display || '\u00a0'}</span>;
}

type ButtonRendererProps = {
  label: string;
  isLoading: boolean;
  error?: string;
  onClick: () => void;
};

function ButtonRenderer({ label, isLoading, error, onClick }: ButtonRendererProps): React.ReactElement {
  return (
    <div className={styles.buttonWrapper}>
      <button
        className={styles.button}
        type="button"
        disabled={isLoading}
        onClick={onClick}
      >
        {isLoading ? <span className={styles.spinner} aria-hidden="true" /> : null}
        {isLoading ? 'Running…' : (label || 'Button')}
      </button>
      {error && (
        <div className={styles.buttonError} role="alert" title={error}>
          {error}
        </div>
      )}
    </div>
  );
}

function TextInputRenderer({
  label,
  placeholder,
  value,
  disabled,
  onChange,
}: {
  label: string;
  placeholder: string;
  value: string;
  disabled: boolean;
  onChange: (value: string) => void;
}): React.ReactElement {
  return (
    <div className={styles.inputWrapper}>
      {label && <label className={styles.inputLabel}>{label}</label>}
      <input
        className={styles.input}
        type="text"
        value={value}
        placeholder={placeholder}
        disabled={disabled}
        onChange={(e) => onChange(e.target.value)}
      />
    </div>
  );
}

// ── Dropdown renderer ─────────────────────────────────────────────────────────

type DropdownRendererProps = {
  label: string;
  placeholder: string;
  options: DropdownOption[];
  /** Current selection: runtimeState.value if set, else configured properties.value */
  selectedValue: string;
  disabled: boolean;
  onChange: (value: string) => void;
};

function DropdownRenderer({
  label,
  placeholder,
  options,
  selectedValue,
  disabled,
  onChange,
}: DropdownRendererProps): React.ReactElement {
  return (
    <div className={styles.dropdownWrapper}>
      {label && <label className={styles.dropdownLabel}>{label}</label>}
      <select
        className={styles.dropdown}
        value={selectedValue}
        disabled={disabled}
        onChange={(e) => onChange(e.target.value)}
      >
        {/* Disabled placeholder option — shown when no value selected */}
        <option value="" disabled>
          {placeholder || 'Select an option'}
        </option>
        {options.map((opt) => (
          <option key={opt.value} value={opt.value}>
            {opt.label}
          </option>
        ))}
      </select>
    </div>
  );
}

// ── Table renderer ────────────────────────────────────────────────────────────

/** Converts a cell value to a display string per the step spec. */
function renderTableCellValue(value: unknown): string {
  if (value === undefined) return '';
  if (value === null) return 'null';
  if (typeof value === 'string') return value;
  if (typeof value === 'number' || typeof value === 'boolean') return String(value);
  try {
    return JSON.stringify(value);
  } catch {
    return '[serialisation error]';
  }
}

type TableRendererProps = {
  label: string;
  columns: TableColumn[];
  rows: TableRow[];
  disabled: boolean;
  selectedIndex?: number;
  onRowSelect: (index: number, row: TableRow) => void;
};

function TableRenderer({
  label,
  columns,
  rows,
  disabled,
  selectedIndex,
  onRowSelect,
}: TableRendererProps): React.ReactElement {
  const effectiveCols: TableColumn[] =
    columns.length > 0
      ? columns
      : rows.length > 0
        ? Object.keys(rows[0]).map((k) => ({ key: k, header: k }))
        : [];

  return (
    <div className={[styles.tableWrapper, disabled ? styles.tableDisabled : ''].join(' ')}>
      {label && <div className={styles.tableLabel}>{label}</div>}
      {effectiveCols.length === 0 && rows.length === 0 ? (
        <div className={styles.tableEmpty}>No data</div>
      ) : (
        <div className={styles.tableScroll}>
          <table className={styles.table}>
            <thead>
              <tr>
                {effectiveCols.map((col) => (
                  <th key={col.key} className={styles.tableTh}>{col.header}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {rows.length === 0 ? (
                <tr>
                  <td
                    className={styles.tableTd}
                    colSpan={effectiveCols.length || 1}
                    style={{ textAlign: 'center', color: '#8b949e' }}
                  >
                    No rows
                  </td>
                </tr>
              ) : (
                rows.map((row, ri) => (
                  <tr
                    key={ri}
                    className={[
                      styles.tableRow,
                      ri === selectedIndex ? styles.tableRowSelected : '',
                      disabled ? '' : styles.tableRowClickable,
                    ].join(' ')}
                    onClick={() => { if (!disabled) onRowSelect(ri, row); }}
                  >
                    {effectiveCols.map((col) => (
                      <td key={col.key} className={styles.tableTd}>
                        {renderTableCellValue(row[col.key])}
                      </td>
                    ))}
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

type JsonViewerRendererProps = {
  defaultValue: string;
  runtimeState?: ComponentRuntimeState;
};

function JsonViewerRenderer({ defaultValue, runtimeState }: JsonViewerRendererProps): React.ReactElement {
  // Loading state
  if (runtimeState?.loading) {
    return (
      <div className={styles.jsonViewer}>
        <div className={styles.jsonStatus}>
          <span className={styles.spinner} aria-hidden="true" />
          Waiting for response…
        </div>
      </div>
    );
  }

  // Error state
  if (runtimeState?.error) {
    return (
      <div className={styles.jsonViewer}>
        <div className={styles.jsonError} role="alert">
          <strong>Error</strong>
          <pre className={styles.jsonErrorPre}>{runtimeState.error}</pre>
        </div>
      </div>
    );
  }

  // Runtime value from a completed action call
  if (runtimeState?.value !== undefined) {
    const display = typeof runtimeState.value === 'string'
      ? runtimeState.value
      : JSON.stringify(runtimeState.value, null, 2);
    return (
      <div className={styles.jsonViewer}>
        <pre className={styles.jsonPre}>{display}</pre>
      </div>
    );
  }

  // Design-time defaultValue (no action has fired yet)
  let display: string;
  if (defaultValue) {
    try {
      display = JSON.stringify(JSON.parse(defaultValue), null, 2);
    } catch {
      display = defaultValue;
    }
  } else {
    display = JSON.stringify(
      { status: 'No data', hint: 'Click a bound button to populate this viewer.' },
      null,
      2,
    );
  }

  return (
    <div className={styles.jsonViewer}>
      <pre className={styles.jsonPre}>{display}</pre>
    </div>
  );
}

// ── Main component ────────────────────────────────────────────────────────────

type PreviewComponentProps = {
  component: CanvasComponent;
  runtimeState?: ComponentRuntimeState;
  isLoading: boolean;
  onButtonClick: (componentId: string) => void;
  onTextInputChange: (componentId: string, value: string) => void;
  onDropdownChange: (componentId: string, value: string) => void;
  onTableRowSelect: (componentId: string, index: number, row: Record<string, unknown>) => void;
};

function PreviewComponent({
  component,
  runtimeState,
  isLoading,
  onButtonClick,
  onTextInputChange,
  onDropdownChange,
  onTableRowSelect,
}: PreviewComponentProps): React.ReactElement | null {
  const { type, position, size, properties } = component;

  // Respect the visibility flag — hidden components are not rendered
  if (properties.visible === false) return null;

  const label        = typeof properties.label        === 'string' ? properties.label        : '';
  const placeholder  = typeof properties.placeholder  === 'string' ? properties.placeholder  : '';
  const defaultValue = typeof properties.defaultValue === 'string' ? properties.defaultValue : '';
  const disabled     = properties.disabled === true;

  return (
    <div
      className={styles.wrapper}
      style={{
        left:   position.x,
        top:    position.y,
        width:  size.width,
        ...(type === 'TextInput' || type === 'JsonViewer' || type === 'Dropdown' || type === 'Table'
            ? { minHeight: size.height }
            : { height: size.height }),
      }}
    >
      {type === 'Label' && (
        <LabelRenderer configuredLabel={label} runtimeState={runtimeState} />
      )}

      {type === 'Button' && (
        <ButtonRenderer
          label={label}
          isLoading={isLoading}
          error={runtimeState?.error}
          onClick={() => onButtonClick(component.id)}
        />
      )}

      {type === 'TextInput' && (
        <TextInputRenderer
          label={label}
          placeholder={placeholder}
          value={runtimeState?.textValue ?? defaultValue}
          disabled={disabled}
          onChange={(val) => onTextInputChange(component.id, val)}
        />
      )}

      {type === 'JsonViewer' && (
        <JsonViewerRenderer
          defaultValue={defaultValue}
          runtimeState={runtimeState}
        />
      )}

      {type === 'Table' && (() => {
        const rawColumns = properties.columns;
        const configuredColumns: TableColumn[] = Array.isArray(rawColumns)
          ? (rawColumns as TableColumn[]).filter(
              (c) => c && typeof c.key === 'string' && typeof c.header === 'string',
            )
          : [];
        const rawRows = properties.rows;
        const configuredRows: TableRow[] = Array.isArray(rawRows)
          ? (rawRows as TableRow[]).filter(
              (r) => r !== null && typeof r === 'object' && !Array.isArray(r),
            )
          : [];
        // Runtime rows override configured rows when defined (including an empty array).
        // undefined means "no runtime rows yet" — fall back to configured rows.
        const effectiveRows: TableRow[] =
          runtimeState?.rows !== undefined ? runtimeState.rows : configuredRows;
        return (
          <TableRenderer
            label={label}
            columns={configuredColumns}
            rows={effectiveRows}
            disabled={disabled}
            selectedIndex={runtimeState?.selectedIndex}
            onRowSelect={(idx, row) => onTableRowSelect(component.id, idx, row)}
          />
        );
      })()}

      {type === 'Dropdown' && (() => {
        // Configured options from design-time properties
        const rawConfiguredOptions = properties.options;
        const configuredOptions: DropdownOption[] = Array.isArray(rawConfiguredOptions)
          ? (rawConfiguredOptions as DropdownOption[]).filter(
              (o) => o && typeof o.label === 'string' && typeof o.value === 'string',
            )
          : [];
        // Runtime options override configured options when defined.
        // An empty runtime array is meaningful — do not fall back.
        const effectiveOptions: DropdownOption[] =
          runtimeState?.options !== undefined
            ? runtimeState.options
            : configuredOptions;
        // configured value from properties (string)
        const configuredValue = typeof properties.value === 'string' ? properties.value : '';
        // runtime selection overrides configured value (undefined means no runtime selection yet)
        const selectedValue =
          runtimeState?.value !== undefined
            ? String(runtimeState.value)
            : configuredValue;
        return (
          <DropdownRenderer
            label={label}
            placeholder={placeholder}
            options={effectiveOptions}
            selectedValue={selectedValue}
            disabled={disabled}
            onChange={(val) => onDropdownChange(component.id, val)}
          />
        );
      })()}
    </div>
  );
}

export default PreviewComponent;
