import { useState, useEffect, useCallback } from 'react';
import type { Config, AudioDevice, ToastItem } from './types';
import * as api from './api';
import { ToastContainer } from './components/Toast';
import { Connection } from './components/Connection';
import { GlobalLeds } from './components/GlobalLeds';
import { KnobCard } from './components/KnobCard';
import { ButtonCard } from './components/ButtonCard';
import { Presets } from './components/Presets';

const KNOB_INDICES = ['0', '1', '2', '3', '4'] as const;
const BTN_INDICES  = ['0', '1', '2', '3', '4'] as const;

let toastSeq = 0;

export default function App() {
  const [config,      setConfig]      = useState<Config | null>(null);
  const [saved,       setSaved]       = useState<Config | null>(null);
  const [presets,     setPresets]     = useState<string[]>([]);
  const [toasts,      setToasts]      = useState<ToastItem[]>([]);
  const [loading,     setLoading]     = useState(true);
  const [saving,      setSaving]      = useState(false);
  const [runningApps, setRunningApps] = useState<string[]>([]);
  const [sinks,       setSinks]       = useState<AudioDevice[]>([]);
  const [sources,     setSources]     = useState<AudioDevice[]>([]);
  const [appsLoading, setAppsLoading] = useState(false);

  const addToast = useCallback((message: string, type: ToastItem['type'] = 'success') => {
    const id = ++toastSeq;
    setToasts((prev) => [...prev, { id, message, type }]);
  }, []);

  const removeToast = useCallback((id: number) => {
    setToasts((prev) => prev.filter((t) => t.id !== id));
  }, []);

  const loadPresets = useCallback(async () => {
    try {
      const names = await api.listPresets();
      setPresets(names);
    } catch {
      // Non-fatal — presets dir may not exist yet
    }
  }, []);

  const loadApps = useCallback(async () => {
    setAppsLoading(true);
    try {
      const [apps, sinkList, sourceList] = await Promise.all([
        api.fetchRunningApps(),
        api.fetchSinks(),
        api.fetchSources(),
      ]);
      setRunningApps(apps);
      setSinks(sinkList);
      setSources(sourceList);
    } finally {
      setAppsLoading(false);
    }
  }, []);

  // Initial load
  useEffect(() => {
    void (async () => {
      try {
        const cfg = await api.fetchConfig();
        setConfig(cfg);
        setSaved(cfg);
      } catch (err) {
        addToast(`Failed to load config: ${(err as Error).message}`, 'error');
      } finally {
        setLoading(false);
      }
      await loadPresets();
      await loadApps();
    })();
  }, [addToast, loadPresets, loadApps]);

  const isDirty = config !== null && JSON.stringify(config) !== JSON.stringify(saved);

  const handleSave = async () => {
    if (!config) return;
    setSaving(true);
    try {
      await api.saveConfig(config);
      setSaved(config);
      addToast('Config saved — daemon will reload automatically');
    } catch (err) {
      addToast(`Save failed: ${(err as Error).message}`, 'error');
    } finally {
      setSaving(false);
    }
  };

  const handleRevert = () => {
    if (saved) setConfig(saved);
  };

  const patchConfig = useCallback(<K extends keyof Config>(key: K, value: Config[K]) => {
    setConfig((prev) => prev ? { ...prev, [key]: value } : prev);
  }, []);

  if (loading) {
    return (
      <div id="loading">
        <p>Connecting to daemon…</p>
      </div>
    );
  }

  if (!config) {
    return (
      <div id="loading">
        <p>Could not reach the API server. Is <code>turnup-ui</code> running?</p>
      </div>
    );
  }

  return (
    <>
      <header id="app-header">
        <h1>TurnUp</h1>
        {isDirty && <span id="dirty-badge" className="visible">unsaved changes</span>}
        <button
          className="btn-secondary"
          onClick={handleRevert}
          disabled={!isDirty}
        >
          Revert
        </button>
        <button
          className="btn-primary"
          onClick={() => void handleSave()}
          disabled={!isDirty || saving}
        >
          {saving ? 'Saving…' : 'Save Config'}
        </button>
      </header>

      <main>
        <Connection
          config={config}
          onChange={(patch) => setConfig((prev) => prev ? { ...prev, ...patch } : prev)}
        />

        <GlobalLeds
          leds={config.leds}
          onChange={(leds) => patchConfig('leds', leds)}
        />

        <section className="card" id="knobs">
          <h2>
            Knobs
            <button
              className="btn-secondary btn-refresh-apps"
              onClick={() => void loadApps()}
              disabled={appsLoading}
              title="Refresh list of running apps"
            >
              {appsLoading ? '…' : '↻'} Running apps
            </button>
          </h2>
          <div className="cards-grid">
            {KNOB_INDICES.map((i) => (
              <KnobCard
                key={i}
                index={Number(i)}
                knob={config.knobs[i] ?? { action: 'sink_volume', target: 'default' }}
                runningApps={runningApps}
                sinks={sinks}
                sources={sources}
                onChange={(knob) =>
                  patchConfig('knobs', { ...config.knobs, [i]: knob })
                }
              />
            ))}
          </div>
        </section>

        <section className="card" id="buttons">
          <h2>Buttons</h2>
          <div className="cards-grid">
            {BTN_INDICES.map((i) => (
              <ButtonCard
                key={i}
                index={Number(i)}
                btn={config.buttons[i] ?? { action: 'mute_sink', target: 'default' }}
                onChange={(btn) =>
                  patchConfig('buttons', { ...config.buttons, [i]: btn })
                }
              />
            ))}
          </div>
        </section>

        <Presets
          presets={presets}
          config={config}
          onLoad={(cfg) => setConfig(cfg)}
          onRefresh={loadPresets}
          onToast={addToast}
        />
      </main>

      <ToastContainer toasts={toasts} onRemove={removeToast} />
    </>
  );
}
