import React, { useCallback, useState } from 'react';
import Palette from './Palette/Palette';
import Canvas from './Canvas/Canvas';
import ProjectToolbar from '../ProjectToolbar/ProjectToolbar';
import { useProject } from '../../context/ProjectContext';
import styles from './VisualEditor.module.css';
import type { ComponentType, DropdownOption, TableColumn, TableRow } from '../../types/project';

// ── Dropdown options row editor ───────────────────────────────────────────────

type DropdownOptionsEditorProps = {
  componentId: string;
  options: DropdownOption[];
  configuredValue: string;
  onOptionsChange: (options: DropdownOption[]) => void;
  onConfiguredValueChange: (value: string) => void;
};

function DropdownOptionsEditor({
  options,
  configuredValue,
  onOptionsChange,
  onConfiguredValueChange,
}: DropdownOptionsEditorProps): React.ReactElement {
  const [optionsError, setOptionsError] = useState<string | null>(null);

  const handleAddOption = () => {
    onOptionsChange([...options, { label: '', value: '' }]);
  };

  const handleChangeOption = (index: number, field: 'label' | 'value', text: string) => {
    const updated = options.map((o, i) =>
      i === index ? { ...o, [field]: text } : o,
    );
    onOptionsChange(updated);
    // Warn on duplicate values (non-blocking)
    const values = updated.map((o) => o.value).filter(Boolean);
    const hasDups = values.length !== new Set(values).size;
    setOptionsError(hasDups ? 'Duplicate option values detected.' : null);
  };

  const handleRemoveOption = (index: number) => {
    const updated = options.filter((_, i) => i !== index);
    onOptionsChange(updated);
    const values = updated.map((o) => o.value).filter(Boolean);
    const hasDups = values.length !== new Set(values).size;
    setOptionsError(hasDups ? 'Duplicate option values detected.' : null);
  };

  // Warn if configured value is not in option list (non-blocking)
  const configuredValueValid =
    configuredValue === '' ||
    options.some((o) => o.value === configuredValue);

  return (
    <div style={{ marginTop: 8 }}>
      <div className={styles.infoKey} style={{ marginBottom: 4, display: 'block' }}>Options</div>
      {options.map((opt, i) => (
        <div key={i} style={{ display: 'flex', gap: 4, marginBottom: 3, alignItems: 'center' }}>
          <input
            className={styles.infoInput}
            style={{ width: 70 }}
            placeholder="Label"
            value={opt.label}
            onChange={(e) => handleChangeOption(i, 'label', e.target.value)}
          />
          <input
            className={styles.infoInput}
            style={{ width: 70 }}
            placeholder="Value"
            value={opt.value}
            onChange={(e) => handleChangeOption(i, 'value', e.target.value)}
          />
          <button
            style={{ fontSize: 10, padding: '2px 5px', cursor: 'pointer', background: 'none',
              border: '1px solid #d0d7de', borderRadius: 3, color: '#b91c1c', flexShrink: 0 }}
            onClick={() => handleRemoveOption(i)}
            title="Remove option"
          >
            ✕
          </button>
        </div>
      ))}
      {optionsError && (
        <div style={{ fontSize: 10, color: '#b91c1c', marginBottom: 4 }}>{optionsError}</div>
      )}
      <button
        style={{ fontSize: 11, padding: '2px 8px', cursor: 'pointer', background: '#f7f8fa',
          border: '1px solid #d0d7de', borderRadius: 3, color: '#1f2328', marginBottom: 6 }}
        onClick={handleAddOption}
      >
        + Add option
      </button>
      <div className={styles.infoRow}>
        <label className={styles.infoKey} htmlFor="prop-dropdown-value">Default value</label>
        <input
          id="prop-dropdown-value"
          className={styles.infoInput}
          type="text"
          placeholder="e.g. dev"
          value={configuredValue}
          onChange={(e) => onConfiguredValueChange(e.target.value)}
          style={!configuredValueValid ? { borderColor: '#f59e0b' } : undefined}
          title={!configuredValueValid ? 'Value not found in options' : undefined}
        />
      </div>
      {!configuredValueValid && (
        <div style={{ fontSize: 10, color: '#92400e', marginBottom: 4 }}>
          Default value not found in options.
        </div>
      )}
    </div>
  );
}

// ── Table columns editor ──────────────────────────────────────────────────────

type TableColumnsEditorProps = {
  columns: TableColumn[];
  onColumnsChange: (columns: TableColumn[]) => void;
};

