import { useCallback, useEffect, useRef, useState } from 'react';
import { ArrowLeft, ArrowUpDown, Check, TriangleAlert } from 'lucide-react';
import { Button, Card, Spinner, Switch } from '../components';
import {
  ApiError,
  getRemoteLibraries,
  messageFromMediaSourcesError,
  replaceRemoteLibraryPreferences,
  type RemoteFamily,
  type RemoteLibrary
} from '../api';
import { navigateToPath } from '../routing';
import { familyMeta, familyRoute } from '../mediaSources/familyMeta';
import { useDirtyGuard } from '../mediaSources/useDirtyGuard';

const DIRTY_PROMPT = 'You have unsaved library changes. Discard them?';

type SortColumn = 'name' | 'mediaKind';
type SortDir = 'asc' | 'desc';

function formatMediaKind(kind: string): string {
  switch (kind) {
    case 'MusicVideos':
      return 'Music Videos';
    case 'OtherVideos':
      return 'Other Videos';
    case 'RemoteStreams':
      return 'Remote Streams';
    default:
      return kind;
  }
}

// A draft is keyed by (name, mediaKind), NOT id (design §C4a): disabling a library removes and
// re-adds its row with a fresh id, so the id is not stable across a save. We compare/reset by the
// stable (name, mediaKind, shouldSyncItems) tuple and refetch the reloaded list after every save.
function draftKey(library: Pick<RemoteLibrary, 'name' | 'mediaKind'>): string {
  return `${library.name ?? ''}\u0000${library.mediaKind}`;
}

function toComparable(libraries: RemoteLibrary[]): string {
  return JSON.stringify(
    [...libraries]
      .map((library) => ({ key: draftKey(library), shouldSyncItems: library.shouldSyncItems }))
      .sort((a, b) => a.key.localeCompare(b.key))
  );
}

interface LoadedState {
  libraries: RemoteLibrary[];
}

