import { useCallback, useEffect, useRef, useState } from 'react';
import { ArrowLeft, Check, Plus, Trash2, TriangleAlert } from 'lucide-react';
import { Button, Card, Input, Spinner } from '../components';
import {
  ApiError,
  getPathReplacements,
  messageFromMediaSourcesError,
  replacePathReplacements,
  type PathReplacement,
  type RemoteFamily
} from '../api';
import { navigateToPath } from '../routing';
import { familyMeta, familyRoute } from '../mediaSources/familyMeta';
import { useDirtyGuard } from '../mediaSources/useDirtyGuard';

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

interface DraftRow {
  _key: string;
  id: number;
  remotePath: string;
  localPath: string;
}

let keySeq = 0;
function nextKey(): string {
  keySeq += 1;
  return `row-${keySeq}`;
}

function fromResponse(rows: PathReplacement[]): DraftRow[] {
  return rows.map((row) => ({ _key: nextKey(), id: row.id, remotePath: row.remotePath ?? '', localPath: row.localPath ?? '' }));
}

// Compare by (remotePath, localPath) — ids are assigned by the server on save, so the dirty flag and
// baseline reset must ignore them (design §C4b round-trip identity).
function toComparable(rows: DraftRow[]): string {
  return JSON.stringify(rows.map((row) => ({ remotePath: row.remotePath, localPath: row.localPath })));
}

// Shared path-replacements editor for Plex / Jellyfin / Emby
// (RemoteMediaSourcePathReplacementsEditor). A row list + a selected-row edit form; the remote-path
// column label comes from familyMeta ("Plex Path" / "Jellyfin Path" / "Emby Path"). Both fields are
// required per row. Saved as one PUT; the response (reloaded list) becomes the new baseline.
export function PathReplacementsEditScreen({ family, sourceId }: { family: RemoteFamily; sourceId: number }) {
  const meta = familyMeta(family);
  const [loaded, setLoaded] = useState(false);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [notFound, setNotFound] = useState(false);
  const [draft, setDraft] = useState<DraftRow[]>([]);
  const [saved, setSaved] = useState<DraftRow[]>([]);
  const [selectedKey, setSelectedKey] = useState<string | null>(null);
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  const activeRef = useRef(true);

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

  const ingest = useCallback((rows: PathReplacement[]) => {
    const drafted = fromResponse(rows);
    setDraft(drafted);
    setSaved(drafted.map((row) => ({ ...row })));
    setSelectedKey(drafted[0]?._key ?? null);
    setLoaded(true);
  }, []);

  const load = useCallback(() => {
    getPathReplacements(family, sourceId)
      .then((rows) => {
        if (activeRef.current) {
          ingest(rows);
        }
      })
      .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} path replacements`));
        }
      });
  }, [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) {
    return (
      <div className="ctv-settings-loading">
        <Spinner size={16} tone="muted" />
        <span>Loading {meta.label} path replacements…</span>
      </div>
    );
  }

  const selected = draft.find((row) => row._key === selectedKey) ?? null;
  // Every row must have a non-blank remote and local path (Blazor required both).
  const valid = draft.every((row) => row.remotePath.trim().length > 0 && row.localPath.trim().length > 0);

  const updateSelected = (patch: Partial<Pick<DraftRow, 'remotePath' | 'localPath'>>) => {
    if (!selected || saving) {
      return;
    }
    setDraft((current) => current.map((row) => (row._key === selected._key ? { ...row, ...patch } : row)));
  };

  const addRow = () => {
    if (saving) {
      return;
    }
    const row: DraftRow = { _key: nextKey(), id: 0, remotePath: '', localPath: '' };
    setDraft((current) => [...current, row]);
    setSelectedKey(row._key);
  };

  const removeRow = (key: string) => {
    if (saving) {
      return;
    }
    setDraft((current) => {
      const index = current.findIndex((row) => row._key === key);
      const next = current.filter((row) => row._key !== key);
      setSelectedKey(next[Math.min(index, next.length - 1)]?._key ?? null);
      return next;
    });
  };

  const discard = () => {
    if (!dirty || window.confirm(DIRTY_PROMPT)) {
      const restored = saved.map((row) => ({ ...row }));
      setDraft(restored);
      setSelectedKey(restored[0]?._key ?? null);
      setSaveError(null);
    }
  };

  const save = () => {
    if (!valid || !dirty || saving) {
      return;
    }
    setSaving(true);
    setSaveError(null);
    replacePathReplacements(family, sourceId, {
      items: draft.map((row) => ({ id: row.id, remotePath: row.remotePath.trim(), localPath: row.localPath.trim() }))
    })
      .then((rows) => {
        if (!activeRef.current) {
          return;
        }
        ingest(rows);
        setSaving(false);
      })
      .catch((error: unknown) => {
        if (!activeRef.current) {
          return;
        }
        setSaving(false);
        setSaveError(messageFromMediaSourcesError(error, `Unable to save ${meta.label} path replacements`));
        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} path replacements</span>
          <h2>Edit path replacements</h2>
          <p>Map {meta.label} media paths onto the paths ErsatzTV can reach.</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>
      )}

      <div className="ctv-schedule-grid">
        <section className="ctv-schedule-lineup-panel" aria-label="Path replacements">
          <div className="ctv-schedule-panel-head">
            <span>Replacements</span>
            <Button disabled={saving} onClick={addRow} size="sm" startIcon={<Plus aria-hidden="true" size={14} />} variant="ghost">Add</Button>
          </div>
          {draft.length === 0 ? (
            <div className="ctv-schedule-empty">No path replacements</div>
          ) : (
            <div className="ctv-library-list" role="list" aria-label="Path replacement rows">
              {draft.map((row) => (
                <div className={`ctv-library-row${row._key === selectedKey ? ' ctv-library-row-active' : ''}`} role="listitem" key={row._key}>
                  <button className="ctv-library-row-main" onClick={() => setSelectedKey(row._key)} type="button" style={{ textAlign: 'left', background: 'none', border: 'none', cursor: 'pointer' }}>
                    <strong>{row.remotePath || '(new)'}</strong>
                    <span>→ {row.localPath || '(unset)'}</span>
                  </button>
                  <div className="ctv-library-row-status">
                    <Button disabled={saving} onClick={() => removeRow(row._key)} size="sm" startIcon={<Trash2 aria-hidden="true" size={14} />} variant="ghost">Remove</Button>
                  </div>
                </div>
              ))}
            </div>
          )}
        </section>

        <section className="ctv-schedule-inspector" aria-label="Path replacement editor">
          {selected ? (
            <Card title={<h3>Edit replacement</h3>}>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
                <Input
                  label={meta.remotePathColumnLabel}
                  value={selected.remotePath}
                  disabled={saving}
                  onChange={(event) => updateSelected({ remotePath: event.target.value })}
                  error={selected.remotePath.trim() ? null : `${meta.remotePathColumnLabel} is required`}
                />
                <Input
                  label="Local Path"
                  value={selected.localPath}
                  disabled={saving}
                  onChange={(event) => updateSelected({ localPath: event.target.value })}
                  error={selected.localPath.trim() ? null : 'Local Path is required'}
                />
              </div>
            </Card>
          ) : (
            <div className="ctv-schedule-empty">Select or add a path replacement</div>
          )}
        </section>
      </div>

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