function TableColumnsEditor({ columns, onColumnsChange }: TableColumnsEditorProps): React.ReactElement {
  const [colError, setColError] = useState<string | null>(null);

  const validate = (cols: TableColumn[]): string | null => {
    const keys = cols.map((c) => c.key).filter(Boolean);
    const dupKeys = keys.filter((k, i) => keys.indexOf(k) !== i);
    if (dupKeys.length > 0) return `Duplicate column keys: ${dupKeys.map((k) => `"${k}"`).join(', ')}.`;
    const emptyKey = cols.some((c) => !c.key);
    if (emptyKey) return 'Column key must not be empty.';
    return null;
  };

  const handleAdd = () => {
    const updated = [...columns, { key: '', header: '' }];
    onColumnsChange(updated);
    setColError(validate(updated));
  };

  const handleChange = (index: number, field: 'key' | 'header', value: string) => {
    const updated = columns.map((c, i) => i === index ? { ...c, [field]: value } : c);
    onColumnsChange(updated);
    setColError(validate(updated));
  };

  const handleRemove = (index: number) => {
    const updated = columns.filter((_, i) => i !== index);
    onColumnsChange(updated);
    setColError(validate(updated));
  };

  return (
    <div style={{ marginTop: 8 }}>
      <div className={styles.infoKey} style={{ marginBottom: 4, display: 'block' }}>Columns</div>
      {columns.map((col, i) => (
        <div key={i} style={{ display: 'flex', gap: 4, marginBottom: 3, alignItems: 'center' }}>
          <input
            className={styles.infoInput}
            style={{ width: 70 }}
            placeholder="Label"
            value={col.header}
            onChange={(e) => handleChange(i, 'header', e.target.value)}
          />
          <input
            className={styles.infoInput}
            style={{ width: 70, borderColor: !col.key ? '#f59e0b' : undefined }}
            placeholder="Key"
            value={col.key}
            onChange={(e) => handleChange(i, 'key', e.target.value)}
            title={!col.key ? 'Key must not be empty' : undefined}
          />
          <button
            style={{ fontSize: 10, padding: '2px 5px', cursor: 'pointer', background: 'none',
              border: '1px solid #d0d7de', borderRadius: 3, color: '#b91c1c', flexShrink: 0 }}
            onClick={() => handleRemove(i)}
            title="Remove column"
          >
            ✕
          </button>
        </div>
      ))}
      {colError && (
        <div style={{ fontSize: 10, color: '#b91c1c', marginBottom: 4 }}>{colError}</div>
      )}
      <button
        style={{ fontSize: 11, padding: '2px 8px', cursor: 'pointer', background: '#f7f8fa',
          border: '1px solid #d0d7de', borderRadius: 3, color: '#1f2328', marginBottom: 6 }}
        onClick={handleAdd}
      >
        + Add column
      </button>
    </div>
  );
}

// ── Table rows editor ─────────────────────────────────────────────────────────

type TableRowsEditorProps = {
  rows: TableRow[];
  onRowsChange: (rows: TableRow[]) => void;
};

