import { useCallback, useEffect, useRef, useState } from 'react';
import { ArrowLeft, Check, Plus, Repeat, Trash2, TriangleAlert } from 'lucide-react';
import { Badge, Button, Card, ConfirmDialog, IconButton, Input, Select, Spinner } from '../components';
import type { SelectOption } from '../components';
import { usePrimaryAction } from '../primaryAction';
import {
  ApiError,
  createRerunCollection,
  deleteRerunCollection,
  getCollections,
  getLibraryBrowseItems,
  getMultiCollections,
  getRerunCollectionWithMeta,
  getRerunCollections,
  getSmartCollections,
  messageFromRerunCollectionError,
  updateRerunCollection,
  type CreateRerunCollectionRequest,
  type LibraryBrowseMediaType,
  type RerunCollection
} from '../api';

type RerunCollectionType = CreateRerunCollectionRequest['collectionType'];
type PlaybackOrder = CreateRerunCollectionRequest['firstRunPlaybackOrder'];

interface PickerOption {
  id: number;
  name: string;
}

// The REST-supported selection types (RerunCollectionRequestMapping.IsSupportedSelectionType).
// Playlist / RerunFirstRun / RerunRerun / SearchQuery / Fake* are intentionally excluded.
// Each entry knows how to load its picker options; every media-item type maps to a
// LibraryBrowseMediaType that GetLibraryBrowseItemsHandler.MediaTypesFor actually supports.
const COLLECTION_TYPES: Array<{
  browse?: LibraryBrowseMediaType;
  label: string;
  source: 'browse' | 'collection' | 'multi' | 'smart';
  value: RerunCollectionType;
}> = [
  { label: 'Collection', source: 'collection', value: 'Collection' },
  { label: 'Multi Collection', source: 'multi', value: 'MultiCollection' },
  { label: 'Smart Collection', source: 'smart', value: 'SmartCollection' },
  { browse: 'TelevisionShow', label: 'Television Show', source: 'browse', value: 'TelevisionShow' },
  { browse: 'TelevisionSeason', label: 'Television Season', source: 'browse', value: 'TelevisionSeason' },
  { browse: 'Artist', label: 'Artist', source: 'browse', value: 'Artist' },
  { browse: 'Movie', label: 'Movie', source: 'browse', value: 'Movie' },
  { browse: 'Episode', label: 'Episode', source: 'browse', value: 'Episode' },
  { browse: 'MusicVideo', label: 'Music Video', source: 'browse', value: 'MusicVideo' },
  { browse: 'OtherVideo', label: 'Other Video', source: 'browse', value: 'OtherVideo' },
  { browse: 'Song', label: 'Song', source: 'browse', value: 'Song' },
  { browse: 'Image', label: 'Image', source: 'browse', value: 'Image' },
  { browse: 'RemoteStream', label: 'Remote Stream', source: 'browse', value: 'RemoteStream' }
];

const ORDER_LABELS: Record<string, string> = {
  Chronological: 'Chronological',
  Random: 'Random',
  SeasonEpisode: 'Season, Episode',
  Shuffle: 'Shuffle'
};

function orderOption(order: PlaybackOrder): SelectOption {
  return { label: ORDER_LABELS[order] ?? order, value: order };
}

// Mirrors the type-conditional option sets in RerunCollectionEditor.razor.
function orderOptionsFor(type: RerunCollectionType): SelectOption[] {
  switch (type) {
    case 'MultiCollection':
      return [orderOption('Shuffle')];
    case 'Collection':
    case 'SmartCollection':
      return ['Chronological', 'Random', 'Shuffle'].map((order) => orderOption(order as PlaybackOrder));
    case 'TelevisionShow':
      return ['Chronological', 'SeasonEpisode', 'Random', 'Shuffle'].map((order) => orderOption(order as PlaybackOrder));
    default:
      return ['Chronological', 'Random', 'Shuffle'].map((order) => orderOption(order as PlaybackOrder));
  }
}

