import { useMemo, useState } from 'react';
import type { RemoteSectionDefinition, RowTypeDefinition } from '../api/types';
import { Button, Card, Chip, Confirm, Empty, Field, Note, Tag, Toggle } from './ui';

/* The visual row editor: what an operator sees instead of the JSON section-definition
 * array. It is a thin presentation over exactly the same document the gateway already
 * stores — every action here ends by calling onSave with a complete, reordered array,
 * through the same revisioned "*.sectionDefinitions" configuration value the raw JSON
 * view edits directly. Neither view is more authoritative than the other; they are two
 * ways of looking at one document, and the gateway validates whichever arrives.
 *
 * Positions are never exposed. Every mutation here renumbers the whole list to clean
 * multiples of ten in its new order, so "duplicate position" is not a state this editor
 * can produce and an operator never sees the number at all — only Up/Down and drag order. */

const PAGE_LABEL: Record<string, string> = { home: 'Home', movies: 'Movies', tv: 'TV Shows' };

const DEFAULT_DESTINATION: Record<string, string> = {
  continueWatching: 'home',
  favorites: 'home',
  forYou: 'for-you',
  latest: 'home',
};

function renumber(rows: RemoteSectionDefinition[]): RemoteSectionDefinition[] {
  return rows.map((row, index) => ({ ...row, position: (index + 1) * 10 }));
}

function slugify(title: string): string {
  const base = title
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
  return base || 'row';
}

function uniqueId(title: string, taken: Set<string>): string {
  const base = slugify(title);
  if (!taken.has(base)) return base;
  let suffix = 2;
  while (taken.has(`${base}-${suffix}`)) suffix += 1;
  return `${base}-${suffix}`;
}

function rowTypeFor(row: RemoteSectionDefinition, rowTypes: RowTypeDefinition[]): RowTypeDefinition | undefined {
  return rowTypes.find((candidate) => candidate.type === row.type);
}

function describeRow(row: RemoteSectionDefinition, rowTypes: RowTypeDefinition[]): string {
  const rowType = rowTypeFor(row, rowTypes);
  if (rowType && !rowType.custom) return rowType.label;
  return row.type ? `Custom · ${row.type}` : 'Custom';
}

const LAYOUT_LABEL: Record<string, string> = {
  poster: 'Poster cards',
  thumb: 'Thumbnail cards',
};