function TableRowsEditor({ rows, onRowsChange }: TableRowsEditorProps): React.ReactElement {
  // Draft text for the JSON textarea; initialise from current rows
  const [draft, setDraft] = useState<string>(() => JSON.stringify(rows, null, 2));
  const [rowsError, setRowsError] = useState<string | null>(null);

  // Sync draft when rows prop changes externally (e.g. on component selection)
  const prevRowsRef = React.useRef<TableRow[]>(rows);
  if (rows !== prevRowsRef.current) {
    prevRowsRef.current = rows;
    setDraft(JSON.stringify(rows, null, 2));
    setRowsError(null);
  }

  const handleApply = () => {
    let parsed: unknown;
    try {
      parsed = JSON.parse(draft);
    } catch (e: unknown) {
      setRowsError(`Invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
      return;
    }
    if (!Array.isArray(parsed)) {
      setRowsError('Rows must be a JSON array.');
      return;
    }
    for (let i = 0; i < parsed.length; i++) {
      const el = parsed[i];
      if (el === null || typeof el !== 'object' || Array.isArray(el)) {
        setRowsError(
          `Row at index ${i} must be a non-null object (got ${
            el === null ? 'null' : Array.isArray(el) ? 'array' : typeof el
          }).`,
        );
        return;
      }
    }
    setRowsError(null);
    onRowsChange(parsed as TableRow[]);
  };

  return (
    <div style={{ marginTop: 8 }}>
      <div className={styles.infoKey} style={{ marginBottom: 4, display: 'block' }}>
        Rows (JSON array)
      </div>
      <textarea
        style={{
          width: '100%', minHeight: 80, fontFamily: 'monospace', fontSize: 11,
          border: '1px solid #d0d7de', borderRadius: 3, padding: '4px 6px',
          resize: 'vertical', boxSizing: 'border-box',
          borderColor: rowsError ? '#f59e0b' : undefined,
        }}
        value={draft}
        onChange={(e) => setDraft(e.target.value)}
        spellCheck={false}
      />
      {rowsError && (
        <div style={{ fontSize: 10, color: '#b91c1c', marginBottom: 4 }}>{rowsError}</div>
      )}
      <button
        style={{ fontSize: 11, padding: '2px 8px', cursor: 'pointer', background: '#f7f8fa',
          border: '1px solid #d0d7de', borderRadius: 3, color: '#1f2328', marginBottom: 6 }}
        onClick={handleApply}
      >
        Apply rows
      </button>
    </div>
  );
}

// ── Component ─────────────────────────────────────────────────────────────────

function VisualEditor(): React.ReactElement {
  const {
    doc,
    activePage,
    canvas,
    selectedId,
    selectComponent,
    isLoading,
  } = useProject();

  const {
    addComponent,
    moveComponent,
    removeComponent,
    updateComponentProperty,
    updateComponentName,
    updateComponentSize,
  } = canvas;

  const selectedComponent = activePage.components.find((x) => x.id === selectedId) ?? null;

  // Local state for width/height text inputs (allows typing without snapping mid-edit)
  const [widthDraft, setWidthDraft] = useState<string>('');
  const [heightDraft, setHeightDraft] = useState<string>('');

  // Sync drafts when selection changes
  const prevSelectedId = React.useRef<string | null>(null);
  if (selectedComponent && selectedComponent.id !== prevSelectedId.current) {
    prevSelectedId.current = selectedComponent.id;
    setWidthDraft(String(selectedComponent.size.width));
    setHeightDraft(String(selectedComponent.size.height));
  }
  if (!selectedComponent && prevSelectedId.current !== null) {
    prevSelectedId.current = null;
  }

  // Duplicate-name detection
  const isDuplicateName = useCallback(
    (name: string, excludeId: string) =>
      activePage.components.some((c) => c.id !== excludeId && c.name === name),
    [activePage.components],
  );

  const handleNameChange = useCallback(
    (id: string, value: string) => {
      updateComponentName(id, value);
    },
    [updateComponentName],
  );

  const handleWidthCommit = useCallback(
    (id: string, currentHeight: number) => {
      const n = parseInt(widthDraft, 10);
      if (!isNaN(n) && n > 0) {
        updateComponentSize(id, n, currentHeight);
      } else {
        // Reset draft to actual value if invalid
        setWidthDraft(selectedComponent ? String(selectedComponent.size.width) : widthDraft);
      }
    },
    [widthDraft, updateComponentSize, selectedComponent],
  );

  const handleHeightCommit = useCallback(
    (id: string, currentWidth: number) => {
      const n = parseInt(heightDraft, 10);
      if (!isNaN(n) && n > 0) {
        updateComponentSize(id, currentWidth, n);
      } else {
        setHeightDraft(selectedComponent ? String(selectedComponent.size.height) : heightDraft);
      }
    },
    [heightDraft, updateComponentSize, selectedComponent],
  );

  // Add from palette click — place at a staggered default position
  const handlePaletteAdd = useCallback(
    (type: ComponentType) => {
      const offset = activePage.components.length * 24;
      addComponent(type, 32 + offset, 32 + offset);
    },
    [activePage.components.length, addComponent],
  );

  // Add from canvas drop
  const handleCanvasDrop = useCallback(
    (type: ComponentType, x: number, y: number) => {
      addComponent(type, x, y);
    },
    [addComponent],
  );

  // Show a loading overlay while fetching a project from the backend
  if (isLoading) {
    return (
      <div className={styles.editor}>
        <ProjectToolbar />
        <div className={styles.loadingOverlay}>Loading project…</div>
      </div>
    );
  }

  return (
    <div className={styles.editor}>
      {/* ── Project toolbar (New / Save / Load) ───────────────────── */}
      <ProjectToolbar />

      {/* ── Editor toolbar (page info) ────────────────────────────── */}
      <div className={styles.editorBar}>
        <span className={styles.projectName}>{doc.project.name}</span>
        <span className={styles.pageName}>{activePage.name}</span>
        <span className={styles.componentCount}>
          {activePage.components.length} component
          {activePage.components.length !== 1 ? 's' : ''}
        </span>
      </div>

      {/* ── Main area: palette | canvas | info ────────────────────── */}
      <div className={styles.body}>
        <Palette onAdd={handlePaletteAdd} />

        <Canvas
          components={activePage.components}
          selectedId={selectedId}
          onSelect={selectComponent}
          onMove={moveComponent}
          onRemove={(id) => { removeComponent(id); selectComponent(null); }}
          onDrop={handleCanvasDrop}
        />

        {/* Selection info + live JSON */}
        <aside className={styles.info}>
          <div className={styles.infoHeader}>Selection</div>
          {selectedComponent ? (
            <div className={styles.infoBody}>
                {/* Type + position — read-only */}
                {([
                  ['Type', selectedComponent.type],
                  ['X',    `${selectedComponent.position.x}px`],
                  ['Y',    `${selectedComponent.position.y}px`],
                ] as [string, string][]).map(([k, v]) => (
                  <div key={k} className={styles.infoRow}>
                    <span className={styles.infoKey}>{k}</span>
                    <span className={styles.infoVal}>{v}</span>
                  </div>
                ))}
  
                {/* Editable name */}
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-name">Name</label>
                  <input
                    id="prop-name"
                    className={styles.infoInput}
                    type="text"
                    value={selectedComponent.name}
                    onChange={(e) => handleNameChange(selectedComponent.id, e.target.value)}
                    style={
                      selectedComponent.name === '' ||
                      isDuplicateName(selectedComponent.name, selectedComponent.id)
                        ? { borderColor: '#f59e0b' }
                        : undefined
                    }
                    title={
                      selectedComponent.name === ''
                        ? 'Name must not be empty'
                        : isDuplicateName(selectedComponent.name, selectedComponent.id)
                          ? 'Duplicate name — components must have unique names'
                          : undefined
                    }
                  />
                </div>
                {selectedComponent.name === '' && (
                  <div style={{ fontSize: 10, color: '#b91c1c', marginBottom: 2 }}>
                    Name must not be empty.
                  </div>
                )}
                {selectedComponent.name !== '' &&
                  isDuplicateName(selectedComponent.name, selectedComponent.id) && (
                  <div style={{ fontSize: 10, color: '#92400e', marginBottom: 2 }}>
                    Duplicate name detected.
                  </div>
                )}
  
                {/* Editable width */}
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-width">W</label>
                  <input
                    id="prop-width"
                    className={styles.infoInput}
                    type="number"
                    min={1}
                    value={widthDraft}
                    onChange={(e) => setWidthDraft(e.target.value)}
                    onBlur={() => handleWidthCommit(selectedComponent.id, selectedComponent.size.height)}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter') handleWidthCommit(selectedComponent.id, selectedComponent.size.height);
                    }}
                  />
                </div>
  
                {/* Editable height */}
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-height">H</label>
                  <input
                    id="prop-height"
                    className={styles.infoInput}
                    type="number"
                    min={1}
                    value={heightDraft}
                    onChange={(e) => setHeightDraft(e.target.value)}
                    onBlur={() => handleHeightCommit(selectedComponent.id, selectedComponent.size.width)}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter') handleHeightCommit(selectedComponent.id, selectedComponent.size.width);
                    }}
                  />
                </div>
  
                {/* Visible checkbox */}
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-visible">Visible</label>
                  <input
                    id="prop-visible"
                    type="checkbox"
                    checked={selectedComponent.properties.visible !== false}
                    onChange={(e) =>
                      updateComponentProperty(selectedComponent.id, 'visible', e.target.checked)
                    }
                  />
                </div>
  
                {/* Disabled checkbox */}
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-disabled">Disabled</label>
                  <input
                    id="prop-disabled"
                    type="checkbox"
                    checked={selectedComponent.properties.disabled === true}
                    onChange={(e) =>
                      updateComponentProperty(selectedComponent.id, 'disabled', e.target.checked)
                    }
                  />
                </div>
                {['TextInput', 'TextArea', 'Checkbox', 'RadioGroup'].includes(selectedComponent.type) && (
                  <div className={styles.infoRow}>
                    <label className={styles.infoKey} htmlFor="prop-required">Required</label>
                    <input
                      id="prop-required"
                      type="checkbox"
                      checked={selectedComponent.properties.required === true}
                      onChange={(e) =>
                        updateComponentProperty(selectedComponent.id, 'required', e.target.checked)
                      }
                    />
                  </div>
                )}

              {selectedComponent.properties.label !== undefined && (
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-label">Label</label>
                  <input
                    id="prop-label"
                    className={styles.infoInput}
                    type="text"
                    value={String(selectedComponent.properties.label ?? '')}
                    onChange={(e) =>
                      updateComponentProperty(selectedComponent.id, 'label', e.target.value)
                    }
                  />
                </div>
              )}
              {selectedComponent.properties.placeholder !== undefined && (
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-placeholder">Placeholder</label>
                  <input
                    id="prop-placeholder"
                    className={styles.infoInput}
                    type="text"
                    value={String(selectedComponent.properties.placeholder ?? '')}
                    onChange={(e) =>
                      updateComponentProperty(selectedComponent.id, 'placeholder', e.target.value)
                    }
                  />
                </div>
              )}
              {(selectedComponent.type === 'Dropdown' || selectedComponent.type === 'RadioGroup') && (
                <DropdownOptionsEditor
                  componentId={selectedComponent.id}
                  options={
                    Array.isArray(selectedComponent.properties.options)
                      ? (selectedComponent.properties.options as DropdownOption[])
                      : []
                  }
                  configuredValue={
                    typeof selectedComponent.properties.value === 'string'
                      ? selectedComponent.properties.value
                      : ''
                  }
                  onOptionsChange={(opts) =>
                    updateComponentProperty(selectedComponent.id, 'options', opts)
                  }
                  onConfiguredValueChange={(val) =>
                    updateComponentProperty(selectedComponent.id, 'value', val)
                  }
                />
              )}
              {selectedComponent.type === 'Checkbox' && (
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-checked">Default checked</label>
                  <input
                    id="prop-checked"
                    type="checkbox"
                    checked={selectedComponent.properties.defaultValue === true}
                    onChange={(e) =>
                      updateComponentProperty(selectedComponent.id, 'defaultValue', e.target.checked)
                    }
                  />
                </div>
              )}
              {['TextInput', 'TextArea', 'StatusPanel', 'Container'].includes(selectedComponent.type) && (
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-default-value">
                    {selectedComponent.type === 'Container' ? 'Body' : selectedComponent.type === 'StatusPanel' ? 'Message' : 'Default'}
                  </label>
                  <textarea
                    id="prop-default-value"
                    className={styles.infoInput}
                    value={typeof selectedComponent.properties.defaultValue === 'string' ? selectedComponent.properties.defaultValue : ''}
                    onChange={(e) =>
                      updateComponentProperty(selectedComponent.id, 'defaultValue', e.target.value)
                    }
                  />
                </div>
              )}
              {selectedComponent.type === 'StatusPanel' && (
                <div className={styles.infoRow}>
                  <label className={styles.infoKey} htmlFor="prop-status">Tone</label>
                  <select
                    id="prop-status"
                    className={styles.infoInput}
                    value={typeof selectedComponent.properties.status === 'string' ? selectedComponent.properties.status : 'info'}
                    onChange={(e) =>
                      updateComponentProperty(selectedComponent.id, 'status', e.target.value)
                    }
                  >
                    <option value="info">Info</option>
                    <option value="success">Success</option>
                    <option value="warning">Warning</option>
                    <option value="error">Error</option>
                  </select>
                </div>
              )}
              {selectedComponent.type === 'Table' && (
                <>
                  <TableColumnsEditor
                    columns={
                      Array.isArray(selectedComponent.properties.columns)
                        ? (selectedComponent.properties.columns as TableColumn[])
                        : []
                    }
                    onColumnsChange={(cols) =>
                      updateComponentProperty(selectedComponent.id, 'columns', cols)
                    }
                  />
                  <TableRowsEditor
                    rows={
                      Array.isArray(selectedComponent.properties.rows)
                        ? (selectedComponent.properties.rows as TableRow[])
                        : []
                    }
                    onRowsChange={(rowData) =>
                      updateComponentProperty(selectedComponent.id, 'rows', rowData)
                    }
                  />
                </>
              )}
            </div>
          ) : (
            <p className={styles.infoEmpty}>No component selected</p>
          )}

          {/* Live project JSON */}
          <div className={styles.jsonSection}>
            <div className={styles.infoHeader}>Project JSON</div>
            <pre className={styles.jsonPre}>{JSON.stringify(doc, null, 2)}</pre>
          </div>
        </aside>
      </div>
    </div>
  );
}

export default VisualEditor;