function defaultOrderFor(type: RerunCollectionType): PlaybackOrder {
  return type === 'MultiCollection' ? 'Shuffle' : 'Chronological';
}

// The REST API accepts any PlaybackOrder for any collectionType (the per-type restriction
// enforced by orderOptionsFor is only a UI convention), so a rerun collection created via the
// API can carry a stored order outside the type's normal set. On edit-load, union that value in
// rather than letting the Select coerce it away (which would silently change what gets saved).
function orderOptionsWithCurrent(type: RerunCollectionType, current: PlaybackOrder): SelectOption[] {
  const options = orderOptionsFor(type);
  return options.some((option) => option.value === current) ? options : [...options, orderOption(current)];
}

function loadPickerOptions(type: RerunCollectionType): Promise<PickerOption[]> {
  const config = COLLECTION_TYPES.find((entry) => entry.value === type);
  if (!config) {
    return Promise.resolve([]);
  }

  switch (config.source) {
    case 'collection':
      return getCollections().then((list) => list.map((entry) => ({ id: entry.id, name: entry.name ?? `#${entry.id}` })));
    case 'multi':
      return getMultiCollections({ pageSize: 1000 }).then((result) =>
        (result.page ?? []).map((entry) => ({ id: entry.id, name: entry.name ?? `#${entry.id}` }))
      );
    case 'smart':
      return getSmartCollections().then((list) => list.map((entry) => ({ id: entry.id, name: entry.name ?? `#${entry.id}` })));
    default:
      return getLibraryBrowseItems({ mediaType: config.browse, pageSize: 500 }).then((result) =>
        (result.page ?? []).map((item) => ({ id: item.mediaItemId ?? item.id, name: item.title ?? `#${item.id}` }))
      );
  }
}

/* ---------- data hook ---------- */

type ListState =
  | { data: RerunCollection[]; error: null; status: 'success' }
  | { data: null; error: string; status: 'error' }
  | { data: null; error: null; status: 'loading' };

function useRerunCollectionsData() {
  const [state, setState] = useState<ListState>({ data: null, error: null, status: 'loading' });
  const activeRef = useRef(true);

  useEffect(() => {
    activeRef.current = true;
    return () => {
      activeRef.current = false;
    };
  }, []);

  const load = useCallback(() => {
    getRerunCollections({ pageSize: 1000 })
      .then((result) => {
        if (activeRef.current) {
          setState({ data: result.page ?? [], error: null, status: 'success' });
        }
      })
      .catch((error: unknown) => {
        if (activeRef.current) {
          setState({ data: null, error: messageFromRerunCollectionError(error), status: 'error' });
        }
      });
  }, []);

  useEffect(() => {
    load();
  }, [load]);

  const refresh = useCallback(
    (quiet = false) => {
      if (!quiet) {
        setState({ data: null, error: null, status: 'loading' });
      }

      load();
    },
    [load]
  );

  return { refresh, state };
}

/* ---------- editor ---------- */

interface Draft {
  collectionType: RerunCollectionType;
  firstRunPlaybackOrder: PlaybackOrder;
  name: string;
  rerunPlaybackOrder: PlaybackOrder;
  selectedId: number | null;
  selectedName: string;
}

function draftFromRerun(rerun: RerunCollection): Draft {
  return {
    collectionType: rerun.collectionType,
    firstRunPlaybackOrder: rerun.firstRunPlaybackOrder,
    name: rerun.name ?? '',
    rerunPlaybackOrder: rerun.rerunPlaybackOrder,
    selectedId: rerun.selectedId ?? null,
    selectedName: rerun.selectedName ?? ''
  };
}