export function RowsEditor({
  page,
  rows,
  rowTypes,
  busy,
  onSave,
}: {
  page: 'home' | 'movies' | 'tv';
  rows: RemoteSectionDefinition[];
  rowTypes: RowTypeDefinition[];
  busy: boolean;
  onSave: (next: RemoteSectionDefinition[]) => Promise<void>;
}) {
  const [editing, setEditing] = useState<{ row: RemoteSectionDefinition; isNew: boolean } | null>(null);
  const [deleting, setDeleting] = useState<RemoteSectionDefinition | null>(null);
  const [showRaw, setShowRaw] = useState(false);
  const [rawDraft, setRawDraft] = useState('');
  const [rawError, setRawError] = useState('');
  const [busyRow, setBusyRow] = useState<string | null>(null);

  const sorted = useMemo(() => [...rows].sort((a, b) => a.position - b.position), [rows]);
  const availableTypes = useMemo(() => rowTypes.filter((type) => type.pages.includes(page)), [rowTypes, page]);

  const save = async (next: RemoteSectionDefinition[], key: string) => {
    setBusyRow(key);
    try {
      await onSave(renumber(next));
    } finally {
      setBusyRow(null);
    }
  };

  const move = (row: RemoteSectionDefinition, direction: -1 | 1) => {
    const index = sorted.findIndex((candidate) => candidate.id === row.id);
    const target = index + direction;
    if (index < 0 || target < 0 || target >= sorted.length) return;
    const next = [...sorted];
    const swapped = next[target];
    next[target] = next[index]!;
    next[index] = swapped!;
    void save(next, `move:${row.id}`);
  };

  const toggle = (row: RemoteSectionDefinition) => {
    void save(sorted.map((candidate) => (candidate.id === row.id ? { ...candidate, enabled: !candidate.enabled } : candidate)), `toggle:${row.id}`);
  };

  const remove = () => {
    if (!deleting) return;
    const target = deleting;
    void save(sorted.filter((candidate) => candidate.id !== target.id), `delete:${target.id}`).then(() => setDeleting(null));
  };

  const openRaw = () => {
    setRawDraft(JSON.stringify(sorted, null, 2));
    setRawError('');
    setShowRaw(true);
  };

  const applyRaw = async () => {
    try {
      const parsed = JSON.parse(rawDraft);
      if (!Array.isArray(parsed)) throw new Error('Must be a JSON array of rows.');
      setRawError('');
      await save(parsed as RemoteSectionDefinition[], 'raw');
      setShowRaw(false);
    } catch (error) {
      setRawError(error instanceof Error ? error.message : 'Invalid JSON.');
    }
  };

  return (
    <Card
      title={`${PAGE_LABEL[page]} rows`}
      intro="What is on this page and in what order, described the way a person watching would describe it — not the JSON that composes it."
      icon="list"
      actions={
        <div className="row tight">
          <Button size="sm" variant="quiet" onClick={openRaw}>
            Advanced · raw JSON
          </Button>
          <Button
            size="sm"
            variant="primary"
            icon="plus"
            onClick={() => setEditing({
              isNew: true,
              row: {
                id: '', type: availableTypes[0]?.type ?? 'custom', title: '', enabled: true,
                position: 0, dataSource: '', component: '', maxItems: 20, destination: '', layout: '',
              },
            })}
          >
            Add row
          </Button>
        </div>
      }
    >
      {sorted.length === 0 ? (
        <Empty>No rows are configured for this page. Add one to get started.</Empty>
      ) : (
        <div className="rows-editor-list">
          {sorted.map((row, index) => (
            <div className="rows-editor-item" key={row.id} data-enabled={row.enabled}>
              <div className="rows-editor-item-main">
                <div className="rows-editor-item-title">
                  <b>{row.title || row.id}</b>
                  {!row.enabled ? <Tag>off</Tag> : null}
                </div>
                <div className="chips">
                  <Chip>{describeRow(row, rowTypes)}</Chip>
                  {row.maxItems ? <Chip>{row.maxItems} items</Chip> : null}
                  {row.layout && LAYOUT_LABEL[row.layout] ? <Chip tone="note">{LAYOUT_LABEL[row.layout]}</Chip> : null}
                  {row.destination ? <Chip tone="note">{row.destination}</Chip> : null}
                </div>
              </div>
              <div className="rows-editor-item-actions">
                <Button
                  size="sm"
                  variant="quiet"
                  title="Move up"
                  disabled={index === 0}
                  busy={busyRow === `move:${row.id}`}
                  onClick={() => move(row, -1)}
                >
                  ↑
                </Button>
                <Button
                  size="sm"
                  variant="quiet"
                  title="Move down"
                  disabled={index === sorted.length - 1}
                  busy={busyRow === `move:${row.id}`}
                  onClick={() => move(row, 1)}
                >
                  ↓
                </Button>
                <Button
                  size="sm"
                  variant="quiet"
                  busy={busyRow === `toggle:${row.id}`}
                  onClick={() => toggle(row)}
                >
                  {row.enabled ? 'Disable' : 'Enable'}
                </Button>
                <Button size="sm" variant="quiet" onClick={() => setEditing({ row, isNew: false })}>
                  Edit
                </Button>
                <Button size="sm" variant="quiet" icon="trash" title="Delete row" onClick={() => setDeleting(row)} />
              </div>
            </div>
          ))}
        </div>
      )}

      {editing ? (
        <RowEditorDialog
          page={page}
          row={editing.row}
          isNew={editing.isNew}
          rowTypes={availableTypes}
          existingIds={new Set(sorted.map((candidate) => candidate.id).filter((id) => id !== editing.row.id))}
          busy={busy}
          onCancel={() => setEditing(null)}
          onSave={(next) => {
            const isNew = editing.isNew;
            void save(
              isNew ? [...sorted, next] : sorted.map((candidate) => (candidate.id === editing.row.id ? next : candidate)),
              isNew ? 'add' : `edit:${editing.row.id}`,
            ).then(() => setEditing(null));
          }}
        />
      ) : null}

      {deleting ? (
        <Confirm
          title={`Delete "${deleting.title || deleting.id}"?`}
          body="This row is removed from the page immediately on every television. Nothing else is affected."
          confirmLabel="Delete row"
          destructive
          busy={busyRow === `delete:${deleting.id}`}
          onConfirm={remove}
          onCancel={() => setDeleting(null)}
        />
      ) : null}

      {showRaw ? (
        <div className="scrim" onPointerDown={(event) => event.target === event.currentTarget && setShowRaw(false)}>
          <div className="dialog" role="dialog" aria-modal="true">
            <h2>Raw section definitions</h2>
            <p>
              For development and troubleshooting. The visual editor above covers ordinary
              administration; this is the exact document the gateway stores and validates.
            </p>
            <textarea
              rows={16}
              className="mono"
              value={rawDraft}
              onChange={(event) => setRawDraft(event.target.value)}
            />
            {rawError ? <Note tone="warn">{rawError}</Note> : null}
            <div className="dialog-actions">
              <Button variant="quiet" onClick={() => setShowRaw(false)}>Cancel</Button>
              <Button variant="primary" busy={busyRow === 'raw'} onClick={() => void applyRaw()}>Apply</Button>
            </div>
          </div>
        </div>
      ) : null}
    </Card>
  );
}