// Shared library-preferences editor for Plex / Jellyfin / Emby (RemoteMediaSourceLibrariesEditor).
// Client-side sortable Name + MediaKind columns (finding 10) + a per-library sync Switch, saved as
// one PUT. The PUT returns the reloaded list (ids may have changed) which becomes the new baseline.
export function RemoteLibrariesEditScreen({ family, sourceId }: { family: RemoteFamily; sourceId: number }) {
  const meta = familyMeta(family);
  const [loaded, setLoaded] = useState<LoadedState | null>(null);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [notFound, setNotFound] = useState(false);
  const [draft, setDraft] = useState<RemoteLibrary[]>([]);
  const [saved, setSaved] = useState<RemoteLibrary[]>([]);
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  const [sortColumn, setSortColumn] = useState<SortColumn>('name');
  const [sortDir, setSortDir] = useState<SortDir>('asc');
  const activeRef = useRef(true);

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

  const ingest = useCallback((libraries: RemoteLibrary[]) => {
    setDraft(libraries.map((library) => ({ ...library })));
    setSaved(libraries.map((library) => ({ ...library })));
    setLoaded({ libraries });
  }, []);

  const load = useCallback(() => {
    getRemoteLibraries(family, sourceId)
      .then((libraries) => {
        if (activeRef.current) {
          ingest(libraries);
        }
      })
      .catch((error: unknown) => {
        if (!activeRef.current) {
          return;
        }
        if (error instanceof ApiError && error.status === 404) {
          setNotFound(true);
        } else {
          setLoadError(messageFromMediaSourcesError(error, `Unable to load ${meta.label} libraries`));
        }
      });
  }, [family, sourceId, meta.label, ingest]);

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

  const dirty = toComparable(draft) !== toComparable(saved);
  useDirtyGuard(dirty, DIRTY_PROMPT);

  if (notFound) {
    return (
      <div className="ctv-settings-error" role="alert">
        <TriangleAlert aria-hidden="true" size={16} />
        <span>That {meta.label} server no longer exists.</span>
        <Button onClick={() => navigateToPath(familyRoute(family))} size="sm" variant="secondary">Back to {meta.label}</Button>
      </div>
    );
  }

  if (loadError) {
    return (
      <div className="ctv-settings-error" role="alert">
        <TriangleAlert aria-hidden="true" size={16} />
        <span>{loadError}</span>
        <Button onClick={() => navigateToPath(familyRoute(family))} size="sm" variant="secondary">Back to {meta.label}</Button>
      </div>
    );
  }

  if (loaded === null) {
    return (
      <div className="ctv-settings-loading">
        <Spinner size={16} tone="muted" />
        <span>Loading {meta.label} libraries…</span>
      </div>
    );
  }

  const toggleSort = (column: SortColumn) => {
    if (sortColumn === column) {
      setSortDir((dir) => (dir === 'asc' ? 'desc' : 'asc'));
    } else {
      setSortColumn(column);
      setSortDir('asc');
    }
  };

  const sorted = [...draft].sort((a, b) => {
    const left = sortColumn === 'name' ? a.name ?? '' : a.mediaKind;
    const right = sortColumn === 'name' ? b.name ?? '' : b.mediaKind;
    const cmp = left.localeCompare(right);
    return sortDir === 'asc' ? cmp : -cmp;
  });

  const setSync = (key: string, shouldSyncItems: boolean) => {
    if (saving) {
      return;
    }
    setDraft((current) => current.map((library) => (draftKey(library) === key ? { ...library, shouldSyncItems } : library)));
  };

  const discard = () => {
    if (!dirty || window.confirm(DIRTY_PROMPT)) {
      setDraft(saved.map((library) => ({ ...library })));
      setSaveError(null);
    }
  };

  const save = () => {
    if (!dirty || saving) {
      return;
    }
    setSaving(true);
    setSaveError(null);
    replaceRemoteLibraryPreferences(family, sourceId, {
      libraries: draft.map((library) => ({ id: library.id, shouldSyncItems: library.shouldSyncItems }))
    })
      .then((libraries) => {
        if (!activeRef.current) {
          return;
        }
        // Refetch semantics: the endpoint returns the reloaded list (ids change on disable), so the
        // response — not the in-memory draft — becomes the new baseline.
        ingest(libraries);
        setSaving(false);
      })
      .catch((error: unknown) => {
        if (!activeRef.current) {
          return;
        }
        setSaving(false);
        setSaveError(messageFromMediaSourcesError(error, `Unable to save ${meta.label} libraries`));
        // On a 409 the resource moved under us; refetch so the draft catches up (design §C6).
        if (error instanceof ApiError && error.status === 409) {
          load();
        }
      });
  };

  return (
    <div className="ctv-libraries-screen">
      <section className="ctv-libraries-header">
        <div>
          <span className="ctv-kicker">{meta.label} libraries</span>
          <h2>Edit libraries</h2>
          <p>Choose which {meta.label} libraries ErsatzTV syncs.</p>
        </div>
        <div className="ctv-libraries-header-actions">
          <Button onClick={() => navigateToPath(familyRoute(family))} startIcon={<ArrowLeft aria-hidden="true" size={14} />} variant="ghost">Back to {meta.label}</Button>
        </div>
      </section>

      {saveError && (
        <div className="ctv-channels-error" role="alert">
          <TriangleAlert aria-hidden="true" size={15} />
          <span>{saveError}</span>
        </div>
      )}

      <Card padded={false}>
        <div className="ctv-channels-table-frame">
          <div className="ctv-channels-table-scroll">
            <table className="ctv-channels-table" aria-label={`${meta.label} libraries`}>
              <thead>
                <tr>
                  <th aria-sort={sortColumn === 'name' ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none'}>
                    <button className="ctv-logs-sort-button" onClick={() => toggleSort('name')} type="button">
                      Name
                      {sortColumn === 'name' && <ArrowUpDown aria-hidden="true" size={13} />}
                    </button>
                  </th>
                  <th aria-sort={sortColumn === 'mediaKind' ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none'}>
                    <button className="ctv-logs-sort-button" onClick={() => toggleSort('mediaKind')} type="button">
                      Media Kind
                      {sortColumn === 'mediaKind' && <ArrowUpDown aria-hidden="true" size={13} />}
                    </button>
                  </th>
                  <th>Sync</th>
                </tr>
              </thead>
              <tbody>
                {sorted.length === 0 ? (
                  <tr><td colSpan={3}><div className="ctv-schedule-empty">No libraries discovered</div></td></tr>
                ) : sorted.map((library) => {
                  const key = draftKey(library);
                  return (
                    <tr key={key}>
                      <td>{library.name}</td>
                      <td>{formatMediaKind(library.mediaKind)}</td>
                      <td>
                        <Switch checked={library.shouldSyncItems} disabled={saving} onChange={(next) => setSync(key, next)} label={`Sync ${library.name ?? ''}`} size="sm" />
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      </Card>

      <div className="ctv-schedule-toolbar">
        <div className="ctv-schedule-toolbar-spacer" />
        <Button disabled={!dirty || saving} onClick={discard} variant="ghost">Discard</Button>
        <Button disabled={!dirty || saving} loading={saving} onClick={save} startIcon={<Check aria-hidden="true" size={14} />}>Save</Button>
      </div>
    </div>
  );
}