function RerunCollectionEditor({
  initial,
  onBack,
  onSaved
}: {
  initial: RerunCollection | null;
  onBack: () => void;
  onSaved: () => void;
}) {
  const [draft, setDraft] = useState<Draft>(() =>
    initial
      ? draftFromRerun(initial)
      : {
          collectionType: 'Collection',
          firstRunPlaybackOrder: 'Chronological',
          name: '',
          rerunPlaybackOrder: 'Chronological',
          selectedId: null,
          selectedName: ''
        }
  );
  const [pickerItems, setPickerItems] = useState<PickerOption[]>([]);
  const [pickerError, setPickerError] = useState<string | null>(null);
  const [saveError, setSaveError] = useState<string | null>(null);
  const [saving, setSaving] = useState(false);
  const [conflictOpen, setConflictOpen] = useState(false);
  const [reloadKey, setReloadKey] = useState(0);
  // Concurrency ETag (issue #253): captured from the single-record GET when editing an existing
  // rerun collection, sent as If-Match on save. There's no rotation-on-save because the editor
  // always returns to the list after a successful save (see onSaved below).
  const etagRef = useRef<string | null>(null);

  const { collectionType } = draft;

  // Re-fetch the record's ETag (and current data, on a conflict reload) when editing an existing
  // rerun collection. Runs once on mount and again after reloadAfterConflict bumps reloadKey.
  useEffect(() => {
    if (!initial) {
      return;
    }

    let active = true;

    getRerunCollectionWithMeta(initial.id)
      .then((meta) => {
        if (active) {
          etagRef.current = meta.etag;
          setDraft(draftFromRerun(meta.data));
        }
      })
      .catch((error: unknown) => {
        if (active) {
          setSaveError(messageFromRerunCollectionError(error, 'Unable to load rerun collection'));
        }
      });

    return () => {
      active = false;
    };
  }, [initial, reloadKey]);

  // Load the picker list for the active type. Resets only in the async callbacks (never
  // synchronously in the effect body) per spa-conventions §3.
  useEffect(() => {
    let active = true;

    loadPickerOptions(collectionType)
      .then((items) => {
        if (active) {
          setPickerItems(items);
          setPickerError(null);
        }
      })
      .catch((error: unknown) => {
        if (active) {
          setPickerItems([]);
          setPickerError(messageFromRerunCollectionError(error, 'Unable to load picker items'));
        }
      });

    return () => {
      active = false;
    };
  }, [collectionType]);

  const setCollectionType = (type: RerunCollectionType) => {
    setDraft((current) => ({
      ...current,
      collectionType: type,
      firstRunPlaybackOrder: defaultOrderFor(type),
      rerunPlaybackOrder: defaultOrderFor(type),
      selectedId: null,
      selectedName: ''
    }));
  };

  const setSelected = (value: string) => {
    const id = value === '' ? null : Number(value);
    const match = pickerItems.find((item) => item.id === id);
    setDraft((current) => ({ ...current, selectedId: id, selectedName: match?.name ?? current.selectedName }));
  };

  const activeConfig = COLLECTION_TYPES.find((entry) => entry.value === collectionType);
  const trimmedName = draft.name.trim();
  const validationError =
    trimmedName.length === 0 ? 'Name is required' : draft.selectedId == null ? 'A selection is required' : null;

  const save = async () => {
    if (validationError || saving || draft.selectedId == null) {
      return;
    }

    setSaving(true);
    setSaveError(null);

    try {
      const body = {
        collectionType: draft.collectionType,
        firstRunPlaybackOrder: draft.firstRunPlaybackOrder,
        name: trimmedName,
        rerunPlaybackOrder: draft.rerunPlaybackOrder,
        selectedId: draft.selectedId
      };

      if (initial) {
        await updateRerunCollection(initial.id, body, etagRef.current);
      } else {
        await createRerunCollection(body);
      }

      onSaved();
    } catch (error) {
      if (error instanceof ApiError && error.status === 412) {
        // Another edit landed since we loaded — force a reload rather than overwriting it (#253).
        setConflictOpen(true);
      } else {
        setSaveError(messageFromRerunCollectionError(error, 'Unable to save rerun collection'));
      }
    } finally {
      setSaving(false);
    }
  };

  const reloadAfterConflict = () => {
    setConflictOpen(false);
    setSaveError(null);
    setReloadKey((key) => key + 1);
  };

  // Ensure the currently-selected item is always an option even if it isn't in the fetched
  // page (e.g. a large library where browse returned a different slice).
  const selectedInList = draft.selectedId != null && pickerItems.some((item) => item.id === draft.selectedId);
  const pickerOptions: SelectOption[] = [
    { label: 'Select…', value: '' },
    ...(draft.selectedId != null && !selectedInList
      ? [{ label: draft.selectedName || `#${draft.selectedId}`, value: String(draft.selectedId) }]
      : []),
    ...pickerItems.map((item) => ({ label: item.name, value: String(item.id) }))
  ];

  const firstRunOrderOptions = orderOptionsWithCurrent(collectionType, draft.firstRunPlaybackOrder);
  const rerunOrderOptions = orderOptionsWithCurrent(collectionType, draft.rerunPlaybackOrder);

  return (
    <div className="ctv-collections">
      <div className="ctv-channels-actionbar">
        <Button onClick={onBack} size="sm" startIcon={<ArrowLeft aria-hidden="true" size={14} />} variant="ghost">
          All rerun collections
        </Button>
        <span className="ctv-collections-detail-title">
          {initial ? 'Edit rerun collection' : 'New rerun collection'}
        </span>
        <span className="ctv-channels-spacer" />
        {saveError && <span className="ctv-settings-savebar-error">{saveError}</span>}
        {validationError && <Badge tone="neutral">{validationError}</Badge>}
        <Button
          disabled={validationError !== null || saving}
          loading={saving}
          onClick={() => void save()}
          size="sm"
          startIcon={<Check aria-hidden="true" size={14} />}
        >
          {initial ? 'Save rerun collection' : 'Add rerun collection'}
        </Button>
      </div>

      <Card title="Rerun collection">
        <Input
          label="Name"
          onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))}
          placeholder="Rerun collection name"
          value={draft.name}
        />

        <div style={{ marginTop: 16 }}>
          <Select
            label="Collection Type"
            onChange={(event) => setCollectionType(event.target.value as RerunCollectionType)}
            options={COLLECTION_TYPES.map((entry) => ({ label: entry.label, value: entry.value }))}
            value={draft.collectionType}
          />
        </div>

        <div style={{ marginTop: 12 }}>
          <Select
            label={activeConfig?.label ?? 'Selection'}
            onChange={(event) => setSelected(event.target.value)}
            options={pickerOptions}
            value={draft.selectedId == null ? '' : String(draft.selectedId)}
          />
          {pickerError && (
            <span className="ctv-field-error" role="alert">
              {pickerError}
            </span>
          )}
        </div>

        <div style={{ marginTop: 12 }}>
          <Select
            label="First Run Playback Order"
            onChange={(event) =>
              setDraft((current) => ({ ...current, firstRunPlaybackOrder: event.target.value as PlaybackOrder }))
            }
            options={firstRunOrderOptions}
            value={draft.firstRunPlaybackOrder}
          />
        </div>

        <div style={{ marginTop: 12 }}>
          <Select
            label="Rerun Playback Order"
            onChange={(event) =>
              setDraft((current) => ({ ...current, rerunPlaybackOrder: event.target.value as PlaybackOrder }))
            }
            options={rerunOrderOptions}
            value={draft.rerunPlaybackOrder}
          />
        </div>
      </Card>

      <ConfirmDialog
        cancelLabel="Keep editing"
        confirmLabel="Reload"
        message="This was changed elsewhere since you opened it. Reload to get the latest — your unsaved changes will be discarded."
        onCancel={() => setConflictOpen(false)}
        onConfirm={reloadAfterConflict}
        open={conflictOpen}
        title="Rerun collection changed elsewhere"
        tone="danger"
      />
    </div>
  );
}