function RowEditorDialog({
  page,
  row,
  isNew,
  rowTypes,
  existingIds,
  busy,
  onSave,
  onCancel,
}: {
  page: 'home' | 'movies' | 'tv';
  row: RemoteSectionDefinition;
  isNew: boolean;
  rowTypes: RowTypeDefinition[];
  existingIds: Set<string>;
  busy: boolean;
  onSave: (row: RemoteSectionDefinition) => void;
  onCancel: () => void;
}) {
  const [title, setTitle] = useState(row.title);
  const [type, setType] = useState(row.type || rowTypes[0]?.type || 'custom');
  const [maxItems, setMaxItems] = useState(row.maxItems && row.maxItems > 0 ? row.maxItems : 20);
  const [enabled, setEnabled] = useState(row.enabled);
  const [customDataSource, setCustomDataSource] = useState(row.dataSource);
  const [customComponent, setCustomComponent] = useState(row.component || 'mediaRow');
  const [customDestination, setCustomDestination] = useState(row.destination ?? '');
  const [layout, setLayout] = useState(row.layout ?? '');

  // A row saved by an older console, or through the raw JSON view, can carry a type this
  // page's catalogue does not offer (moved off this page, or never catalogued at all). It
  // still needs an option in the picker, or the select silently shows something else while
  // `type` state disagrees — which would rewrite the row to a type nobody chose on save.
  const selectableTypes = rowTypes.some((candidate) => candidate.type === row.type) || !row.type
    ? rowTypes
    : [...rowTypes, { type: row.type, label: `Current: ${row.type}`, description: '', component: row.component, dataSource: row.dataSource, pages: [page], requiresMatchingDestination: false, custom: true }];

  const rowType = rowTypeFor({ ...row, type }, selectableTypes) ?? selectableTypes.find((candidate) => candidate.type === 'custom');
  const isCustom = !rowType || rowType.custom;
  const trimmedTitle = title.trim();
  const validMaxItems = Number.isFinite(maxItems) && maxItems >= 1 && maxItems <= 100;
  const valid = trimmedTitle.length > 0 && validMaxItems &&
    (!isCustom || (customDataSource.trim().length > 0 && customComponent.trim().length > 0));

  // Layout only means something for a horizontal card row. A genre browser or a paged
  // library grid draws neither poster nor thumbnail cards, so the control is hidden and any
  // stray value is dropped on save.
  const resolvedComponent = isCustom ? customComponent.trim() : rowType?.component ?? '';
  const layoutApplies = resolvedComponent === 'mediaRow';

  const submit = () => {
    if (!valid) return;
    const resolvedType = rowType?.type ?? 'custom';
    const destination = isCustom
      ? customDestination.trim()
      : rowType?.requiresMatchingDestination
        ? page
        : (row.destination || DEFAULT_DESTINATION[resolvedType] || page);
    onSave({
      id: isNew ? uniqueId(trimmedTitle, existingIds) : row.id,
      type: resolvedType,
      title: trimmedTitle,
      enabled,
      position: row.position,
      dataSource: isCustom ? customDataSource.trim() : rowType!.dataSource,
      component: isCustom ? customComponent.trim() : rowType!.component,
      maxItems,
      destination,
      ...(layoutApplies && (layout === 'poster' || layout === 'thumb') ? { layout } : {}),
    });
  };

  return (
    <div className="scrim" onPointerDown={(event) => event.target === event.currentTarget && onCancel()}>
      <div className="dialog" role="dialog" aria-modal="true" aria-labelledby="row-editor-title">
        <h2 id="row-editor-title">{isNew ? 'Add row' : 'Edit row'}</h2>
        <p>{PAGE_LABEL[page]} · what this row shows and how it behaves.</p>

        <div className="fields">
          <Field label="Row name" grow>
            <input type="text" value={title} onChange={(event) => setTitle(event.target.value)} placeholder="e.g. Genres" autoFocus />
          </Field>
        </div>

        <div className="fields">
          <Field label="Row type" grow>
            <select value={type} onChange={(event) => setType(event.target.value)}>
              {selectableTypes.map((option) => (
                <option key={option.type} value={option.type}>{option.label}</option>
              ))}
            </select>
          </Field>
          <Field label="Maximum items">
            <input
              type="number"
              min={1}
              max={100}
              value={maxItems}
              onChange={(event) => setMaxItems(Number(event.target.value))}
            />
          </Field>
        </div>
        {rowType?.description ? <p className="hint">{rowType.description}</p> : null}

        {layoutApplies ? (
          <div className="fields">
            <Field
              label="Card layout"
              hint="Automatic shows episodes as wide thumbnails and everything else as upright posters. Override to force one shape — thumbnails are the same wide cards Continue Watching uses."
              grow
            >
              <select value={layout} onChange={(event) => setLayout(event.target.value)}>
                <option value="">Automatic</option>
                <option value="poster">Poster cards</option>
                <option value="thumb">Thumbnail cards</option>
              </select>
            </Field>
          </div>
        ) : null}

        {rowType?.requiresMatchingDestination ? (
          <Note>Browses the {PAGE_LABEL[page]} catalogue — this row can only ever point at the page it lives on.</Note>
        ) : null}

        {isCustom ? (
          <div className="fields">
            <Field label="Data source" hint="e.g. emby.latest, gateway.recommendations" grow>
              <input type="text" value={customDataSource} onChange={(event) => setCustomDataSource(event.target.value)} />
            </Field>
            <Field label="Component" hint="e.g. mediaRow, mediaGrid, genreBrowser" grow>
              <input type="text" value={customComponent} onChange={(event) => setCustomComponent(event.target.value)} />
            </Field>
            <Field label="Destination" hint="Optional" grow>
              <input type="text" value={customDestination} onChange={(event) => setCustomDestination(event.target.value)} />
            </Field>
          </div>
        ) : null}

        <Toggle label="Row enabled" checked={enabled} onChange={setEnabled} />

        <div className="dialog-actions">
          <Button variant="quiet" onClick={onCancel}>Cancel</Button>
          <Button variant="primary" disabled={!valid} busy={busy} onClick={submit}>
            {isNew ? 'Add row' : 'Save row'}
          </Button>
        </div>
      </div>
    </div>
  );
}