/* ---------- screen ---------- */

export function RerunCollectionsScreen() {
  const { refresh, state } = useRerunCollectionsData();
  const [editing, setEditing] = useState<{ kind: 'new' } | { kind: 'edit'; item: RerunCollection } | null>(null);
  const [deleteTarget, setDeleteTarget] = useState<RerunCollection | null>(null);
  const [deleteBusy, setDeleteBusy] = useState(false);
  const [deleteError, setDeleteError] = useState<string | null>(null);

  // TopBar "Add Rerun Collection" primary action.
  usePrimaryAction('rerunCollections', () => setEditing({ kind: 'new' }));

  if (editing) {
    return (
      <RerunCollectionEditor
        initial={editing.kind === 'edit' ? editing.item : null}
        onBack={() => setEditing(null)}
        onSaved={() => {
          setEditing(null);
          refresh(true);
        }}
      />
    );
  }

  if (state.status === 'loading') {
    return (
      <div className="ctv-collections-loading" role="status">
        <Spinner size={18} />
        <span>Loading rerun collections…</span>
      </div>
    );
  }

  if (state.status === 'error') {
    return (
      <div className="ctv-collections">
        <div className="ctv-channels-error" role="alert">
          <TriangleAlert aria-hidden="true" size={15} />
          <span>{state.error}</span>
          <span className="ctv-channels-spacer" />
          <Button onClick={() => refresh()} size="sm" variant="secondary">
            Retry
          </Button>
        </div>
      </div>
    );
  }

  const rerunCollections = [...state.data].sort((a, b) =>
    (a.name ?? '').localeCompare(b.name ?? '', undefined, { sensitivity: 'base' })
  );

  const typeLabel = (type: RerunCollectionType) => COLLECTION_TYPES.find((entry) => entry.value === type)?.label ?? type;

  const confirmDelete = async () => {
    if (!deleteTarget) {
      return;
    }

    setDeleteBusy(true);
    setDeleteError(null);

    try {
      await deleteRerunCollection(deleteTarget.id);
      setDeleteTarget(null);
      refresh(true);
    } catch (error) {
      setDeleteError(messageFromRerunCollectionError(error, 'Unable to delete rerun collection'));
    } finally {
      setDeleteBusy(false);
    }
  };

  return (
    <div className="ctv-collections">
      <div className="ctv-channels-actionbar">
        <Badge tone="neutral">{rerunCollections.length} rerun collections</Badge>
        <span className="ctv-channels-spacer" />
        <Button onClick={() => setEditing({ kind: 'new' })} size="sm" startIcon={<Plus aria-hidden="true" size={14} />}>
          New rerun collection
        </Button>
      </div>

      <Card padded={false}>
        {rerunCollections.length === 0 ? (
          <div className="ctv-collections-empty">No rerun collections yet.</div>
        ) : (
          rerunCollections.map((collection, index) => (
            <div
              className="ctv-settings-flush-row"
              key={collection.id}
              style={index === 0 ? { borderTop: 'none' } : undefined}
            >
              <Repeat aria-hidden="true" color="var(--ctv-accent)" size={15} />
              <button
                className="ctv-settings-flush-row-main ctv-settings-flush-row-title ctv-collections-linkbtn"
                onClick={() => setEditing({ item: collection, kind: 'edit' })}
                type="button"
              >
                {collection.name}
              </button>
              <Badge tone="neutral">
                {typeLabel(collection.collectionType)}
                {collection.selectedName ? ` · ${collection.selectedName}` : ''}
              </Badge>
              <IconButton onClick={() => setDeleteTarget(collection)} size="sm" title="Delete" variant="ghost">
                <Trash2 aria-hidden="true" size={14} />
              </IconButton>
            </div>
          ))
        )}
      </Card>

      <ConfirmDialog
        busy={deleteBusy}
        confirmLabel="Delete"
        message={
          deleteTarget ? (
            <>
              <span>{`Delete "${deleteTarget.name ?? 'this rerun collection'}"? This cannot be undone.`}</span>
              {deleteError && (
                <span className="ctv-field-error" role="alert">
                  {deleteError}
                </span>
              )}
            </>
          ) : (
            ''
          )
        }
        onCancel={() => {
          setDeleteTarget(null);
          setDeleteError(null);
        }}
        onConfirm={() => void confirmDelete()}
        open={deleteTarget !== null}
        title="Delete rerun collection"
        tone="danger"
      />
    </div>
  );
}
